テクノなまこ

科学の力

pythonで軌道シミュレーション

目次

画像書き出し

# Import Python Modules
import numpy as np # 数値計算ライブラリ
import matplotlib.pyplot as plt # 描画ライブラリ
from scipy.integrate import odeint # 常微分方程式を解くライブラリ

# 定数
# G = 6.67430 * 10 **(-20) [km^3 kg^-1 s^-2] 万有引力定数(km)
# M = 5.972 * 10 ** 24 # [kg] 地球質量
GM = 398600 # [km^3 s^-2] 地球の重力定数

# 二体問題の運動方程式
def func(x, t):
    r_E = np.linalg.norm([x[0],x[1],x[2]])
    dxdt = [x[3],
            x[4],
            x[5],
            -GM*x[0]/(r_E**3),
            -GM*x[1]/(r_E**3),
            -GM*x[2]/(r_E**3)]
    return dxdt 

# 微分方程式の初期条件
x0 = [6400, 0, 0, 0, 9, 0] # 位置(x,y,z)+速度(vx,vy,vz)
t  = np.linspace(0, 30000, 1000) # (開始、終了、ステップ数) 1日分 軌道伝播

# 微分方程式の数値計算
sol = odeint(func, x0, t)

# 二次元描画
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_xlabel('km')
ax.set_ylabel('km')

#地球をプロット
ax.plot(0,0,'.', c='black') #点をプロットする場合

ax.plot(sol[:, 0],sol[:, 1], 'black') #numpyのnparrayの記法で、各時間の0列目(x座標)、1列目(y座標)を抽出
ax.set_aspect('equal') # グラフのアスペクト比を揃える
plt.savefig('orbit.png') # 画像を保存
plt.show()

kmで計算しているので、万有引力定数が-11乗でなく-20乗になっている。万有引力万有引力定数Gと地球質量Mから計算すると誤差が大きくなるため、重力定数GMとしてハードコードしている

地球の大きさを描画する

# Import Python Modules
import numpy as np # 数値計算ライブラリ
import matplotlib.pyplot as plt # 描画ライブラリ
from scipy.integrate import odeint # 常微分方程式を解くライブラリ
from matplotlib import patches

# 定数
# G = 6.67430 * 10 **(-20) [km^3 kg^-1 s^-2] 万有引力定数(km)
# M = 5.972 * 10 ** 24 # [kg] 地球質量
GM = 398600 # [km^3 s^-2] 地球の重力定数
R = 6378
# 二体問題の運動方程式
def func(x, t):
    r_E = np.linalg.norm([x[0],x[1],x[2]])
    dxdt = [x[3],
            x[4],
            x[5],
            -GM*x[0]/(r_E**3),
            -GM*x[1]/(r_E**3),
            -GM*x[2]/(r_E**3)]
    return dxdt 

# 微分方程式の初期条件
x0 = [R+600, 0, 0, 0, 9, 0] # 位置(x,y,z)+速度(vx,vy,vz)
t  = np.linspace(0, 30000, 1000) # (開始、終了、ステップ数) 1日分 軌道伝播

# 微分方程式の数値計算
sol = odeint(func, x0, t)

# 二次元描画
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_xlabel('km')
ax.set_ylabel('km')

#地球をプロット
c = patches.Circle( (0,0), R)
ax.add_patch(c)
# ax.plot(0,0,'.') #点をプロットする場合

ax.plot(sol[:, 0],sol[:, 1], 'black') #numpyのnparrayの記法で、各時間の0列目(x座標)、1列目(y座標)を抽出
ax.set_aspect('equal') # グラフのアスペクト比を揃える
plt.savefig('orbit.png') # 画像を保存
plt.show()

マウスを使って三次元で観察する

# Import Python Modules
import numpy as np # 数値計算ライブラリ
import matplotlib.pyplot as plt # 描画ライブラリ
from scipy.integrate import odeint # 常微分方程式を解くライブラリ

# 定数
# G = 6.67430 * 10 **(-20) [km^3 kg^-1 s^-2] 万有引力定数(km)
# M = 5.972 * 10 ** 24 # [kg] 地球質量
GM = 398600 # [km^3 s^-2] 地球の重力定数

