質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

4回答

374閲覧

ルンゲクッタ法 誤差 python

sshhoo

総合スコア15

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

0クリップ

投稿2018/02/21 02:07

ルンゲクッタ法を用いて微分方程式を解きたいのですが、ループ文がうまくつくれません。
具体的には関数RKで出た値'u0'をfor文で複数回繰り返す際に、値を更新していって欲しいのですが、コードを書くことが出来ずに同じ計算結果がリストに溜まってしまいます。

どのようなコードを書けばよいでしょうか?
初歩的なテクニックかもしれませんが、うまく書けません。
よろしくお願いいたします。

python

1#各モジュールをインポート 2import numpy as np 3import matplotlib.pyplot as plt 4%matplotlib inline 5 6#時間経過を定義 7dt_1=0.0001 8 9#時間の初期値を入力 10t0=0 11 12#時間の最大値 13t_max=10 14#試行回数 15trials_1=np.arange(t0,t_max,dt_1) 16 17#関数の値を入れるオブジェクトを生成 18a_points_1=[] 19 20#関数を定義 21def u(t,x,x1): 22 return -4*x 23#x'の式 24def y(t,x,x1): 25 return x1 26 27#runge-kuttaをtrials回実行 28#結果をa_pointsに入れる 29def rp(): 30 t,y0,u0=0,1,0 31 for t in trials_1: 32 x1=RK(y0,u0,dt_1) 33 a_points_1.append(x1) 34 print(a_points_1[:3]) 35 36#runge-kuttaの実行式 37#k:y, l:u 38def RK(y0,u0,dt): 39 40 k1=y0*dt 41 l1=u0*dt 42 43 k2=(u0+l1*0.5)*dt 44 l2=-4*(y0+k1*0.5)*dt 45 46 k3=(u0+l2*0.5)*dt 47 l3=-4*(y0+k2*0.5)*dt 48 49 k4=(u0+l3)*dt 50 l4=-4*(y0+k3)*dt 51 52 k=(k1+2*k2+2*k3+k4)/6 53 l=(l1+2*l2+2*l3+l4)/6 54 55 y0=y0+k 56 u0=u0+l 57 58 return u0 59#このu0の値をfor文で繰り返す際に更新していきたい 60#現状では初期値のu0=0が常に代入されて、同じ計算結果が返ってくる 61 62#プログラムを実行 63rp()

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

ベストアンサー

デバッグに伴って、逆にわかりにくくなっているので、落ち着いて書き直したほうが早いかもしれません。
実装自体が目的と想定して、正しい値を出すであろうscipyの結果との比較を入れました。

python

1import numpy as np 2import scipy.integrate 3import matplotlib.pyplot as plt 4%matplotlib inline 5 6# initial values 7dt = 0.001 8t0 = 0. 9t1 = 10. 10x0 = np.array([0., 1.]) 11 12# time 13N = int((t1-t0)/dt) 14dt = (t1-t0)/N 15trials = np.linspace(t0, t1, N) 16 17# target function 18def f(t, x): 19 A = np.array([[0., 1.], [-1., 0.]]) 20 return np.dot(A, x) 21 22# integrator 23def gen_evolve(dt, f): 24 def evolve(x, t): 25 k1 = f(t, x) 26 k2 = f(t + dt/2., x + k1*dt/2.) 27 k3 = f(t + dt/2., x + k2*dt/2.) 28 k4 = f(t + dt, x + k3*dt) 29 k = (k1 + 2.*k2 + 2.*k3 + k4)*dt/6. 30 x = x + k 31 t = t + dt 32 return x, t 33 return evolve 34 35# runge-kutta 36def rp(evolve, x0, t0, t1): 37 ans = [] 38 x = x0.copy() 39 t = t0 40 while t < t1: 41 x, t = evolve(x, t) 42 ans.append(x) 43 return np.array(ans) 44 45# self-defined integrator 46ans0 = rp(gen_evolve(dt, f), x0, t0, t1) 47 48# from scipy 49r = scipy.integrate.ode(f) 50r.set_integrator('dopri5') 51r.set_initial_value(x0, t0) 52ans1 = [] 53while r.successful() and r.t < t1: 54 r.integrate(r.t+dt) 55 ans1.append(r.y) 56ans1 = np.array(ans1) 57 58# two methods should give same result 59assert(np.linalg.norm(ans0-ans1)<0.1) 60 61# plot 62fig, ax = plt.subplots(ncols=3, dpi=200) 63ax[0].set_aspect('equal') 64ax[0].scatter(ans0[::100, 0], ans0[::100, 1], s=0.5, color='blue') 65ax[1].set_aspect('equal') 66ax[1].scatter(ans1[::100, 0], ans1[::100, 1], s=0.5, color='red') 67ax[2].set_aspect('equal') 68ax[2].scatter(ans0[::100, 0], ans0[::100, 1], s=0.5, color='blue') 69ax[2].scatter(ans1[::100, 0], ans1[::100, 1], s=0.5, color='red') 70plt.show()

