4

この回答で私に与えられたアドバイスに従って、私は重力シミュレーターにルンゲクッタ積分器を実装しました。

ただし、ソーラーシステムの1年間をシミュレートした後でも、位置はcca 110 000 kmずれており、これは許容できません。

私の最初のデータはNASAのHORIZONSシステムによって提供されました。それを通して、私は特定の時点での惑星、冥王星、衛星、デイモス、フォボスの位置と速度のベクトルを取得しました。

これらのベクトルは3Dでしたが、惑星が太陽の周りのプレートに整列しているため、3次元を無視できると言われたので、そうしました。xy座標をファイルにコピーしただけです。

これは私の改善された更新方法のコードです:

"""
Measurement units:

[time] = s
[distance] = m
[mass] = kg
[velocity] = ms^-1
[acceleration] = ms^-2
"""

class Uni:
    def Fg(self, b1, b2):
        """Returns the gravitational force acting between two bodies as a Vector2."""

        a = abs(b1.position.x - b2.position.x) #Distance on the x axis
        b = abs(b1.position.y - b2.position.y) #Distance on the y axis

        r = math.sqrt(a*a + b*b)

        fg = (self.G * b1.m * b2.m) / pow(r, 2)

        return Vector2(a/r * fg, b/r * fg)

    #After this is ran, all bodies have the correct accelerations:
    def updateAccel(self):
        #For every combination of two bodies (b1 and b2) out of all bodies:
        for b1, b2 in combinations(self.bodies.values(), 2):
            fg = self.Fg(b1, b2) #Calculate the gravitational force between them

            #Add this force to the current force vector of the body:
            if b1.position.x > b2.position.x:
                b1.force.x -= fg.x
                b2.force.x += fg.x
            else:
                b1.force.x += fg.x
                b2.force.x -= fg.x


            if b1.position.y > b2.position.y:
                b1.force.y -= fg.y
                b2.force.y += fg.y
            else:
                b1.force.y += fg.y
                b2.force.y -= fg.y

        #For body (b) in all bodies (self.bodies.itervalues()):
        for b in self.bodies.itervalues():
            b.acceleration.x = b.force.x/b.m
            b.acceleration.y = b.force.y/b.m
            b.force.null() #Reset the force as it's not needed anymore.

    def RK4(self, dt, stage):
        #For body (b) in all bodies (self.bodies.itervalues()):
        for b in self.bodies.itervalues():
            rd = b.rk4data #rk4data is an object where the integrator stores its intermediate data

            if stage == 1:
                rd.px[0] = b.position.x
                rd.py[0] = b.position.y
                rd.vx[0] = b.velocity.x
                rd.vy[0] = b.velocity.y
                rd.ax[0] = b.acceleration.x
                rd.ay[0] = b.acceleration.y

            if stage == 2:
                rd.px[1] = rd.px[0] + 0.5*rd.vx[0]*dt
                rd.py[1] = rd.py[0] + 0.5*rd.vy[0]*dt
                rd.vx[1] = rd.vx[0] + 0.5*rd.ax[0]*dt
                rd.vy[1] = rd.vy[0] + 0.5*rd.ay[0]*dt
                rd.ax[1] = b.acceleration.x
                rd.ay[1] = b.acceleration.y

            if stage == 3:
                rd.px[2] = rd.px[0] + 0.5*rd.vx[1]*dt
                rd.py[2] = rd.py[0] + 0.5*rd.vy[1]*dt
                rd.vx[2] = rd.vx[0] + 0.5*rd.ax[1]*dt
                rd.vy[2] = rd.vy[0] + 0.5*rd.ay[1]*dt
                rd.ax[2] = b.acceleration.x
                rd.ay[2] = b.acceleration.y

            if stage == 4:
                rd.px[3] = rd.px[0] + rd.vx[2]*dt
                rd.py[3] = rd.py[0] + rd.vy[2]*dt
                rd.vx[3] = rd.vx[0] + rd.ax[2]*dt
                rd.vy[3] = rd.vy[0] + rd.ay[2]*dt
                rd.ax[3] = b.acceleration.x
                rd.ay[3] = b.acceleration.y

            b.position.x = rd.px[stage-1]
            b.position.y = rd.py[stage-1]

    def update (self, dt):
        """Pushes the uni 'dt' seconds forward in time."""

        #Repeat four times:
        for i in range(1, 5, 1):
            self.updateAccel() #Calculate the current acceleration of all bodies
            self.RK4(dt, i) #ith Runge-Kutta step

        #Set the results of the Runge-Kutta algorithm to the bodies:
        for b in self.bodies.itervalues():
            rd = b.rk4data
            b.position.x = b.rk4data.px[0] + (dt/6.0)*(rd.vx[0] + 2*rd.vx[1] + 2*rd.vx[2] + rd.vx[3]) #original_x + delta_x
            b.position.y = b.rk4data.py[0] + (dt/6.0)*(rd.vy[0] + 2*rd.vy[1] + 2*rd.vy[2] + rd.vy[3])

            b.velocity.x = b.rk4data.vx[0] + (dt/6.0)*(rd.ax[0] + 2*rd.ax[1] + 2*rd.ax[2] + rd.ax[3])
            b.velocity.y = b.rk4data.vy[0] + (dt/6.0)*(rd.ay[0] + 2*rd.ay[1] + 2*rd.ay[2] + rd.ay[3])

        self.time += dt #Internal time variable