# 二体問題の運動方程式
def func(x, t):
    r_E = np.linalg.norm([x[0],x[1],x[2]])
    dxdt = [x[3],
            x[4],
            x[5],
            -GM*x[0]/(r_E**3),
            -GM*x[1]/(r_E**3),
            -GM*x[2]/(r_E**3)]
    return dxdt 

# 微分方程式の初期条件
x0 = [6000, 0, 0, 0, 8, 1] # 位置(x,y,z)+速度(vx,vy,vz)
t  = np.linspace(0, 30000, 1000) # (開始、終了、ステップ数) 1日分 軌道伝播

# 微分方程式の数値計算
sol = odeint(func, x0, t)

# 三次元描画
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
ax.plot(0,0,0,'.')#地球をプロット
ax.plot(sol[:, 0], sol[:, 1], sol[:, 2],linewidth = 0.5)
ax.set_aspect('equal') # グラフのアスペクト比を揃える
plt.show()

初速のz成分を0km/sから1km/sに変更し、三次元でプロットした

gif動画を書き出す

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from scipy.integrate import odeint # 常微分方程式を解くライブラリ


GM_E = 398600 # km3/s2 地球の重力定数
L_E = [0,0,0]

def func(x, t):
    r_E = np.linalg.norm([x[0]-L_E[0],x[1]-L_E[1],x[2]-L_E[2]])
    dxdt = [x[3],
            x[4],
            x[5],
            -GM_E*x[0]/(r_E**3),
            -GM_E*x[1]/(r_E**3),
            -GM_E*x[2]/(r_E**3)]
    return dxdt 

def update(i, x, y):

    plt.plot(x[0:i], y[0:i],c="blue")

def draw():
    # 微分方程式の初期条件
    r0 = [6500, 0, 0, 0, 9, 0] # 位置(x,y,z)+速度(vx,vy,vz)
    t  = np.linspace(0, 10000, 1000) # 1日分 軌道伝播
    sol = odeint(func, r0, t)
    print("keisan owari")

    # 抽出して二次元化
    x1, y1, z1 = sol[:, 0], sol[:, 1], sol[:, 2]
    x = x1[::10]
    y = y1[::10]

    fig = plt.figure() #figure objectを取得
    plt.plot(0,0,'.',c='blue')# 地球をプロット
    plt.xlim(min(x),max(x))
    plt.ylim(min(y),max(y))
    plt.gca().set_aspect('equal')

    ani = animation.FuncAnimation(fig, update, fargs = (x, y), frames = len(x),interval = 10)
    ani.save("orbit.gif", writer = 'pillow',fps=30)
draw()

mp4を書き出す

mp4を書き出す場合は、writerをpillowから次のように変える

    ani = animation.FuncAnimation(fig, update, fargs = (x, y), frames = len(x),interval = 10)
    writer = animation.writers['ffmpeg'](fps=60, metadata=dict(artist='mika'), bitrate=200)
    ani.save("orbit.mp4", writer = writer)

月と、太陽の摂動と、太陽地球回転フレーム

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from scipy.integrate import odeint # 常微分方程式を解くライブラリ
import glob

# G = 6.67430 * 10 **(-11) [m3 kg-1 s-2] 万有引力定数(mksa)
# G = 6.67430 * 10 **(-20) [km3 kg-1 s-2] 万有引力定数(km)

# ME = 5.972 * 10 ** 24 # [kg] 地球質量
# MM = 7.345 * 10 ** 22 # [kg] 月質量
MS = 1.989 * 10 ** 30 # [kg] 太陽質量

GM_E = 398600.4354360959 # [km3 s-2] 地球の重力定数
GM_M = 4902.8072 # [km3/s2] 月の重力定数
GM_S = 13.27 * 10 ** 10 # [km3 s-2] 太陽の重力定数

L_E = [0,0,0] # 地球の座標[km]

# R_SE = 1.496 * 10 ** 8 # [km] 地球公転半径
R_EM = 3.8 * 10 ** 5 # [km] 月公転半径

# A = 5.927 * 10 ** -6 # [km s-2] 地球付近での太陽重力の加速度
# GM_S/R_SE
W = 2*np.pi/(86400*365)
w = 2*np.pi/(86400*30)
gradA = 17.74 * 10 ** (-14) # GM_S/R_SE ** 3 # [km s-2] 地球付近での太陽重力と遠心力による加速度の傾斜  GM_S/R_SE**2 + W**2

