Ccmmutty logo
Commutty IT
0 pv20 min read

微分方程式と数値解析の基本③ [強制振動・減衰振動]

https://cdn.magicode.io/media/notebox/7c75f033-565a-400c-98f3-8a48c20dfa07.jpeg
微分方程式の数値解析の基本②の単振動から発展して減衰を考慮した強制振動を対象にする。

強制振動の微分方程式と理論解

対象の微分方程式

質量mmのおもりが付いているバネ(バネ定数kk)が水平に置かれており一端が固定された状態を考える。
バネの左端を原点x=0x=0、バネが自然長の時のおもりの位置をx=xex=x_eとし、x=x0x=x_0の位置でおもりから手を離す。
その後、バネの左端にはacosωta\cos\omega tの振動(aa:振幅、ω\omega:角振動数)を強制的に与え続ける。
任意の時刻ttで自然長の位置が元のxex_eからacosωta\cos\omega tだけずれていると考えれば、この時のバネの伸びはx(xe+acosωt)x - (x_e +a\cos \omega t)になる。
さらに、おもりには速度に比例する抵抗力が速度と逆向きに働くものとし、その抵抗係数をbb(正の値)とする。
それ以外の摩擦力などは全無視。この時の運動方程式は
mx¨=k(xxeacosωt)bx˙m\ddot{x} = -k(x - x_e - a\cos\omega t) - b\dot{x}
となるので、解析対象の微分方程式は下記になる。
x¨+bmx˙+km(xxe)=kamcosωt\ddot{x} + \frac{b}{m} \dot{x} + \frac{k}{m} (x-x_e) = \frac{ka}{m} \cos\omega t
ただし、xxの時間微分をx˙,x¨\dot{x},\ddot{x}で表している。
これをもう少し扱いやすくするためにX=xxeX=x-x_eω0=kmω_0=\sqrt{\frac{k}{m}}γ=b2m\gamma = \frac{b}{2m}ka=Fka=Fとそれぞれ置き換えると
X¨+2γX˙+ω02X=Fmcosωt(1)\ddot{X} + 2\gamma \dot{X} + \omega _0^2 X = \frac{F}{m} \cos\omega t \qquad \cdots (1)
となるのでこの(1)を解くことになる。
しかし、(1)はFmcosωt\frac{F}{m} \cos\omega tという非同次(非斉次)項、つまりXXやその微分に関わらない項が入っている非同次(非斉次)線形微分方程式と言われるもので、単振動のような同次線形微分方程式とは異なる解き方が必要になる。
非同次線形微分方程式の一般解は、その方程式の非同次項を無視した同次線形微分方程式の一般解と、非同次線形微分方程式を満たす特殊解を何かしら1つ見つけてそれらを重ね合わせたものになる。
つまり、今回の場合で言えば(1)の右辺を無視した同次線形微分方程式
X¨+2γX˙+ω02X=0(2)\ddot{X} + 2\gamma \dot{X} + \omega _0^2 X = 0 \qquad \cdots (2)
の一般解XhX_hと、(1)を満たす特殊解XsX_sを見つけ、それらを合わせたXh+XsX_h+X_sが微分方程式(1)の一般解になる。

同次線形微分方程式の一般解