アルゴリズムは次のとおりです。

  1. システム内のすべてのボディの加速度を更新します
  2. RK4(最初のステップ)
  3. goto 1
  4. RK4(秒)
  5. goto 1
  6. RK4(3番目)
  7. goto 1
  8. RK4(4番目)

RK4の実装で何かを台無しにしましたか?または、破損したデータから始めただけですか(重要なボディが少なすぎて、3次元を無視しています)?

これはどのように修正できますか?


私のデータなどの説明...

私の座標はすべて太陽を基準にしています(つまり、太陽は(0、0)にあります)。

./my_simulator 1yr

Earth position: (-1.47589927462e+11, 18668756050.4)

HORIZONS (NASA):

Earth position: (-1.474760457316177E+11,  1.900200786726017E+10)

110 000 kmシミュレーターで予測されたものからNASAによって与えられた地球のx座標を引くことによって、エラーが発生しました。

relative error = (my_x_coordinate - nasa_x_coordinate) / nasa_x_coordinate * 100
               = (-1.47589927462e+11 + 1.474760457316177E+11) / -1.474760457316177E+11 * 100
               = 0.077%

相対誤差はごくわずかに見えますが、それは単に、私のシミュレーションとNASAの両方で、地球が太陽から本当に遠く離れているためです。距離はまだ大きく、シミュレータが役に立たなくなります。

4

6 に答える 6

4

110 000 km絶対誤差とは、どのような相対誤差を意味しますか?

予測された地球のx座標をNASAの地球のx座標から差し引くことにより、110000kmの値を取得しました。

ここで何を計算しているのか、「NASA​​の地球x座標」が何を意味しているのかわかりません。それは、どの原点から、どの座標系で、いつですか?(私が知る限り、地球は太陽の周りを周回するため、太陽を中心とする座標系でのx座標は常に変化しています。)

いずれの場合も、「NASA​​の地球x座標」から計算値を差し引いて、110,000kmの絶対誤差を計算しました。これは悪い答えだと思われるようです。あなたの期待は何ですか?それにスポットを当てるには?メートル以内にいるには?1キロ?あなたに受け入れられるものとその理由は何ですか?

誤差の差を「NASA​​の地球x座標」で割ると、相対誤差が得られます。パーセンテージと考えてください。どのような価値がありますか?1%以下の場合は、おめでとうございます。それはかなり良いでしょう。

浮動小数点数はコンピューターでは正確ではないことを知っておく必要があります。(0.1を2進数で正確に表すことは、1/3を10進数で正確に表すことができる以上にできません。)エラーが発生します。シミュレーターとしてのあなたの仕事は、エラーを理解し、可能な限りそれらを最小化することです。

ステップサイズの問題が発生する可能性があります。時間ステップのサイズを半分に減らしてみて、うまくいくかどうかを確認してください。そうした場合、結果は収束していないと表示されます。許容できるエラーが発生するまで、もう一度半分に減らします。

方程式の条件が悪い可能性があります。それが本当なら、小さな初期エラーは時間とともに増幅されます。

方程式を無次元化し、安定限界のステップサイズを計算することをお勧めします。「十分に小さい」ステップサイズがどうあるべきかについてのあなたの直感はあなたを驚かせるかもしれません。

多体問題についてももう少し読みたいと思います。微妙です。

積分スキームの代わりに数値積分ライブラリを試すこともできます。方程式をプログラムして、産業用強度積分器に渡します。それは、問題を引き起こしているのがあなたの実装なのか物理学なのかについての洞察を与えるかもしれません。

個人的に、私はあなたの実装が好きではありません。数学的なベクトルを念頭に置いてそれを行った場合、それはより良い解決策になるでしょう。相対位置の「if」テストは私を冷たくします。ベクトル力学は、兆候を自然に出させます。

アップデート:

OK、相対誤差はかなり小さいです。

もちろん、絶対的なエラーは重要です-要件によっては。惑星に乗り物を着陸させる場合、それほど離れたくないでしょう。

したがって、ステップサイズが小さすぎることを想定するのをやめ、エラーを許容レベルに引き上げるために必要なことを実行する必要があります。

計算のすべての量は64ビットIEEE浮動小数点数ですか?そうでなければ、そこにたどり着くことはありません。

64ビット浮動小数点数の精度は約16桁です。それ以上が必要な場合は、JavaのBigDecimalのような無限精度のオブジェクトを使用するか、それを待つか、キロメートル以外の長さの単位を使用するように方程式を再スケーリングする必要があります。問題に意味のあるもの(たとえば、地球の直径や地球の軌道の長軸/短軸の長さ)ですべての距離をスケーリングすると、より良い結果が得られる可能性があります。

