2

モデリングとシミュレーション クラスのプロジェクトで、太陽系をシミュレートしたいと考えています。私は星 (太陽) と惑星 (地球) から始めていますが、すでにいくつかの問題に直面しています。私は今、惑星の軌道が星と周囲の物体によってどのように影響を受けるかをシミュレートするためのさまざまな式と方法について確認し、学習することに時間を費やしました. 速度ベルレットを使用して、最終的に n 体の問題を調べたいと考えています。Velocity 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

インテグレーターがスカラーを受け入れ、質問が 2 次元システムに関するものであるという事実から、コンポーネントごとに 1 回ずつ、インテグレーターを 2 回呼び出していると思います。システムが位相空間を非現実的に移動するため、これは単に機能しません。積分器はベクトル量を操作します。

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) はすべての粒子の速度の列ベクトルであり、技術的には運動量部分空間を表します。(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;
   }
}

次のラウンドのフォース評価の前に、すべての位置が最初に更新されることに注意してください。また、速度の 2 回目の更新中に位置が変更されないため、反復ごとに 1 回だけ力を評価する必要があります。上記のコード例Vectorは、n 次元のベクトルを実装し、nコンポーネントを保持するクラスです (たとえば、2 次元の場合は 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は 2D 空間での角度CelesitalObject) = dx/r であるため、これを a_x = F*dx/(m*r) にすることができますが、これはほとんどではありますが、得られたものとはまったく異なります。あなたの計算では(除数に r がありません)。

別のアプローチとして を使用するstd::complexこともできますが、このモデルを 3 次元に拡張することを想定して、そのアプローチについては説明しません。

これにより、コードの問題が発生します。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());
}

最後に実際のベルレット:

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.1 秒のタイム ステップを使用しますが、これは太陽系には短すぎますが、既知のシステムで妥当な結果が得られるかどうかを確認するこのクイック テストには問題ありません。この場合、惑星地球と金床からなる 2 体システムを選択しました。このコードは、空気抵抗を無視した場合、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 になる必要があることです。これは、このコードが数値解析の観点からあまり適切に設計されていないためではありません。ある数値が大きく、別の数値が小さい場合に浮動小数点数の加算と減算を行うことは、1 つの問題です。もう 1 つは、atan2fa のみを返す関数をfloat使用し、その結果をcos()andで使用することsin()です。三角関数は、実際には可能であれば避けるのが最善です。

最後に、このプログラムは現在 2 つのオブジェクトでしか動作しません。この種のスキームでは 3 分の 1 を追加するのは面倒なので、他のすべての位置と質量を考慮して各オブジェクトにかかる正味の力std::vector<CelestialObject>を最初に計算することにより、a を処理することをお勧めします。それはあなたに任せますが、これで少なくとも正しい方向へのスタートが切れるはずです。

于 2014-04-05T14:38:35.960 に答える