Runge-Kuttaの式に関しては、

python

1k1=y0*dt 2l1=u0*dt

が多分正しくない気がします。

投稿2018/02/21 08:49

編集2018/02/21 09:00
mkgrei

総合スコア8560

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sshhoo

2018/02/21 12:12

ご回答誠にありがとうございます。 ご指摘頂いた通り、自作したプログラムだと解析解との乖離が依然としてありましたので、大変助かりました。 お伺いしたいのですが、 # target function def f(t, x): A = np.array([[0., 1.], [-1., 0.]]) return np.dot(A, x) Aに関して、なぜこのような行列が出てきたのかわからないのですが、解説して頂けないでしょうか? お手数ですがよろしくお願い致します。
mkgrei

2018/02/21 12:37

もとの問題は、 dx/dt = y dy/dt = -4x だったので、 v = [[x], [y]]として、 dv/dt = Av A = [[0,1], [-4,0]] となります。 -4ではなく-1になっているのは手元で丸い円をとりあえず描こうとして変更し忘れたからです。 ぼーっとしていました。
sshhoo

2018/02/22 07:43

ご解説ありがとうございます。 行列で少し躓きましたが、コードを参考にしながらようやく書き終えることが出来ました。 御礼申し上げます。
guest

0

正直ルンゲクッタ法の事は名前しか覚えていません。
ですから、想像の範囲になりますが、こういう風に書けばいいんじゃないかなぁ。

Python

1def rp(): 2 y0, u0 = 1, 0 3 for _ in trials_1: 4 y0, u0 = RK(y0, u0) 5 a_points_1.append(u0) 6 7 print(a_points_1[:3]) 8 9def RK(y0, u0, dt=0.0001): 10 ... 11 12 return y0, u0

関数内での再代入は、関数外には一切影響しません。(グローバル宣言している場合を除く)
基本的に数値や文字列を関数に与えた場合、その値が変わることはあり得ません。

投稿2018/02/21 05:02

LouiS0616

総合スコア35660

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

Pythonのintやfloatはimmutableなので、関数の中で値を更新されてもその外には反映されません。たとえば、numpy.arrayに変換してあげると、関数内で数値を更新させることができます。

Python

1t = np.array(0.0, dtype=np.float) 2y0 = np.array(1.0, dtype=np.float) 3u0 = np.array(0.0, dtype=np.float)

これをt,y0,u0=0,1,0の代わりに使ってください。
それから、値を更新する部分:

Python

1 y0=y0+k 2 u0=u0+l 3 4 return u0

を、次のようにしてください(LouiS0616さんのコメントを受けて修正)。

Python

1 y0 += k 2 u0 += l 3 4 return u0.copy()

以上を踏まえてコード全体を再掲

python

