数値計算講義 第9 回 複素数の取り 扱い – フ ラ ク タ ル図形の描画 – カ ーネ ン コ 金 子 ア レ ク セイ 晃 kanenko@mbk.nifty.com alexei.kanenko@docomo.ne.jp http://www.kanenko.com/ FORTRAN における 複素数型 (プロ グラ ム 例 mandelbrot.f) 1 FORTRAN に は複素数型が存在する COMPLEX C,Z,W と 宣言する と , W=Z**2+C と 書く だけ で , 複素数の計算 w = z 2 + c が実現でき る . 複素数と 実数や整数と の混合算も OK. 結果は複素数型と な る : W=X*Z+2-1/Z 関連し た函数: Z=CMPLX(X,Y) ! 実数 x, y から 複素数 z = x+iy を 作る X=REAL(Z) ! 複素数 z の実部 x を 取り 出す Y=AIMAG(Z) ! 複素数 z の虚部 y を 取り 出す W=CONJG(Z) ! 複素数 z の複素共役を 作る 複素数に 対する 組み込み函数は次のよ う な 名前と な る : CABS(Z) CEXP(Z) CSIN(Z) CCOS(Z) ! ! ! ! 複素数 複素数 複素数 複素数 z z z z の絶対値 の指数函数 の sin の cos mandelbrot.f の実行結果 2 Mandelbrot 集合の解説 3 Mandelbrot 集合は次の規則で定義さ れる : 複素平面の点 c ∈ C が Mandelbrot 集合に 属する と は, 原点 z0 = 0 から 出発し zn+1 = zn2 + c と いう 漸化式で定ま る 複素数列が いつま で経っ て も 有界集合に 留ま っ て いる こ と を いう . Mandelbrot 集合は |z| ≤ 2 に 含ま れる . ∵ |c| > 2 な ら z1 = c, |z2 | = |c2 + c| = |c||c + 1| ≥ |c|(|c| − 1). n−2 な ら , |c| − 1 > 1 に 注意し て 以下帰納的に , |zn | ≥ |c|(|c| − 1)2 n−1 n−1 − 1} − |c| = |c|{|c|(|c| − 1)2 |zn+1 | = |zn2 + c| ≥ |c|2 (|c| − 1)2 n−1 n−1 2 2 } − (|c| − 1) > |c|{|c|(|c| − 1) n−1 n−1 = |c|{(|c| − 1)2 +1 } > |c|{(|c| − 1)2 } よ っ て |zn | → ∞ と な る . |c| ≤ 2 であっ て も , 一度 |zn | > 2 と な っ たら 発散する . ∵ |zn+1 | = |zn2 + c| ≥ |zn |2 − |c| > 2|zn | − 2 = 2(|zn | − 1) > 2 × 1 = 2 (従っ て 以後ずっ と |zn+k | > 2) よって |zn+1 | = |zn2 + c| ≥ |zn |2 − |c| > |zn |2 − 2 ∴ |zn+1 | − 2 > |zn |2 − 4 = (|zn | + 2)(|zn | − 2 > 4(|zn | − 2) と な り , こ れを 繰り 返せば |zn+k | − 2 ≥ 4k (|zn | − 2) が示せる ので, |zn | − 2 → ∞, 従っ て |zn | → ∞. oo Q 昨年ま での証明はも う 少し 複雑でし た . こ こ に 紹介し たのは 教科書の読者, 鈴木春生氏に よ る 簡易化です. Mandelbrot 集合の FORTRAN プロ グラ ム 4 PARAMETER(IXMIN=0,IXMAX=800,IYMIN=0,IYMAX=600) PARAMETER(XMAX=2,XMIN=-XMAX,YMAX=XMAX/IXMAX*IYMAX,YMIN=-YMAX) COMPLEX C,F,Z GX(IX)=XMIN+(XMAX-XMIN)*(IX-IXMIN)/(IXMAX-IXMIN) GY(IY)=YMIN+(YMAX-YMIN)*(IY-IYMIN)/(IYMAX-IYMIN) F(Z)=Z*Z+C CALL INIT(IXMIN,IYMIN,IXMAX,IYMAX) CALL CLS() DO 200 I=IXMIN,IXMAX DO 100 J=IYMIN,IYMAX C=CMPLX(GX(I),GY(J)) Z=(0,0) ! 複素定数 0 DO 50 K=1,80 Z=F(Z) IF (CABS(Z).GT.2.0) GO TO 100 50 CONTINUE CALL PSET(I,J,15) 100 CONTINUE 200 CONTINUE PAUSE CALL CLOSEX() END ※ GX, GY は今ま で用いて き た IGX,IGY の逆函数で , 整数型のス ク リ ーン 座標を 実数型のワ ールド 座標に 変換する . ※ a, b が定数のと き に 限り (a,b) で複素数 a + bi を 表すこ と ができ る . 倍精度の複素数型 (プロ グラ ム 例 mandelbrotd.f) 5 複素数はデフ ォ ールト では実部・ 虚部と も 単精度の実数である . 実部・ 虚部と も 倍精度の実数である 倍精度複素数は , F77 のコ ン パイ ラ では必ずし も サポート さ れて いな かっ たが , FORTRAN 90 から 正式に 採り 入れら れ, g77 でも 使え る . 型宣言は DOUBLE COMPLEX ある いは COMPLEX*16 Z, W Z, W な ど と する . 倍精度複素数に 対し て は, 対応する 組み込み函数は CMPLX,CABS,CEXP,CSIN,CCOS,REAL,AIMAG 等はそ れぞれ, DCMPLX,DCABS,DCEXP,DCSIN,DCCOS,DREAL,DIMAG 等と な る . 倍精度の複素数を 作る に は, 構成要素の実数も 倍精度に し な け れば 精度が保て な い. 函数 Z=DCMPLX(X,Y) は X, Y が倍精度でな く 単精度でも エラ ーに な ら な いので, 特に 注意が必要. C 言語における 複素数の取り 扱い (プロ グラ ム 例 mandelakko.c) 6 C 言語に は複素数型は無いので, 実部・ 虚部二つの実数のペア で表し , 自分で管理し な け ればな ら な い. double z[2],w[2],zeta[2]; /* 複素数の和 zeta = z + w に 相当する 計算 */ zeta[0]=z[0]+w[0]; zeta[1]=z[1]+w[1]; /* 複素数の積 zeta = z * w に 相当する 計算 */ zeta[0]=z[0]*w[0]-z[1]*w[1]; zeta[1]=z[1]*w[0]+z[0]*w[1]; も う ち ょっ と 見やすく する ため, 構造型を 使う こ と ができ る : typedef struct { double x; /* 実部のつも り */ double y; /* 虚部のつも り */ } complex; のよ う に 型の定義を し て おき , complex z,w,zeta; と 複素数型の変数宣言する と , z の実部, 虚部は構造型の規則に よ り そ れぞれ z.x, z.y で表せる . こ う し て も 複素数の演算が z+w 等と 書け る 訳ではな く , こ れら の演算を 実行する 函数を 作ら ねばな ら な い. C 言語における 複素数の取り 扱い – 続き 7 /* 複素数の和 zeta = z + w を 返す函数 */ complex cadd(complex z,complex w)[ complex zeta; zeta.x=z.x+w.x; zeta.y=z.y+w.y; return zeta; } /* 複素数の積 zeta = z * w を 返す函数 */ complex cmul(complex z,complex w)[ complex zeta; zeta.x=z.x*w.x-z.y*w.y; zeta.y=z.y*w.x+z.x*w.y; return zeta; } こ れら を 使う と き は, complex alpha,beta,gamma,z,w,zeta; gamma=cadd(alpha,beta); zeta=cmul(z,w); のよ う に な り , ML の演算子のよ う で少々 かっ こ う が悪い. (^^; gcc に は, 後述の C++ の機能を 一部取り 込む形で複素数型がある . #include<complex.h> と する と , 標準で倍精度の複素数型と な り , complex z,w; z=2+1i; /* な ぜか 2+i はエラ ーに な る */ w=1-2i; printf("%lf+%lf i\n",z+w); printf("%lf+%lf i\n",z*w); で複素数の和や積が計算でき る . C の標準仕様ではな いので, 互換性に 注意. 複素数に 対する ラ イ ブラ リ 函数は cabs, conj 程度し かな い. 出力フ ォ ーマ ッ ト の書き 方が見付から な かっ たが, 上の間に 合わせの例では 残念な がら 3.000000+-1.000000 i 等と 出力さ れる . C++ 言語における 複素数の取り 扱い (プロ グラ ム 例 julia.cc) 8 実は C 後継言語と し て C++ と いう も のが有り , そ こ では 複素数型が使え る . C++ は全く 異な る 言語だが , 複素数を 使う だけ な ら , C の上位互換と し て 手っ 取り 早い使い方も でき る : 1 ソ ース フ ァ イ ル名を hoge.c から hoge.cc に 変更する ⃝ 2 イ ン ク ルード する ヘッ ダフ ァ イ ルを ⃝ #include<cstdio> /* C 言語の stdio.h の代わり */ #include<cstdlib> /* C 言語の stdlib.h の代わり */ #include<cmath> /* C 言語の math.h の代わり */ #include<complex> /* C++ 言語の複素数型のク ラ ス 定義 */ と し て おけ ば, 複素数型のみな ら ず C の函数も 引続き 使え る . そ の上で 3 複素数型の宣言 ⃝ std::complex<double> z,w,zeta1,zeta2; を 行う と , 演算は通常の演算形式 zeta1=z+w; zeta2=z*w; 等で可能と な る . こ れは C++ 言語が演算の多重定義 (overload) を 許す仕様のためである . ま た C++ は型に 厳密で, 函数は引数の型が異な る と 同名でも 別物と みな さ れる . そ のため, 例え ば指数函数も , 単精度, 倍精度, 複素数のすべて に 共通し て exp と いう 一つの名前で済み (函数の多重定義), 数学に よ り 近く な る . C++ 言語における 複素数の取り 扱い – 続き (おたく 向け ) 9 C++ 言語は C 言語と コ ン パイ ル時の函数名の付け 方が異な る . C 言語に おいて 函数 hoge の定義 int hoge(int n){ return n+2; } を 含むラ イ ブラ リ フ ァ イ ル libhoge.c を gcc -c libhoge.c でコ ン パイ ル する と , でき た object module libhoge.o の中に hoge と いう 函数が 登録さ れる . こ れに 対し C++ 言語の場合は同様のフ ァ イ ル libhoge.cc を g++ -c hoge.cc でコ ン パイ ルする と , libhoge.o の中に hogei と いう 函数が登録さ れる . (型の異な る 同名の函数 hoge がも う 一つあれば hogeii と し て 登録さ れる . こ れに よ り 函数の多重定義を 実現し て いる .) こ れに 合わせて , main 函数で hoge を 呼んだと き , リ ン ク 時に C 言語では hoge が , C++ 言語では hogei, hogeii, 等の函数が ラ イ ブラ リ から 探さ れる . こ のため , C 言語で書かれたラ イ ブラ リ 函数を そ のま ま リ ン ク でき な い. C 言語の別フ ァ イ ルでコ ン パイ ルさ れて いる 函数 int sub(int, int) 等を C++ のソ ース から 呼ぶと き は, 使用する 函数の型宣言と し て extern "C" { int sub(int,int); int sub2(double); } な ど と すればそ のま ま 使え , 既存のラ イ ブラ リ 遺産を 活かすこ と ができ る . C 言語用の xgrc.c を C++ で使う と き も , こ のよ う に する . 参考: C++ 言語に関する 解説 10 C++ の複素数演算を 可能に し て いる のが, class の概念である . complex (複素数) と いう class を 新たに定義し , こ の型のオブジェ ク ト に 対し て 既存の演算記号 + や * 等の意味を 拡張定義する (演算子の overload). 複素数型に は, 実部・ 虚部が整数, 単精度実数, 倍精度実数のも のな ど いろ いろ 必要で, こ れら を template と いう 機構で区別する (最初の例は double complex の場合で complex<double> と 宣言さ れて いた ). こ れに 合わせて , 演算子 + や * の定義も , 複素数対複素数, 複素数対倍精度実数, 倍精度実数対複素数, 複素数対整数, etc. な ど , あら ゆる 可能性に ついて 与え て おく 必要があ る . 更に , オブジェ ク ト の生成と 消滅, 入出力フ ォ ーマ ッ ト な ど の定義も 必要. class ラ イ ブ ラ リ は, 使う 方は極楽だが, 作る 方は地獄. complex な ど , 標準的に 使われる も のに ついて は, 標準ラ イ ブラ リ std が用意さ れて いる . 同じ 名前の class が衝突し な いよ う , namespace と いう 概念で区別し , 標準ク ラ ス ラ イ ブラ リ を 使う と き は std:: を かぶせる か, using namespace std と 冒頭で宣言し な け ればな ら な い. (ver.2 ま では省略でき たが, ver.3 以降は明白に 宣言し な いと エラ ーに な る . ) ク ラ ス complex の詳細は定義フ ァ イ ル (5 階の場合は /usr/include/c++/4.0.0/complex) を 覗いて 見よ . (Java 言語を 学んだ人は, オブジェ ク ト 指向言語と し て の共通性から , 理解し やすいだろ う . ) julia++.cc は C++ 言語固有の書き 方に な っ て いる . 例え ば, #include<cstdio> の代わり に #include<iostream> printf("message"); の代わり に std::cout << "message"; scanf("%lf",&a); の代わり に std::cin >> a; な ど . こ の入出力の書き 方は, フ ォ ーマ ッ ト を 気に せずに 済むので便利. (FORTRAN 77 の WRITE(*,*) と 似て る !) ま た, 引数の参照渡し が可能な ので scanf 函数のよ う に 引数で値を 返さ ねば な ら な い場合も ポイ ン タ ーを 使わずに 済み, プロ グラ ム が安全に な る . Julia 集合の解説 11 Julia 集合はパラ メ ータ c に 依存し , 次の規則で定義さ れる : 複素平面の点 z ∈ C がパラ メ ータ c の Julia 集合に 属する と は, z0 = z から 出発し zn+1 = zn2 + c と いう 漸化式で定ま る 複素数列が いつま で経っ て も 有界集合に 留ま っ て いる こ と を いう . Julia 集合は |z| ≤ 2 に 含ま れる . 従っ て 発散の判定も |zn | > 2 で可能. 証明は Mandelbrot 集合の場合で実質的に 済んでいる . Julia 集合はパラ メ ータ c を 変化さ せる と , 形が非常に 変化する . Mandelbrot 集合も Julia 集合も , 単に 有界に 留ま る か, 発散する かで 白黒に 塗り 分け る のでな く , |z| > 2 の領域に 飛び出すま でに 何回かかっ たか で色分け する と , 非常に 美し い模様ができ る . 色づけ に は美的才能が必要. C 言語で書かれた mandelakko.c を 味わっ て 見よ . フ ラ ク タ ル性 Mandelbrot 集合も Julia 集合も 部分を 拡大し て ゆく と , いく ら でも 複雑に な っ て いる だけ でな く , 同じ よ う な パタ ーン が 繰り 返し 現れる (自己相似性). こ れを 調べる ため, 改良版のプロ グラ ム では, マ ウ ス で描画範囲を 指定し , そ の部分を 拡大描画でき る よ う に し て ある . julia.f の実行結果 12 c = 0.4 + 0.31i Newton 法の吸引領域の解説 13 複素数に 対し て も Newton 法は通用する . 実は一般に N 元連立の非線型方程式系 F (x) = 0 に 対し て も Newton 法が通用する : x0 (初期値) を 適当に 選ぶ. x1 = x0 − DF (x0 )−1 [F (x0 )] ...... xn+1 = xn − DF (xn )−1 [F (xn )] こ の漸化式は, 近似解 xn に おけ る 方程式 F (x) = 0 の線型近似 · F (x) = · F (xn ) + DF (xn )[x − xn ] = 0 を x に ついて 解いたも のである . (DF (xn ) は写像 F の点 xn に おけ る 微分すな わち Jacobi 行列, DF (xn )−1 はそ の逆行列を 表す .) D 2 F が非退化な ら , 初期値が真の解に 十分近け れば, 近似解の列が 真の解に 2 次の収束を する こ と が, 1 変数の場合と 全く 同様に し て 示さ れる . 複素方程式 f (z) = 0 に 対する Newton 法は, こ の N = 2 の場合に すぎな い: f (zn ) zn+1 = zn − ′ f (zn ) Newton 法の吸引領域の解説 – 続き 14 f (z) が多項式のと き は, 反復列が の零点に ぶつかる か, 周期軌道に ぶつかる と いう 例外的な 場合を 除き , 必ずいつかは f (z) = 0 のど れかの根に 収束する . 全複素平面を , そ の点を 初期値と する Newton 法の反復列がど の根に 収束する かに よ り 色分け する と , 美し い図ができ る . こ の図形も , f ′ (z) = 0 の零点の逆像の周り に フ ラ ク タ ル的な 構造を 持つ. f ′ (z) プロ グラ ム例: 第7 回に 配布し た num7-3.f は Wilkinson 多項式に対する Newton 法の吸引領域を 可視化し たも の . C++ 言語の例と し て は z 3 − 1 = 0 に 対する cubic.cc (右図). 参考: FORTRAN と C のリ ン ク (おたく 向け ) 15 FORTRAN で書いたプロ グラ ム を コ ン パイ ルする と , CALL HOGE(X) と いう サブルーチン コ ールは, hoge_ と いう 副プロ グラ ム 名に 翻訳さ れる . 引数の渡し 方は, ス タ ッ ク 経由な ので, 参照渡し に な っ て いる こ と さ え 留意すれば, 副プロ グラ ム の方を , こ の名前で C で書いて も リ ン ク 可能. xgrf.c の FORTRAN 用描画ラ イ ブラ リ は実際こ のよ う に し て 作ら れて いる . こ の他に も , FORTRAN では書け な いが, FORTRAN でも 使いたい シス テム コ ール関連の指令を , C で書いて おき FORTRAN から サブルーチン コ ールし て 使う こ と ができ る . 例え ば: 時刻呼び出し CPU の切り 捨て ・ 切り 上げ指定 キ ーボード でキ ー押し 下げの即時反映 環境変数の値の取得 など. 高速な FORTRAN のラ イ ブラ リ を 作る のに , サブルーチン を ア セ ン ブラ で書いて リ ン ク する こ と も , 同じ 要領で可能と な る . 本日の講義内容の自習課題 16 [1] FORTRAN 見本プロ グラ ム mandelbrot.f およ び mandelbrotd.f のソ ース を エディ タ ーで読みな がら , g77 mandelbrot.f xgrf.o -lX11 -L/usr/X11R6/lib 等で コ ン パイ ルし , 実行し て 講義で学んだこ と を 確認する . [2] C 言語の見本プロ グラ ム mandelakko.c のソ ース を エディ タ ーで読みな がら , gcc mandelakko.c xgrc.o -lX11 -L/usr/X11R6/lib で コ ン パイ ルし , 実行し て 講義で学んだこ と を 確認する . [3] C++ 言語の見本プロ グラ ム julia.cc と julia++.cc のソ ース を 比較し な がら 読み, g++ julia.cc xgrc.o -lX11 -L/usr/X11R6/lib 等でコ ン パイ ル・ 実行し て 講義で学んだこ と を 確認する . [4] C++ 言語の見本プロ グラ ム cubic.cc のソ ース を エディ タ ーで読み, コ ン パイ ルし , 実行し て 講義で学んだこ と を 確認する . [5] julia.f を いろ んな パラ メ ータ で実行し , でき ればプロ グラ ム を 変更し て 色付け も 変え , 気に 入っ た図形を 作っ て みよ . ど う し て も 僕に 見せたい素敵な 図ができ た ら , メ ールで送っ て く ださ い. 僕を 感動さ せる こ と ができ れば, レ ポート と し て カ ウ ン ト し ま す. [6] cubic.cc を x3 − 1 = 0 の代わり に 別の代数方程式, 例え ば x5 − x − 1 = 0 に 対する も のに 書き 直し て みよ . [ ヒ ン ト : 根の数が3 から 5 に 増え る ので, そ れに 応じ て 解の近似値の格納や, 色指定のための配列も 拡大する 必要がある . (最初から 大き めに と っ て おけ ば安直に 汎用化でき る (^^;)] [7] フ ラ ク タ ルの参考プロ グラ ム leaf.f, von_koch.f, peano.c, sierpinski.c を xgrf.o ある いは xgrc.o と リ ン ク し て コ ン パイ ルし , 実行し て みよ . 本日の範囲の試験予想問題 17 問題 9.1 C 言語で複素数の計算を 行う 方法を 一つ説明せよ . ただし , gcc の complex ラ イ ブラ リ のよ う な も のは用いな いも のと する . 問題 9.2 次はマ ン デルブロ ート 集合を 描画する FORTRAN プロ グラ ム の 一部である . こ れを C 言語に 翻訳せよ . ただし , GX(), GY() はス ク リ ーン のピ ク セ ル座標を ワ ールド 座標 −2 ≤ x ≤ 2, −1.5 ≤ y ≤ 1.5 に 変換する 線形関数であり , PSET() はピ ク セ ル (I,J) を 着色する サブルーチン である . こ れら は C 言語に 翻訳後も そ のま ま 用いて よ いも のと する . COMPLEX C,Z ... DO 200 I=0,800 DO 100 J=0,600 C=CMPLX(GX(I),GY(J)) Z=(0,0) DO 50 K=1,80 Z=Z**2+C IF (CABS(Z).GT.2.0) GO TO 100 50 CONTINUE CALL PSET(I,J,15) 100 CONTINUE 200 CONTINUE 問題 9.3 z 3 − 1 = 0 の根で , 単位円周上 1 のすぐ 次に 現れる も の (1 の原始 3 乗根 ω) の近似値を 複素 Newton 法で計算する と き , 初期値 i から 出発し て 2 回反復し たと き の値を 求めよ .
© Copyright 2024 Paperzz