数値計算講義 第9回 複素数の取り扱い – フラクタル図形の描画

数値計算講義 第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 回反復し たと き の値を 求めよ .