1#各モジュールをインポート 2import numpy as np 3import matplotlib.pyplot as plt 4%matplotlib inline 5 6#時間経過を定義 7dt_1=0.0001 8 9#時間の初期値を入力 10t0=0 11 12#時間の最大値 13t_max=10 14#試行回数 15trials_1=np.arange(t0,t_max,dt_1) 16 17#関数の値を入れるオブジェクトを生成 18a_points_1=[] 19 20#関数を定義 21def u(t,x,x1): 22 return -4*x 23#x'の式 24def y(t,x,x1): 25 return x1 26 27#runge-kuttaをtrials回実行 28#結果をa_pointsに入れる 29def rp(): 30 y0 = np.array(1.0, dtype=np.float) 31 u0 = np.array(0.0, dtype=np.float) 32 for t in trials_1: 33 x1=RK(y0,u0,dt_1) 34 a_points_1.append(x1) 35 print(a_points_1[:3]) 36 37#runge-kuttaの実行式 38#k:y, l:u 39def RK(y0,u0,dt): 40 41 k1=y0*dt 42 l1=u0*dt 43 44 k2=(u0+l1*0.5)*dt 45 l2=-4*(y0+k1*0.5)*dt 46 47 k3=(u0+l2*0.5)*dt 48 l3=-4*(y0+k2*0.5)*dt 49 50 k4=(u0+l3)*dt 51 l4=-4*(y0+k3)*dt 52 53 k=(k1+2*k2+2*k3+k4)/6 54 l=(l1+2*l2+2*l3+l4)/6 55 56 y0+=k 57 u0+=l 58 59 return u0.copy() # もしくはfloat(u0) 60#このu0の値をfor文で繰り返す際に更新していきたい 61#現状では初期値のu0=0が常に代入されて、同じ計算結果が返ってくる 62 63#プログラムを実行 64rp()

投稿2018/02/21 02:53

編集2018/02/21 06:46
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

LouiS0616

2018/02/21 04:55

イミュータブルであることは関係しないのでは?再代入ですから。 --- def hoge(lst): ...lst = [1, 2, 3] lst = [] hoge(lst) print(lst) # []
退会済みユーザー

退会済みユーザー

2018/02/21 05:07

あー関数の中y0+=kとやる前提での対応法をかいておりました。そこも修正がいりますね。
退会済みユーザー

退会済みユーザー

2018/02/21 05:11

修正しました。これでどうでしょうか。
退会済みユーザー

退会済みユーザー

2018/02/21 05:29

イミュータブルに関するご指摘ですが、確かにa = a+1は再代入が発生してしいます。ただし、(objectのメソッドの定義次第ですが)これはa += 1と同じではなく、inplaceで行われるものがあります。今回の回答はnumpy.arrayがmutableであること、+=がinplaceで行われることを利用した回答です。そもそも関数の中でオブジェクトの状態を変えてしまうので、トリッキーと言えばトリッキーですし、デバッグしにくくなります。ただ、質問者さんは関数の中で値を更新したい、とのことでしたので、それを実現するための方法を提案してみました。
LouiS0616

2018/02/21 05:30

迅速な対応感謝します。 うーん... 個人的には0次元配列、あるいは要素数1のコンテナは避けたい感があります。 ただこれに関しては単に好みの問題かもしれません。
退会済みユーザー

退会済みユーザー

2018/02/21 05:33 編集

その点に関しては同意します。やるとしてもlistやdictに放り込んでしまうほうがマシな気がします。
LouiS0616

2018/02/21 05:33

確かに文字通り『関数の中で値を更新したい』のならこのようになるでしょうね。
sshhoo

2018/02/21 06:38 編集