于 2013-03-02T16:58:24.110 に答える
4

太陽系のRK4統合を行うには、非常に優れた精度が必要です。そうしないと、ソリューションがすぐに発散します。すべてが正しく実装されていると仮定すると、この種のシミュレーションではRKの欠点が見られる場合があります。

これが当てはまるかどうかを確認するには、別の統合アルゴリズムを試してください。Verlet統合を使用すると、太陽系シミュレーションの混乱がはるかに少なくなることがわかりました。Verletは、RK4よりも実装がはるかに簡単です。

Verlet(および派生メソッド)が長期予測(全軌道など)でRK4よりも優れていることが多い理由は、それらがシンプレクティックである、つまりRK4がそうではない運動量を保存するためです。したがって、Verletは発散した後でもより良い動作を示します(現実的なシミュレーションですが、エラーがあります)が、RKは発散すると非物理的な動作を示します。

また、浮動小数点をできるだけ適切に使用していることを確認してください。浮動小数点数の精度は0..1間隔ではるかに優れているため、太陽系ではメートル単位の距離を使用しないでください。AUまたはその他の正規化されたスケールを使用する方が、メーターを使用するよりもはるかに優れています。他のトピックで提案されているように、タイムステップを追加するときにエラーが蓄積しないように、時間のエポックを使用するようにしてください。

于 2013-03-02T19:15:52.907 に答える
4

このようなシミュレーションは、信頼性が低いことで有名です。丸め誤差が蓄積され、不安定になります。精度を上げてもあまり役に立ちません。問題は、有限のステップサイズを使用する必要があり、自然はゼロのステップサイズを使用することです。

ステップサイズを小さくすることで問題を減らすことができるため、エラーが明らかになるまでに時間がかかります。これをリアルタイムで行わない場合は、動的ステップサイズを使用できます。これにより、2つ以上のボディが非常に接近している場合に減少します。

これらの種類のシミュレーションで私が行うことの1つは、各ステップの後に「再正規化」して、総エネルギーを同じにすることです。システム全体の重力エネルギーと運動エネルギーの合計は一定である必要があります(エネルギー保存)。各ステップの後に総エネルギーを計算し、次にすべてのオブジェクト速度を一定量でスケーリングして、総エネルギーを一定に保ちます。これにより、少なくとも出力がより妥当に見えるようになります。このスケーリングがないと、各ステップの後にわずかな量のエネルギーがシステムに追加またはシステムから削除され、軌道は無限大に爆発するか、太陽に向かってらせん状になる傾向があります。

于 2013-03-03T07:14:10.583 に答える
3

物事を改善する非常に単純な変更(浮動小数点値の適切な使用法)

  • できるだけ多くの仮数ビットを使用するように、単位系を変更します。メーターを使用すると、間違っています...上記のように、AUを使用してください。さらに良いことに、太陽系が1x1x1の箱に収まるように物事を拡大縮小します
  • すでに別の投稿で述べています:あなたの時間、それを追加するのではなく、time = epoch_count *time_stepとして計算してください!このようにして、エラーの蓄積を回避します。
  • 複数の値の合計を行う場合は、Kahan合計などの高精度の合計アルゴリズムを使用してください。Pythonでは、math.fsumがそれを行います。
于 2013-05-12T02:29:34.257 に答える
1

力の分解はすべきではありません

th = atan(b, a);
return Vector2(cos(th) * fg, sin(th) * fg)

http://www.physicsclassroom.com/class/vectors/Lesson-3/Resolution-of-Forcesまたはhttps://fiftyexamples.readthedocs.org/en/latest/gravity.html#solutionを参照してください)

ところで:あなたは距離を計算するために平方根を取ります、しかしあなたは実際には二乗された距離を必要とします...

単純化してみませんか

 r = math.sqrt(a*a + b*b)
 fg = (self.G * b1.m * b2.m) / pow(r, 2)

 r2 = a*a + b*b
 fg = (self.G * b1.m * b2.m) / r2

Pythonについてはよくわかりませんが、中間結果のより正確な計算が得られる場合があります(Intel CPUは80ビットのfloatをサポートしていますが、変数に割り当てると、64ビットに切り捨てられます): 中間に格納されている場合、浮動小数点の計算が変更されます。 double"変数

于 2016-01-27T23:55:02.963 に答える
0

どの基準系で惑星座標と速度を持っているかははっきりしていません。それがヘリオセントリックフレームにある場合(参照フレームが太陽に関連付けられている場合)、2つのオプションがあります:(i)非慣性参照フレームで計算を実行する(太陽は静止していない)、(ii)位置を変換する慣性座標系への速度。座標と速度が重心座標系にある場合は、太陽の座標と速度も必要です。

于 2018-04-04T23:01:55.957 に答える