def L_M(t):
    # 月の運動
    offset = 170/360 * 2*np.pi
    return [-384400*np.cos(w*t+offset),-384400*np.sin(w*t+offset),0]

def F(r, t):
    r_E = np.linalg.norm([r[0]-L_E[0],r[1]-L_E[1],r[2]-L_E[2]])
    r_M = np.linalg.norm([r[0]-L_M(t)[0],r[1]-L_M(t)[1],r[2]-L_M(t)[2]])
    LM = L_M(t)

    dxdt = [r[3],
            r[4],
            r[5],
            GM_E*(L_E[0]-r[0])/(r_E**3) + GM_M*(LM[0]-r[0])/(r_M**3) + gradA * r[0] * np.cos(W*t),
            GM_E*(L_E[1]-r[1])/(r_E**3) + GM_M*(LM[1]-r[1])/(r_M**3) + gradA * r[1] * np.sin(W*t),
            GM_E*(L_E[2]-r[2])/(r_E**3) + GM_M*(LM[2]-r[2])/(r_M**3)]
    return dxdt

def update(i, x, y, unittime,framew):
    t = i*unittime
    LM = L_M(t)
    plt.cla() #現在描写されているグラフを消去
    plt.plot(0,0,0,'.',c='blue')# 地球をプロット
    plt.plot(LM[0]*np.cos(framew*t)+LM[1]*np.sin(framew*t),
             -LM[0]*np.sin(framew*t)+LM[1]*np.cos(framew*t),
             '.',c='red')# 月をプロット
    plt.xlim(-2000000,1500000)
    plt.ylim(-1500000,1500000)

    plt.plot(x[0:i], y[0:i],c="blue") #plot
    plt.text(-1500000,-1000000,f'day = {int(t/86400)}', fontsize=10)

def draw():
    # 微分方程式の初期条件r0
    v0 = 11.48964 #初速12.5922 12.592
    h0 = 6000 # 初期高度
    th0 = np.pi*1 # 初期位置の経度(太陽側が0、反時計回り)
    r0 = [-h0*np.cos(th0), -h0*np.sin(th0), 0, v0*np.sin(th0), -v0*np.cos(th0), 0] # 位置[km](x,y,z)+速度[km/s](vx,vy,vz)

    # 微分方程式の計算設定
    timeRange = int(86400 * 320) #時間範囲[s] (参考 1d=86400s)
    calcStep = 40 #時間ステップ[s]
    t  = np.linspace(0, timeRange, int(timeRange/calcStep)) # 時間範囲(開始[s],終了[s],分割数)
    
    # 微分方程式を解く
    sol = odeint(F, r0, t) # [x, y, z, u, v, w]

    # print(sol[5])
    x1 = []
    y1 = []
    z1 = []

    framew = W

    for i, raw in enumerate(sol):
        t = i*calcStep
        x1.append(raw[0]*np.cos(framew*t) + raw[1]*np.sin(framew*t))
        y1.append(-raw[0]*np.sin(framew*t) + raw[1]*np.cos(framew*t))
        z1.append(raw[2])

    # 動画保存用にフレームを抜き出す
    skiprate = int(1440/calcStep*15) #144skiprateで、60fps 10秒step計算で1秒が一日になる
    x = x1[::skiprate]
    y = y1[::skiprate]

    dic = calcStep*skiprate

    fig = plt.figure() #figure objectを取得
    plt.gca().set_aspect('equal')

    ani = animation.FuncAnimation(fig, update, fargs = (x, y,dic,framew), frames = len(x),interval = 10)

    # savemp4(ani,'ww.mp4')
    savegif(ani,'16.gif')
    # savemp4(ani,getname())

def getname():   
    filelist = [int(filename.split('.')[0].replace('test','')) for filename in glob.glob('*.mp4')]
    return f'test{max(filelist)+1}.mp4'

def savemp4(ani,filename):
    writer = animation.writers['ffmpeg'](fps=60, metadata=dict(artist='mika'), bitrate=200)
    ani.save(filename, writer = writer)

def savegif(ani,filename):
    ani.save(filename, writer = 'pillow', fps=30)

draw()