演習:2D軌道力学シミュレーション(Python)


12

事前にちょっとした免責事項:私は天文学やその問題に関して(ITでさえも)正確な科学を勉強したことがないので、独学でこのギャップを埋めようとしています。天文学は私の注目を集めた分野の1つであり、独学の私の考えは応用アプローチに向かっています。要するに、これは軌道シミュレーションモデルであり、時間/気分があるときに気軽に取り組んでいます。私の主な目標は、他の惑星への宇宙船の打ち上げを計画する能力と運動中の完全な太陽系を作成することです。

このプロジェクトをいつでも自由に選んで、実験を楽しんでください!

更新!!!(Nov10)

  • 速度は適切なdeltaVになり、追加のモーションを与えることで速度の合計ベクトルが計算されるようになりました
  • モーション内のユニットオブジェクトがすべてのソースからの重力ベクトルをチェックする(および衝突をチェックする)たびに、必要な数の静的オブジェクトを配置できます。
  • 計算のパフォーマンスが大幅に向上しました
  • matplotlibのインタラクティブなmodを説明する修正。これはipython専用のデフォルトオプションのようです。通常のpython3では、そのステートメントが明示的に必要です。

基本的に、地球の表面から宇宙船を「発射」し、giveMotion()を介してdeltaVベクトル補正を行うことにより、月へのミッションを計画することが可能になりました。次に、グローバル時間変数を実装して同時運動を可能にしようとしています。たとえば、月が地球を周回し、宇宙船が重力支援操縦を試みます。

改善のためのコメントと提案はいつでも歓迎します!

matplotlibライブラリを使用してPython3で実行

import matplotlib.pyplot as plt
import math
plt.ion()

G = 6.673e-11  # gravity constant
gridArea = [0, 200, 0, 200]  # margins of the coordinate grid
gridScale = 1000000  # 1 unit of grid equals 1000000m or 1000km

plt.clf()  # clear plot area
plt.axis(gridArea)  # create new coordinate grid
plt.grid(b="on")  # place grid

class Object:
    _instances = []
    def __init__(self, name, position, radius, mass):
        self.name = name
        self.position = position
        self.radius = radius  # in grid values
        self.mass = mass
        self.placeObject()
        self.velocity = 0
        Object._instances.append(self)

    def placeObject(self):
        drawObject = plt.Circle(self.position, radius=self.radius, fill=False, color="black")
        plt.gca().add_patch(drawObject)
        plt.show()

    def giveMotion(self, deltaV, motionDirection, time):
        if self.velocity != 0:
            x_comp = math.sin(math.radians(self.motionDirection))*self.velocity
            y_comp = math.cos(math.radians(self.motionDirection))*self.velocity
            x_comp += math.sin(math.radians(motionDirection))*deltaV
            y_comp += math.cos(math.radians(motionDirection))*deltaV
            self.velocity = math.sqrt((x_comp**2)+(y_comp**2))

            if x_comp > 0 and y_comp > 0:  # calculate degrees depending on the coordinate quadrant
                self.motionDirection = math.degrees(math.asin(abs(x_comp)/self.velocity))  # update motion direction
            elif x_comp > 0 and y_comp < 0:
                self.motionDirection = math.degrees(math.asin(abs(y_comp)/self.velocity)) + 90
            elif x_comp < 0 and y_comp < 0:
                self.motionDirection = math.degrees(math.asin(abs(x_comp)/self.velocity)) + 180
            else:
                self.motionDirection = math.degrees(math.asin(abs(y_comp)/self.velocity)) + 270

        else:
            self.velocity = self.velocity + deltaV  # in m/s
            self.motionDirection = motionDirection  # degrees
        self.time = time  # in seconds
        self.vectorUpdate()

    def vectorUpdate(self):
        self.placeObject()
        data = []

        for t in range(self.time):
            motionForce = self.mass * self.velocity  # F = m * v
            x_net = 0
            y_net = 0
            for x in [y for y in Object._instances if y is not self]:
                distance = math.sqrt(((self.position[0]-x.position[0])**2) +
                             (self.position[1]-x.position[1])**2)
                gravityForce = G*(self.mass * x.mass)/((distance*gridScale)**2)

                x_pos = self.position[0] - x.position[0]
                y_pos = self.position[1] - x.position[1]

                if x_pos <= 0 and y_pos > 0:  # calculate degrees depending on the coordinate quadrant
                    gravityDirection = math.degrees(math.asin(abs(y_pos)/distance))+90

                elif x_pos > 0 and y_pos >= 0:
                    gravityDirection = math.degrees(math.asin(abs(x_pos)/distance))+180

                elif x_pos >= 0 and y_pos < 0:
                    gravityDirection = math.degrees(math.asin(abs(y_pos)/distance))+270

                else:
                    gravityDirection = math.degrees(math.asin(abs(x_pos)/distance))

                x_gF = gravityForce * math.sin(math.radians(gravityDirection))  # x component of vector
                y_gF = gravityForce * math.cos(math.radians(gravityDirection))  # y component of vector

                x_net += x_gF
                y_net += y_gF

            x_mF = motionForce * math.sin(math.radians(self.motionDirection))
            y_mF = motionForce * math.cos(math.radians(self.motionDirection))
            x_net += x_mF
            y_net += y_mF
            netForce = math.sqrt((x_net**2)+(y_net**2))

            if x_net > 0 and y_net > 0:  # calculate degrees depending on the coordinate quadrant
                self.motionDirection = math.degrees(math.asin(abs(x_net)/netForce))  # update motion direction
            elif x_net > 0 and y_net < 0:
                self.motionDirection = math.degrees(math.asin(abs(y_net)/netForce)) + 90
            elif x_net < 0 and y_net < 0:
                self.motionDirection = math.degrees(math.asin(abs(x_net)/netForce)) + 180
            else:
                self.motionDirection = math.degrees(math.asin(abs(y_net)/netForce)) + 270

            self.velocity = netForce/self.mass  # update velocity
            traveled = self.velocity/gridScale  # grid distance traveled per 1 sec
            self.position = (self.position[0] + math.sin(math.radians(self.motionDirection))*traveled,
                             self.position[1] + math.cos(math.radians(self.motionDirection))*traveled)  # update pos
            data.append([self.position[0], self.position[1]])

            collision = 0
            for x in [y for y in Object._instances if y is not self]:
                if (self.position[0] - x.position[0])**2 + (self.position[1] - x.position[1])**2 <= x.radius**2:
                    collision = 1
                    break
            if collision != 0:
                print("Collision!")
                break

        plt.plot([x[0] for x in data], [x[1] for x in data])