まずは同次線形微分方程式(2)の一般解XhX_hを求めよう。
この形の微分方程式を解く常套手段で、Xh=eλtX_h=e^{\lambda t}になるだろうと仮定して(2)に代入すると
λ2eλt+2γλeλt+ω02eλt=0\lambda ^2 e^{\lambda t} + 2\gamma \lambda e^{\lambda t} + \omega_0^2 e^{\lambda t} = 0
eλt0e^{\lambda t} \not = 0なので
λ2+2γλ+ω02=0\lambda ^2 + 2\gamma \lambda + \omega_0^2 = 0
これはλ\lambdaに関する二次方程式であるから解の公式でλ\lambdaを求められる。
最初にγ=b2m\gamma = \frac{b}{2m}とわざわざ1/2を付けて置き換えたのはλ\lambdaをちょっとだけ見やすくするためです。
λ=γ±γ2ω02\lambda = -\gamma \pm \sqrt{\gamma^2 - \omega_0^2}
ここで注意しなければならないのがルートの中身が負、0、正のどれになるかで場合分けが必要になることである。
中身が負、つまりγ2<ω02\gamma^2 < \omega_0^2の場合はλ\lambdaは虚数解となり、(2)の解は減衰振動を表す。
中身が0、つまりγ2=ω02\gamma^2 = \omega_0^2の場合はλ\lambdaは実数の重解となり、(2)の解は臨界減衰を表す。
中身が正、つまりγ2>ω02\gamma^2 > \omega_0^2の場合はλ\lambdaは2つの実数解となり、(2)の解は過減衰を表す。
減衰振動はおもりが振動しながら徐々に速度が0に収束するが、臨界減衰と過減衰はいずれも振動することなく指数関数的に速度が0に収束する。
各パターンで得られる解の形が変わってくるので本当はそれぞれ求めないといけないのだが、今回は減衰振動になる場合に限定する。
この時λ=γ±iω02γ2\lambda = -\gamma \pm i\sqrt{\omega_0^2 - \gamma^2} となり、α=ω02γ2\alpha=\sqrt{\omega_0^2 - \gamma^2}と置いてやれば
λ=γ±iα=λ±\lambda = -\gamma \pm i\alpha =\lambda_{\pm}
λ\lambdaが2つ求まるので、(2)の解をX1X_1, X2X_2とすると以下のようになる。
X1=eλ+t=e(γ+iα)t=eγteiαt=eγt(cosαt+isinαt)X2=eλt=e(γiα)t=eγteiαt=eγt(cosαtisinαt)\begin{aligned} X_1 &=e^{\lambda_+ t} = e^{(-\gamma +i\alpha )t} = e^{-\gamma t} e^{i\alpha t} = e^{-\gamma t} (\cos \alpha t + i \sin \alpha t) \\[0.5em] X_2 &=e^{\lambda_- t} = e^{(-\gamma -i\alpha )t} = e^{-\gamma t} e^{-i\alpha t} = e^{-\gamma t} (\cos \alpha t - i \sin \alpha t) \end{aligned}
ただし、途中でオイラーの公式 eiθ=cosθ+isinθe^{i\theta} = \cos \theta + i\sin \theta を使っている。
したがって、任意定数A, Bを用いると(2)の一般解XhX_hは以下のように表せる。(ここの式展開の詳細は補足に記載)
Xh=AX1+X22+BX1X22i=eγt(Acosαt+Bsinαt)(3)\color{red} X_h = A\frac{X_1+X_2}{2} + B\frac{X_1-X_2}{2i} = e^{-\gamma t} (A\cos \alpha t + B\sin \alpha t) \qquad \cdots (3)
eγte^{-\gamma t}は減衰を表し、Acosαt+BsinαtA\cos \alpha t + B\sin \alpha tは単振動と同じ形である。

非同次線形微分方程式の特殊解と一般解

