Nspired Pythonによる単振り子のシミュレーション / 常微分方程式の解法

参考: パソコンで見る天体の動き, pp.26-29

まず微分方程式が解けるようにする。何もインポートしていないので普通のPythonでもそのまま走る。

# Python による常微分方程式の数値解法_改良版 / 古典的 Runge-Kutta 法
class RK4:
  def __init__(self, funcs, inits, h, numOfDiv=1):
    self.funcs = funcs
    self.t0 = 0.0
    self.inits = inits
    self.h = h / numOfDiv
    self.half_h = self.h / 2.0
    self.sixth_h = self.h / 6.0
    self.range_dim = range(len(funcs))
    self.range_div = range(numOfDiv)
    self.f = [[0] * len(funcs)] * 4
    self.temp = [[0] * len(funcs)] * 3

  def update(self):
    for i in self.range_div:
      self.f[0] = [self.funcs[j](self.t0, *self.inits) for j in self.range_dim]
      self.temp[0] = [self.inits[j] + self.half_h * self.f[0][j] for j in self.range_dim]
      self.f[1] = [self.funcs[j](self.t0 + self.half_h, *self.temp[0]) for j in self.range_dim]
      self.temp[1] = [self.inits[j] + self.half_h * self.f[1][j] for j in self.range_dim]
      self.f[2] = [self.funcs[j](self.t0 + self.half_h, *self.temp[1]) for j in self.range_dim]
      self.temp[2] = [self.inits[j] + self.h * self.f[2][j] for j in self.range_dim]
      self.f[3] = [self.funcs[j](self.t0 + self.h , *self.temp[2]) for j in self.range_dim]
      self.inits = [self.inits[j] + self.sixth_h * (self.f[0][j] + 2 * (self.f[1][j] + self.f[2][j]) + self.f[3][j]) for j in self.range_dim]
      self.t0 += self.h
    return self

  def print(self):
    print(self.t0, self.inits)
    return self

########
# test #
########
#if __name__ == "__main__":
def main():
  # 解くべき連立常微分方程式を定義する。
  # 確認のため、厳密解の存在する方程式を定義する。単振り子とは無関係。
  def xDot(t, x, y): # x'(t) = y(t)
    return y
  def yDot(t, x, y): # y'(t) = t - x(t)
    return t - x
  funcs = [xDot, yDot]

  # 従属変数の初期値を指定する。
  x0 = 0
  y0 = 0
  inits = [x0, y0]

  # ステップ幅を指定する。
  h = 0.25 # =1/2**2
  
  # 1ステップだけ計算する函数を実体化して、
  sol = RK4(funcs, inits, h)

  # 初期値を何度か更新して確認する。
  while sol.t0 < 7.5:
    sol.update().print()

実行結果:
f:id:ti-nspire:20200923075018p:plain:w400