优化零重力2d空间中粒子的重力计算

时间:2011-08-18 23:40:08

标签: python optimization numpy physics

我已经在python中创建了一个小的粒子可视化。 我在没有重力的2D空间中对粒子的运动进行了解释。 因为每个粒子都会根据粒子质量和距离吸引所有其他粒子。

我在pygame中做了一个视觉效果,一切都按照计划(用caluclation)工作,但是我需要极大地优化计算。今天,该系统可以以相邻的帧速率计算大约100-150个粒子。我把所有的计算都放在一个单独的线程中,它给了我更多但不是我想要的东西。

我看过scipy和numpy,但因为我不是科学家或数学家,所以我只是感到困惑。看起来我在正确的轨道上,但我不知道怎么做。

我需要计算所有粒子上的所有吸引力,我需要循环中的循环。 因为我需要找到是否有任何碰撞,我必须重新做同样的事情。

写下那种代码让我心碎......

Numpy能够使用数组计算数组,但是我没有找到任何计算数组中所有项目的内容以及来自相同/另一个数组的所有项目。有吗? 如果是这样我可以创建几个数组并计算得更快,并且必须有一个函数将索引从2个数组中得到它们的值匹配(Collitiondetect iow)

这是今天的吸引力/收集计算:

class Particle:
    def __init__(self):
        self.x = random.randint(10,790)
        self.y = random.randint(10,590)
        self.speedx = 0.0
        self.speedy = 0.0
        self.mass = 4

#Attraction    
for p in Particles:
    for p2 in Particles:
        if p != p2:
            xdiff = P.x - P2.x
            ydiff = P.y - P2.y
            dist = math.sqrt((xdiff**2)+(ydiff**2))
            force = 0.125*(p.mass*p2.mass)/(dist**2)
            acceleration = force / p.mass
            xc = xdiff/dist
            yc = ydiff/dist
            P.speedx -= acceleration * xc
            P.speedy -= acceleration * yc
for p in Particles:
    p.x += p.speedx
    p.y += p.speedy

#Collision
for P in Particles:
   for P2 in Particles:
        if p != P2:
            Distance = math.sqrt(  ((p.x-P2.x)**2)  +  ((p.y-P2.y)**2)  )
            if Distance < (p.radius+P2.radius):
                p.speedx = ((p.mass*p.speedx)+(P2.mass*P2.speedx))/(p.mass+P2.mass)
                p.speedy = ((p.mass*p.speedy)+(P2.mass*P2.speedy))/(p.mass+P2.mass)
                p.x = ((p.mass*p.x)+(P2.mass*P2.x))/(p.mass+P2.mass)
                p.y = ((p.mass*p.y)+(P2.mass*P2.y))/(p.mass+P2.mass)
                p.mass += P2.mass
                p.radius = math.sqrt(p.mass)
                Particles.remove(P2)

5 个答案:

答案 0 :(得分:5)

你可以先尝试使用复数:相关的引力和动力学公式在这种形式中非常简单,也可以非常快(因为NumPy可以在内部进行计算,而不是你单独处理x和y坐标)。例如,z和z'处两个粒子之间的力是简单的:

(z-z')/abs(z-z')**3