次に非同次線形微分方程式(1)を満たす特殊解XsX_sをなんとか1個でいいので求めたい。
とは言ってもこのままの形でXsX_sを直接予想して確かめるのは非常に困難なので、これまたトリッキーな常套手段で求めていくことになる。
まず、(1)のXXYYに置き換え(単にXXと区別するため)右辺の外力をFmsinωt\frac{F}{m} \sin \omega tに変えた式
Y¨+2γY˙+ω02Y=Fmsinωt(1)\ddot{Y} + 2\gamma \dot{Y} + \omega _0^2 Y = \frac{F}{m} \sin\omega t \qquad \cdots (1)^{\prime}
を考える。
(1)’の両辺に虚数単位iiをかけると
iY¨+2iγY˙+iω02Y=Fmisinωt(1)i\ddot{Y} + 2i\gamma \dot{Y} + i\omega _0^2 Y = \frac{F}{m} i\sin\omega t \qquad \cdots (1)^{\prime \prime}
(1)’ + (1)’’として整理すると
(X¨+iY¨)+2γ(X˙+iY˙)+ω02(X+iY)=Fm(cosωt+isinωt)(\ddot{X} + i\ddot{Y}) + 2\gamma (\dot{X} + i\dot{Y}) + \omega _0^2 (X+iY) = \frac{F}{m} (\cos\omega t + i\sin\omega t)
ここでZ=X+iYZ=X+iYとして右辺はオイラーの公式を使えば上の式は以下のようにまとめられる。
Z¨+2γZ˙+ω02Z=Fmeiωt(1)\ddot{Z} + 2\gamma \dot{Z} + \omega_0^2 Z = \frac{F}{m} e^{i\omega t} \qquad \cdots (1)^{\prime \prime \prime}
で、この微分方程式(1)’’’を満たす複素数解ZZを求めてその実部を取ると、それが元の微分方程式(1)の解とみなせるというのが定石らしい。
ちなみに、(1) で外力として与える振動がsinωt\sin \omega tならZZの虚部を取ればいい。
それでは(1)’’’の解を求めるため、その解をZ=CeiωtZ=Ce^{i\omega t}と仮定して(1)’’’に代入すると(Cは任意定数)
ω2Ceiωt+2iγCωeiωt+Cω02eiωt=Fmeiωt-\omega^2 Ce^{i\omega t} + 2i\gamma C\omega e^{i\omega t} + C \omega_0^2 e^{i\omega t} = \frac{F}{m} e^{i\omega t}
eiωt0e^{i\omega t} \not =0なので、辺々割ってCを求めると
C=Fm1ω02ω2+2γωi(4)C=\frac{F}{m} \frac{1}{\omega_0^2 - \omega^2 + 2\gamma \omega i} \qquad \cdots (4)
(4)の分母分子にω02ω22γωi\omega_0^2 - \omega^2 - 2\gamma \omega i(分母の共役複素数)をかけると
C=Fmω02ω22γωi(ω02ω2)2+4γ2ω2C=\frac{F}{m} \frac{\omega_0^2 - \omega^2 - 2\gamma \omega i}{(\omega_0^2 - \omega^2)^2 + 4\gamma^2 \omega^2}
さらにもう一工夫として分子にあるω02ω22γωi\omega_0^2 - \omega^2 - 2\gamma \omega iについて複素平面上で考えれば
cosϕ=ω02ω2(ω02ω2)2+4γ2ω2sinϕ=2γω(ω02ω2)2+4γ2ω2\begin{aligned} \cos \phi &= \frac{\omega_0^2 - \omega^2}{\sqrt{(\omega_0^2 - \omega^2)^2 + 4\gamma^2 \omega^2}} \\[0.5em] \sin \phi &= \frac{-2\gamma \omega}{\sqrt{(\omega_0^2 - \omega^2)^2 + 4\gamma^2 \omega^2}} \end{aligned}
と表せるので、結果的に
ω02ω22γωi=(ω02ω2)2+4γ2ω2(cosϕ+isinϕ)=(ω02ω2)2+4γ2ω2eiϕ\begin{aligned} \omega_0^2 - \omega^2 - 2\gamma \omega i &= \sqrt{(\omega_0^2 - \omega^2)^2 + 4\gamma^2 \omega^2} (\cos \phi + i\sin \phi) \\[0.5em] &= \sqrt{(\omega_0^2 - \omega^2)^2 + 4\gamma^2 \omega^2} e^{i\phi} \end{aligned}
とまとめられる。ϕ\phiは複素数ω02ω22γωi\omega_0^2 - \omega^2 - 2\gamma \omega iの偏角である。
よって、最終的なCの形は
C=Fmeiϕ(ω02ω2)2+4γ2ω2C = \frac{F}{m} \frac{e^{i\phi}}{\sqrt{(\omega_0^2 - \omega^2)^2 + 4\gamma^2 \omega^2}}
であるから、(1)’’’の解ZZ
Z=Fmei(ωt+ϕ)(ω02ω2)2+4γ2ω2(5)Z = \frac{F}{m} \frac{e^{i(\omega t +\phi)}}{\sqrt{(\omega_0^2 - \omega^2)^2 + 4\gamma^2 \omega^2}} \qquad \cdots (5)
そして、前述したように(1)の特殊解XsX_sは(5)の実部を取ればいいので
Xs=Fmcos(ωt+ϕ)(ω02ω2)2+4γ2ω2(6)\color{red} X_s = \frac{F}{m} \frac{\cos (\omega t + \phi)}{\sqrt{(\omega_0^2 - \omega^2)^2 + 4\gamma^2 \omega^2}} \qquad \cdots (6)
となる。これで(3)(6)の通りXhX_h, XsX_sが求まったので、(1)の一般解は
X=Xh+Xs=eγt(Acosαt+Bsinαt)+Fmcos(ωt+ϕ)(ω02ω2)2+4γ2ω2X=X_h+X_s=e^{-\gamma t} (A\cos \alpha t + B\sin \alpha t) + \frac{F}{m} \frac{\cos (\omega t + \phi)}{\sqrt{(\omega_0^2 - \omega^2)^2 + 4\gamma^2 \omega^2}}
であり、X=xxeX=x-x_eと置き換えていたので最終的なxxの一般解は以下のようになる。
x=eγt(Acosαt+Bsinαt)+Fmcos(ωt+ϕ)(ω02ω2)2+4γ2ω2+xe(7)\color{red} x=e^{-\gamma t} (A\cos \alpha t + B\sin \alpha t) + \frac{F}{m} \frac{\cos (\omega t + \phi)}{\sqrt{(\omega_0^2 - \omega^2)^2 + 4\gamma^2 \omega^2}} + x_e \qquad \cdots (7)

