teratail header banner
teratail header banner
質問するログイン新規登録
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

1回答

830閲覧

C言語 ルンゲクッタ法2階微分の計算

yusuke.

総合スコア66

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2022/07/06 14:23

編集2022/07/11 08:12

0

0

ご覧いただきありがとうございます。

ルンゲクッタ法を用いて

dq/dt=p dp/dt=-q p0=1 q0=0

を解きたいです。
現在、ウェブサイトでルンゲクッタ法の解き方を自分で調べて書いてみたのですが、明らかにおかしな値(sin,cos の値になっていない)になっています。
参考にしたところに書いてあったのは

d^2y/dx^2=f(x,y,y') を解くのに y'=p とおいて dp1=h*f(x,y,p) dy1=h*p dp2=h*f(x+h/2,y+dy1/2,p+dp1/2) dy2=h*(p+dp1/2) dp3=h*f(x+h/2,y+dy2/2,p+dp2/2) dy3=h*f(p+dp2/2) dp4=h*f(x+h,y+dy3,p+dp3) dy4=h*(p+dp3)

で私の場合
y=q,x=t,y'=p,f(x,y,y')=-q
と置き換えました。
こう置き換えた場合、p,qがtの関数であるはずなのにうまくtでパラメータ表示できていないのですが、どう改善すればいいのでしょうか。

c

