2

对于我的建模和模拟类项目,我想模拟一个太阳系。我从一颗恒星(太阳)和一颗行星(地球)开始,但我已经遇到了一些问题。我现在花了一些时间来回顾和学习不同的公式和方法来模拟行星的轨道将如何受到恒星和周围物体的影响。我想使用velocity verlet并最终研究n体问题。我的速度 verlet 功能有很多问题。有时它的行为就好像它正在正常运行地球轨道,然后它“扭曲驱动”地球进入某个随机位置。我还注意到我从来没有得到“负”加速度,所以我的 x 和 y 坐标。一直在增加,所以我看不到地球应该如何环绕太阳。任何帮助是极大的赞赏。我刚刚拥有的 AGK::Prints 可以看到不同的变量是如何变化的。

double velocityVerlet(float positionCalc, double position2, 
                      float &velocity, double massCalc, double mass2)
//positionCalc is the position being updated, position 2 is position of 
// other object, same with mass
{
    float force = forceFunc(positionCalc, position2, massCalc, mass2);
    agk::PrintC("Force is: ");
    agk::Print(force);
    float acceleration = accelerationFunc(force,massCalc);
    agk::PrintC("Accel is: ");
    agk::Print(acceleration);`;

    double newAccel = 0;

    positionCalc = positionCalc + velocity*dt + 
                   (.5*acceleration)*pow(dt,2); //calculates new position
    agk::PrintC("New Position is: ");
    agk::Print(positionCalc);
    force = forceFunc(positionCalc,position2,massCalc,mass2);
    newAccel = accelerationFunc(force, massCalc);

    velocity = velocity + .5*(acceleration + newAccel)*dt; //new velocity
    agk::PrintC("Velocity is: ");
    agk::Print(velocity);

    return positionCalc;
}
4

2 回答 2

3

您的积分器接受标量并且您的问题是关于二维系统的事实使我认为您两次调用积分器,每个组件一次。这根本行不通,因为您的系统将在相空间中采取不切实际的移动。积分器使用向量:

X (t+dt) = X (t) + V (t) dt + (1/2) A (t) dt 2

V (t+dt) = V (t) + (1/2)( A (t) + A (t+dt)) dt

这里X (t) 是一个列向量,由所有粒子的坐标组成 - 这是系统相空间的配置子空间。V (t) 是所有粒子速度的列向量,技术上表示动量子空间。这同样适用于A (t)。这些必须同时更新,而不是单独更新。

整个速度 Verlet 程序在不依赖于速度(例如经典重力)的力场的代码中转换如下:

Vector forces[num_particles];

// Compute initial forces
forces = computeForces(positions);

for (int ts = 0; ts < num_timesteps; ts++)
{
   // Update positions and half-update velocities
   for (int i = 0; i < num_particles; i++)
   {
      positions[i] += velocities[i]*dt + 0.5*(forces[i] / m[i]) * dt*dt;
      velocities[i] += 0.5*(forces[i] / m[i]) * dt;
   }

   // Compute new forces and half-update velocities
   forces = computeForces(positions);

   for (int i = 0; i < num_particles; i++)
   {
      velocities[i] += 0.5*(forces[i] / m[i]) * dt;
   }
}

请注意,在下一轮力评估之前首先更新所有位置。此外,每次迭代只需评估一次力,因为在第二次更新速度期间位置不会改变。在上面的示例代码中,Vector是一个实现 n 维向量并保存n组件的类(例如,在您的 2d 案例中为 2)。它还重载了+and+=运算符以实现向量(按分量)加法,以及*实现/标量的乘法/除法。这只是为了说明这种情况,可以用每个位置/速度矢量的分量上的内部循环代替。

时间步长的正确选择非常重要。时间步太小会显着减慢模拟速度。太大的时间步长会导致不可能的物理,例如跳跃的行星。

于 2014-03-30T21:52:21.270 回答
2

物理有一些问题,代码有一些问题。

首先,物理问题。假设我们不是在模拟一个物理定律不同的替代宇宙,牛顿的万有引力定律表明 F=G*m1*m2/(r*r)。然而,力是一个向量,而不是一个标量,所以它既有大小又有方向。

代码计算的forceFuncX实际上是大小,而不仅仅是平行于 X 轴的力的分量。 forceFuncY有同样的缺陷。

接下来是加速度的计算。物理学说这是a = F / m。质量是标量,但加速度和力都是矢量。因此,要计算 a_x,我们可以使用 F_x/m,也可以计算 F*cos( a )/m。因为 cos( a )(a是二维空间中一个到另一个的角度CelesitalObject)= dx/r,我们可以使这个 a_x = F*dx/(m*r) 几乎但不完全是你得到的在您的计算中(除数中缺少 r)。

另一种方法是使用std::complex,但我不会在假设您可能希望将此模型扩展到三个维度的情况下展示该方法。

这给我们带来了代码问题。CelestialObject首先,由于您正在使用 C++ 并编写离散对象物理系统的模拟,因此您定义了一个类是有意义的。不太有意义的是,您的函数是通过挑选这些对象的各个部分然后调用 C 样式函数来调用的。我们可以通过更好地使用这些对象来改进代码。首先,由于您还没有发布,这里有一个CelestialObject基于我从您的代码中推断出的接口的类:

class CelestialObject 
{
public:
    CelestialObject(std::string name, float mass, float X, float Y, 
        float VX, float VY)
            : myname(name), m(mass), x(X), y(Y), vx(VX), vy(VY) {}
    void setPosition(float X, float Y) { x=X; y=Y; }
    void setVelocity(float VX, float VY) { vx=VX; vy=VY; }
    float getMass() const { return m; } 
    float getX() const { return x; } 
    float getY() const { return y; } 
    float getVX() const { return vx; } 
    float getVY() const { return vy; } 
    friend std::ostream& operator<<(std::ostream& out, 
                                    const CelestialObject& obj) {
        return out << obj.myname << '\t' << obj.x << '\t' << obj.y 
                   << '\t' << obj.vx << '\t' << obj.vy << std::endl;
    }
private:
    std::string myname;
    float m, x, y;
    float vx, vy;
};

接下来,一些辅助函数:

// returns square of distance between objects
float distance_sq(const CelestialObject &a, const CelestialObject &b)
{
    // distance squared is (dy^2 + dx^2)
    return pow(a.getY()-b.getY(),2) + pow(a.getX()-b.getX(),2);
}

// returns magnitude of the force between the objects
float force(const CelestialObject &a, const CelestialObject &b)
{
    //  F=(G * m1 * m1)/(r^2) in the direction a->b and b->a
    return G*a.getMass()*b.getMass()/distance_sq(a, b);
}

// returns the angle from a to b
float angle(const CelestialObject &a, const CelestialObject &b)
{
    return atan2f(b.getY()-a.getY(),b.getX()-a.getX());
}

最后是实际的verlet:

void updatePosition(CelestialObject &a, CelestialObject &b )
{ 
    float F = force(a,b);
    float theta = angle(a,b);
    float accela = F/a.getMass();
    float accelb = -F/b.getMass();

    // now that we have the acceleration of both objects, update positions
    // x = x +v *dt + a*dt*dt/2
    //   = x + dt * (v + a*dt/2)
    a.setPosition(
     a.getX() + dt * (a.getVX() + accela*cos(theta)*dt/2),
     a.getY() + dt * (a.getVY() + accela*sin(theta)*dt/2) 
    );
    b.setPosition(
     b.getX() + dt * (b.getVX() + accelb*cos(theta)*dt/2),
     b.getY() + dt * (b.getVY() + accelb*sin(theta)*dt/2)
    );
    // get new acceleration a'
    F = force(a,b);
    float thetap = angle(a,b);
    float accelap = F/a.getMass();
    float accelbp = -F/b.getMass();
    // and update velocities
    // v = v + (a + a')*dt/2
    a.setVelocity(
     a.getVX() + (accela*cos(theta) + accelap*cos(thetap))*dt/2,
     a.getVY() + (accela*sin(theta) + accelap*sin(thetap))*dt/2
    );
    b.setVelocity(
     b.getVX() + (accelb*cos(theta) + accelbp*cos(thetap))*dt/2,
     b.getVY() + (accelb*sin(theta) + accelbp*sin(thetap))*dt/2
    );
}

最后是一些简单的测试代码。

#include <string>
#include <iostream>
#include <vector>
#include <cmath>

const float G(6.67e-11);  // N*(m/kg)^2
const float dt(0.1);      // s
// all of the other code goes here...
int main()
{
    CelestialObject anvil("anvil", 70, 370, 0, 0, 0);
    CelestialObject earth("earth", 5.97e+24, -6.378e6, 0, 0, 0);
    std::cout << "Initial values:\n" << earth << anvil;
    std::cout << "Dropping an anvil from the top of a 370m building...\n"
              "It should hit the ground in about 8.7 seconds.\n";
    int t;
    for (t=0; anvil.getX() > 0; ++t) {
    std::cout << dt*t << '\t' << anvil; 
    updatePosition(anvil, earth);
    }
    std::cout << "Final values at t = " << dt*t << " seconds:\n" 
              << earth << anvil;
    return 0;
}

测试代码使用 0.1s 的时间步长,这对于你的太阳系来说太短了,但对于这个快速测试来说很好,看看我们是否能得到一个已知系统的合理结果。在这种情况下,我选择了由地球和铁砧组成的二体系统。这段代码模拟了从 370m 建筑物顶部掉落的铁砧,如果我们忽略空气阻力,我们可以很容易地计算出它会在大约 8.7 秒内落地。为了保持坐标简单,我选择将原点 (0,0) 放置在地球表面,并认为建筑物的顶部位于 (370,0)。当代码编译并运行时,它会产生以下内容:

Initial values:
earth   -6.378e+06  0   0   0
anvil   370 0   0   0
Dropping an anvil from the top of a 370m building...
It should hit the ground in about 8.7 seconds.
0   anvil   370 0   0   0
0.1 anvil   369.951 -4.27834e-09    -0.97877    -8.55668e-08
0.2 anvil   369.804 -1.71134e-08    -1.95754    -1.71134e-07
0.3 anvil   369.56  -3.85051e-08    -2.93631    -2.567e-07
   ...
8.3 anvil   32.8567 -2.9474e-05 -81.2408    -7.1023e-06
8.4 anvil   24.6837 -3.01885e-05    -82.2197    -7.18787e-06
8.5 anvil   16.4127 -3.09116e-05    -83.1985    -7.27345e-06
8.6 anvil   8.04394 -3.16432e-05    -84.1774    -7.35902e-06
Final values at t = 8.7 seconds:
earth   -6.378e+06  3.79705e-28 9.98483e-22 8.72901e-29
anvil   -0.422744   -3.23834e-05    -85.1563    -7.4446e-06

如您所见,这似乎可行,但存在问题。第一个问题是,由于对象应该只沿 X 轴移动,所有 Y 分量都应该为 0。它们不是因为从数值分析的角度来看,这段代码设计得不是很好。当一个数字很大而另一个数字很小时,对浮点数进行加法和减法是一个问题。另一个是使用atan2f仅返回 a 的函数,float然后在cos()and中使用该结果sin()。如果可能,实际上最好避免使用三角函数。

最后,该程序目前仅适用于两个对象。使用这种方案添加三分之一会很痛苦,因此更好的设计是std::vector<CelestialObject>通过首先考虑所有其他对象的位置和质量来计算每个对象上的净力来处理 a。我会把它留给你,但这至少应该让你朝着正确的方向开始。

于 2014-04-05T14:38:35.960 回答