初期値問題を解く

(7)は一般解なので今回想定する初期条件を代入して定数A, Bを求める。
(7)を時間微分して速度x˙\dot{x}を計算すると
x˙=eγt{(AγBα)cosαt+(Aα+Bγ)sinαt}Fmωsin(ωt+ϕ)(ω02ω2)2+4γ2ω2\dot{x} = -e^{\gamma t} \lbrace (A\gamma - B\alpha)\cos \alpha t + (A\alpha + B\gamma)\sin \alpha t \rbrace -\frac{F}{m} \frac{\omega \sin (\omega t + \phi)}{\sqrt{(\omega_0^2 - \omega^2)^2 + 4\gamma^2 \omega^2}}
なので、初期条件t=0t=0x(0)=x0x(0)=x_0x˙(0)=0\dot{x}(0)=0を代入して頑張って計算すればA, Bは以下のように求められる。
A=Fmcosϕ(ω02ω2)2+4γ2ω2+x0xeB=Fmωsinϕγcosϕα(ω02ω2)2+4γ2ω2+(x0xe)γα\begin{aligned} A &= -\frac{F}{m} \frac{\cos \phi}{\sqrt{(\omega_0^2 - \omega^2)^2 + 4\gamma^2 \omega^2}} + x_0 - x_e \\[0.5em] B &= \frac{F}{m} \frac{\omega \sin \phi - \gamma \cos \phi}{\alpha \sqrt{(\omega_0^2 - \omega^2)^2 + 4\gamma^2 \omega^2}} + \frac{(x_0 - x_e)\gamma}{\alpha} \end{aligned}
最終的にまとめると、今回の初期条件の下で強制振動の位置を表すxxの解は
x=eγt(Acosαt+Bsinαt)+Fmcos(ωt+ϕ)(ω02ω2)2+4γ2ω2+xex=e^{-\gamma t} (A\cos \alpha t + B\sin \alpha t) + \frac{F}{m} \frac{\cos (\omega t + \phi)}{\sqrt{(\omega_0^2 - \omega^2)^2 + 4\gamma^2 \omega^2}} + x_e
ただし、AとBは上記の形で
ω0=kmγ=b2mF=kaα=ω02γ2ϕ=tan1(2γωω02ω2)\begin{aligned} \omega_0 = \sqrt{\frac{k}{m}} \qquad \gamma = \frac{b}{2m} \qquad F=ka \\[0.5em] \alpha=\sqrt{\omega_0^2 - \gamma^2} \qquad \phi=\tan^{-1} \Big( \frac{-2\gamma \omega}{\omega_0^2 - \omega^2} \Big) \end{aligned}
はい、できましたー…

理論解のプロット

