提问人:Malmel 提问时间:11/12/2023 最后编辑:Malmel 更新时间:11/14/2023 访问量:111
C++ 中的 N 体模拟具有很大的动量守恒和巨大的能量偏差
N-body simulation in C++ has great momentum conservation and huge energy deviation
问:
我正在使用 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);
}
我有理由怀疑不准确是由于双打溢出造成的。我把所有东西都改成了长双打,并在能量低得多的系统上进行了测试,但偏差仍然存在。 此外,另一篇文章提出了一个类似的问题,但在他们的案例中,解决方案是加速度的有序计算(中间没有更新物体),我已经实现了,如上面的代码所示。
答:
在势能中,每个相互作用只包含在势能中一次。目前,您将交互作用计算为对整个主体列表的双重迭代,以便计算每对的交互势两次。因此,要么在计算中纠正它,要么将结果除以 2。
顺便说一句,IMO 对 Verlet 可变时间步长的整个想法是铺位。准能量守恒特性主要取决于固定的时间步长,几乎恒定的仿行能量具有取决于步长的扰动项。使用不同的步长,您将获得保持恒定的不同仿制能量,因此第一个仿制能量可以改变。在从小步长到大步长的周期性变化中,这些能量差异会累积
动量表现得更好一些,因为辛方法步骤完全保留了状态变量中的线性和二次表达式。
通过n体模拟,你通常无法避免物体接近碰撞,在这些时候,即使是固定步长的Verlet也会崩溃,因为ersatz能量的扰动项比能量函数本身的奇点更差,通常人们会从系统中得到无意义的爆炸。
对于具有高偏心率的双体问题,即非常椭圆的非圆形轨道,人们会得到类似的发散。
评论
(position - lastPosition) * dt/lastDt
,不应该吗?我的意思是你的变量未使用velocity * dt
velocity