C++ 中的 N 体模拟具有很大的动量守恒和巨大的能量偏差

N-body simulation in C++ has great momentum conservation and huge energy deviation

提问人:Malmel 提问时间:11/12/2023 最后编辑:Malmel 更新时间:11/14/2023 访问量:111

问:

我正在使用 Verlet 积分(更具体地说,Verlet 积分维基百科页面中“非恒定时差”部分中的最后一个方程)与 C++ 和 Eigen 库一起创建数值 N 体模拟。

我试过测量模拟的动量和能量守恒。对于动量守恒,即使对于物体非常接近和快速的系统,我也得到了大约10^(-7)%的偏差,与最初计算的动量。至于能量,我看到它一路跃升到800%。它开始时很低,约为 0.0001%,但随着模拟的进行,它会变得更大,当物体非常接近时,它会达到 100%+。

(澄清一下,我所说的偏差是指 (y/x) - 1,其中 y 是当前值,x 是初始值。

我的程序很好地保持了动量,但能量却完全关闭了,这似乎非常非常奇怪。

根据我在 Meta StackOverflow 上读到的内容,不建议发布大量代码。我在这里包括了我正在做的事情的基本要素(Verlet 集成实现、重力计算)。这里有一个最小的可重现示例的 Github 存储库,如果有人想查看更精细的细节。

    void verletNextStep(double dt) {

        // as per wikipedia
        if (!firstStepDone) {
            lastPosition = position;
            position = lastPosition
                       + velocity * dt
                       + 0.5 * acceleration * dt * dt;
            lastDt = dt;
            firstStepDone = true;
            return;
        }

        Vector2<long double> nextPosition = position
                                + (position - lastPosition) * dt/lastDt
                                + acceleration * dt * (dt + lastDt)/2;
        lastDt = dt;

        lastPosition = position;
        position = nextPosition;

        velocity = (position - lastPosition)/dt;

    };
    Vector2<long double> totalGravitationalAccelerationOf(Body &body) {

        Vector2<long double> acc(0, 0);

        for (Body &otherBody : bodyList)
            if (body.vectorTo(otherBody).norm() != 0)
                acc += gravitationalAccelerationOf(body, otherBody);

        return acc;
    };

    //acceleration exerted by body2, on body1
    Vector2<long double> gravitationalAccelerationOf(Body &body1, Body &body2) {

        Vector2<long double> rHat = body1.vectorTo(body2).normalized();
        long double rSquared = body1.vectorTo(body2).squaredNorm();

        return rHat * G * body2.mass/rSquared;
    };

    //executes verlet integration on all bodies
    void doVerlet(double dt) {

        for (Body &body : bodyList)
            body.acceleration = totalGravitationalAccelerationOf(body);


        for (Body &body : bodyList)
            body.verletNextStep(dt);

    }

我有理由怀疑不准确是由于双打溢出造成的。我把所有东西都改成了长双打,并在能量低得多的系统上进行了测试,但偏差仍然存在。 此外,另一篇文章提出了一个类似的问题,但在他们的案例中,解决方案是加速度的有序计算(中间没有更新物体),我已经实现了,如上面的代码所示。

C++ 精密 博弈-物理 数值方法 微变积分

评论

0赞 Quimby 11/12/2023
(position - lastPosition) * dt/lastDt,不应该吗?我的意思是你的变量未使用velocity * dtvelocity
1赞 Malmel 11/12/2023
我想你可以这样写,但由于速度最初不是 Verlet 公式的一部分,我更喜欢只使用常规版本。
0赞 Ripi2 11/12/2023
我对 N-body 知之甚少,最主要的是它是一个混沌系统,数字不准确性大大增加。
0赞 gnasher729 11/12/2023
如果 double 和 long double 给出的结果非常相似,那么这不是舍入错误,而是您的公式实际计算的内容。
0赞 gnasher729 11/12/2023
通常,您会将位置和速度作为两个变量,其中 pos' = vel 和 vel' = 加速度,并通过求解器运行这两个变量。您将失去求解器的所有精度增益。

答:

0赞 Lutz Lehmann 11/12/2023 #1

在势能中,每个相互作用只包含在势能中一次。目前,您将交互作用计算为对整个主体列表的双重迭代,以便计算每对的交互势两次。因此,要么在计算中纠正它,要么将结果除以 2。


顺便说一句,IMO 对 Verlet 可变时间步长的整个想法是铺位。准能量守恒特性主要取决于固定的时间步长,几乎恒定的仿行能量具有取决于步长的扰动项。使用不同的步长,您将获得保持恒定的不同仿制能量,因此第一个仿制能量可以改变。在从小步长到大步长的周期性变化中,这些能量差异会累积

动量表现得更好一些,因为辛方法步骤完全保留了状态变量中的线性和二次表达式。

通过n体模拟,你通常无法避免物体接近碰撞,在这些时候,即使是固定步长的Verlet也会崩溃,因为ersatz能量的扰动项比能量函数本身的奇点更差,通常人们会从系统中得到无意义的爆炸。

对于具有高偏心率的双体问题,即非常椭圆的非圆形轨道,人们会得到类似的发散。

评论

0赞 Malmel 11/12/2023
我试图在谷歌上搜索“ersatz energy”,但一无所获。无论如何,我的最小可重复示例(显示在 Github 链接中)使用固定的时间步长,但仍然不能保存能量。它也(根据我使用 sfml 的可视化)没有非常接近的身体。使用 RK4 可以解决这个问题吗?
0赞 Lutz Lehmann 11/12/2023
那只是我是德国人,并且对“ersatz”和“ansatz”是专有的英语单词感到有趣。寻找关于几何积分的海勒论文和书籍,他们在那里讨论如何找到沿数值解常数的哈密顿量的扰动。
0赞 Malmel 11/12/2023
明白了。我的母语不是英语,所以我不知道“ersatz”是一个真实的词,哈哈。我试过阅读这篇论文,虽然其中大部分超出了我的范围,但让我印象深刻的部分是图 3.1,它们表明 Verlet 积分与哈密顿量有周期性偏差,并且没有漂移。这与我的模拟形成鲜明对比。我还查看了您的其他答案之一,您提到插管的顺序为 O(h^2),“具有正确的初始化”,这不是我的情况。同样,在我的 MRE 中,步长小且均匀,h=0.001。那么,为什么这种巨大的偏差会持续存在呢?
0赞 Lutz Lehmann 11/13/2023
您正在计算两次势能。这减少了一些观察到的效果,但可能不是全部。
0赞 Malmel 11/13/2023
谢谢,这似乎已经做到了。能量守恒现在比动量更好,使用 Ruth 4 算法(我实现该算法以尝试调试问题),它大约为 10^-17。