求めたxxの理論解を実際にプロットして振動の様子を見る。
各種係数は強制振動と減衰の様子がいい感じに見やすくなるように適当に設定している。
係数の計算がこの話の肝かつ煩わしいところなので、最初に計算して使いまわせるようにinit_constant_params()という初期化用の関数にまとめている。
import numpy as np
import matplotlib.pyplot as plt
from numpy import pi, sqrt, sin, cos, atan2, exp


def init_constant_params(x0, amplitude):
    """ 計算で使う各種定数を初期化する
        外力の振幅amplitudeは引数として受け取り色々な強制振動パターンで計算できるようにする
    """
    # 物理量の設定
    xe = 0.3  # バネの自然長[m]
    k = 0.5  # バネ定数[N/m]
    m = 0.5  # おもりの質量[kg]
    b = 0.1  # 減衰係数[kg/s]
    omega = pi * 2 / 3  # 外力の角振動数[rad/s]
    # 計算用の各種係数
    force = k * amplitude  # 外力[N]
    gamma = b / (2 * m)
    omega_0 = sqrt(k / m)  # バネ系の固有振動数
    alpha = sqrt(omega_0**2 - gamma**2)
    phi = atan2(-2 * gamma * omega, omega_0**2 - omega**2)
    denominator = sqrt((omega_0**2 - omega**2) **
                       2 + 4 * gamma**2 * omega**2)
    A = - force / m * cos(phi) / denominator + x0 - xe
    B = force / m * (omega * sin(phi) - gamma * cos(phi)) / \
        (alpha * denominator) + (x0 - xe) * gamma / alpha

    return {
        'equilibrium_length': xe,
        'mass': m,
        'gamma': gamma,
        'omega': omega,
        'alpha': alpha,
        'force': force,
        'phi': phi,
        'denominator': denominator,
        'A': A,
        'B': B,
    }


def calc_theoretical_solution(t, params):
    """ 理論解を計算する
    """
    # 計算に使う定数を設定
    xe = params['equilibrium_length']
    m = params['mass']
    gamma = params['gamma']
    omega = params['omega']
    alpha = params['alpha']
    force = params['force']
    phi = params['phi']
    denominator = params['denominator']
    A = params['A']
    B = params['B']

    # 減衰振動だけを想定した計算であり臨界減衰や過減衰ではないので注意!!!
    x = exp(-gamma * t) * (A * cos(alpha * t) + B * sin(alpha * t)) \
        + force / m * cos(omega * t + phi) / denominator + xe

    return x


def forced_vibration(t_list, params):
    """ 全時刻の理論解を計算する """
    x_list = [calc_theoretical_solution(t, params) for t in t_list]
    return x_list


def plot_graph(t_list, xu_list, xf_list):
    fig = plt.figure(figsize=(8, 4))
    fig.suptitle('Forced vibration (theoretical solution)')
    plt.plot(t_list, xu_list, label='unforced', color='b', linewidth=1)
    plt.plot(t_list, xf_list, label='forced', color='r', linewidth=1)
    plt.xlabel("time [s]")
    plt.ylabel("x [m]")
    plt.legend(['unforced', 'forced'],
               loc='upper right', bbox_to_anchor=(1, 1))
    plt.grid()
    plt.show()


if __name__ == '__main__':
    t_end = float(input('計算終了時刻[s]:'))
    x0 = float(input('おもりの初期位置 x0[m]: '))
    v0 = 0  # 初速度は0で固定
    h = 0.01  # 時間刻み幅

    # 初期化した各種定数を取得
    params_u = init_constant_params(x0, amplitude=0)  # 強制力なし unforced
    params_f = init_constant_params(x0, amplitude=0.1)  # 強制力あり forced

    # 計算時間ステップのリストを生成する(最後の時刻t_endも含めたいので+hしている)
    t_list = np.arange(0, t_end + h, h)

    # 振動の計算
    xu_list = forced_vibration(t_list, params_u)  # 強制力なし
    xf_list = forced_vibration(t_list, params_f)  # 強制力あり

    plot_graph(t_list, xu_list, xf_list)
強制振動があるパターンを赤線、ないパターン(強制振動の振幅が0)を青線でプロット。

odeintを使った数値解

