Comments
Description
Transcript
x - 牛と戯れた日々を想って
GAMBAS 始めました。 Linux で動かす BASIC プログラム 第 4 回 数値計算 by HOLY Ver.0(2014.10.03〜2015.06.27) はじめに ずいぶん前からコンピュータは数値計算や研究活動の道具と して使われてきました。現在、コンピュータは市場のニーズに基づ いて新しい利用法を以って発展していると言えます。 つまり、情報の伝達と通信です。 端末はかつて建物の1フロアを占めるほどの大きさから、ノート 型になり、タブレットや携帯型になっています。現在、生活の場面 では、コンピュータが数値計算の道具であることが忘れられよう としています。 80年代頃のパソコンの入門書にはたいてい BASIC のサンプ ルプログラムが掲載されておりました。ハナタレ小僧の頃、それら のリストの中ではゲームばかり目がいってたんですが、その中で もキラリと光る分野がありました。そう、数値計算です。今にしてみ れば、それら数値計算のサンプルは重要なことを書いてたなあと 想います。そりゃあ当時、電子計算機の本ってのは、その筋のプロ たちが書いてたんですからねぇ。 この章ではコンピュータの基本的な役割にたち帰って、数値解 法を紹介したいと想います。 (仮称)十進 BASIC と比較 GAMBAS の結果を比較するために(仮称)十進 BASIC のリス トと結果も記述することにします。(仮称)十進 BASIC は、JIS 規格 に準拠しており、標準規定の BASIC 言語として比較するのにちょ うどよい位置づけです。 (仮称)十進 BASIC も自由にダウンロードして使用することので きる BASIC 言語のひとつです。しかもれっきとした日本製で、初 学者や初心者に限らず、ベテランまで普段から電卓代わりに使え るツールとして国内では知られた由緒正しい BASIC です。 プラットフォームも、Windows、MaxOS、Linux のように主な OS を網羅してるので、複数端末をもってるとソースを使いまわせるの でありがたいです。 両方のプログラムを比較してみると、 (仮称)十進 BASIC の特 徴は (1) 変数の型の宣言がいらない。 (GAMBAS は変数宣言が必要です) (2) (仮称)十進 BASIC では while〜end 文はない。 (Loop 文は、For〜Next と Do〜until がある) (3) 行番号なしの構造化 BASIC 様の書き方でも、行番号を使う 方法にも対応。(昔から使ってるユーザへのアフターケア的仕様) (4) 十数年間、ずっと名前に”(仮称)”がついたままである。 (以下、(仮称)十進 BASIC を JIS-BASIC と表記します) 平方根 加減乗除の演算のみを用いて正の数の平方根の近似値を求 める方法を考えます。 縦の長さが a、横の長さが 1 の長方形と面積の等しい正方形 の 1 辺の長さが √ a です。そこではじめの長方形を面積を変えな いようにして縦と横の長さが近づくように変形していけばこの長 方形の辺の長さは √ a に近づくはずです。 変形して得られる長方形の縦の長さをもとの長方形の縦、横 の長さの平均にとることにします。すなわち、第 k 回目に得られる 長方形の縦横の長さをそれぞれ x(k), y(k)とするとき、 x( k +1)= x( k) + y( k ) 2 とします。すると、この変形で面積を変えないという条件から、横 の長さ y(k+1)は、 y ( k +1)= a x ( k +1) として定められます。これらの関係式から x(k)のみの式を導くと、 1 a x(0) =a, x( k +1)= ×( x ( k ) + ) 2 x(k) となります。次のプログラムは、a を入力すると、この数列{x(k)}の 第 8 項までを求めます。例は a=2 で、2 の平方根 1.41421356・・・ に収束していく様子です。 (JIS-BASIC) 10 INPUT a 20 LET x=a 30 FOR n=1 TO 8 40 LET x=(x+a/x)/2 50 PRINT n,x 60 NEXT n 70 END (Result) ?2 1 2 3 4 5 6 7 8 1.5 1.41666666666667 1.41421568627451 1.41421356237469 1.4142135623731 1.4142135623731 1.4142135623731 1.4142135623731 (GAMBAS) ' Gambas module file Sub Main() Dim a As Float Dim x As Float Dim n As Integer Print " root(a): a= ? "; Input a x=a For n = 1 To 8 x = (x + a / x) / 2 Print n, x Next End (Result) root(a): a= ? 2 1 1.5 2 1.41666666666667 3 1.41421568627451 4 5 6 7 8 1.41421356237469 1.4142135623731 1.4142135623731 1.4142135623731 1.4142135623731 たとえば SQR(36)の計算結果は正確に 6 になります。また角の 大きさの単位が度である場合には SIN(30)の計算結果は 0.5 に なります。しかし計算結果の真の値が 16 桁の十進数で表現でき ないときその結果は近似値になります。たとえば 1/3 の計算結果 には誤差が含まれます。 GAMBAS の数値変数は 15 桁の精度を持ちます。有効数字の 桁数が 15 桁を越える数値を代入すると有効数字が 15 桁になる ように数値が丸められます。 一方、JIS-BASIC の数値式の計算結果は 16 桁を越える精度 を持ちます。たとえば 1/3 の計算結果は 0.333333333333333333333333333 となって有効数字が 27 桁あ ります。ただし PRINT 文は有効数字が 15 桁になるように計算結 果を丸めて表示します。 数値式の桁数が数値変数の桁数より大きいのは日本工業規 格(JIS)の規定によるものですが、そこが GAMBAS の計算結果と は異なる現象を引き起こします。たとえば次のプログラムでは JIS-BASIC だと 70 行目の実行結果は 0 ではありません。これは JIS-BASIC での数値式と数値変数の丸める桁数に相違がある ためです。 (JIS-BASIC) 10 Print " SQRT(36)="; Sqr(36) 20 PRINT " sin(30)="; SIN(PI / 6) 30 ! 数値の丸め 40 LET A=1/3 50 Print A 60 Print 1 / 3 70 PRINT 1/3-A 80 END (Result) SQRT(36)= 6 sin(30)= .5 .333333333333333 .333333333333333 3.33333333333333E-16 (GAMBAS) ' Gambas module file Sub Main() Dim A As Float Print " SQRT(36)="; Sqr(36) Print " sin(30)="; Sin(Pi / 6) ' 数値の丸め A=1/3 Print A Print 1 / 3 Print 1 / 3 - A End (Result) SQRT(36)=6 sin(30)=0.5 0.33333333333333 0.33333333333333 0 桁落ち (JIS-BASIC) 10 LET A=1/3 20 PRINT A-0.33333333333333 30 END (Result) .000000000000003 (GAMBAS) ' Gambas module file Sub Main() Dim A As Float A=1/3 Print A - 0.33333333333333 End (Result) 3.3306690738755E-15 JIS-BASIC を実行すると 20 行では A の値が 0.333333333333333 になっているのですから実行結果は .000000000000003 になります。けれども、この結果は 3.33333333333333E-15 と書か れてもいいわけで、この結果は有効数字が 1 桁になってしまった といえます。 一方、GAMBAS では、計算結果は・・・33 と無限の循環少数に なるはずですが、e-17 からおかしな数値になっています。 このように計算結果が 15 桁では正確に表現できない数値の 引き算を行うと、正確に表現できなくなる現象を「桁落ち」と呼ん でいます。 関数 y=f(x)の x=a における微分係数 f'(a)は増分 h を限りなく 0 に近づけたときの平均変化率 f ( x +hh)−f ( x ) の極限で定義されま す。次のプログラムは f ( x )= √ x であるときに x=2 における平均変 化率が h の値を 10E-1 10E-2 10E-3 10E-4 ... 10E-15 と変化さ せたときにどう変化するか調べようとしたものです。h を 0 に近づ けたときの極限は理論的な計算では 2 1 2 (≒0.353553390593274)で √ すが実行結果をみると h が 0 に近づくとかえって正しい値から離 れていってしまっています。これは上に述べた減算に伴う桁落ち によるものです。 (JIS-BASIC) 10 DEF f(x)=SQR(x) 20 FOR i=1 TO 15 30 LET h=10^(-i) 40 PRINT h, (f(2+h)-f(2))/h 50 NEXT i 60 END (Result) .1 .01 .001 .349241122458488 .35311255026876 .3535092074646 .0001 .353548971286 .00001 .35355294866 .000001 .3535533464 .0000001 .353553386 .00000001 .3535534 .000000001 .3535534 .0000000001 .353554 .00000000001 .35356 .000000000001 .3536 .0000000000001 .354 .00000000000001 .36 .000000000000001 0 (GAMBAS) ' Gambas module file Sub Main() Dim i As Integer Dim h As Float For i = 1 To 15 h = 10 ^ (- i) Print h, (Sqr(2 + h) - Sqr(2)) / h Next End (Result) 0.1 0.34924112245849 0.01 0.35311255026873 0.001 0.35350920746446 0.0001 0.35354897128581 1E-5 0.35355294865091 1E-6 0.35355334637721 1E-7 0.35355338434684 1E-8 0.35355338656728 1E-9 0.35355340877175 1E-10 0.35355274263793 1E-11 0.35353941996163 1E-12 0.35349501104065 1E-13 0.3530509218308 1E-14 0.35527136788005 1E-15 0.22204460492503 円周率 π 直径 1 の円に内接する正 n 角形の辺の長さの和は ですから、正 2k 角形の辺の長さの和を ak とすると、 となります。 半角の公式から cos √ 180 ° 1 180 ° = ⋅(1+cos k −1 ) k 2 2 2 180 ° n 180 ° ak =2k⋅sin k 2 となるので、 n⋅sin cos90 °, cos45° , cos が順に計算できます。 sin 45 ° 180 ° ,⋯ , cos k , ⋯ 2 2 √ 180 ° 180 ° = 1−cos2 k k 2 2 の関係を利用すれば ak の計算ができるのですが、実際にはうまくいきません。k が大き くなると、 180k ° が 0 に近くなるので、 cos 180k ° は 1 に近くなりま 2 2 2 す。そのため、 1−cos2 180 ° 2k の計算で桁落ちが発生します。 そこで、2 倍角の公式を変形して得られる関係式 sin θ sinθ = 2 θ 2⋅cos 2 を用いて、 cos90 °, cos45° , cos 45 ° 180 ° ,⋯ , cos k , ⋯ 2 2 を計算することにします。 cos 90° =1、sin 90° = 0 を出発点としてこれらの値を順に計算 し 2 k と 2k sin 180k ° を出力するプログラムを作ると次のようになり 2 ます。 (JIS-BASIC) 10 LET c=0 20 LET s=1 30 FOR k=2 TO 30 40 LET c=SQR((1+c)/2) 50 LET s=s/c/2 60 PRINT 2^k,2^k*s,pi-2^k*s 70 NEXT k 80 END (Result) 4 8 16 32 64 128 256 512 2.82842712474619 3.06146745892071 3.12144515225805 3.13654849054594 3.14033115695475 3.14127725093277 3.14151380114429 3.14157294036708 .313165528843605 8.01251946690812E-2 2.01475013317452E-2 5.04416304385724E-3 1.26149663504124E-3 3.15402657018838E-4 7.8852445498838E-5 1.9713222709718E-5 1024 2048 4096 8192 16384 32768 65536 131072 262144 524288 1048576 2097152 4194304 8388608 16777216 33554432 67108864 134217728 268435456 536870912 1073741824 3.14158772527715 3.14159142151119 3.14159234557011 3.14159257658486 3.14159263433856 3.14159264877698 3.14159265238659 3.14159265328899 3.14159265351459 3.14159265357099 3.14159265358509 3.14159265358862 3.1415926535895 3.14159265358972 3.14159265358977 3.14159265358978 3.14159265358979 3.14159265358979 3.1415926535898 3.1415926535898 3.1415926535898 4.928312639958E-6 1.232078604758E-6 3.08019685846E-7 7.700493359E-8 1.9251235286E-8 4.812812349E-9 1.203207434E-9 3.00802928E-10 7.5201802E-11 1.8798899E-11 4.700794E-12 1.177579E-12 2.96775E-13 7.4477E-14 2.4145E-14 1.0724E-14 7.368E-15 7.368E-15 -6.054E-15 -6.054E-15 -1.142281792E-14 (GAMBAS) ' Gambas module file Sub Main() Dim c As Float Dim s As Float Dim k As Integer c=0 s=1 For k = 2 To 30 c = Sqr((1 + c) / 2) s=s/c/2 Print 2 ^ k, 2 ^ k * s, Pi - 2 ^ k * s Next End (Result) 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 2.82842712474619 3.06146745892072 3.12144515225805 3.13654849054594 3.14033115695475 3.14127725093277 3.1415138011443 3.14157294036709 3.14158772527716 3.1415914215112 3.14159234557012 3.14159257658487 3.14159263433856 3.14159264877699 0.3131655288436 0.08012519466908 0.02014750133174 0.00504416304385 0.00126149663504 0.00031540265702 7.8852445492217E-5 1.9713222701778E-5 4.9283126335453E-6 1.2320785933717E-6 3.0801967554339E-7 7.7004920662205E-8 1.9251229943507E-8 4.8128074858766E-9 65536 131072 262144 524288 1048576 2097152 4194304 8388608 16777216 33554432 67108864 134217728 268435456 536870912 1073741824 3.14159265238659 3.14159265328899 3.14159265351459 3.14159265357099 3.14159265358509 3.14159265358862 3.1415926535895 3.14159265358972 3.14159265358977 3.14159265358979 3.14159265358979 3.14159265358979 3.14159265358979 3.14159265358979 3.14159265358979 1.2032019824915E-9 3.0080027357826E-10 7.519984634996E-11 1.8799628520583E-11 4.6993520186334E-12 1.1746159600534E-12 2.9354296771089E-13 7.327471962526E-14 1.8207657603853E-14 4.4408920985006E-15 8.8817841970013E-16 0 0 0 0 JIS-BASIC で、数値は浮動小数点 10 進数です。単精度、倍精 度の区別や、整数「型」というのはありません。10 進小数を 2 進 小数で近似するために発生する誤差がなく、0.1 を 10 回加算す れば正確に 1 になります。たとえば、次のプログラムでは、11 回繰 り返しを実行し、最後の結果は正確に 0 になります。 (JIS-BASIC) 10 FOR x=-1 TO 0 STEP 0.1 20 PRINT x 30 NEXT x 40 END Result -1 -.9 -.8 -.7 -.6 -.5 -.4 -.3 -.2 -.1 0 一方、GAMBAS です。 (GAMBAS) ' Gambas module file Public Sub Main() Dim x As Float For x = -1 To 0 Step 0.1 Print x Next Stop End (Result) -1 -0.9 -0.8 -0.7 -0.6 -0.5 -0.4 -0.3 -0.2 -0.1 -1.3877787807814E-16 ぐぬぬ、これでもダメか? (GAMBAS) ' Gambas module file Public Sub Main() Dim x As Float x = -1.0 x = x + 1.0E-01 x = x + 1.0E-01 x = x + 1.0E-01 x = x + 1.0E-01 x = x + 1.0E-01 x = x + 1.0E-01 x = x + 1.0E-01 x = x + 1.0E-01 x = x + 1.0E-01 x = x + 1.0E-01 Print x Stop End (Resutl) -1.3877787807814E-16 ねぇねぇ ねぇ 今どんな気持ち? 今どんな気持ち? ∩__∩ ∩__∩ ♪ / ⌒ ⌒丶 /⌒ ⌒ 丶 ♪ /(●) (●) ハッ (●) (●)| | (_●_) | ハッ ハッ | (_●_) | __彡 |∪| ミ ハッ 彡 |∪| ミ__ \__ 丶ノ> > < < 丶ノ __/ / /(_/ ♪ ♪ \_)\ 丶 / / \ 丶 (_⌒丶′ (フ ̄つ Y⌒_) `| /ヽ| / 丶. |/丶| (ノ U :| ー |: (ノ U : >、 U(_● :/ _ /ミノ. || 三| |゚。 || 三| |: (_(  ̄(_): 結果が同じ数値だけに、同程度の誤差が発生してるんだろう なあ。(*1) 演算結果の正確さ JIS-BASIC には数値演算の実行結果の正確さに関する規定 があります。この規定は四則演算ばかりでなく、べき乗演算や SQR 関数などの組込み関数にも適用されます。その規定のおか げで、べき乗演算や SQR 関数の予期せぬ誤差を心配する必要 がなくなります。 次のプログラムは 100 以下のピタゴラス数をすべて求めるも のですが、JIS-BASIC では正しいプログラムです。しかし、規格外 の BASIC では正しい結果が得られることが保証できません。実 際、昔の N88-BASIC では正しくない結果が得られます。 (JIS-BASIC) 10 FOR x=1 TO 100 20 FOR y=x TO 100 30 LET z=SQR(x^2+y^2) 40 IF INT(z)=z THEN PRINT x,y,z 50 NEXT y 60 NEXT x 70 END (GAMBAS) ' Gambas module file Public Sub Main() Dim x As Integer Dim y As Integer Dim z As Float For x = 1 To 100 For y = x To 100 z = Sqr(x ^ 2 + y ^ 2) If Int(z) = z Then Print x, y, z Endif Next Next Stop End (Result) 3 4 5 12 6 8 7 24 8 15 9 12 9 40 10 24 11 60 12 16 12 35 13 84 14 48 15 20 15 36 16 30 16 63 18 24 18 80 20 21 20 48 20 99 21 28 21 72 24 32 24 45 24 70 5 13 10 25 17 15 41 26 61 20 37 85 50 25 39 34 65 30 82 29 52 101 35 75 40 51 74 25 27 28 28 30 30 32 33 33 35 36 36 39 39 40 40 40 42 45 48 48 48 51 54 56 57 60 60 60 63 65 66 69 72 75 80 60 36 45 96 40 72 60 44 56 84 48 77 52 80 42 75 96 56 60 55 64 90 68 72 90 76 63 80 91 84 72 88 92 96 100 84 65 45 53 100 50 78 68 55 65 91 60 85 65 89 58 85 104 70 75 73 80 102 85 90 106 95 87 100 109 105 97 110 115 120 125 116 うん、よし。おk 参考文献 ・Microsoft BASIC 習熟者のための Full BASIC 入門 ・白石 和夫、(仮称)十進 BASIC による JIS Full BASIC 入門の手引き、1999.1.18 ・森本光生、長岡亮介、平尾淳一、奥村晴彦、数学とコンピュータ、放送大学教材、 2003.3.20 (*1)これはコンピュータの「間違い」ではなく、浮動小数点から必然的に由来する結 果というべきである。この「間違い」は決してコンピュータの欠点ではない。十進 BASIC は数学教育的観点からこのような誤差を避けるための特別処置を行なって いる。 素数の計算 2 ch の十進 BASIC スレに先人が載せていたプログラムを GAMBAS に移植してみました。 コンソールに数値を出力するだけですので、コマンドラインアプ リケーションを選んでください。 最初の while ループのCの上限値を変えてやればそこの値ま で延々と素数をダンプします。 マシンパワーに酔い痴れてください、とのことです。 ' Gambas module file ' sosu2jis.bas => sosu140916gam.bas Public Sub Main() Dim C As Integer Dim K As Integer C = 11 While C < 1000 K=3 While (Int(C / K) <> (C / K)) And (C > K * K) K=K+2 Wend If Int(C / K) <> (C / K) Then Print C End If C=C+2 Wend Stop End 十進 BASIC のソースファイルも書いておきます。REM 130行 目のCの値を変えてやればそこの値まで延々と素数をダンプしま す。 (JIS-BASIC) 10 OPTION ARITHMETIC RATIONAL 20 LET C=11 30 LET K=3 40 IF INT(C/K)<>(C/K) THEN 50 IF C>K*K THEN 60 LET K=K+2 70 GOTO 40 80 END IF 90 END IF 100 REM 110 IF INT(C/K)<>(C/K) THEN PRINT C; 120 LET C=C+2 130 IF C>10000 THEN STOP 1221 GOTO 30 END 二次方程式の解 まがいなりにも数値計算についてプログラミングの講釈をしよ うというのなら、まずここからはじめなければなりませんでした。今 回は二次方程式の解です。 方法は中学校で習う、二次方程式の解そのものです。二次方 程式、Y=A×X2+B×X+C があります。定数 A,B,C を与えて、Y=0 を満たす解 X1,X2 を求めます。 ずいぶん「基本だぜ!」みたいなことを書きましたが、実は先人 が作ったソースを GAMBAS への焼き直しました。というか工夫 のしようがない。むしろこの方のウェブサイトを紹介するためにコ ードを引用したということにしてください。他のページをご覧にな ってみてください。この方は含蓄深いことを書いてらっしゃいます。 ・ソース&参考 教育利用を目的とした簡易な Basic インタプリタ PlainBasic プログラミング例題、 2006.08.20 版 (GAMBAS) ' Gambas module file ' PlaineBASIC ex21equation2.bas => pbasex21gam.bas ' 二次方程式を AX^2+BX+C=0 解く Public Sub Main() Dim A As Float Dim B As Float Dim C As Float Dim D As Float Dim X1 As Float Dim X2 As Float A=2 B=3 C = -3 D=B*B-4*A*C If D < 0 Then Print "根がありません" Else If D = 0 Then Print "等根です X1=X2="; - B / (A + A) Else D = Sqr(D) X1 = (- B + D) / (A + A) X2 = (- B - D) / (A + A) Print "X1= "; X1, "X2= "; X2 Endif Stop End (Result) ex1 A=2, B=3, C=-3 の場合 X1= 0.68614066163451 X2= -2.18614066163451 ex2 A=1, B=2, C=1 の場合 等根です X1=X2=-1 ex3 A=1, B=2, C=5 の場合 根がありません 三次方程式の解 前回の続きです。これもやはり参考コードを元に GAMBAS に 焼き直しました。 三次方程式、Y=X3+A×X2+B×X+C=0 を満たす X の解を計算 します。 ここではカルダノ(Cardano,1501-1576)の公式を使います。 具体的には、3 次の対称マトリックスの固有値を計算するとき に使うことができます。 実用例は、鉄筋コンクリート梁の設計計算のときに現われる・・ ・そうです。この参考にしたウェブページを作った方は硬派なんで すなぁ(技術者的に)。 3 つ実数解があるとき、とくに大小順に並べ直しはしてません。 ・ソース&参考 教育利用を目的とした簡易な Basic インタプリタ PlainBasic プログラミング例題、 2006.08.20 版 (GAMBAS) ' Gambas module file ' PlaineBASIC ex22equation3.txt => pbasex22gam.bas ' 三次方程式の解(カルダノの解法) ' Cardano の公式を応用して ' 三次方程式を X^3+AX^2+BX+C=0 解く Public Sub main() Dim P As Float Dim A As Float Dim B As Float Dim C As Float Dim D As Float Dim Q As Float Dim D2 As Float Dim U1 As Float Dim U3 As Float Dim V1 As Float Dim X1 As Float Dim X2 As Float Dim X3 As Float Dim ANG3 As Float Dim CC As Float Dim SS As Float Dim R As Float A = -6 B = 11 C = -6 P=A*A/9-B/3 Q = -2 * A * A * A / 27 + A * B / 3 - C D2 = Q * Q - 4 * P * P * P D = Sqr(Abs(D2)) If D2 > 0 Then U3 = (Q + D) / 2 U1 = (Abs(U3)) ^ (1 / 3) If U3 < 0 Then U1 = - U1 V1 = P / U1 X1 = U1 + V1 - A / 3 Print "実数解は一つです X1="; X1 Endif Else If D2 = 0 Then X1 = (4 * Q) ^ (1 / 3) Print "等根です X1=X2=X3="; X1 - A / 3 Else ANG3 = Atn2(D, Q) / 3 CC = Cos(ANG3) SS = Sin(ANG3) R = Sqr(P) Print "三つの実根があります" X1 = R * (CC + CC) Print "X1= "; X1 - A / 3 X2 = R * (- CC - Sqr(3) * SS) Print "X2= "; X2 - A / 3 X3 = R * (CC - Sqr(3) * SS) Print "X3= "; X3 - A / 3 Endif Stop End (Result) ex1 A=-6, B=11, C=-6 の場合 三つの実根があります X1= 3 X2= 1 X3= 2 ex2 A=-5, B=8, C=-4 の場合 実数解は一つです X1=1 ex3 A=-3, B=3, C=-1 の場合 等根です X1=X2=X3=1 方程式の解について、放送大学のテキストにわかりやすく説明 されてましたので該当ページを抜粋させていただきます。 いずれの抜粋も ・森本光生、長岡亮介、平尾淳一、奥村晴彦、数学とコンピュータ、放送大学教材、 2003.3.20 からです。 「方程式を満たす未知数 x の値(根 root, 解 solution)を求めることを方程式を解く (solve)という。K が複素数のときは、 n 次方程式には、(重複も考慮して)n 個の根が 存在する(「代数学の基本定理」)。 1 次方程式 ax+b=0 の解は x =− b である。 a −b±√ b2−4ac 2 次方程式 ax2+bx+c=0 の解は x = である。 2a 2 次方程式の解から分るように、数体系が実数のみであるなら方程式の根が存 在するとは限らないが、複素数まで考慮するなら重複度を含めて 2 個の根が存在す ることになる。 3 次方程式、4 次方程式についてはそれぞれ Cardano の解法,Ferrari の解法など と呼ばれる代数的解法が知られている。 」 (この箇所までは部分的に省略してます) さて、ここからが白眉です。 「Cardano の解法とは、3 次方程式 ax 3+ bx 2 + cx + d =0 を x 3+ 3px+ q =0 という形に変形し、 x= u + v とおくと、 ( u + v )3 +3p( u + v )+ q=0 u 3+ v 3+ 3( uv + p)( u + v )+ q=0 となることから, 3 3 uv =−p かつ u + v =−q を満たすように u, v を決めれば良い、とするものである。そのためには、 u 3 v 3 =−p3 かつ u 3+ v 3=−q を満たす u3, v3 が、2 次方程式 t 2 + qt − p3=0 2 3 の 2 根、すなわち、 −q ±√ q + 4p であることから、 2 uv =−p を考慮して u, v を見い出せば良いということになる。 2 3 p,q が q2+4p3>0 を満たす実数のときは、 −q ±√q + 4p が実数であるから、 2 α= √ 3 √ 2 3 3 − q − √ q +4p −q + √ q 2 +4p3 ,β = 2 2 さらに、1 の原始立方根(虚数 3 乗根)の一つを ω として {u , v }={α, β }, {αω, βω2} , {αω2 ,βω } これより、 x =α + β ,αω + βω2 ,αω2 + βω という 3 根を得る。 しかし,「p, q が q2+4p3>0 を満たす実数のとき」という前提が崩れたときは、この 解法は必ずしも我々が理解しているような意味での解法にはなっていない。実際、3 次方程式 x3-63x-162=0 に上の解法を形式的に適用すると、 3 3 α=√ 81+ 30 √−3 ,β= √81−30 √−3 のようなものが現れるが、 3 3 x =√ 81+ 30 √−3+ √ 81−30 √−3 は、初等的な解法によって見い出される 3 次方程式 x3-63x-162=0 の解 x=-3, -6, 9 との関係は不明である。このような事態は昔から「非還元の場合」と呼ばれてきたも のである。 このような困難は、4 次方程式に関しても存在する。3 次方程式の解法が、もっと も基本的な 3 次方程式 x3=a と 2 次方程式に帰着されたように、4 次方程式の解法 が、もっとも基本的な 4 次方程式 x4=a と 3 次方程式に帰着されるからである。 さらに、5 次以上の方程式については代数的な一般的解法が存在しないことが 証明されている(「Abel-Galois の定理」)。 代数学の基本定理とこれらを組み合わせると、“存在は証明されているが代数的 な方法では見つけることができない”(いわば神は知っているが人間には発見でき ない)ということになる。 」 じゃあ 5 次方程式以上の解はどうすんの、といった感じでしょう。 そこまで来ると、方程式を解くのではなくて、解を探すという方向 に進みます。 テイラー展開 級数というのは、関数を整関数で近似しようという考え方です。 代表的な無限級数の表現方法にテイラー展開というのがあり、 exp(x)、sin(x)、cos(x)の展開式 exp(x)=1+x+x2/2!+x3/3!+x4/4!+...... sin(x)=x-x3/3!+x5/5!-x7/7!+..... cos(x)=1-x2/2!+x4/4!-x6/6!+..... がよく使われます。 右辺の級数は、1点 x における高次微分によって決まるもので すから、点 x 近傍の情報から決まります。いわば局所的に決定す るものです。左辺の意味するところは、変数 x の値を任意にとれる ことから、いわばグローバルなものと言えるでしょう。 つまりテーラー展開というのは、微分という手段を繰り返し使 って、局所的な情報を取り出し、それを使って全体を再構成しよう という試みなわけです。この発想の飛躍はすごいもんです。 自然対数 e を展開式と GAMBAS が持ってる関数 EXP(X)と 比較するプログラムを示します。 nmax:項数の最大値 x:変数値 x=1 の場合は自然対数の底 e を与える。 次数 n=8 で、exp(1)=2.7182539....となり、小数点以下 5 桁まで 正確な値を与える。x の値にもよるけれども、低次の展開でもよい 近似ができることがわかります。 (GAMBAS) ' Gambas module file ' taylor1gam.bas ' コンピュータ物理の功罪 ' exponential(True BASIC = > GAMBAS) Public Sub Main() Dim nmax As Integer Dim n As Integer Dim x As Float Dim t As Float Dim s As Float Print " nmax ? "; Input nmax Print " x ? "; Input x Print "order cal. Let n = 1 Let t = 1.0 Let s = 1.0 Print n, s, Exp(x) - s For n = 2 To nmax Step 1 value error" Let t = t * x / (n - 1) Let s = s + t Print n, s, Exp(x) - s Next Print " exp(x) ="; Exp(x) Print " exp(cal)="; s Stop End (Result) nmax ? 8 x ?1 order cal. value error 1 1 1.71828182845905 2 2 0.71828182845905 3 2.5 0.21828182845905 4 2.66666666666667 0.05161516179238 5 2.70833333333333 0.00994849512571 6 2.71666666666667 0.00161516179238 7 2.71805555555556 0.00022627290349 8 2.71825396825397 2.7860205076724E-5 exp(x) =2.71828182845904 exp(cal)=2.71825396825397 sin の近似 振動問題では x が小さい場合、sin(x)≒x と線形近似して計算 することが結構な頻度であります。 sin(x)=x-x3/3!+x5/5!-x7/7!+..... x<1 ではよい近似になります。項数を多くとれば x>1 でもよい 近似値を返します。 (GAMBAS) ' Gambas module file ' taylor2gam.bas ' コンピュータ物理の功罪 ' sin (True BASIC = > GAMBAS) Public Sub Main() Dim n As Integer Dim nmax As Integer Dim x As Float Dim t As Float Dim sn As Float Print " nmax ? "; Input nmax Print " x ? "; Input x Print "order cal. value error" n=2 t=x sn = x For n = 2 To nmax Step 2 t = - t * x ^ 2 / (n * (n + 1)) sn = sn + t Print n, sn, Sin(x) - sn Next Print "sin(x) ="; Sin(x) Print "sin(cal)="; sn Stop End (Result) nmax ? 10 x ?2 order cal. value error 2 0.66666666666667 0.24263076015901 4 0.93333333333333 -0.02403590650765 6 0.90793650793651 0.00136091888917 8 0.90934744268078 -5.0015855094343E-5 10 0.9092961359628 1.2908628790331E-6 sin(x) =0.90929742682568 sin(cal)=0.9092961359628 GAMBAS の sin 関数(実線)と n 次のテイラー展開(x)を比べた 図を表示します。x を 0.0 から始めて For〜Next で 0.01 ずつ増加 させてます。n=13 くらまでとると x=6.28 までよく再現します。図に してみると級数でよく近似できるもんだなぁ、と関心します。 (GAMBAS) ' Gambas class file ' taylor3gam.bas ' Taylor expansion of sin (True BASIC => GAMBAS) Public Sub _new() End Public Sub Form_Open() DrawingArea1_Draw() End Public Sub DrawingArea1_Draw() Dim x As Float Dim dx As Float Dim t As Float Dim sn As Float Dim n As Integer Dim nmax As Integer Dim x_interval As Float Dim y_interval As Float Dim x_origin As Integer Dim y_origin As Integer Dim x1 As Integer Dim y1 As Integer Dim x2 As Integer Dim y2 As Integer Dim xp As Integer Dim yp As Integer ' ' ' ' ' Print " nmax(<15) ? " Input nmax x = 0.0 dx = 0.01 Initialize the DrawingArea object DrawingArea1.Clear データを描画 draw.Begin(DrawingArea1) 縦軸と横軸の目盛りを決める。表示する項目の数、データの最大値によって変わる。 x_interval = DrawingArea1.Width / (6.28 - 0.0) y_interval = DrawingArea1.Height / (1.0 - (-1.0)) Define the origin for the chart x_origin = Int(Abs(0.0) * x_interval) y_origin = DrawingArea1.Height - Int(Abs(-1.0) * y_interval) x1 = x_origin y1 = y_origin For x = 0 To 6.28 Step dx t=x sn = x x2 = x_origin + Int(x * x_interval) y2 = y_origin - Int(Sin(x) * y_interval) draw.line(x1, y1, x2, y2) For n = 2 To nmax + 1 Step 2 xp = x_origin + Int(x * x_interval) yp = y_origin - Int(sn * y_interval) draw.point(xp, yp) t = - t * x ^ 2 / (n * (n + 1)) sn = sn + t Next x1 = x2 y1 = y2 Next Finish with the DrawingArea object draw.End End (Result) 参考文献 ・十進 BASIC ではじめる BASIC、講談社ブルーバックス ・和田正信著、有馬朗人、大槻義彦編、コンピュータ物理の功罪、物理数学 OnePoint10、共立出版 で、この参考文献ですが。『コンピュータ物理の功罪』と言いま すと、やってることを否定すんのかよッ!と煽ってる題名のように感 じますが、中身はちゃんとしたことが書かれてます。 このシリーズの編者のお二人はかなり有名な方々なので特に 説明する必要はないと思います。この本が出版された当時は、32 ビット PC が普及しはじめた時代でして、大学の研究室でも PC で 数値計算させるようになってきた頃だと思います。そういうパーソ ナルコンピュータが普及するのを見て、やはり物理屋であるお二 人は、「数値計算だけまとめてわかったふりしてんじゃないぞ」と 釘を差すつもりもあったんじゃないでしょうか。ただの妄想ですが。 まあ世間でよく聞く慣用句に、「そんなこと言っても、理屈と現 実は違うんだ!」と言うセリフがありますが、これはまさにそういう ことです。理屈と現実が違うように見えてるのは、そう言う人。その 方の世界の見方を言い表してるんだなと思うわけです。そんなこ たない。理屈と現実が違うのなら、なぜ違うのかを考えるのが人 間というものです。 ニュートン法 ここまでくれば、だいぶ数値計算してるっぽくなりますよね。 例によって他人様が作ったコードの GAMBAS への焼き直し です。根性なしです。「根なし」だけに。 一般的な問題の応用には、解きたい関数本体 Y、その一回微 分した式 dY を差し換えればよいです。 Newton 法では、初期値から出発して補正を繰り返して一つの 解を得ます。二つ以上の解があるときは、あらかじめ、どのあたり に解があるか初期値を入れ直さなければなりません。 また、繰り返し計算を止める条件として、精度を決めておかな ければなりません。もし極大または極小値があると無限ループに 入る危険があります。これを避けるため、For-Next 文で 50 回の繰 り返しで計算を停止するようにしてあります。 ・ソース&参考 教育利用を目的とした簡易な Basic インタプリタ PlainBasic プログラミング例題、 2006.08.20 版 (GAMBAS) ' Gambas module file ' 三次方程式の解(ニュートン法) ' ex23Newton.bas => pbasex23gam.bas ' Nweton の逐次近似法を使って ' 三次方程式 X^3+AX^2+BX+C=0 を解く Public Sub Main() Dim A As Float Dim B As Float Dim C As Float Dim XN As Float Dim ERR As Float Dim X As Float Dim Y As Float Dim DY As Float Dim Count As Integer A = -6 ' 例題です B = 11 C = -6 XN = 0 ' 初期値です ' XN=10 ' 別の初期値です ERR = 1.0E-15 ' 精度設定です X = XN ' 初期値です Y = ((X + A) * X + B) * X + C DY = (3 * X + 2 * A) * X + B XN = X - Y / DY While ((Abs(XN - X) > ERR) And (Count < 50)) X = XN Y = ((X + A) * X + B) * X + C DY = (3 * X + 2 * A) * X + B XN = X - Y / DY Count = Count + 1 Print Count, "X="; X, "XN="; XN, "XN-X="; XN - X Wend Stop End (Result) 例題 A = -6 B = 11 C = -6 初期値 XN = 0 の場合 1 X=0.54545454545455 XN=0.84895321062245 XN-X=0.30349866516791 2 X=0.84895321062245 XN=0.9746740710233 XN-X=0.12572086040085 3 X=0.9746740710233 XN=0.99909154805695 XN-X=0.02441747703365 4 X=0.99909154805695 XN=0.99999876469105 XN-X=0.00090721663411 5 X=0.99999876469105 XN=0.99999999999771 XN-X=1.2353066561888E-6 6 X=0.99999999999771 XN=1 XN-X=2.2888357875672E-12 7 X=1 XN=1 XN-X=0 別の初期値 XN = 10 の場合 1 X=7.36125654450262 XN=5.61610701395443 XN-X=-1.74514953054819 2 X=5.61610701395443 XN=4.47379897271395 XN-X=-1.14230804124047 3 X=4.47379897271395 XN=3.74420450714993 XN-X=-0.72959446556402 4 X=3.74420450714993 XN=3.30588643643379 XN-X=-0.43831807071614 5 X=3.30588643643379 XN=3.08210386279587 XN-X=-0.22378257363792 6 X=3.08210386279587 XN=3.00848840787698 XN-X=-0.07361545491889 7 X=3.00848840787698 XN=3.00010598093566 XN-X=-0.00838242694132 8 X=3.00010598093566 XN=3.00000001684377 XN-X=-0.00010596409189 9 X=3.00000001684377 XN=3 XN-X=-1.684377126665E-8 10 X=3 XN=3 XN-X=-8.8817841970013E-16 二元連立一次方程式 行列の計算です。逆行列の計算に至って、パソコンやスマフォ を計算機として使ってる感が出てくると思います。 これは典型的な二元連立一次方程式を解く問題で、二つの直 線の交点を求めます。やっぱりこれも中学校でならう範囲ですよ ね。この例では、6 個の定数を入力データとします。リストの変数 値を他の数値に差しかえると、別の計算ができます。 まあ GAMBAS に限らず、プログラミング言語のほとんどが行 列記号そのものを使うという文法がないので、代数式の形で扱う ことになります。言語(コンパイラ)という括りではなく、数式処理 アプリという分類では FreeMat とか Octave というフリーウェアが 世に出ていますので、そちらもご参考にしてください。 ・ソース&参考 教育利用を目的とした簡易な Basic インタプリタ PlainBasic プログラミング例 題、2006.08.20 版 (GAMBAS) ' Gambas module file ' PlainBASIC ex24lineareq2.bas => pbasex24gam.bas ' 二元連立一次方程式を解く ' 2 直線の交点を求める問題は二元一次連立方程式を解きます ' A*X+B*Y=P ' C*X+D*Y=Q Public Sub Main() Dim A As Float Dim B As Float Dim P As Float Dim C As Float Dim D As Float Dim Q As Float Dim DET As Float Dim X As Float Dim Y As Float A=1 B=2 P=5 C=3 D = -1 Q=1 DET = A * D - C * B If Abs(DET) < 1.0E-15 Then Print "2 直線は平行です" Else X = (P * D - Q * B) / DET Y = (Q * A - P * C) / DET Print "X= "; X Print "Y= "; Y Endif Stop End (ex1) A = 1, B = 2, P = 5 C = 3, D = -1, Q = 1 (Reslut1) X= 1 Y= 2 例:平行線の場合 DET がゼロになると、ゼロで割ることになるので DET の値が GAMBAS の有効 桁数で 1.0E-15 より小さければ平行線と判定する。 (ex2) A = 1, B = 2, P = 5 C = 1, D = 2, Q = 1 (Reslut2) DET = 0 2 直線は平行です (ex3) A = 1.0 + 1.0E-15, B = 2, P = 5 C = 1, D = 2, Q = 1 (Result3) DET = 2.2204460492503E-15 X= 3.6028797018964E+15 Y= -1.8014398509482E+15 (ex4) A = 1.0 + 1.0E-16, B = 2, P = 5 C = 1, D = 2, Q = 1 (Result4) DET = 0 2 直線は平行です 逆行列の計算 逆行列を求めることができれば、連立方程式も解けるよね。と いうわけで、数値計算で逆行列を求める方法は、ほんと良く使い ます。 逆行列の求め方となると、はじめはクラメルの方法を教わるこ とになります。けれど、N 次の逆行列の求め方となると収拾がつ かなくなるでしょう。 大学教養過程では『基礎線形代数』とか『線形代数の基礎』 という講義でマシな方法を習います。早い話がいかに係数を減ら せるかにかかってます。実際に教科書の演習問題を解いてみると 記号の操作が良く理解できると思います。ここでは数値計算でよ く使われるガウスの方法を紹介します。 (1)ガウス・ジョルダン法 ガウス・ジョルダン法は「吐き出し法」ともいわれ、次元が大き い場合、クラメル法に比べて演算回数が少なくてすむため数値 解法としてよく使われます。N 元の場合、乗除算では N(N2-3N-1)/3 回で、N=5 で 65 回、N=10 でも 430 回という感じで す。 まとめると、N 元連立1次方程式の場合、以下のことを実行しま す。 1)ai,k←ai,k/ai,i を i=1,2,...,N について繰り返し、k=i+1,...,N-1 まで 繰り返す。 2)aj,N+1←aj,N+1-aj,i・ai,k を j=1,...,N(j≠i)まで繰り返し、k=i+1,...,N-1 ま で繰り返す。 3)xj=aj,N+1 で解を求める。 『コンピュータ物理の功罪』(p.41)より (例題)次のような 4 元連立方程式を解きます。 3 −4 1 6 x 0 22 −2 10 −5 3 x 1 = 15 1 2 −2 8 x 2 31 1 −5 7 7 x 3 40 ( )( ) ( ) (GAMBAS) ' Gambas module file Public Sub Main() ' GaussJordanGam.bas ' TrueBASIC => GAMBAS ' Linear Equation(Gauss-Jordan method) Dim i As Integer Dim j As Integer Dim k As Integer Dim n As Integer = 4 Dim a As New Float[n, n+1] a[0, 0] = 3.0 a[0, 1] = -4.0 a[0, 2] = 1.0 a[0, 3] = 6.0 a[0, 4] = 22.0 a[1, 0] = -2.0 a[1, 1] = 10.0 a[1, 2] = -5.0 a[1, 3] = 3.0 a[1, 4] = 15.0 a[2, 0] = 1.0 a[2, 1] = 2.0 a[2, 2] = -2.0 a[2, 3] = 8.0 a[2, 4] = 31.0 a[3, 0] = 1.0 a[3, 1] = -5.0 a[3, 2] = 7.0 a[3, 3] = 7.0 a[3, 4] = 40.0 k=0 While k < n For j = k + 1 To n Let a[k, j] = a[k, j] / a[k, k] Next For i = 0 To n - 1 If i <> k Then For j = k + 1 To n a[i, j] = a[i, j] - a[i, k] * a[k, j] Next Endif Next k=k+1 Wend For i = 0 To n - 1 Print " X"; i; " = "; a[i, n] Next Stop End (Result) X0 = 1 X1 = 2 X2 = 3 X3 = 4 (2)ガウスの消去法 未知数を1個ずつ消去し、最終的に 0 でない係数が 1 個だけ の方程式を作り(前進消去法)、その解を前の式に順次代入して (後進代入)すべての未知数を求める方法です。ガウスの消去法 はガウス・ジョルダン法より計算手順が複雑なんですが、必要な 演算回数が 6 割くらい少なくて数値計算向きの方法です。 1)ak,j←ak,j/ak,k を k=1,...,N-1、j=k+1,...,N まで繰り返す。 2)ai,j←ai,j-ai,k・ak,j、ai,N+1←ai,N+1-ai,N+1・ak,k を i=k+1,...,N、j=k,...,N ま で繰り返す。 3)xN←aN,N+1/aN,N 4)xk←ak,N+1-ak,jxj を k=N-1,...,1 と減らしながら解を求める。 ガウス・ジョルダン法の例題と同じ行列式を解きます。結果は 同じになるはずですね。 (GAMBAS) ' Gambas module file Public Sub Main() ' GaussmethodGam.bas ' TrueBASIC => GAMBAS ' Linear Equation-Gauss method Dim i As Integer Dim j As Integer Dim k As Integer Dim n As Integer = 4 Dim p As Float Dim s As Float Dim a As New Float[n, n + 1] Dim x As New Float[n] a[0, 0] = 3.0 a[0, 1] = -4.0 a[0, 2] = 1.0 a[0, 3] = 6.0 a[0, 4] = 22.0 a[1, 0] = -2.0 a[1, 1] = 10.0 a[1, 2] = -5.0 a[1, 3] = 3.0 a[1, 4] = 15.0 a[2, 0] = 1.0 a[2, 1] = 2.0 a[2, 2] = -2.0 a[2, 3] = 8.0 a[2, 4] = 31.0 a[3, 0] = 1.0 a[3, 1] = -5.0 a[3, 2] = 7.0 a[3, 3] = 7.0 a[3, 4] = 40.0 For k = 0 To n - 2 For i = k + 1 To n - 1 p = a[i, k] / a[k, k] For j = k To n a[i, j] = a[i, j] - p * a[k, j] Next Next Next x[n - 1] = a[n - 1, n] / a[n - 1, n - 1] For k = n - 2 To 0 Step -1 s = a[k, n] For j = k + 1 To n - 1 s = s - a[k, j] * x[j] Next x[k] = s / a[k, k] Next For i = 0 To n - 1 Print " x"; i; " = "; x[i] Next Stop End (Result1) x0 = 0.99999999999999 x1 = 1.99999999999999 x2 = 2.99999999999999 x3 = 4 違う参考文献から持って来たソースも載せておきます。同じ計 算を違うソースで比較してみると、コードのポイントがわかりやす いと思いますので。こちらの方は、元ネタが ExcelVBA マクロだっ たのを GAMBAS に焼き直しました。 前の例題と同じ式を解きま す。 (GAMBAS) ' Gambas module file ' gaussvbagam.bas ' エクセルプログラム => GAMBAS ' プログラム 1.1 ガウスの消去法 ' 表 1.1 ガウスの消去法プログラムリスト ' 元数...2 元 1 次方程式であれば 2 となり 4 元 1 次方程式であれば 4 になる Public n As Integer = 4 Public A As New Float[n, n] Public B As New Float[n] Public X As New Float[n] Public Sub Main() Dim i As Integer A[0, 0] = 3.0 A[0, 1] = -4.0 A[0, 2] = 1.0 A[0, 3] = 6.0 A[1, 0] = -2.0 A[1, 1] = 10.0 A[1, 2] = -5.0 A[1, 3] = 3.0 A[2, 0] = 1.0 A[2, 1] = 2.0 A[2, 2] = -2.0 A[2, 3] = 8.0 A[3, 0] = 1.0 A[3, 1] = -5.0 A[3, 2] = 7.0 A[3, 3] = 7.0 B[0] = 22.0 B[1] = 15.0 B[2] = 31.0 B[3] = 40.0 Calculation(n) ' 解 For i = 0 To n - 1 Print " X"; i; " = "; X[i] Next Stop End ' ***** ガウスの消去法 Sub Calculation(n As Integer) Dim PVT As Float Dim DIAG As Float Dim AM As Float Dim S As Float Dim h As Integer Dim i As Integer Dim j As Integer Dim k As Integer PVT = 0.00005 ' ***** 前進消去 For h = 0 To n - 1 If (Abs(A[h, h]) < PVT) Then Print "ピボットが" & h & "番目の消去で小さくなり過ぎました" Stop Endif DIAG = 1.0 / A[h, h] For j = h + 1 To n - 1 AM = A[j, h] * DIAG For k = h + 1 To n - 1 A[j, k] = A[j, k] - AM * A[h, k] Next B[j] = B[j] - AM * B[h] Next For k = h + 1 To n - 1 A[h, k] = A[h, k] * DIAG Next B[h] = B[h] * DIAG Next ' ***** 後退代入 X[n - 1] = B[n - 1] / A[n - 1, n - 1] For j = n - 1 To 0 Step -1 S = 0.0 For k = j + 1 To n - 1 S = S + A[j, k] * X[k] Next X[j] = B[j] - S Next End (Result2) X0 = 0.99999999999999 X1 = 2 X2 = 3 X3 = 4 (3)コレスキー法 おそらく、ポケコンジャーナルに掲載されていたプログラムを GAMBAS に書き直してみました。手元に残っていたのがコピー の切れ端のため初出は不確かです。 今回のプログラムのポイントは「逆行列を求める」というテーマ を逸脱したところにありまして、プログラミングそのもののクール さに注目して頂きたいところです。逆行列を計算するという連載 は、このコードを読んでいただきたいがため続けたと言っても過 言ではないでしょう。 ポケコンという機能が制限された環境で実に工夫してプログラ ムしています。この頃のポケコンの BASIC では、変数名が A から Z までのアルファベット 26 文字。そこから配列を使うとなると、そ の文変数の数が制限されるというタイトなものだったんです。しか も表示は1行だけ。兎にも角にも、それはメモリ容量に起因した制 約なんです。このプログラムのソースを読むと、キレーイにアルフ ァベット 26 文字を使い切ってる! GAMBAS に翻訳した都合上、もともと 3 つのプログラムに分 割されていたソースを1つにまとめています。GAMBAS を使えば 配列の次元を複数とるのはわけのない話ですが、このプログラム が作られた当時の時代背景を想像して、そのダンディズムを味わ っていただきたい。 (例題)次のような 3 元連立方程式を解きます。 ( 1 2 3 x 1 −74 2 20 26 x 2 = 52 3 26 70 x 3 100 )( ) ( ) (GAMBAS) ' Gambas module file ' cholesky0216gam.bas ' 2014/02/15 ' program title : コレスキー法 ' PJ かな?あなたの仕事をスピーディにお手伝い(データ処理用) ' coding : 前田修三郎 氏 Public DAT As New Float[26] Public Sub Main() Dim A As Integer Print "- 元の数を入力してください" Print " ? = "; A=3 Input A AA1(A) AA2(A) AA3(A) Stop End Sub AA1(A As Integer) Dim B As Integer Dim D As Integer Dim G As Integer Dim H As Integer Dim K As Integer K=A B=A-1 Print "- 左辺 要素 a(G 行,H 列)の数値入力" For G = 1 To A D=A-B For H = D To A Print "("; G; ","; H; ") = "; Input DAT[K] K=K+1 Next B=B-1 Next End Sub AA2(A As Integer) Dim B As Integer Dim D As Integer Dim E As Float Dim F As Integer Dim G As Integer Dim H As Integer Dim I As Integer Dim J As Integer Dim K As Integer K=A DAT[K] = Sqr(DAT[K]) E = DAT[K] K=K+1 For G = 1 To A - 1 DAT[K] = DAT[K] / E K=K+1 Next B=A-1 For G = 0 To A - 3 GOSUB500(K, B, G) E = DAT[K] K=K+1 For H = 1 To B - 1 F=K-B J=0 For I = 1 To A - B J = J + DAT[F] * DAT[F - H] F=F-B-I Next DAT[K] = (DAT[K] - J) / E K=K+1 Next B=B-1 Next G=A-2 GOSUB500(K, B, G) ' コレスキー分解の結果 K=A B=A-1 Print "- コレスキー分解の結果" For G = 1 To A D=A-B For H = D To A Print "("; G; ","; H; ")="; DAT[K] K=K+1 Next B=B-1 Next End Sub GOSUB500(K As Integer, B As Integer, G As Integer) Dim E As Integer Dim F As Integer Dim H As Integer F=K-B E=0 For H = 1 To G + 1 E = E + DAT[F] * DAT[F] F=F-B-H Next DAT[K] = Sqr(DAT[K] - E) End Sub AA3(A As Integer) Dim B As Integer Dim C As Integer Dim F As Integer Dim G As Integer Dim H As Integer Dim J As Integer Dim K As Integer Dim L As Float Print "- 右辺入力" For G = 1 To A Print "("; G; ") = "; C=G-1 Input DAT[C] Next B=A-1 L = L / DAT[A] For G = 1 To A - 1 C=0 K=C+A+G J=0 For H = 1 To A - B J = J + DAT[K] * DAT[C] K=K+A-H C=C+1 Next B=B-1 K = A + G * A - (G * G - G) / 2 C=G DAT[C] = (DAT[C] - J) / DAT[K] Next C=A-1 K = C + (A * A + A) / 2 DAT[C] = DAT[C] / DAT[K] B=A-1 For G = 2 To A C=A-1 J=0 F=K-1 For H = 1 To A - B J = J + DAT[F] * DAT[C] C=C-1 F=F-1 Next K=K-G C=A-G DAT[C] = (DAT[C] - J) / DAT[K] B=B-1 Next Print "-Result" For G = 1 To A C=G-1 Print G; " = "; DAT[C] Next End (Result) - 元の数を入力してください ?=3 - 左辺 要素 a(G 行,H 列)の数値入力 (1,1) = 1 (1,2) = 2 (1,3) = 3 (2,2) = 20 (2,3) = 26 (3,3) = 70 - コレスキー分解の結果 (1,1)=1 (1,2)=2 (1,3)=3 (2,2)=4 (2,3)=5 (3,3)=6 - 右辺入力 (1) = -74 (2) = 52 (3) = 100 -Result 1 = -100 2 = 10 3=2 〜〜掲載オリジナルの解説〜〜 あなたの仕事をスピーディにお手伝い(データ処理用) coding : 前田修三郎 氏 行列式でお悩みの方、ちょっと聞いてください(ガウスの消去法の改良版) 複雑な行列式を前に、頭を抱えているあなた。でも、よーく見てください。あなたが 計算しようとしている連立方程式の係数行列は、対称かつ正定値ではありませんか ?もしそうなら、とてもラッキー。このプログラムがあなたに代わって、コレスキー法を 用いて解を求めます。また、コレスキー法の持つ特性(掃き出し法と比べて演算回数 が少なく、対称性を利用して三角行列を解く)から、ここでは15元までの係数行列を 計算することができます。 なお、このプログラムでは、1つのプログラムを”AA1"”AA2"”AA3"の3つに分割し ています。CHAIN 文によって続けることになっているので、あらかじめ、3つのプログ ラムをカセットテープにセーブしておくことが必要です。 使い方 たとえば、次のような3元連立方程式がある場合、 (1)(2)=(3) ただし 3つのプログラムの内容は、次のとおりです。 プログラム 内容 AA1 左辺(1)右上半分の三角行列の入力および確認・訂正 AA2 (1)のコレスキー分解・結果出力 AA3 右辺(3)の入力および演算・結果(2)出力 (GAMBAS では、AA1,AA2,AA3 を Sub プロシージャとして引数を受け渡しながら 順番に処理して行ってます。) AA1 プログラム 1.AA1 のプログラムを開始します。 2.最初に方程式の元数を入力します。次に、表示にしたがって、左辺(1)の値を入力 していきます。表示は(例)を参照してください。 (例)”1 1”→行列の1行1列の要素 a11 3.左辺の値の入力が終わると、AA2 で、入力した値の確認を行います。”(1、1)=1 ”というふうに表示されるので、その値に誤りがなければ、[Enter]を押し、”OK THEN INPUT 1"に1を入力してください。誤りがあった場合は、[Enter]を押して”OK THEN INPUT 1"のところで1以外の数を入力します。続いて、"TEISEI="と聞いてくるので、 正しい値を入力してください。(以下、くり返します) AA2 プログラム 4.確認が終わると、”AA2”のプログラムがセーブされたテープを読みこめる状態に セットし、プログラムをロードします。 5.ロードが完了すると、自動的に計算を始めます。計算が終了すると、ピー音が3回 鳴ります。コレスキー分解の結果が表示されます。 AA3 プログラム 6.”AA3”のプログラムがセーブされたデータを読みこめる状態にセットします。 7.ロードが完了したら、右辺の値を表示にしたがい、順に入力します。 8.計算を開始し、結果(2)を表示していきます。(以下、順次入力) 9.再び解を表示させたい時は、そう入力します。 10.さらに、左辺(1)が同一で、右辺(3)のみ異なる方程式を解きたい場合は、RUN を入力し、7以下の操作をくり返します。 参考(計算内容など) AA2 の左辺計算 まず C11=√ a11 . 次で i=2,3,....,n に対し Ci1=ai1 / C11 . √ k −1 k=2,3,...,n の項に Ckk = a kk− ∑ C2kj . j=1 k=n ならば、ここで終わり。 k −1 k<n ならば i=k+1,...,n に対し n-1 まで Cik =( aik − ∑ Cij Ckj )/ Ckk . j=1 √ n−1 最後に Cnn = a nn− ∑ C2nj . j=1 AA3 の右辺計算 まず y1= b1 / C11 . n−1 k=2,3,...,n の順に yk =( bk − ∑ Cki y i )/C kk (前進消去)。 i =1 k=n-1,...,2,1 の順に x k =( y k − となる。 数値積分 (1) 区分求積法 n ∑ j= k +1 Cik X i)/ C kk (後進代入)。 定積分は面積と関連づけるとたやすく理解できます。関数のグ ラフと x 軸によって囲まれた面積が定積分であるというわけです。 曲線によって囲まれた図形を面積をどうやって計算するかとい いますと、この図形を細長い長方形の束であると見なして近似す るわけです。 積分の教科書には大抵そういう模式図が描かれてると思いま す。では計算機で積分値 ∫f(x)dx を計算してみましょう。 x=a から x=b まで N 等分して、その幅を v=(b-a)/n とすると、 面 積 s は、 s=f(a)v+f(a+h)v+ … + f(a+(n-1)v)v で近似される。分割数 N を増やしていけば、より正確な積分値に 近づくはずです。 サンプルプログラムは、y=x2 の 0 から 1 までの面積を求めるプ ログラムです。関数の式は Func 関数に書いてあるとおりです。 For 文の Step 0.01 が 100 等分することを意味しています。これ を 1000 とか 10000 に増やせば、より細かく分割されるので時間 はかかるけども厳密解に近づくはず、という寸法です。 参考文献 ・木村良夫、パソコンを遊ぶ簡単プログラミング、コンピュータを自由に操る「十進 BASIC」入門、講談社ブルーバックス、2003.01.20 ・大学院分子機能薬学専攻、創薬基盤分子設計学講座 情報処理教育用 BASIC 言語入門(UNIX(Linux)実習前の BASIC プログラミン グ自習資料) (GAMBAS) ' Gambas module file ' kubun9gam.bas ' 区分求積法 Public Sub Main() Dim i As Float Dim l As Float Dim s As Float Dim t As Float t=0 For i = 0 To 1 Step 0.01 l = Fnc(i) t = 0.01 * l s=s+t Next Print s Stop End Function Fnc(x As Float) As Float Return x * x End (2)シンプソンの積分則 数値積分の公式としてよく知られている公式に次のようなのが あります。 ・ニュートン・コーツ(Newton-Coutes)系の公式 直線近似:台形則、中点則 2次曲線近似:シンプソン則 ・チェビシェフ(Chebyshev)の公式 (区間を不等間隔で重みを一定にする方法) ・ガウス(Gauss)の公式 (区間を不等間隔で重みも一定でない方法。もっとも精度が高 い方法) シンプソンの積分則のサンプルプログラムを提示いたします。x の関数 f(x)の区間[a,b]の定積分を求めます。やってることは区分 求積法の発展バージョンです。お約束ですが、f(x)は区間[a,b]で 連続で、区間内で求めうるものとします。 f(x)を直接積分するのが難しいとき、f(x)を別の積分可能な関 数 g(x)に置き換えることで、f(x)の近似積分を求める手段を取りま す。数値積分を使おうとするからには、厳密に求めるのが困難だ からなわけで、それじゃ簡単な近似に置き換えようという考え方 です。そういう発想の求積法をニュートン・コーツ系の公式という そうです。 それ系の公式で、区間[a,b]の両端点 a,b とその中間 x=(a+b)/2 で、f(x)と一致するような 2 次の多項式 g(x)を使うのを シンプソン則といいます。たくらんでることは、なんとなくイメージ つくと思います。 微小区間 h をさらに 2 つの区間にして積分を行うわけですの で h=(b-a)/2n x(i)=a+i・h (ここで、i=0,1,...,2n) とおくと、x(0)=a,x(2n)=b となるように工夫する。n を大きくするほ ど細かく分割することになるので精度がよくなります。 例えば N=10 のとき f ( x )=4 /(1+ x ) を 0 から 1 まで積分すると、 正確には 3.14159...(=PI)となります。ちなみにこれは x=tanθ と 置換積分すると解析的に求められます。 2 (GAMBAS) simpson1gam.bas ' Gambas module file ' simpsongam.bas ' Simpson Integral Method (TrueBASIC => GAMBAS) Public Sub Main() Dim i As Integer Dim j As Integer Dim m As Integer Dim n As Integer Dim x As Float Dim a As Float Dim b As Float Dim h As Float Dim s As Float Dim s1 As Float Dim s2 As Float Print "Simpson Method" Print " - interval [a,b] " Print " a ? "; Input a Print " b ? "; Input b Print " - division m" Print " m ? "; Input m n=2*m h = (b - a) / n s1 = 0.0 s2 = 0.0 s = 0.0 For i = 1 To n - 1 Step 2 x=a+i*h s1 = s1 + fx(x) Next For j = 2 To n - 2 Step 2 x=a+j*h s2 = s2 + fx(x) Next s = h * (fx(a) + fx(b) + 4 * s1 + 2 * s2) / 3 Print " cal.value", "exact.value", "error" Print s, Pi, s - Pi Stop End Function fx(x As Float) As Float Dim fx As Float fx = 4 / (1 + x ^ 2) Return fx End (Result) Simpson Method - interval [a,b] a?0 b?1 - division m m ? 10 cal.value exact.value error 3.14159265296979 3.14159265358979 また、関数 g(x)を工夫して h=(b-a)/n -6.200076008156E-10 x(i)=a+i・h (ここで、i=0,...,n) とおくと、n を奇数偶数に関係なくとれるので少し簡単になります。 (GAMBAS) simpson2gam.bas ' Gambas module file ' simpson2gam.bas ' Integral(Simpson method) (True BASIC => GAMBAS) Public Sub Main() Dim a As Float Dim h As Float Dim n As Integer Dim b As Float Dim s As Float Print "Simpson method" Print " - interval [a. b]" Print " a ? "; Input a Print " b ? "; Input b Print " - division n" Print " n ? "; Input n h = (b - a) / n Print "cal.value", "exact.value", "error" s = Smp3(a, h, n) Print s, Pi, s - Pi Stop End Function fx(x As Float) As Float Dim fx As Float fx = 4 / (1 + x * x) Return fx End Function Smp3(a As Float, h As Float, n As Integer) As Float Dim s As Float Dim x As Float Dim i As Integer s=0 For i = 0 To n - 1 x=a+i*h s = s + fx(x) + 4 * fx(x + h / 2) + fx(x + h) Next s=h*s/6 Return s End (Result) Simpson method - interval [a. b] a?0 b?1 - division n n ? 10 cal.value exact.value error 3.14159265296979 3.14159265358979 -6.2000715672639E-10 まあ私にとってのシンプソンといえば『シンプソンズ』(黄色い 人類のでる漫画)なんですが、外国人の名前の付いたなんか憎 ったらしい公式なのかなぁ、と思いきや。わりと素直な発想で好 感がもてますね。 参考文献 ・和田正信著、有馬朗人、大槻義彦編、コンピュータ物理の功罪、物理数学 OnePoint10、共立出版 ・河西朝雄、VisualBasic によるはじめてのアルゴリズム入門、技術評論社、平成 11 年 6 月 10 日 (3)ガウスの積分則 区分求積法ではあたりまえのように考えてた、積分点が等間 隔であるというお約束はおしまいです。 あと積分区間の正規化を行います。今まで積分区間を a から b までとしてましたが、それを-1 から+1 までのように規格化しま す。そうすると問題を一般化できて楽なんですね。 また一般に、任意の関数 f(x) は n 次のルジャンドル多項式と N-1 次以下の関数 Q,R で表される。 f ( x)=Q( x)⋅P N + R(x ) ここで Pi(x) は区間[-1,1]において次の式を満たす直交したル ジャンドル多項式である。 1 ∫ P i ( x)⋅x k dx=0 (k=0,1,2,・・・,N-1) −1 Pi (x)=0 (i=1,2,・・・,N-1) 上の 2 つの条件を満たす i 個の解が区間[-1,1]の分点である。 この分点はルジャンドル多項式のゼロ点である。 N-1 次またはそれより低い次数の関数 R が正確に積分される ように wi を連立した線形方程式を満たすように選ぶ。ルジャンド ル多項式の性質を利用して w i= 2(1−xi )2 [N P N−1 ( x i)] 2 を得る。したがってガウスの積分公式は ∫ f ( x)dx= 12 (b−a)⋅∑ w f (x ) b N a i=1 i i で与えられる。 サンプルプログラムは、N=3 or 5 の場合の精度を f ( x )=4 /(1+ x ) を用い、積分区間 0 から 1 まで積分して調べます。 2 (GAMBAS) ' Gambas module file ' gaussintegam.bas ' Gauss Integral (True BASIC => GAMBAS) Public Sub Main() Dim t As New Float[6] Dim c As New Float[6] Dim dx As Float Dim hx As Float Dim ss As Float Dim s As Float Dim x0 As Float Dim x As Float Dim a As Float Dim b As Float Dim i As Integer Dim k As Integer Dim m As Integer Dim n As Integer Print " Nth Gauss Integral(N=3 or 5)" Print " interval [a, b] " Print " a ? "; Input a Print " b ? "; Input b Print " division m ? "; Input m Print " degree N(3 or 5) ? "; Input n dx = (b - a) / m hx = dx / 2.0 ss = 0.0 t[1] = -0.7745966692 t[2] = 0.0 t[3] = 0.7745966692 t[4] = 0.0 t[5] = 0.0 c[1] = 0.5555555556 c[2] = 0.8888888889 c[3] = 0.5555555556 c[4] = 0.0 c[5] = 0.0 If n <> 3 Then t[1] = -0.9061798459 t[2] = -0.5384693101 t[3] = 0.0 t[4] = 0.5384693101 t[5] = 0.9061798459 c[1] = 0.2369268851 c[2] = 0.4786286705 c[3] = 0.5688888889 c[4] = 0.4786286705 c[5] = 0.2369268851 End If For k = 0 To m - 1 x0 = a + k * dx + hx For i = 1 To n x = x0 + t[i] * hx ss = ss + c[i] * f(x) Next Next s = ss * hx Print "cal.value", "exact.value", "error" Print s, Pi, s - Pi Stop End Function f(x As Float) As Float Dim f As Float f = 4.0 / (1 + x * x) Return f End (Result) Nth Gauss Integral(N=3 or 5) interval [a, b] a?0 b?1 division m ? 10 degree N(3 or 5) ? 3 cal.value exact.value error 3.14159265371718 3.14159265358979 1.2738432531023E-10 分割数 m を適当に 1 からあげてみると、次数が低くてもシンプ ソンの公式よりも良い結果が出てくる。 積分区間が大きい場合は区間を m 個に分割して、その小区間 にガウス積分を適用し全体の和をとると、次数 N を小さくしても 精度のよい積分値を得ることができる。 参考文献 ・和田正信著、有馬朗人、大槻義彦編、コンピュータ物理の功罪、物理数学 OnePoint10、共立出版 常微分程式の数値解法 関数 y=f(x)と、これを微分したもの y'=f'(x)、あるいはこれをさら に微分したもの y''=f''(x)などの間の関係を表す式を常微分方程 式といいます。 どういう企みか一言でいいますと、初期状態から今までの状態 を元に、現在の結果を少しづつ足して、次の瞬間を予測しよう、と いったところです。 当然ながら誤差が発生します。これは、未来を予測するのは困 難だ、という現実の世界の教訓とよく合致する意見だと思います。 (1)オイラー法 ここでは工学的に考えやすいように、元の関数を時間 t が経つ に従って変化する関数 x=f(t,x)として話をします。x'=f'(t,x)の形の 微分方程式(1階常微分方程式)を x0=f(t0)の形の条件(初期条件) で解きます。 これまでの使ってきた数値積分と同じで、解を求めようとする 区間を微小区間 h に分割し、t1=t0+i・h (i=1,2,...,N)における x(ti)=xi を順次求めていく。x'=dx/dt を t に関する微係数とする。1 次の微分方程式の一般形は x'=f(t,x) 初期値:x(t0)=x0 (*1) です。ti における x(i)が求まれば、式(*1)より x'=f(i)が決まります。 したがって、ti から ti+1 の微小区間 h の積分は xi+1=xi+h・f(i) (*2) で与えられます。 サンプルプログラムは、単振り子の運動です。変位 x、時間 t、 力が復元力 f=-x とした場合、x'=-1・f'(x)と定義される常微分方 程式です。解析解 sin(t)と数値解を比較して表示します。本当は 数値解と解析解の結果を表示させてるのですが、この範囲では 線同士が重なりあって青線しか見えていません。お互いの初期状 態が合致するように注意しときましょう。 この解法には h2・f/2!に相当する誤差が含まれ、時間とともに誤 差(図の黄線)が大きくなっていくことがわかると思います。ちな みに図では見やすいように、プログラムで誤差を 1.0E+4 倍してあ ります。誤差を小さくするには h を小さくとる必要があります。それ は時間刻みを小さくするということで、同じ様な結果を得るにはそ れだけ計算時間がかかることになります。ちょうどよい時間刻み を見い出すのがコツとよく言われます。 (GAMBAS) ' Gambas module file ' Euler method (True BASIC => GAMBAS) ' EulerMethodGam.bas Public Sub Main() Dim x As Float Dim v As Float Dim t As Float Dim dt As Float Dim ya As Float Dim yb As Float Dim yc As Float ' Define the variables that we'll need x = 0.0 v = 1.0 dt = 0.01 For t = 0 To 31.4 Step dt ya = Sin(t) yb = x yc = 10 ^ 4 * (Sin(t) - x) Print t; ", "; ya; ", "; yb; ", "; yc v = v - x * dt x = x + v * dt Next Stop End (Result) 0, 0, 0, 0 0.01, 0.00999983333417, 0.01, -0.00166665833336 0.02, 0.01999866669333, 0.019999, -0.00333306666923 0.03, 0.0299955002025, 0.0299960001, -0.00499897504343 0.04, 0.03998933418663, 0.03999000059999, -0.00666413355839 0.05, 0.04997916927068, 0.04998000209992, -0.00832829241668 0.06, 0.05996400647944, 0.05996500559964, -0.00999120195404 0.07, 0.06994284733753, 0.0699440125988, -0.01165261267283 0.08, 0.07991469396917, 0.0799160251967, -0.01331227527518 0.09, 0.08987854919801, 0.08988004619208, -0.01496994069664 : : : : 31.39 までズラーッと出力されます。 (2)ルンゲ・クッタ法 オイラー法からの続きです。コンセプトは、もうちょっとましな予 測の仕方を考えよう、ということです。 二次のルンゲ・クッタ法は、点(ti,xi)における微係数から x の増 分 ki を推定します。つまり、 k1=h・f(ti,xi) です。 次に、点(ti,xi)と(ti+1,xi+1)の中間点を考えて、(ti+h/2, xi+ki/2)での 微係数から、これに平行な直線を描いて増分 k2 を推定します。 k2=h・f(ti+h/2, xi+k1/2) から、二次のルンゲ・クッタ法の式が、 xi+1=xi+k2 で与えられます。k2 の中に、k1 が含まれてるのがポイントです。 四次のルンゲ・クッタ法の場合も同様にして、k1 から k4 までの 量を求めた後、xi+1-xi の誤差が h4 の項まで消えるように重みを (k1:k2:k3:k4)=(1:2:2:1)として平均した量を k として、x(i+1)=xi+k を 求めます。 xi+1=xi+1/6・(k1+2・k2+2・k3+k4) で、 k1=h・f(ti, xi) k2=h・f(ti+h/2, xi+k1/2) k3=h・f(ti+h/2, xi+k2/2) k4=h・f(ti+h, xi+k3) k4 には k3 が、k3 には k2 が、k2 には k1 が含まれてるのがポイント。 解の曲線が連続してるんだったら、それらしい辺りに次の点がく るはず。という予測のもと、なんとか誤差を小さくしようとしてる努 力が忍ばれます。 今回のサンプルプログラムの結果も、オイラー法による出力結 果と同じ形式で出力します。今回も誤差を 1.0E+4 倍してます。オ イラー法での誤差の発散具合と比較してください。 (GAMBAS) ' Gambas module file ' RungeKuttagam.bas ' True BASIC => GAMBAS ' 4th Runge-Kutta method (True BASIC) Public Sub Main() Dim x As Float Dim v As Float Dim t As Float Dim dt As Float Dim k1 As Float Dim m1 As Float Dim k2 As Float Dim m2 As Float Dim k3 As Float Dim m3 As Float Dim k4 As Float Dim m4 As Float Print "4th Runge-Kutta method m・d2x/dt2=-kx" x = 0.0 v = 1.0 dt = 0.01 Print "t"; ","; "x=sin(t)"; ","; "x(cal)"; ","; "error" For t = 0 To 31.4 Step dt Print t; ","; Sin(t); ","; x; ","; 10 ^ 4 * (Sin(t) - x) k1 = dt * gFnc(v) m1 = dt * fFnc(x) k2 = dt * gFnc(v + m1 / 2) m2 = dt * fFnc(x + k1 / 2) k3 = dt * gFnc(v + m2 / 2) m3 = dt * fFnc(x + k2 / 2) k4 = dt * gFnc(v + m3) m4 = dt * fFnc(x + k3) x = x + (k1 + 2 * k2 + 2 * k3 + k4) / 6 v = v + (m1 + 2 * m2 + 2 * m3 + m4) / 6 Next Stop End Function gFnc(v As Float) As Float Return v End Function fFnc(x As Float) As Float Return - x End 温度変化を数値積分 ここで元ネタとした論文は、この GAMBAS を紹介するブログ のテーマ「ロスト・テクノロジーを蘇らせる」によく合致した内容で す。教育現場で、もとは N88-BASIC で作られた教材プログラムを VBA に書きなおして再利用しようといった論文です。PDF で配布 されてるので各自検索してダウンロードしてみてください。 しかし、遅いとか読みにくいとか、なにかと評価の低い従来型 の行番号つき BASIC ですが、使ったことのない世代にとやかく 言われる筋合いはないというものです。そこでそれらを「旧制 BASIC」と呼ぶことを提案したい。こう呼べば、バンカラながらも 硬派な印象を受けるのではないだろうか。お勤め先やご家庭で、 ご利用していただきたいと思います。 それにしても「旧制 BASIC」という語感よりも、「旧制 FORTLAN」のほうがより硬派な感じを受けるのは否めませんな。 論文「BASIC リユースと創発」より、 例題 4.6 ルンゲ・クッタ法 File Name: "ex4-8" 均一液相反応の温度変化 引用「よくわかる化学の情報技術」オーム社、83 ページ このプログラムは常微分方程式の軌道を求めるルンゲ・クッタ 法という数値積分法を使っています。元の微分方程式から少しづ つ次の点を予測していく仕組みなのでどうしても誤差が積み上げ られていきます。ここでは、均一液相反応の温度変化を予想する サンプルを載せます。 反応率 x と温度 T(℃)の間に次のような関係があるとします。 dt/dx=-65.0-15.58・(t-340)/{k(1-x)} 温度 T の初期値は 340℃とします。これは、厳密に解くことので きない反応率 x の非線型一階微分方程式です。反応係数 k は次 のように与えられます。 k=1.17・1017・exp(-44500/RT)[h-1] x の範囲が 0 から 0.95 について、x と T の間の関係を計算しま す。 (GAMBAS) ' Gambas module file ' ex4-8gam.bas ' File Name: "ex4-8" ' 引用「よくわかる化学の情報技術」オーム社、83 ページ ' ' DEF FNK(T)=1.17E+17 * Exp(-22400/(T+273.15)) ' DEF FNF(X,T)=-65-15.58*(T-340)/FNK(T)/(T-X) Public Sub Main() Dim T As Float Print "均一液相反応の温度変化" Print "浴槽温度 T(℃)" ' Print " T = ? "; ' Input T T = 340.0 RKuta(T) Stop End Sub RKuta(T As Float) Dim x0 As Float = 0.00 Dim xe As Float = 0.95 Dim dx As Float = 0.05 Dim x As Float Dim K1 As Float Dim K2 As Float Dim K3 As Float Dim K4 As Float Print "反応率(-)", "温度変化(℃)" Print x0, T For x = x0 To xe Step dx K1 = dx * FNF(x, T) K2 = dx * FNF(x + dx * 0.5, T + K1 * 0.5) K3 = dx * FNF(x + dx * 0.5, T + K2 * 0.5) K4 = dx * FNF(x + dx, T + K3) T = T + (K1 + 2 * (K2 + K3) + K4) / 6 Print x + dx, T Next End Function FNK(T As Float) As Float Dim FNK As Float FNK = 1.17E+17 * Exp(-22400 / (T + 273.15)) Return FNK End Function FNF(x As Float, T As Float) As Float Dim FNF As Float FNF = -65 - 15.58 * (T - 340) / FNK(T) / (1 - x) Return FNF End (Result) 均一液相反応の温度変化 浴槽温度 T(℃) T = ? 340 反応率(-) 温度変化(℃) 0 340 0.05 336.841755390705 0.1 333.920748802926 0.15 331.323483973615 0.2 329.132212160438 0.25 327.405999397189 0.3 326.164620987596 0.35 325.384398882746 0.4 325.008478036539 0.45 324.96508172997 0.5 325.184477066938 0.55 325.609440219045 0.6 326.199250159372 0.65 326.929794658459 0.7 327.792489114165 0.75 328.794080022554 0.8 329.959337582668 0.85 331.340507568081 0.9 333.0465265363 0.95 335.36741332636 参考文献 ・吉村忠与志, 青山義弘, 西宮辰明, Excel/VBA を用いた BASIC プログラムのリユ ースとそれに伴う創発の育成, J.Comput.Chem.Jpn.,Vol.1,No.2, p.59–64, 2002 (3)二階微分方程式 振り子の運動方程式を解き、運動の軌跡を吟味します。一般の 運動方程式は二階微分方程式で与えられます。 それは、 d2x/dt2=f(t,x,dx/dt) です。ここで、変数 x の時間微分を v と置くことで、二階微分方程 式を二元連立微分方程式の形に置き換えることができます。この 置き換える箇所が今回のポイントです。 dx/dt=g(t,x,v) dv/dt=f(t,x,v) という感じです。 この連立一次微分方程式を解けば運動方程式の軌跡を計算 することができます。 四次のルンゲ・クッタ法は次の式で与えられます。 xi+1=xi+1/6・(k1+2・k2+2・k3+k4) vi+1=vi+1/6・(m1+2・m2+2・m3+m4) で、 k1=h・g(ti, xi, vi) m1=h・f(ti, xi, vi) k2=h・g(ti+h/2, xi+k1/2, vi+m1/2) m2=h・f(ti+h/2, xi+k1/2, vi+m1/2) k3=h・g(ti+h/2, xi+k2/2, vi+m2/2) m3=h・f(ti+h/2, xi+k2/2, vi+m2/2) k4=h・g(ti+h, xi+k3, vi+m3) m4=h・f(ti+h, xi+k3, vi+m3) です。 復元力が非線形-sin(x)の場合と、それを線形近似して-x とした 場合の結果を比較します。 (GAMBAS) ' Gambas module file ' OscillationGam.bas ' True BASIC => GAMBAS ' Linear & Nonlinear Oscillation (True BASIC) ' mode1:-k*sin(x), mode2:-k*x Public Sub Main() Dim d0 As Float Dim x As Float Dim v As Float Dim t As Float Dim dt As Float Print "Linear & Nonlinear Oscillation f(x)" Print "initial angle(deg) = "; Input d0 ' 初期値 d0 = d0 * Pi / 180 x = Sin(d0) v = 0.0 dt = 0.02 ' 計算ループへ Runge(x, v, dt) Stop End Sub Runge(x As Float, v As Float, dt As Float) As Float Dim t As Float Dim k1 As Float Dim m1 As Float Dim k2 As Float Dim m2 As Float Dim k3 As Float Dim m3 As Float Dim k4 As Float Dim m4 As Float For t = 0 To 28 Step dt k1 = dt * gFnc(v) m1 = dt * fFnc(x) k2 = dt * gFnc(v + m1 / 2) m2 = dt * fFnc(x + k1 / 2) k3 = dt * gFnc(v + m2 / 2) m3 = dt * fFnc(x + k2 / 2) k4 = dt * gFnc(v + m3) m4 = dt * fFnc(x + k3) x = x + (k1 + 2 * k2 + 2 * k3 + k4) / 6 v = v + (m1 + 2 * m2 + 2 * m3 + m4) / 6 Print t; ",", x; ",", v Next End Function gFnc(v As Float) As Float Return v End Function fFnc(x As Float) As Float Dim fx As Float ' mode1 ' fx = - Sin(x) ' mode2 fx = - x Return fx End (Result) 時間ステップが同じはずなのに、時間が経つに連れて時間位 相がずれていってしまってるのが見えます。 今回のポイントは変数 x と v の 2 変数の関係を導入したところ です。これによって単に時系列的な運動の見方の他に、位相面解 析という見方で運動を眺めることができるようになります。そうい う見方したのが次の図になります。 力学的な運動方程式でいう位相面は普通、横軸に変位 x、縦 軸にその変位 x の時間微分、つまり速度 v の x-v 平面です。 振り子の運動では変位が最大のところでは速度がゼロに、ま た速度が最大のところでは変位はゼロになります。この軌跡は、 外力もなければ減衰もない、理想状態の振り子がゆらゆら揺れ ている軌跡です。 時系列プロットと位相面図を比較すると、振り子運動の軌道の 性質は割と一致しているのに、時間が経つ方向に誤差が累積し ていってるらしいことがわかります。 そこで、次は時間ステップを変えてみたものを比較して見ること にします。それが次の図です。ここでは初期値 60deg で同じです。 時間ステップが荒いと誤差は大きくなる。けど時間が経つに連 れてというよりも、始めっから誤差がありますな。 一方、初期値をふってみた結果では次のようになります。ここで 縦軸は解析解と数値解の差をとり、時間ステップは dt=0.02 で同 一です。 数値解は小さい θ の場合(sinθ≒θ)のように近似してるため、 振幅が小さいほど誤差の累積が少なくなります。そのとおりの結 果です。 まあ今回の例ではただの振り子を使ったんですが、解析的に 解けない非線形方程式もこの手を使って運動の性質を眺めるこ とができるという楽しい方法です。 ・参考文献 [1] 「シミュレーション物理の功罪」(p.50) [2] 戸川隼人、UNIX ワークステーションによる科学技術計算ハンドブック、基礎篇 C 言語版、サイエンス社 ルンゲ・クッタ法について先人が工夫したいろいろな方法が挙げられています。 [3] 田辺行人、藤原殻夫、常微分方程式、東京大学基礎工学双書、東京大学出版 会 常微分方程式を体系的に解説してます。FORTRAN77 によるオイラー法、ルンゲ・ クッタ法、ミルン法を計算させるサンプルプログラムが掲載。 あとがき ブログにアップロードできる1つのファイルの大きさが 1MByte と制限されているため、PDF ファイルの大きさがそれを超えるまえ にまとめさせていただきました。 この後に続く非線形方程式の数値解法や解析手法。それとカ オスやフラクタルについての記事は、後の回にまとめさせていた だきたいと思います。 「牛と戯れた日々を想って」ブログアドレス http://acc-holy.cocolog-nifty.com/blog/ このマニュアルの著作権は放棄しませんが、転載は好きなようにしてください。この マニュアルよりももっとよくできたものが出来るのならそれに越したことはないと思 います。”すでにあるソースは有効に利用する”というのはプログラマの良い伝統で す。みんなが幸せ。