我目前正在尝试模拟盒子中弹跳的许多粒子。
我已经考虑了@kalhartt 的建议,这是初始化盒子内粒子的改进代码:
import numpy as np
import scipy.spatial.distance as d
import matplotlib.pyplot as plt
# 2D container parameters
# Actual container is 50x50 but chose 49x49 to account for particle radius.
limit_x = 20
limit_y = 20
#Number and radius of particles
number_of_particles = 350
radius = 1
def force_init(n):
# equivalent to np.array(list(range(number_of_particles)))
count = np.linspace(0, number_of_particles-1, number_of_particles)
x = (count + 2) % (limit_x-1) + radius
y = (count + 2) / (limit_x-1) + radius
return np.column_stack((x, y))
position = force_init(number_of_particles)
velocity = np.random.randn(number_of_particles, 2)
初始化的位置是这样的:
初始化粒子后,我想在每个时间步更新它们。更新的代码紧跟之前的代码,如下:
# Updating
while np.amax(abs(velocity)) > 0.01:
# Assume that velocity slowly dying out
position += velocity
velocity *= 0.995
#Get pair-wise distance matrix
pair_dist = d.cdist(position, position)
pair_d = pair_dist<=4
#If pdist [i,j] is <=4 then the particles are too close and so treat as collision
for i in range(len(pair_d)):
for j in range(i):
# Only looking at upper triangular matrix (not inc. diagonal)
if pair_d[i,j] ==True:
# If two particles are too close then swap velocities
# It's a bad hack but it'll work for now.
vel_1 = velocity[j][:]
velocity[j] = velocity[i][:]*0.9
velocity[i] = vel_1*0.9
# Masks for particles beyond the boundary
xmax = position[:, 0] > limit_x
xmin = position[:, 0] < 0
ymax = position[:, 1] > limit_y
ymin = position[:, 1] < 0
# flip velocity and assume that it looses 10% of energy
velocity[xmax | xmin, 0] *= -0.9
velocity[ymax | ymin, 1] *= -0.9
# Force maximum positions of being +/- 2*radius from edge
position[xmax, 0] = limit_x-2*radius
position[xmin, 0] = 2*radius
position[ymax, 0] = limit_y-2*radius
position[ymin, 0] = 2*radius
更新并让它运行完成后,我得到了这个结果:
这比以前好多了,但是仍然有一些补丁太靠近了——例如:
靠得太近了。我认为更新有效......感谢@kalhartt,我的代码变得更好更快(我学到了一些关于 numpy 的东西...... Prop @kalhartt)但我仍然不知道它在哪里搞砸了。我已经尝试将实际更新的顺序更改为最后的成对距离或 position +=velocity
最后但无济于事。我添加了 *0.9 以使整个事物更快地消失,我尝试使用 4 来确保 2*radius (=2) 不是太严格的标准......但似乎没有任何效果。
任何和所有帮助将不胜感激。
最佳答案
只有两个错别字妨碍了您。第一for i in range(len(positions)/2):
只迭代一半以上的粒子。这就是为什么一半的粒子停留在 x 边界内(如果你观察大的迭代它会更清楚)。其次,第二个 y 条件应该是最小值(我假设)position[i][1] < 0
.以下 block 用于为我绑定(bind)粒子(我没有使用碰撞代码进行测试,因此那里可能存在问题)。
for i in range(len(position)):
if position[i][0] > limit_x or position[i][0] < 0:
velocity[i][0] = -velocity[i][0]
if position[i][1] > limit_y or position[i][1] < 0:
velocity[i][1] = -velocity[i][1]
顺便说一句,尽可能利用 numpy 来消除循环。它更快、更高效,而且在我看来更具可读性。例如force_init
看起来像这样:
def force_init(n):
# equivalent to np.array(list(range(number_of_particles)))
count = np.linspace(0, number_of_particles-1, number_of_particles)
x = (count * 2) % limit_x + radius
y = (count * 2) / limit_x + radius
return np.column_stack((x, y))
你的边界条件看起来像这样:
while np.amax(abs(velocity)) > 0.01:
position += velocity
velocity *= 0.995
# Masks for particles beyond the boundary
xmax = position[:, 0] > limit_x
xmin = position[:, 0] < 0
ymax = position[:, 1] > limit_y
ymin = position[:, 1] < 0
# flip velocity
velocity[xmax | xmin, 0] *= -1
velocity[ymax | ymin, 1] *= -1
最后一点,用类似 position[xmax, 0] = limit_x; position[xmin, 0] = 0
的东西将位置硬剪裁到边界框可能是个好主意。 .在某些情况下,速度可能很小,盒子外的粒子会被反射,但在下一次迭代中不会进入盒子内。所以它只会坐在盒子外面,永远被反射。
编辑:碰撞
碰撞检测是一个更难的问题,但让我们看看我们能做些什么。让我们看一下您当前的实现情况。
pair_dist = d.cdist(position, position)
pair_d = pair_dist<=4
for i in range(len(pair_d)):
for j in range(i):
# Only looking at upper triangular matrix (not inc. diagonal)
if pair_d[i,j] ==True:
# If two particles are too close then swap velocities
# It's a bad hack but it'll work for now.
vel_1 = velocity[j][:]
velocity[j] = velocity[i][:]*0.9
velocity[i] = vel_1*0.9
总的来说这是一个很好的方法,cdist
将有效地计算距离
在点集之间,你会发现哪些点与 pair_d = pair_dist<=4
相撞.
嵌套的 for 循环是第一个问题。我们需要遍历 True
pair_d
的值其中 j > i
.首先,您的代码实际上使用 for j in range(i)
遍历下三角区域。这样j < i
,在这种情况下并不特别重要,因为 i,j 对不重复。然而 Numpy 有两个我们可以使用的内置函数,np.triu让我们将对角线下方的所有值设置为 0
和 np.nonzero将为我们提供矩阵中非零元素的索引。所以这个:
pair_dist = d.cdist(position, position)
pair_d = pair_dist<=4
for i in range(len(pair_d)):
for j in range(i+1, len(pair_d)):
if pair_d[i, j]:
...
相当于
pair_dist = d.cdist(position, position)
pair_d = np.triu(pair_dist<=4, k=1) # k=1 to exclude the diagonal
for i, j in zip(*np.nonzero(pair_d)):
...
第二个问题(正如您所指出的)是速度只是切换和缩放而不是反射。我们真正想要做的是否定和缩放每个粒子速度沿连接它们的轴的分量。请注意,为此我们需要连接它们的向量 position[j] - position[i]
以及连接它们的向量的长度(我们已经计算过了)。所以不幸的是 cdist
的一部分重复计算。让我们停止使用 cdist
而不是自己做。这里的目标是制作两个数组 diff
和 norm
其中 diff[i][j]
是一个从粒子 i 指向 j 的向量(所以 diff
是一个 3D 数组)并且 norm[i][j]
是粒子 i 和 j 之间的距离。我们可以像这样用 numpy 做到这一点:
nop = number_of_particles
# Give pos a 3rd index so we can use np.repeat below
# equivalent to `pos3d = np.array([ position ])
pos3d = position.reshape(1, nop, 2)
# 3D arras with a repeated index so we can form combinations
# diff_i[i][j] = position[i] (for all j)
# diff_j[i][j] = position[j] (for all i)
diff_i = np.repeat(pos3d, nop, axis=1).reshape(nop, nop, 2)
diff_j = np.repeat(pos3d, nop, axis=0)
# diff[i][j] = vector pointing from position[i] to position[j]
diff = diff_j - diff_i
# norm[i][j] = sqrt( diff[i][j]**2 )
norm = np.linalg.norm(diff, axis=2)
# check for collisions and take the region above the diagonal
collided = np.triu(norm < radius, k=1)
for i, j in zip(*np.nonzero(collided)):
# unit vector from i to j
unit = diff[i][j] / norm[i][j]
# flip velocity
velocity[i] -= 1.9 * np.dot(unit, velocity[i]) * unit
velocity[j] -= 1.9 * np.dot(unit, velocity[j]) * unit
# push particle j to be radius units from i
# This isn't particularly effective when 3+ points are close together
position[j] += (radius - norm[i][j]) * unit
...
由于这篇文章已经够长了,here is a gist代码的修改。
关于python - 盒中的许多粒子 - 物理模拟,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28144084/