久々に強制振動の理論解の導出をやってみたが結構しんどい。
しかしこれでも微分方程式の初歩に過ぎない…
で、↑みたいなめんどくさい導出なんかやりたくねんだ!という場合は数値的に解こう。
「微分方程式の数値解析の基本①」「微分方程式の数値解析の基本②」でやったのと同様に自分で解析部分のプログラムを作成するのでもいいが、今回は常微分方程式の数値解を計算できるSciPyのodeintを使ってみる。

odeintとは

SciPyは様々な数学的な処理を行う関数やアルゴリズムを提供するPythonのライブラリで、その中に常微分方程式を数値的に解くことができるodeintという関数がある。
odeint (func, y0, t, args = (),...)
公式ドキュメントを見ると指定可能な引数は他にもたくさんあるが基本はこれだけでOK。
func
計算対象の微分方程式を定義しておく関数。
連立微分方程式の場合はそれらをすべてこの中で定義しておく。
関数定義のところでは引数を指定しておき、前ステップの計算結果yのリスト、時刻、その他のパラメータ(argsが指定されていれば)などを指定する。
y0
計算対象となる状態変数の初期値のリスト。
連立微分方程式の場合はそれぞれの状態変数の初期値をリスト形式で渡す。
t
計算の各時刻(時間ステップ)のリスト。
あらかじめnumpyarange()linspace()などを使って生成した時刻のリストを渡すとよい。
args
y0t以外で何かfuncに渡して使いたいパラメータがあればタプル形式で指定する。
特に何もなければargsは不要。

odeintを使う準備