对于所有z / z'对,NumPy可以非常快速地计算出这样的数量。例如,所有zz'值的矩阵只是从坐标的1D数组Z获得Z-Z[:, numpy.newaxis](对角项[z = z']在计算时需要特别小心{{ 1}}:它们应该设置为零。

至于时间演变,你当然可以使用 SciPy的快速微分方程例程:它们比逐步欧拉积分更精确。

无论如何,钻研NumPy非常有用,特别是如果你计划进行科学计算,因为NumPy非常快。

答案 1 :(得分:4)

我之前已经研究过这个问题,而我过去看到的加速碰撞计算的一个原因是实际存储了附近粒子的列表。

基本上,这个想法是在你的重力计算中,你做了类似的事情:

for (int i = 0; i < n; i++)
{
    for (int j = i + 1; j < n; j++)
    {
        DoGravity(Particle[i], Particle[j]);
        if (IsClose(Particle[i], Particle[j]))
        {
            Particle[i].AddNeighbor(Particle[j]);
            Particle[j].AddNeighbor(Particle[i]);
        }
    }
}

然后,您只需传递所有粒子,然后依次对每个粒子进行碰撞检测。在最好的情况下,这通常类似O(n),但在最坏的情况下很容易降级为O(n^2)

另一种方法是尝试将粒子放在Octree内。构建一个类似于O(n)的东西,然后你可以查询它以查看是否有任何东西彼此靠近。那时你只需对对进行碰撞检测。这样做O(n log n)我相信。

不仅如此,您还可以使用八叉树来加速重力计算。它不是O(n^2)行为,而是下降到O(n log n)。大多数八叉树实施都包含一个“开放参数”,可以控制您将要进行的速度与精度之间的权衡。因此,Octrees往往不如直接成对计算准确,编码复杂,但它们也可以进行大规模模拟。

如果您以这种方式使用八叉树,您将执行所谓的Barnes-Hut Simulation

注意:由于您在2D工作,因此Octree的2D模拟称为Quadtree。有关详细信息,请参阅以下维基百科文章:http://en.wikipedia.org/wiki/Quadtree

答案 2 :(得分:4)

要进行快速计算,你需要在numpy数组中存储x,y,speedx,speedy,m。例如:

import numpy as np

p = np.array([
    (0,0),
    (1,0),
    (0,1),
    (1,1),
    (2,2),
], dtype = np.float)

p是一个5x2阵列,用于存储粒子的x,y位置。要计算每对之间的距离,您可以使用:

print np.sqrt(np.sum((p[:, np.newaxis] - p[np.newaxis, :])**2, axis=-1))

输出是:

[[ 0.          1.          1.          1.41421356  2.82842712]
 [ 1.          0.          1.41421356  1.          2.23606798]
 [ 1.          1.41421356  0.          1.          2.23606798]
 [ 1.41421356  1.          1.          0.          1.41421356]
 [ 2.82842712  2.23606798  2.23606798  1.41421356  0.        ]]

或者您可以使用scipy中的cdist:

from scipy.spatial.distance import cdist
print cdist(p, p)

答案 3 :(得分:2)

不确定这是否会对您有所帮助,但它是我一直在努力解决同一问题的解决方案的一部分。我没有注意到以这种方式表现的巨大收益,仍然开始研究停止200粒子,但也许它会给你一些想法。

用于计算2d平面上引力吸引力的x和y分量的C ++模块:

#include <Python.h>
#include <math.h>

double _acceleration(double &Vxa, double &Vya, double &Vxb, double &Vyb, double xa, double ya, double xb, double yb, double massa, double massb)
{
   double xdiff = xa - xb;
   double ydiff = ya - yb;
   double distance = sqrt(xdiff*xdiff + ydiff*ydiff) * pow(10, 5);

   if (distance <= 0)
      distance = 1;

   double force = (6.674 * pow(10, -11))*(massa*massb)/(distance*distance);

   double acca = force / massa;
   double accb = force / massb;
   double xcomponent = xdiff/distance;
   double ycomponent = ydiff/distance;

   Vxa -= acca * xcomponent;
   Vya -= acca * ycomponent;
   Vxb += accb * xcomponent;
   Vyb += accb * ycomponent;

   return distance;
}

static PyObject* gforces(PyObject* self, PyObject* args)
{
   double Vxa, Vya, Vxb, Vyb, xa, ya, xb, yb, massa, massb, distance;

   if (!PyArg_ParseTuple(args, "dddddddddd", &Vxa, &Vya, &Vxb, &Vyb, &xa, &ya, &xb, &yb, &massa, &massb))
      return NULL;

   distance = _acceleration(Vxa, Vya, Vxb, Vyb, xa, ya, xb, yb, massa, massb);

   return Py_BuildValue("ddddd", Vxa, Vya, Vxb, Vyb, distance);
}

static PyMethodDef GForcesMethods[] = {
{"gforces", gforces, METH_VARARGS, "Calculate the x and y acceleration of two masses and the distance between the two."},
{NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC
initgforces(void)
{
(void) Py_InitModule("gforces", GForcesMethods);
}

如果将其编译为pyd文件,则应该获得可以导入的python对象。您必须正确地获取所有编译器和链接器选项。我正在使用dev-C ++并将我的编译器选项设置为-shared -o gforces.pyd并将链接器设置为-lpython27(确保使用已安装的相同版本)并将python目录路径添加到包含和库中标签。

该对象采用参数(p1.speedx,p1.speedy,p2.speedx,p2.speedy,p1.x,p1.y,p2.x,p2.y,p1.mass,p2.mass)和返回新的p1.speedx,p1.speedy,p2.speedx,p2.speedy和p1 p2之间的距离。

使用上面的模块,我还尝试通过将返回的距离与粒子半径之和进行比较,为碰撞检测切出几个步骤:

def updateForces(self):         #part of a handler class for the particles
    prevDone = []
    for i in self.planetGroup:  #planetGroup is a sprite group from pygame
        prevDone.append(i.ID)
        for j in self.planetGroup:
            if not (j.ID in prevDone):               #my particles have unique IDs
                distance = i.calcGForce( j )         #calcGForce just calls the above  
                if distance <= i.radius + j.radius:  #object and assigns the returned 
                    #collision handling goes here    #values for the x and y speed
                                                     #components to the particles

希望这会有所帮助。我欢迎任何进一步的建议或指出我的严重错误,我也是新手。

答案 4 :(得分:1)

(这可能会在评论中发表,但我没有必要的声誉)

我不知道你是如何做时间踩踏的。

P.speedx -= acceleration * xc
P.speedy -= acceleration * yc

但要在时间t + delta_t获得新的速度,你会做

P.speedx -= acceleration * xc * delta_t
P.speedy -= acceleration * yc * delta_t

然后像这样更新位置:

P.x = P.x + P.speedx * delta_t
P.y = P.y + P.speedy * delta_t

然后到你的速度关注。也许最好将粒子信息存储在一个类中,而不是存储在numpy数组中?但我认为你不能避免循环。

另外,你看过wikipedia,它描述了一些加速计算的方法。

(由于Mike的评论而编辑)