我正在尝试复制论文的结果(如果您有兴趣,请参阅它的Nowak和May,1992:进化游戏和空间混沌),该论文通过在nxn网格上运行囚徒困境来创建一组分形(例如, https://www.researchgate.net/figure/Spatial-version-of-the-Prisoners-Dilemma-for-symmetrical-initial-conditions-Nowak_fig3_277476479),但我的结果不符合预期。 这个想法是,除了放置在网格中心的单个Defector对象之外,网格完全由合作者填充。不同的交互产生不同的收益:互斥者的收益为0,互惠者的收益为1,互斥者对协作者的收益为b,叛逃者的收益为0,其中b> 1。网格相互竞争,并根据上述收益结构获得分数。每一代之后,节点上的每个对象都被得分最高的邻居替换。由于叛逃者策略是更好的策略,它应该像细胞自动机那样侵入合作者群体并产生所述分形图像。
我尝试执行此操作的主要方法(也是我遇到问题的主要区域)是通过下面显示的replace_pop函数。每一轮之后,程序循环遍历网格,并用得分较高的邻居对象替换节点上的任何对象。我认为这已经足够了,但是即使经过几代人的努力,人们仍然可以看到某种形式的复制,但是复制的方式并不正确,因此很难确定到底出了什么问题。在N = 1(N是世代数)的情况下,结果似乎是正确的,因为邻近的(邻居在左,右,上和下)合作者成为偏转者,但是当N变大时,图像就会误入歧途。
在每次生成之后,我还将每个对象的得分重新初始化为0,以确保可以进行正确的复制。但是,如果不这样做,种群将以与上面的N = 1情况相同的方式进化,但是对于所有后代来说,这是独特的,因为应该有比周围合作者得分更高的叛逃者。我不确定我要去哪里错了?我的代码在下面(抱歉包含所有代码,但我不知道问题出在哪里)。我是Python和Stack的新手,所以可以提供任何帮助。
import random
import matplotlib.pyplot as plt
row = 99
col = 99
class Cooperator:
def __init__(self):
self.score = 0
self.id = 'C'
class Defector:
def __init__(self):
self.score = 0
self.id = 'D'
class Grid:
def __init__(self, rowsize, colsize):
self.rowsize = rowsize
self.colsize = colsize
def make_grid(self):
n = self.rowsize
m = self.colsize
arr = [[0 for j in range(m)] for i in range(n)]
return arr
def populate_grid(self):
empty_grid = self.make_grid()
for i in range(self.rowsize):
for j in range(self.colsize):
empty_grid[i][j] = Cooperator()
empty_grid[i//2][j//2] = Defector()
return empty_grid
def shuffle_population(self):
populated_grid = self.populate_grid()
for i in range(self.rowsize):
random.shuffle(populated_grid[i])
return populated_grid
def von_neumann_neighbourhood(array, row, col, wrapped=True):
"""gets von neumann neighbours for a specfic point on grid with or without wrapping"""
neighbours = []
#conditions for in bound points
if row + 1 <= len(array) - 1:
neighbours.append(array[row + 1][col])
if row - 1 >= 0:
neighbours.append(array[row - 1][col])
if col + 1 <= len(array[0]) - 1:
neighbours.append(array[row][col + 1])
if col - 1 >= 0:
neighbours.append(array[row][col - 1])
#if wrapped is on, conditions for out of bound points
if row - 1 < 0 and wrapped == True:
neighbours.append(array[-1][col])
if col - 1 < 0 and wrapped == True:
neighbours.append(array[row][-1])
if row + 1 > len(array) - 1 and wrapped == True:
neighbours.append(array[0][col])
if col + 1 > len(array[0]) - 1 and wrapped == True:
neighbours.append(array[row][0])
return neighbours
def play_round(array, row, col):
b = 1.70
player = array[row][col]
neighbours = von_neumann_neighbourhood(array, row, col)
for neighbour in neighbours:
if player.id == 'C' and neighbour.id == 'C':
player.score += 1
neighbour.score += 1
if player.id == 'D' and neighbour.id == 'D':
player.score += 0
neighbour.score += 0
if player.id == 'D' and neighbour.id == 'C':
player.score += b
neighbour.score += 0
if player.id == 'C' and neighbour.id == 'D':
player.score += 0
neighbour.score += b
def replace_pop(array, row, col):
neighbour_score = 0
type_neighbour = ""
neighbours = von_neumann_neighbourhood(array, row, col)
player_score = array[row][col].score
for neighbour in neighbours:
if neighbour.score > neighbour_score:
neighbour_score = neighbour.score
type_neighbour = neighbour.id
if player_score < neighbour_score:
if type_neighbour == "C":
array[row][col] = Cooperator()
if type_neighbour == "D":
array[row][col] = Defector()
N = 1
last_gen = []
def generations(N, row, col, array):
for gen in range(N):
for z in range(row):
for x in range(col):
play_round(array, z, x)
for r in range(row):
last_gen.append([])
for c in range(col):
last_gen[r].append(lattice[r][c].id)
replace_pop(array, r, c)
for obj in lattice:
for ob in obj:
ob.score = 0
lattice = Grid(row, col).populate_grid()
generations(N, row, col, lattice)
heatmap_stuff = []
for z in range(row):
heatmap_stuff.append([])
for v in range(col):
if lattice[z][v].id == 'C' and last_gen[z][v] == 'C':
heatmap_stuff[z].append(1)
if lattice[z][v].id == 'D' and last_gen[z][v] == 'D':
heatmap_stuff[z].append(0)
if lattice[z][v].id == 'C' and last_gen[z][v] == 'D':
heatmap_stuff[z].append(3)
if lattice[z][v].id == 'D' and last_gen[z][v] == 'C':
heatmap_stuff[z].append(4)
plt.imshow(heatmap_stuff, interpolation='nearest')
plt.colorbar()
plt.show()
编辑:我已经按照Ilmari的建议更新了代码。尽管结果看起来更好,并且可以实时返回实际的分形,但结果仍然不是最佳的,这使我认为其他地方可能存在错误,因为单元格似乎正在正确更新。以下是我已添加/替换为先前代码的更新代码。
def get_moore_neighbours(grid, row, col):
neighbours = []
for x, y in (
(row - 1, col), (row + 1, col), (row, col - 1),
(row, col + 1), (row - 1, col - 1), (row - 1, col + 1),
(row + 1, col - 1), (row + 1, col + 1)):
if not (0 <= x < len(grid) and 0 <= y < len(grid[x])):
# out of bounds
continue
else:
neighbours.append(grid[x][y])
return neighbours
def calculate_score(grid, row, col):
b = 1.85
player = grid[row][col]
neighbours = get_moore_neighbours(grid, row, col)
for neighbour in neighbours:
if player.id == 'C' and neighbour.id == 'C':
player.score += 1
neighbour.score += 1
if player.id == 'D' and neighbour.id == 'D':
player.score += 0
neighbour.score += 0
if player.id == 'D' and neighbour.id == 'C':
player.score += b
neighbour.score += 0
if player.id == 'C' and neighbour.id == 'D':
player.score += 0
neighbour.score += b
return player.score
def best_neighbor_type(grid, row, col):
neighbour_score = 0
type_neighbour = ""
neighbours = get_moore_neighbours(grid, row, col)
player_score = grid[row][col].score
for neighbour in neighbours:
if neighbour.score > neighbour_score:
neighbour_score = neighbour.score
type_neighbour = neighbour.id
if player_score < neighbour_score:
if type_neighbour == "C":
return 'C'
if type_neighbour == "D":
return 'D'
if player_score >= neighbour_score:
return grid[row][col].id
N = 15
heatmap_data = Grid(row, col).make_grid()
lattice = Grid(row, col).populate_grid()
dbl_buf = Grid(row, col).populate_grid()
for gen in range(N):
for r in range(row):
for c in range(col):
lattice[r][c].score = calculate_score(lattice, r, c)
for r in range(row):
for c in range(col):
dbl_buf[r][c].id = best_neighbor_type(lattice, r, c)
for r in range(row):
for c in range(col):
if lattice[r][c].id == 'C' and dbl_buf[r][c].id == 'C':
heatmap_data[r][c] = 1
if lattice[r][c].id == 'D' and dbl_buf[r][c].id == 'D':
heatmap_data[r][c] = 2
if lattice[r][c].id == 'C' and dbl_buf[r][c].id == 'D':
heatmap_data[r][c] = 3
if lattice[r][c].id == 'D' and dbl_buf[r][c].id == 'C':
heatmap_data[r][c] = 4
plt.imshow(heatmap_data, interpolation='nearest')
plt.pause(0.01)
(lattice, dbl_buf) = (dbl_buf, lattice)
plt.show()
答案 0 :(得分:2)
看看您的代码,会跳出一些问题:
您永远不会在各代之间重置last_gen
数组,因此您将不断向其添加新(空)行,并使前row
行越来越长。几乎可以肯定这是一个错误。
除了生成热图之外,您也永远不会将last_gen
数组用于任何其他用途。特别是,您的replace_pop()
函数正在修改从中读取邻居状态的相同数组(俗称为array
)。
第二个问题意味着代码的行为将取决于您在每一代中循环遍历单元格以调用replace_pop()
的顺序,因为用不同的邻居替换一个单元格会影响所有单元格的邻域。的这一代尚未更新的邻居。
在您引用的论文中所述的细胞自动机中,所有细胞都应该同时有效地更新其状态,这样直到下一代它们的邻居都看不到每个细胞状态的变化。
在实践中,实现这种“同时”更新的最简单方法是使用双重缓冲,在这种情况下,您首先将所有单元的状态复制到第二个数组中,然后根据所复制的内容更新第一个数组。刚做。或者,更有效地,只需交换数组(对数组的引用),而不是将一个数组复制到另一个数组中。代码看起来像这样:
lattice = Grid(row, col).populate_grid()
dbl_buf = Grid(row, col)
for gen in range(N):
for r in range(row):
for c in range(col):
lattice[r][c].score = calculate_score(lattice, r, c)
# This is probably the best spot for generating output, since both
# buffers contain consistent and up-to-date IDs and scores here.
for r in range(row):
for c in range(col):
dbl_buf[r][c].id = best_neighbor_type(lattice, r, c)
(lattice, dbl_buf) = (dbl_buf, lattice)
其中calculate_score()
函数根据相邻单元格的类型返回格中给定单元格的分数,而best_neighbor_id()
函数返回该单元格得分最高的相邻单元格的类型ID在晶格上。
附录:您在更新的代码中对calculate_score()
的实现存在一些错误:
但是,您之所以获得与Nowak&May论文不同的结果的真正原因是由于概念上的差异:该论文假定单元格也可以自己玩游戏,有效地提高了合作者的得分。您的实现不包含此内容,因此对于相同的参数值会产生不同的动态。
无论如何,这是我重写函数的方法:
def calculate_score(grid, row, col):
neighbours = get_moore_neighbours(grid, row, col)
player = grid[row][col]
score = 0
if player.id == 'C': score += 1 # self-interaction!
for neighbour in neighbours:
if player.id == 'C' and neighbour.id == 'C':
score += 1
if player.id == 'D' and neighbour.id == 'C':
score += b
return score
有了这一更改,您的代码将产生与Nowak&May论文非常相似的模式:
顺便说一句,我不确定Nowak&May如何处理格子的边缘,一旦碰到边缘,这可能会导致图案发散。您的实现有效地从分数计算中排除了边缘以外的任何邻居,就像晶格被未扩展的缺陷包围一样。