###目的と予想していた実行結果
定数係数の二階微斉次・非斉次分方程式の数値解をオイラー法で求め、厳密解と比較を使用としています
比較方法として、数値解の差を絶対値を取り、プロットしました。
オイラー法は積み残し誤差が発生する手法なので、プロットされたグラフは徐々に発散することが予想されます。
今回は問題を簡単にするため、以下のような単振動を想定した定数に設定しています。
1d^2x/dt^2 =-1x
t0=0,x0=2,v0=0
###問題点
プロットされたグラフは徐々に発散するようなものを想定していたのですが、途中まで収束する方向に進み、ある地点から発散へと転換していきました。
実際のプロットされたグラフは下の方に添付しています。
また、質問のついでですが、プログラム初心者のたclassやファイル間でのやり取りを練習するためにこのような複雑なコードになってしまっています。こうすればもっと良いプログラムになるなどのアドバイスがあれば合わせてお願いいたします。
###プログラムの大まかな流れ
最終目的は先述した通りです。各行で行っている事や変数の意味はコメントアウトしているのでそちらをご覧ください。
「class numerical」について(二階微分方程式を数値的に解くことを目的としたクラスです)
・「def init」:扱う微分方程式の係数などの条件を設定しています。
・「def derivative」:ある時刻における二階の微分係数をで求めています。引数はx,z,tで、返り値は一つの定数です
・「def euler」:オイラー法で微分方程式の数値解を求めています。引数は無く、返り値は従属変数の数値解のみが配列で返っています。
・「def exact」:引数で与えられた関数(微分方程式の厳密解を想定)をオイラー法で解いた時と同じ条件(=独立変数のレンジと刻み幅)で解いています。引数は関数となっており、その関数の条件は一つの変数、返り値は定数としています。
「class exact」について(二階微分方程式の厳密解を初期条件に合わせた特解を求めることを目的としています、ただし、非斉次方程式の場合の計算についてはもう少し汎用性が出るように現在制作中です)
・「def init」:扱う微分方程式の係数等の条件を設定しています
・「def oed_exact」:初期条件に合わせた特解を求め、その関数自体を返り値に設定しています。また、特解の決定には、特性方程式が重解の時とそうでないときで場合分けをしており、各係数を行列を用いて計算しています。
「class visualize」について(与えられた二つの配列を視覚化することを目的としたクラスです)
・「def init」:扱う配列とデータの名前を取得しています。
・「def plot」:シンプルな二次元のグラフをプロットしています。
・「stakplot」:新たにもう一つ配列(y成分のデータ)を取得し、事前に取得したデータと合わせてプロットしています。
・「def complot」:新たにもう一つ配列(y成分のデータ)を取得し、事前に取得したデータとの差の値に絶対値を取ってプロットしています。
・「def animation」:事前に取得したy軸のデータを利用してアニメーション(詳細にはパラパラ漫画的なもの)を作ってい表示しています。
以上です。実際のコードは次のようになっています。
該当のソースコード(classのファイル)
python3
1###クラスなどをまとめているファイルです 2import matplotlib.pyplot as plt 3import numpy as np 4import math 5 6class numerical:#定数係数の2階微分方程式を数値的に解くクラス 7 def __init__(self,constant,inhomo,initial,step,Max): 8 #微分方程式の係数,非斉次項,初期条件(独立変数→従属変数の階数が高い順),刻み幅,独立変数の最大値 9 self.constant=constant 10 self.initial=initial 11 self.step=step 12 self.variable=[round((initial[0]+i*step),5) for i in range(int((Max-initial[0])/step)+1)]#数値解と解析解で扱う微小時間を共通にしたいので最初のうちに計算しておく[内包表記] 13 self.inhomo=inhomo#非斉次項の関数を代入 14 def derivative(self,x,z,t):#微分係数を微分方程式から求める 15 return -(self.constant[1]*z+self.constant[2]*x-self.inhomo(t))/self.constant[0] 16 17 def euler(self):#オイラー法 18 x=[] 19 x.append(self.initial[1])#初期条件の代入 20 z=self.initial[2] 21 for i in range(1,len(self.variable)): 22 x.append(x[i-1]+z*self.step) 23 z=z+self.derivative(x[i],z,self.variable[i])*self.step 24 return x 25 26 def exact(self,fanction):#入力された解析解を数値解と同じ条件(レンジや刻み幅)で解く 27 exact=[fanction(i) for i in self.variable] 28 return exact 29 30class exact:#定数係数二階微分方程式を初期条件に合わせた特解を求めるクラス、返り値は関数で設定(高階関数:クロージャ)、返す関数の引数と返り値はともに一つのみ 31 def __init__(self,constant,initial,special,derivative):#微分方程式の係数、初期条件、非斉次方程式の特解、特解の微分係数 32 self.constant=constant 33 self.initial=initial 34 self.special=special 35 self.derivative=derivative 36 def ode_exact(self):#非斉次の二階微分方程式 37 m,c,k=self.constant[0],self.constant[1],self.constant[2] 38 t0,x0,v0=self.initial[0],self.initial[1],self.initial[2] 39 epsilon=c/pow(4*m*k,0.5) 40 alpha=self.special(t0) 41 beta=self.derivative(t0) 42 if (epsilon!=1):#特性方程式が重解でないとき 43 s1=-epsilon+pow(epsilon**2-1,0.5)#一般解の係数 44 s2=-epsilon-pow(epsilon**2-1,0.5) 45 A=np.array([[x0-alpha],[v0-beta]]) 46 H=np.array([[1,1],[s1,s2]]) 47 Ad=np.dot(np.linalg.inv(H),A) 48 a=Ad[0][0] 49 b=Ad[1][0] 50 def f(t): 51 return (a*pow(math.e,s1*t)+b*pow(math.e,s2*t)+self.special(t)).real 52 53 return f 54 else: 55 A=np.array([[x0-alpha],[v0-beta]]) 56 H=np.array([[1,0],[-epsilon*k/m,1]]) 57 Ad=np.dot(np.linalg.inv(H),A) 58 a=Ad[0] 59 b=Ad[1] 60 def f(t): 61 return ((a+b*t)*pow(math.e,-epsilon*k*t/m)+self.special(t)).real 62 return f 63 64 65class visualize:#視覚化するクラス(アニメーションなどを追加予定) 66 def __init__(self,x,y,label):#視覚化したいデータを取得 67 self.x=x 68 self.y=y 69 self.label=label 70 def plot(self,Xlim,Ylim):#取得したデータをグラフ化 71 fig=plt.figure() 72 ax=plt.axes(xlim=Xlim,ylim=Ylim) 73 ax.plot(self.x,self.y,label=self.label) 74 plt.legend() 75 plt.show() 76 77 def stakplot(self,Xlim,Ylim,x2,Label):#既存のデータと入力されたデータを重ねて比較 78 fig=plt.figure() 79 ax=plt.axes(xlim=Xlim,ylim=Ylim) 80 ax.plot(self.x,self.y,label=self.label) 81 ax.plot(self.x,x2,label=Label) 82 plt.legend() 83 plt.show() 84 85 def complot(self,Xlim,Ylim,x2,Label):#二つのデータを差を取って比較し、プロット 86 if (len(self.y)!=len(x2)): 87 print("比較する要素数が違います") 88 return 89 c=[] 90 for i in range(len(self.x)): 91 c.append(abs(x2[i]-self.y[i])) 92 fig=plt.figure() 93 ax=plt.axes(xlim=Xlim,ylim=Ylim) 94 ax.plot(self.x,c,label=Label) 95 plt.legend() 96 plt.show() 97 98 def animetion(self,Xlim,Ylim): 99 fig=plt.figure() 100 ax=plt.axes(xlim=Xlim,ylim=Ylim) 101 ax.set_aspect('equal') 102 circle=plt.Circle((self.y[0],0),0.1,fc="r") 103 ax.add_patch(circle) 104 for i in range(1,len(self.y)-1): 105 circle.center=self.y[i],0.0 106 plt.pause(0.001) 107 plt.show() 108 109
該当のソースコード(mainのファイル)
python3
1import odeclass 2import matplotlib.pyplot as plt 3import numpy as np 4import math 5m,c,k=1,0,1 6constant=[m,c,k]#微分方程式の係数:階数が高い変数にかかっている係数から並べる 7initial=[0,2.0,0]#初期条件:t0,x0,v0 8def zero(t): 9 return 0 10f=odeclass.exact(constant,initial,zero,zero) 11fanc=f.ode_exact()#厳密解を初期条件に合わせた特解を決定 12step=0.01 13z=odeclass.numerical(constant,zero,initial,step,100) 14t=z.variable 15x1=z.euler() 16x2=z.exact(fanc) 17v=odeclass.visualize(t,x1,"euler") 18v.complot((0,100),(0,0.1),x2,"error") 19
###実行結果
オイラー法で解いた数値解と解析解との差の値に対して絶対値を取り、時間との関係を表示しています。
試したこと
実際に手で計算した結果と比較した時も同じ結果が出ました。ここにおける比較対象はオイラー法で解いた数値解以外に、プログラムで決定した特解の数値解両方確認しました。
補足情報
プログラム初心者であり、classの使い方とファイル間のやり取りを練習をしたかったので見にくいプログラムになってしまっています。classの組み方にこうしたほうがもっと見やすくなる、責任を分散のさせ方が悪い部分などのご指摘もあれば合わせてコメントいただけると助かります。よろしくお願いします。
回答1件
あなたの回答
tips
プレビュー