Earth = Object(name="Earth", position=(50.0, 50.0), radius=6.371, mass=5.972e24)
Moon = Object(name="Moon", position=(100.0, 100.0), radius=1.737, mass = 7.347e22)  # position not to real scale
Craft = Object(name="SpaceCraft", position=(49.0, 40.0), radius=1, mass=1.0e4)

Craft.giveMotion(deltaV=8500.0, motionDirection=100, time=130000)
Craft.giveMotion(deltaV=2000.0, motionDirection=90, time=60000)
plt.show(block=True)

使い方

それはすべて、次の2つに要約されます。

  1. Earth = Object(name="Earth", position=(50.0, 50.0), radius=6.371, mass=5.972e24)グリッド上の位置のパラメーター(グリッドの1単位はデフォルトで1000kmですが、これも変更できます)、グリッド単位の半径、kg単位の質量でオブジェクトを作成します。
  2. 前のポイントで述べたように、最初にオブジェクトを作成Craft.giveMotion(deltaV=8500.0, motionDirection=100, time=130000)する必要Craft = Object(...)があることなど、オブジェクトにdeltaVを与える。ここでのパラメーターdeltaVはm / s(現在は加速度は瞬間的です)、motionDirection度単位のdeltaVの方向(現在の位置からオブジェクトの周りの360度の円を想像してください。方向はその円上の点です)、最後にパラメーターtimeは秒数ですオブジェクトのdeltaVプッシュ軌道が監視された後。次giveMotion()の前の最後の位置から開始しgiveMotion()ます。

質問:

  1. これは軌道を計算するための有効なアルゴリズムですか?
  2. 明らかな改善点は何ですか?
  3. 1秒ごとにベクトルと位置を再計算する必要がないため、計算を最適化する「timeScale」変数を検討しています。それがどのように実装されるべきかについての考えはありますか、それは一般的に良いアイデアですか?(精度の低下とパフォーマンスの向上)

基本的に、私の目的は、トピックに関する議論を開始し、それがどこにつながるかを確認することです。そして、可能であれば、新しくて面白いものを学びます(または、さらに良いことを教えます)。

気軽に実験してください!

使用してみてください:

Earth = Object(name="Earth", position=(50.0, 100.0), radius=6.371, mass=5.972e24)
Moon = Object(name="Moon", position=(434.0, 100.0), radius=1.737, mass = 7.347e22)
Craft = Object(name="SpaceCraft", position=(43.0, 100.0), radius=1, mass=1.0e4)

