【问题标题】:Optimizing gravitation calculation for particles in a zero gravity 2d space优化零重力二维空间中粒子的重力计算
【发布时间】:2011-10-30 05:39:07
【问题描述】:

我已经在 python 中创建了一个小的粒子可视化。 我正在计算粒子在零重力的二维空间中的运动。 因为每个粒子根据粒子质量和距离吸引所有其他粒子。

我在 pygame 中进行了可视化,一切都按计划进行(使用计算),但是我需要优化计算。今天,该系统可以以正常的帧速率计算大约 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)

【问题讨论】:

  • 你考虑过PsycoWriting C/C++ module吗?
  • 本文回顾了优化引力模拟的常用方法,包括 Barnes-Hut。专业人士通常在 3D 中进行,但我相信 2D 案例都是类似的。 cs.hut.fi/~ctl/NBody.pdf
  • 如果您对数学不满意(“我不是科学家或数学大师,我只是感到困惑”),那么我认为您需要寻找一个可以做到这一点的库。见stackoverflow.com/questions/6381137/python-physics-librarystackoverflow.com/questions/2298517/…
  • @nagisa 我会更深入地了解 Psyco,感谢您提供的链接。它可能最终会导致我编写一个 c/c++ 模块,但首先我想了解它是如何工作的。
  • @Russell Borogove 谢谢,现在我的睡前阅读已经整理了一段时间

标签: python optimization numpy physics


【解决方案1】:

不确定这是否会对您有很大帮助,但它是我一直在为同一问题努力的解决方案的一部分。我没有注意到这样做的性能有很大的提高,仍然开始停止大约 200 个粒子,但也许它会给你一些想法。

用于计算二维平面上引力的 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

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

【讨论】:

    【解决方案2】:

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

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

    对于所有 z/z' 对,NumPy 可以非常快速地计算出这样的数量。例如,所有 zz' 值的矩阵简单地从坐标为Z-Z[:, numpy.newaxis] 的一维数组Z 获得(对角线项 [z=z'] 在计算1/abs(z-z')**3 时需要特别注意:它们应该设置为零)。

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

    无论如何,深入研究 NumPy 将非常有用,特别是如果您打算进行科学计算,因为 NumPy 非常快。

    【讨论】:

    • 这个等式看起来不错,它是否可以推广到 3D 情况?因为这似乎只适用于 Ztripez 在上面发布的 2D 版本。
    • 对 3D 的推广只是牛顿平方反比定律的向量形式:用 3D 向量 M 和 M' 定义两个点,M 对 M' 的力为 (M-M') /|M-M'|**3... 复数的好处在于它们非常像二维向量。
    【解决方案3】:

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

    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)
    

    【讨论】:

    • 啊,这看起来像是我要求的。但是我不知道如何继续这个
    【解决方案4】:

    (这可能应该放在评论中,但我没有这样做所需的声誉)

    我不明白你是如何进行时间步进的。你有

    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 的评论进行了编辑)

    【讨论】:

    • xcyc 变量是从粒子 i 指向 j 的单位向量的分量。
    • @Mike:我明白了。但是,acceleration_x -= 加速度 * xc。我猜 Ztripez 正在做的是隐式假设时间步长为 1。
    • 确实如此。正如您所建议的,“正确”的方法是显式包含一个 dt 参数。既然他没有,就好像他在一个单位时间内向前迈进了一步。我强烈建议他包含一个 step 参数,因为 1 是一个非常大的单位,可以及时向前,特别是因为他使用的是 Forward Euler 方法。
    【解决方案5】:

    我以前做过这方面的工作,过去我见过的加速碰撞计算的方法之一是实际存储附近粒子的列表。

    基本上,这个想法是在你的重力计算中,你可以这样做:

    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)。大多数八叉树实现都包含一个“开放参数”,用于控制您将要进行的速度与准确性之间的权衡。因此,八叉树往往不如直接成对计算准确且编码复杂,但它们也使大规模模拟成为可能。

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

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

    【讨论】:

    • 巴恩斯小屋模拟看起来很有趣
    猜你喜欢
    • 1970-01-01
    • 2014-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多