お二方ともご回答頂きまして誠にありがとうございます。 少し頭を整理してもう一度再考してみました。 やりたいこととしましては、関数RK内で初期値y0,u0を与えて関数内の計算を処理したのちに y0=y0+k u0=u0+l として、値を更新した後に、更新した値を関数RKの初期値として用い同様の計算を行う行程を複数回実行して、微分方程式の数値解を求めたいという流れです。 関数RKから帰ってくる値をappendでリストに入れていますが、リストに入ってくる値は全て同じ値になってしまいます。 つまり、関数RKが永遠と初期値を使って同じ計算をしているのかなと考えております。 y0=y0+k u0=u0+l を y_new=y0+k u_new=u0+l と書き直しますと 関数RKを実行するたびに、毎回初期値であるy0,u0を用いて計算するのではなく、最新のy_new,u_newの値を使って関数RKを動かしたいと考えております。 具体的には for i in trials_1: で(0<n<=i) n回目の関数RKの施行時だと、RKの引数にはn-1回目で得られたy_new,u_newの値を用いて計算を行うプログラムを作りたいです。 質問力が貧弱であるため、うまく伝えられなかったと思い長々とコメントさせて頂きました。 質問に関してまた何かいい方法があれば、お返事をよろしくお願い申し上げます。 正直ルンゲクッタ法を誤って解釈している可能性も捨てきれません。 その際はご迷惑をおかけしたことを予め謝らせて下さい。
退会済みユーザー

退会済みユーザー

2018/02/21 06:54

念のためコード全体を再掲しました。今回の問題になっているのは、関数内での値の更新が、Pythonの組み込みのデータ(intやfloat)の性質上やりにくい点にあります。私のコードはnumpyライブラリに頼った方法ですが、LouiS0616のほうも見比べて使いやすいほうを選んでいただければいいんじゃないかと思います。なお、ルンゲタックの式があっているかは確認できていません。悪しからず・・・
sshhoo

2018/02/21 07:55

大変丁寧にご回答頂きまして誠にありがとうございます。 自分なりに理解できたつもりでいます。 助かりました。 コメントで a=a+1 と a += 1 は同一ではないとおっしゃっていましたが、どのような違いがあるのですか? 初心者が理解できる範囲外である場合は、そう答えて頂いて結構です。
退会済みユーザー

退会済みユーザー

2018/02/21 08:23

最初に呼び出そうとするメソッドが違います。a+1はa.__add__を最初に、a+=1はa.__iadd__をそれぞれ最初に探しに行きます。ただし、aに__iadd__が無ければ、代わりにa=a.__add__(1)と再代入が発生する形で評価します。
退会済みユーザー

退会済みユーザー

2018/02/21 08:36 編集

ためしに (immutableであるintクラス) int.__dict__を覗いてみると、__add__, __radd__はありますが、__iadd__はありません。 (mutableなnumpy.ndarrayクラス) 一方、numpy.ndarray.__dict__には__iadd__もあります。ちなみに、iaddのiはinplaceのiだと思います。 なお、immutableとはオブジェクトの中身の変更を許さない性質を意味するので、inplaceで作用するiaddがないのも当然ですね。
sshhoo

2018/02/21 09:58

詳細なご回答誠にありがとうございます。 勉強になりました。
guest

0

pythonは使った事がないけど確実に言える事があります。

return u0 していて x1=RK(y0,u0,dt_1) となっているなら
戻り値はx1に入っている訳でして、戻り値を u0 に反映させたいなら
以下のようにする必要があると思われます。

x1=RK(y0,u0,dt_1)
u0=x1 ←この行が足りない

それと引数に対して値を代入するのは基本的にまずいので
y0とu0をグローバル変数にして引数で渡さないようにするか、
以下のようにして別の変数に計算結果を入れて値を返す様にするべきです。

まずい:
y0=y0+k
u0=u0+l
return u0

よい:
y1=y0+k
u1=u0+l
return u1

お二方の回答を見て、そもそもの前提を見落としていた事に気が付きました、お恥ずかしい限りです。
この回答は無視して下さいますようお願いいたします。

投稿2018/02/21 02:19

編集2018/02/21 06:05
rafiene3249

総合スコア53

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sshhoo

2018/02/21 12:15

ご協力並びにお心遣いありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問