解きたい微分方程式は2階の微分方程式なので、odeint()で計算するためには媒介変数を使って2本の連立1階微分方程式に分ける必要がある。
x¨+bmx˙+km(xxe)=kamcosωt\ddot{x} + \frac{b}{m} \dot{x} + \frac{k}{m} (x-x_e) = \frac{ka}{m} \cos\omega t
この微分方程式について媒介変数vv(速度)を使えば、x˙=v\dot{x}=vx¨=v˙\ddot{x}=\dot{v}であるから以下のように分けることができる。
{x˙=vv˙=1m{kacosωtbvk(xxe)}\begin{cases} \dot{x} = v \\ \dot{v} = \frac{1}{m} \lbrace ka\cos \omega t -bv -k(x-x_e) \rbrace \end{cases}
ベクトルの成分表記っぽくすれば
ddt(xv)=(v1m{kacosωtbvk(xxe)})\dfrac{d}{dt} \begin{pmatrix} x \\ v \end{pmatrix} = \begin{pmatrix} v \\ \frac{1}{m} \lbrace ka\cos \omega t -bv -k(x-x_e) \rbrace \end{pmatrix}
これらの式をfuncの中で定義してodeint()に渡してあげればよい。
そして、計算結果であるodeint()の戻り値の中身はNumpy配列で、各時刻のxxvvの結果の配列になっている。
これをそのままmatplotlibのプロット関数に渡すとxxvv両方の結果がプロットされるが、
# 各時刻の結果が[x, v]の順で格納されている
result[:, 0] # x
result[:, 1] # v
のようにすれば片方だけの結果を抽出できる。

数値解のプロット

理論解の時と同じ条件で数値計算を行う。
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
from numpy import pi, cos


def init_constant_params(amplitude):
    """ 計算で使う各種定数を初期化する
        外力の振幅amplitudeは引数として受け取り色々な強制振動パターンで計算できるようにする
    """
    k = 0.5  # バネ定数[N/m]
    force = k * amplitude  # 外力[N]

    return {
        'equilibrium_length': 0.3,  # バネの自然長[m]
        'spring_constant': k,
        'mass': 0.5,  # おもりの質量[kg]
        'damping_coefficient': 0.1,  # 減衰係数[kg/s]
        'force': force,  # 外力[N]
        'omega_f': pi * 2 / 3,  # 外力の角振動数[rad/s]
    }


def func(xv, t, params):
    # 計算に使う定数を設定
    xe = params['equilibrium_length']
    k = params['spring_constant']
    m = params['mass']
    b = params['damping_coefficient']
    force = params['force']
    omega_f = params['omega_f']

    x, v = xv
    dx_dt = v
    dv_dt = (force * cos(omega_f * t) - b * v - k * (x - xe)) / m

    return [dx_dt, dv_dt]


def plot_graph(t_list, xu_list, xf_list):
    fig = plt.figure(figsize=(8, 4))
    fig.suptitle('Forced vibration (numerical solution)')
    plt.plot(t_list, xu_list, label='unforced', color='b', linewidth=1)
    plt.plot(t_list, xf_list, label='forced', color='r', linewidth=1)
    plt.xlabel("time [s]")
    plt.ylabel("x [m]")
    plt.legend(['unforced', 'forced'],
               loc='upper right', bbox_to_anchor=(1, 1))
    plt.grid()
    plt.show()


if __name__ == '__main__':
    t_end = float(input('計算終了時刻[s]:'))
    x0 = float(input('初期位置[m]:'))
    v0 = 0  # おもりの初速度[m/s]
    h = 0.01  # 時間刻み幅

    # 初期化した各種定数を取得
    params_u = init_constant_params(amplitude=0)  # 強制力なし unforced
    params_f = init_constant_params(amplitude=0.1)  # 強制力あり forced

    # 計算時間ステップのリストを生成する(最後の時刻t_endも含めたいので+hしている)
    t_list = np.arange(0, t_end + h, h)

    # 振動の計算
    result_u = odeint(func, [x0, v0], t_list, args=(params_u,))  # 強制力なし
    result_f = odeint(func, [x0, v0], t_list, args=(params_f,))  # 強制力あり

    # xのみプロットしたいので抽出
    xu_list = result_u[:, 0]
    xf_list = result_f[:, 0]

    plot_graph(t_list, xu_list, xf_list)
本当はちゃんと誤差などを出して定量的に評価しないといけないけれど、結果は理論解の時とほぼ同じなのでヨシ。

補足

同次線形微分方程式の解の変形

X1=eγt(cosαt+isinαt)X2=eγt(cosαtisinαt)\begin{aligned} X_1 &= e^{-\gamma t} (\cos \alpha t + i \sin \alpha t) \\[0.5em] X_2 &=e^{-\gamma t} (\cos \alpha t - i \sin \alpha t) \end{aligned}
X1,X2X_1,X_2は共役複素数なので
X1+X22=eγtcosαtX1X22i=eγtsinαt\begin{aligned} \frac{X_1 + X_2}{2} &= e^{-\gamma t} \cos \alpha t \\[0.5em] \frac{X_1 - X_2}{2i} &=e^{-\gamma t} \sin \alpha t \end{aligned}
という計算でそれぞれの実部と虚部を取り出すことができる。
X1,X2X_1, X_2は線形微分方程式(2)の解なので、それらを定数倍して足し合わせた(線形結合した)
X1+X22,X1X22i\frac{X_1 + X_2}{2},\frac{X_1 - X_2}{2i}
もそれぞれ(2)の解になる。さらにそれらを線型結合した
AX1+X22+BX1X22i=eγt(Acosαt+Bsinαt)A \frac{X_1 + X_2}{2} + B\frac{X_1 - X_2}{2i} = e^{-\gamma t} (A\cos \alpha t + B\sin \alpha t)
も(2)の解になるので、結局
Xh=eγt(Acosαt+Bsinαt)X_h = e^{-\gamma t} (A\cos \alpha t + B\sin \alpha t)
のように式変形できる。
変位XhX_hは物理量なので実数でなければいけないから、この変形により虚数単位iiを使わずに解を表現することができる。

数値計算での減衰の場合分けについて

理論計算だとγ\gammaω0\omega_0の大小関係に応じて減衰振動、過減衰、臨界減衰の式を切り替えられるプログラムにする必要があるが、数値計算はそんなことを考えなくても条件に応じた答えを出してくれる。
例えば
減衰振動:γ<ω0\gamma < \omega_0つまりb2<4mkb^2<4mk
臨界減衰:γ=ω0\gamma = \omega_0つまりb2=4mkb^2=4mk
過減衰:γ>ω0\gamma > \omega_0つまりb2>4mkb^2>4mk
となるように係数を定めると、場合分けしなくてもそれぞれの条件に応じた運動を計算して結果を出してくれる。(下の図は強制振動なしで計算した結果)

Discussion

コメントにはログインが必要です。