Craft.giveMotion(deltaV=10575.0, motionDirection=180, time=322000)
Craft.giveMotion(deltaV=400.0, motionDirection=180, time=50000)

2回の火傷で-1回は地球軌道で前進し、1回は月軌道で逆行して、安定した月軌道を達成しました。これらは理論上の期待値に近いですか?

推奨される運動:3回の火傷で試してください-地球表面からの安定した地球軌道、月に到達するための前進火傷、月の周りの軌道を安定させるための逆行火傷。次に、deltaVを最小化してください。

注:python3構文に慣れていない人のために、広範なコメントでコードを更新する予定です。


独学のための非常に良いアイデアです!Python構文に慣れていない私たちのために、あなたの式を要約することは可能でしょうか?

確かに、私は推測します。コードを取り上げ、質問自体の一般的なロジックを要約したい人のために、コードでより広範なコメントを作成します。
状態空間14年

頭の外:速度と方向を別々に扱うのではなく、速度にベクトルを使用することを検討してください。「F = m * v」と言うのは、「F = m * a」という意味ですか?あなたは地球が小惑星よりもはるかに重いので、地球は動かないと仮定していますか?見て考えてみましょうgithub.com/barrycarter/bcapps/blob/master/bc-grav-sim.pl
barrycarter

地球を含む任意のオブジェクトに動きを与えることができます。テストの目的で、メインループにオブジェクト->地球関係のみを含めました。すべてのオブジェクトが、作成された他のすべてのオブジェクトに関連することは簡単に変換できます。そして、すべてのオブジェクトは独自のモーションベクトルを持つことができます。私がそれをしなかった理由-1つのオブジェクトでも非常に遅い計算。時間単位のスケーリングが大いに役立つことを願っていますが、それを正しく行う方法がまだわかりません。
状態空間14年

1
OK。考え:2つの実際のオブジェクト(たとえば、Earth / MoonまたはEarth / Sun)のシミュレーションを行い、結果をssd.jpl.nasa.gov/?horizo​​nsと比較して精度を高めますか?他のソースからの摂動のために完璧ではありませんが、正確さについてのアイデアは得られますか?
バリーカーター14年

回答:


11

m1,m2

F=ma
a

F21=Gm1m2|r21|3r21

r21F12=F21r12=r21(x1,y1)(x2,y2)

r21=(x1x2y1y2).

そして

|r|=(x1x2)2+(y1y2)2.
a=F/m

x1(t)=Gm2(x2x1)|r|3y1(t)=Gm2(y2y1)|r|3x2(t)=Gm1(x1x2)|r|3y2(t)=Gm1(y1y2)|r|3.

初期位置と速度と共に、この常微分方程式(ODE)のシステムは初期値問題を構成します。通常のアプローチは、これを8つの方程式の1次システムとして記述し、ルンゲクッタ法またはマルチステップ法を適用してそれらを解くことです。

前方オイラーや後方オイラーなどの単純なものを適用すると、地球がそれぞれ無限に渦巻くか太陽に向かって渦巻くのが見えますが、それは数値誤差の影響です。古典的な4次のルンゲクッタ法など、より正確な方法を使用すると、しばらくは真の軌道に近いままですが、最終的には無限になります。正しいアプローチはシンプレクティック法を使用することです。これにより、地球は正しい軌道に保たれますが、数値誤差のために位相はまだオフになります。

2体問題では、重心を中心に座標系を作成することにより、より単純なシステムを導き出すことができます。しかし、もしこれがあなたにとって新しいものであれば、上の定式化はより明確だと思います。


これは消化するのに時間がかかります。
状態空間14年

まだ消化中。私にとって未知の言葉が多すぎますが、どういうわけか私はある時点でそこにたどり着くと感じています。今のところ、私自身のアルゴリズムは物事を単純に機能させるのに十分です。しかし、同時にモーションをプラグインするときは、文献を読んで適切なアルゴリズムについて読むことを余儀なくされます。現代のハードウェアの制限がはるかに緩いことを考えると、単純な方程式でだまされる余裕があります。恐れてはいないが。
状態空間14年

確かに、シンプレクティック法は断然最も正確ですが、科学の知識がない人がそれらを実装することは難しいと思います。代わりに、非常にシンプルなオイラー法とファインマン補正を使用できます。私はあなたが独学の目的のためにそれより複雑な何かを必要とするとは思わない。
chrispap 14年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.