想用python模拟随机运动
布朗运动 BROWNIAN MOTION
英国科学家布朗在两百年前第一次观察到水分子推动花粉颗粒在水面做不规则运动
现在我们也想用电脑模拟,在无外力作用下,花粉颗粒在水面的随机运动
如何实现呢?详细解释直接写在代码里面
from random import choice
import matplotlib.pyplot as plt
class RandomWalk():
def __init__(self,num_points=5000):
self.num_points = num_points
"""
初始化第一个点的位置
用列表来存储点沿x轴和y轴分量
num_point为随机模拟点运动的次数
"""
self.x_value = [0]
self.y_value = [0]
def fillwalk(self):
"""
整体思路:
用while循环模拟5000次随机运动
每次随机模拟后,向列表中添加一个元素
直到列表中添加满5000个元素
"""
while len(self.x_value) < self.num_points:
"""
这里运动方向的设置实际上非常理想化,仅仅考虑在平面图上整数格点的
移动,移动的距离也十分理想化,移动不同长度的距离的概率相同
实际情况下移动不同长度有不一样的概率,我们将在后面的模型中对此问题
进行修正
"""
#设置每步沿x轴运动的方向,距离
x_direction = choice([1,-1])
x_distance = choice([0,1,2,3,4])
x_step = x_direction*x_distance
#设置每步沿y轴运动的方向,距离
y_direction = choice([1,-1])
y_distance = choice([0,1,2,3,4])
y_step = y_direction*y_distance
#不能原地踏步
if x_step == 0 and y_step == 0:
continue
#每次从上一个位置上向下一个位置移动
next_x = self.x_value[-1]+x_step
next_y = self.y_value[-1]+y_step
#将新点添加到列表中
self.x_value.append(next_x)
self.y_value.append(next_y)
def resetvalue(self):
#让点重新回到原点
self.x_value = [0]
self.y_value = [0]
def rwvisual(self,mode):
#绘制随机漫步的散点图或者折线图
view = RandomWalk()
view.fillwalk()
"""
根据参数选择不同绘制模式
颜色默认是红色
散点的大小默认是 s=1
"""
if mode == 'scatter':
plt.scatter(view.x_value,view.y_value,s=1,c='red')
elif mode == 'plot':
plt.plot(view.x_value,view.y_value,c='red')
else:
print('error')
plt.show()
实际的模拟情况如图
还可以增加颜色映射,修改每个点的大小让整幅图看起来更加优雅
我们统计每次模拟随机运动最后落点的位置到原点的距离,可以得到一张这样的散点图
仔细观察这张图,每次模拟移动的距离呈现疏-密-更疏的三层结构
但也如上文所说,随机运动每次移动的距离,并不是在不同长度上概率均等,而是呈现正态分布,每次移动较短的距离和较长的距离的情况比较少,常常落在适中的距离范围内
正态分布
如何从零开始模拟优雅的正态分布钟形曲线?
下面请出高尔顿版的发明者——英国生物统计学家高尔顿
戳这里了解正态分布的前世今生------>正态分布的前世今生
来自己模拟正态分布
from random import choice
import matplotlib.pyplot as plt
"""
手动模拟高尔顿版的小球掉落的过程
假设小球在每次遇上板钉是向左右两边掉落的概率相等
连续多次落下小球,统计落在每个位置的小球总数
"""
class NormalDistribution():
def __init__(self,ball_range=10,num_balls=5000):
self.ball_range = ball_range
self.num_balls = num_balls
self.ball = 0
self.box = {}
def normaldistribute(self):
#将某个与原点对称的区域的所有元素全部初始化
for i in range(-self.ball_range,self.ball_range+1):
self.box[i] = 0
for i in range(self.num_balls):
self.ball = 0
for i in range(self.ball_range):
self.ball += choice([-1,1])
self.box[self.ball] += 1
print(self.box)
nord = NormalDistribution()
nord.normaldistribute()
到这里时问题已经初见端倪,按照上述代码运行,得到的 5000 个小球最后的随机落点是这样的
{-10: 3, -9: 0, -8: 48, -7: 0, -6: 228, -5: 0, -4: 604, -3: 0, -2: 988, -1: 0, 0: 1241, 1: 0, 2: 1048, 3: 0, 4: 573, 5: 0, 6: 216, 7: 0, 8: 47, 9: 0, 10: 4}
看的仔细的话已经能发现,任何奇数位置落点上都没有球。
为什么会这样?我们来仔细观察下真实的高尔顿板
小球每次的移动选择似乎并不是严格的向左一格或者是向右一格,而是存在类似于半格的移动
反思上述代码,每个小球都被设定成做出十次 +1 或是 -1 的移动选择,不管两种情况如何分配,是做5次 +1 ,5次 -1 ,后者是3次 +1 ,7次 -1,最终的结果一定是偶数,那么奇数位上一定没有小球落下
怎么修正问题?
一种可行的操作是在小球下落前增加一次条件选择,随机分配小球是做十次模拟移动还是九次模拟移动
改进了下
from random import choice
import matplotlib.pyplot as plt
"""
手动模拟高尔顿版的小球掉落的过程
假设小球在每次遇上板钉是向左右两边掉落的概率相等
连续多次落下小球,统计落在每个位置的小球总数
"""
class NormalDistribution():
def __init__(self,ball_range=10,num_balls=5000):
self.ball_range = ball_range #设置小球下落范围,小球落点将在
#-ball_range 与 ball_range 内
self.num_balls = num_balls #设置总共下落的小球个数
self.ball = 0 #设置小球原初位置
self.box = {} #创建一个字典,
#字典中存储不同落点位置的小球数量
self.basic_box = [] #创建一个列表,存储小球的下落范围
self.basic_box_ball = [] #创建一个列表,存储下落范围内
#每个落点上小球个数
def normaldistribute(self):
"""
将字典中某个与原点对称的区域的所有元素全部初始化
"""
for i in range(-self.ball_range,self.ball_range+1):
self.box[i] = 0
self.basic_box.append(i)
for i in range(self.num_balls):
self.ball = 0
for i in range(choice([self.ball_range,self.ball_range-1])):
self.ball += choice([-1,1])
if i%100 == 0:
print('finish '+str(i/100)) #每完成一百次模拟打印一条消息
self.box[self.ball] += 1
for i in range(-self.ball_range,self.ball_range+1):
self.basic_box_ball.append(self.box[i])
def shownord(self):
plt.scatter(self.basic_box,self.basic_box_ball)
得到的结果很优雅
但是时不时就会出现这样的结果,在 0 处的小球数反而比 -1 和 1 处的小球数量少
当我们增加小球的数量,设置100 0000个小球,得到下面的图象
差强人意,在零点处本该凸起的帽子被削平了
再用一千万个小球试了试,也是同样的结果
本该是优雅的圆顶,现在却变成了35岁程序员的秃顶