在一个二维网格中,假定每一个方格代表一个细胞,每个细胞有存活和死亡两种状态,其初始生存状态随机确定。每隔一段时间检查一次细胞的生存状态,每个细胞的生存状态由其周围的8个细胞的生存状态决定,具体规则如下:
- 如果一个细胞周围的活细胞数量超过3个或少于2个,则该细胞死亡;
- 如果一个细胞周围的活细胞数量等于2个,则该细胞生存状态不变;
- 如果一个细胞周围的活细胞数量等于3个,则该细胞复活或继续存活;
如果用黑色表示死亡的细胞,用白色表示存活的细胞,这个二维网格最初看起来是杂乱无章的,但是经过一段时间的演化之后,就会产生稳定而规律的变化,甚至是持续的、有规律的移动,类似物质交换或生命运动。
这就是英国数学家约翰·何顿·康威(John Horton Conway)于1970年发明的生命游戏,也被称作康威生命游戏。上面这几张图是生命游戏中的经典图案,转自康威生命游戏官方网站。仅凭3条细胞繁衍和死亡的简单规则,生命游戏就可以在计算机上模拟出丰富的生命演化过程,甚至可以模拟出与真实生命相当的复杂度——只要计算机内存足够大、计算能力足够强。
数学家康威证明了生命游戏具有图灵完备性,允许在生命游戏中模拟任何其他生命游戏规则。康威生命游戏是人工生命的经典研究,推动了元胞自动机(Cellular Automaton)理论的发展。元胞自动机作为一种仿真算法在近两年的数学建模竞赛中经常出现,可谓数学建模竞赛的万金油。康威生命游戏就是一种在二维网格上定义的元胞自动机。
如果用python演绎这个游戏的话,似乎不难。比如, 可以用一个二维列表来模拟游戏中的二维表格,用两层嵌套的循环结构来检查每一个细胞的生存状态。不过,这不是一个好的主意:如果二维表格稍微大一点,这样的代码会慢到无法忍受。下面这段代码使用numpy数组来模拟游戏中的二维表格,不需要循环就可以完成一次细胞演化,速度直追c语言。用空间换时间,这是应用numpy时避免显式循环的最常用的手段之一。
import numpy as np
def evolve(bio):
"""细胞演化,bio是用来模拟二维表格的二维数组"""
top = np.hstack((bio[-1,-1], bio[-1], bio[-1,0]))
bottom = np.hstack((bio[0,-1], bio[0], bio[0,0]))
center = np.hstack((bio[:,-1:], bio, bio[:,0:1]))
u = np.vstack((top, center, bottom))
s1 = u[:-2,:-2]
s2 = u[:-2,1:-1]
s3 = u[:-2,2:]
s4 = u[1:-1,:-2]
s5 = u[1:-1,2:]
s6 = u[2:,:-2]
s7 = u[2:,1:-1]
s8 = u[2:,2:]
s = s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8
bio[np.where((s > 3) | (s < 2))] = 0
bio[np.where(s == 3)] = 1
return bio
接下来测试细胞演化函数evolve是否符合设计预期。
rows, cols = 8, 8
n = int(0.5 * rows * cols)
bio = np.zeros((rows, cols), dtype=np.uint8)
bio[(np.random.randint(0, rows, n), np.random.randint(0, cols, n))] = 1
print('初始状态:')
print(bio)
bio = evolve(bio)
print('演化一次:')
print(bio)
测试结果看起来是正确的。
初始状态:
[[0 1 1 0 0 0 0 0]
[1 0 0 1 1 0 0 0]
[0 1 1 0 1 1 1 1]
[1 1 0 0 1 0 0 1]
[1 0 0 1 0 0 1 1]
[1 0 1 0 0 0 1 0]
[0 0 0 0 0 1 0 0]
[1 0 1 0 0 0 0 1]]
演化一次:
[[0 0 1 0 0 0 0 1]
[1 0 0 0 1 0 1 1]
[0 0 1 0 0 0 1 0]
[0 0 0 0 1 0 0 0]
[0 0 1 1 0 1 1 0]
[1 1 0 0 0 1 1 0]
[1 0 0 0 0 0 1 0]
[1 0 1 0 0 0 0 0]]
请按任意键继续. . .
怎么能够直观地看到细胞连续演化的过程呢?matplotlib.animation提供了一个便捷的手段,这就是FuncAnimation函数。FuncAnimation函数用起来稍显繁琐,不过下面的代码展示了一种逻辑清晰、通俗易懂的用法,可以用来生成复杂的动画,唯一的缺点就是刷新频率较低,且难以提升。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def evolve():
"""细胞演化,bio是用来模拟二维表格的二维数组"""
top = np.hstack((bio[-1,-1], bio[-1], bio[-1,0]))
bottom = np.hstack((bio[0,-1], bio[0], bio[0,0]))
center = np.hstack((bio[:,-1:], bio, bio[:,0:1]))
u = np.vstack((top, center, bottom))
s = u[:-2,:-2] + u[:-2,1:-1] + u[:-2,2:] + u[1:-1,:-2] + u[1:-1,2:] + u[2:,:-2] + u[2:,1:-1] + u[2:,2:]
bio[np.where((s > 3) | (s < 2))] = 0
bio[np.where(s == 3)] = 1
def animate(frame):
"""动画函数"""
plt.cla()
plt.imshow(bio, alpha=0.8)
evolve()
rows, cols = 16, 16
n = int(0.5 * rows * cols)
bio = np.zeros((rows, cols), dtype=np.uint8)
bio[(np.random.randint(0, rows, n), np.random.randint(0, cols, n))] = 1
plt.figure(figsize=(8, 8))
anim = FuncAnimation(plt.gcf(), animate)
plt.show()
若想直接生成gif文件或者mp4文件,只需要将上面代码的最后两行做如下改动。
anim = FuncAnimation(plt.gcf(), animate, frames=100)
anim.save(r'd:\bio.gif', fps=100)
这是在16行16列的二维空间中进行100次演化的演示动画。
|