1double runge(double q0, double p0, double t0, double tn, int n) 2{ 3 int i; 4 double q, p, t, h, dq1, dq2, dq3, dq4, dp1, dp2, dp3, dp4; 5 q = q0; 6 p = p0; 7 t = t0; 8 h = (tn - t0) /n; 9 10 11 for ( i=1; i <= n ; i++){ 12 13 dp1 = -q; 14 dq1 = p; 15 dp2 = -(q+dq1/2); 16 dq2 = p + dp1/2; 17 dp3 = -(q + dq2/2); 18 dq3 = p+dp2/2; 19 dp4 = -(q + dq3); 20 dq4 = p+ dp3; 21 q += (dq1 + 2 * dq2 + 2 * dq3 + dq4)*(h/6.0); 22   t = t0 + i*h; 23 printf("q(%f)=%f\n", t, q); 24    p += (dp1 + 2 * dp2 + 2 * dp3 + dp4)*(h/6.0); 25 printf("p(%f)=%f\n", t, p);

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

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

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

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

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

RiaFeed

2022/07/06 15:52

forの外でtに値を代入しているのにforのすぐ下でまたtに違う値を代入していますね。 多分ループ1回分ずれてるんじゃないかな?
fana

2022/07/11 01:05

> 明らかにおかしな値になっています って言うけど,じゃあ「正しい値」っていくつなんですか? っていうのを示さないと伝わらないのでは. で,それはそれとして,pを更新する処理が無いように見えますが.
yusuke.

2022/07/11 08:10

ご指摘ありがとうございます。 pについてはコピーミスです。
fana

2022/07/13 08:27

区間幅 h が各勾配値の計算にも現れねばならないと思うのですが,どうなのでしょう?
guest

回答1

0

ベストアンサー

正直,自信はありませんが,以下の考えで実装してみたところ,それっぽい値が得られた感じです.

イメージ説明


[考え]

(いろいろ省略せずに)数式を以下のように書いてみましょう.

P'(t) = -Q( t, P(t) )
Q'(t) = P( t, Q(t) )

すなわち,右辺の関数の引数に P(t)Q(t) が含まれている形に.

で,ルンゲクッタ法について考えてみます.
関数 P(t) の側を例にすると,次の時刻での値 P(t+dt) を求めるために,

  1. 時刻 t における勾配 P'(t) (: これは数式で与えられている)
  2. 1.の勾配を用いて推定した区間中点での勾配 P'(t+dt/2)
  3. 2.の勾配を用いて推定した区間中点での勾配 P'(t+dt/2)
  4. 3.の勾配を用いて推定した区間末尾での勾配の推定値 P'( t+dt )

を用いるわけで,これらの計算をどうするのか? っていうのが問題点でしょう.

例えば,2.について考えてみる(3.や4.も話は同様なので)と,求める P'(t+dt/2) というのは,最初に書いたように,
P'(t+dt/2) = -Q( t+dt/2, P(t+dt/2) ) ですから,

  • ここの右辺にある P(t+dt/2) の値はどうすればよいのか?
  • そしてそれをどう使うのか?

を考える必要がありそうですね.
ルンゲクッタ法のアルゴリズムの意味に沿って考えれば,このステップ(2.)というのは,
【1.の勾配を用いて,区間中点での勾配をオイラー法的に求める】ですから,その値については
P(t) + 0.5*dt + (1.で求めた勾配)
とすれば良さそうです.

では,この値の用途はどうするのか?
すなわち,この値をどうにかして用いて Q( t+dt/2, この値 ) の推定値を作る必要があるのだけど,どうするのか?っていう.
当然(?),ここでの Q(t+dt/2) もオイラー法的に{ Q(t), P(t) と,この P'(t+dt/2) 推定値 }から作ればよいのだと思いますが,しかしながら素のオイラー法を考えると P'(t+dt/2) 推定値の出番がなくなってしまう^^
→なので,ここを「修正オイラー法な感じにすることで,こいつを使ってやるぜ!」とすればよいのではないかな,と考えました.すなわち,
P'(t+dt/2) = -Q( t+dt/2, P(t+dt/2) ) の右辺にある Q( t+dt/2, P(t+dt/2) ) の値を
= Q(t) + 0.5*dt * ( Q'(t) + Q'(t+dt)推定値 )*0.5 として2つの勾配値から求めることとし,ここで Q'(t+dt)推定値 = P(t+dt/2)推定値 として使用しています.


[コード]

C++

1int main() 2{ 3 double t = 0; //※tはprintf()にしか使ってないです. 4 double P_t = 1; //初期値 P(t=0) = 1 5 double Q_t = 0; //初期値 Q(t=0) = 0 6 7 //適当な刻み幅でやってみた 8 const double PI = acos(-1.0); 9 const double dt = PI/180.0; 10 11 for( int i=0; i<360; ++i ) 12 { 13 //P(t+dt)をルンゲクッタ法で求む 14 double P_next; 15 {//P_next 16 //tでの勾配 17 double dP_t = -Q_t; //P'(t) = -Q(t) 18 19 //中点 t+dt/2 での勾配の推定値2つ 20 double dP_mid[2]; // P'(中点) = -Q( 中点, P(中点)推定値 ) 21 {//dP_mid[0] 22 //dP_t を用いて推定した P(中点) 23 double P_mid_est = P_t + 0.5*dt*dP_t; 24 25 //Q(t)に関する2つの勾配値 26 // Q'(t) = P(t) 27 // Q'(中点) = P(中点) 28 //の2つから Q(t+dt/2) を推定(修正オイラー法な感じで) 29 double Q_mid_est = Q_t + 0.5 * 0.5*dt * ( P_t + P_mid_est ); 30 // 31 dP_mid[0] = -Q_mid_est; 32 } 33 {//dP_mid[1] 34 //dP_mid[0] を用いて推定した P(中点) 35 double P_mid_est = P_t + 0.5*dt*dP_mid[0]; 36 //(dP_mid[0] と同様) 37 double Q_mid_est = Q_t + 0.5 * 0.5*dt * ( P_t + P_mid_est ); 38 dP_mid[1] = -Q_mid_est; 39 } 40 41 //区間末尾 t+dt での勾配推定値 42 double dP_next; //P'(末尾) = -Q( 末尾, P(末尾)の推定値 ) 43 { 44 //dP_mid[1] を用いて推定した P(末尾) 45 double P_next_est = P_t + dt*dP_mid[1]; 46 // 47 double Q_next_est = Q_t + 0.5 * dt * ( P_t + P_next_est ); 48 dP_next = -Q_next_est; 49 } 50 51 // 52 P_next = P_t + dt*( dP_t + 2*( dP_mid[0] + dP_mid[1] ) + dP_next )/6.0; 53 } 54 55 //Q(t+dt)側も同様 56 double Q_next; 57 {//Q_next 58 //tでの勾配 59 double dQ_t = P_t; //Q'(t) = P(t) 60 61 //中点 t+dt/2 での勾配の推定値2つ 62 double dQ_mid[2]; // Q'(中点) = P( 中点, Q(中点)推定値 ) 63 {//dQ_mid[0] 64 double Q_mid_est = Q_t + 0.5*dt*dQ_t; 65 66 //P(t)に関する2つの勾配値 67 // P'(t) = -Q(t) 68 // P'(中点) = -Q(中点) 69 //の2つから P(t+dt/2) を推定(修正オイラー法な感じで) 70 double P_mid_est = P_t + 0.5 * 0.5*dt * -( Q_t + Q_mid_est ); 71 dQ_mid[0] = P_mid_est; 72 } 73 {//dQ_mid[1] 74 double Q_mid_est = Q_t + 0.5*dt*dQ_mid[0]; 75 double P_mid_est = P_t + 0.5 * 0.5*dt * -( Q_t + Q_mid_est ); 76 dQ_mid[1] = P_mid_est; 77 } 78 79 //区間末尾 t+dt での勾配推定値 80 double dQ_next; //Q'(末尾) = P( 末尾, Q(末尾)の推定値 ) 81 { 82 double Q_next_est = Q_t + dt*dQ_mid[1]; 83 double P_next_est = P_t + 0.5 * dt * -( Q_t + Q_next_est ); 84 dQ_next = P_next_est; 85 } 86 87 // 88 Q_next = Q_t + dt*( dQ_t + 2*( dQ_mid[0] + dQ_mid[1] ) + dQ_next )/6.0; 89 } 90 91 //更新 92 P_t = P_next; 93 Q_t = Q_next; 94 t += dt; 95 printf( "%f, %f, %f\n", t, P_t, Q_t ); 96 } 97 98 return 0; 99}

投稿2022/07/13 08:18

編集2022/07/13 08:24
fana

総合スコア12229

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.30%

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

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

質問する

関連した質問