前回は、投稿直後にBUG発見、XとYを取り違えていたという体たらく。たまたまXとYが同じサイズで動いたように見えていた、と。修正いたしました。さて今回は、アイキャッチ画像に貼り付けました「ありがちな」ロジスティック写像のカオスなパターンを描くというもの。白黒2値画像で描けますです。
※「やっつけな日常」投稿順 indexはこちら
※末尾に今回実験した Go言語のソース一式を掲げました。
「ロジスティック写像」とか「ロジスティック方程式」とかで検索すれば解説記事は大量にヒットする筈なので、特に引用はいたしませぬ。ただ、Goのコード(Hello Worldから3つめ)を書くにあたって参照させていただいたのが以下であります。
C言語による最新アルゴリズム事典 奥村晴彦著 技術評論社
最新というのは初版の1991年の時点だと思われます。しかしその後も版を重ねて定番となり、どうも現在では改訂新版が出ているみたい。
スマホ上でのソース
例によって、スマホ上でユーザモードLinuxを使えるTermuxを使っております。編集画面が以下に。スマホの画面は文字は小さくて老眼には辛いですが、縦長なので、ソースを見渡すのには不自由はない感じです。
Command-Line Flags
今回、新たに勉強したのが、Go言語のCommand-Line Flagsというモジュールです。Pythonでいつもお世話になっている argparse モジュールに相当するみたいなものです。Exampleページが以下に。
Go by Example: Command-Line Flags
まあ、これがあれば argparse同様に、コマンドライン引数の処理はバッチリ。その上 -h オプションで自動でヘルプも出してくれるので、忘却力にも対抗できると(老人は自分で作ったプログラムの引数オプション、しばしば忘れます。)
$ go run discreatLogisticEquation.go -h
実機上での実行
実機上での実行は簡単(今回はビルドしてません、インタプリタ実行のみ)。-graphオプションつけて走らせ、出力をpbmファイルにリダイレクト。出力先のファイルをconvertコマンド(ImageMagickのインストール必要)にて png ファイルに変換してます。
$ go run discreatLogisticEquation.go -graph >chaos.pbm $ convert chaos.pbm chaos.png
ロジスティック写像の係数 k (文書によっては a など)、値 p についても設定できるようにフラグを用意しましたが、上記はデフォルトで走らせてます。すべからく忘却力に対抗するにはデフォルト値を仕込むことなんであります。閑話休題。
デフォルト値は
-
- kmin 1.5
- kmax 3.0
- pmin 0.0
- pmax 1.5
です。走らせると、以下のようなサイズの白黒画面にカオスなプロットが描かれます。
-
- 画面X軸は、左端 kmin、右端 kmax
- 画面Y軸は、上端 pmin、下端 pmax
k値を kminからだんだん大きくしながら以下を繰り返します。
-
- とりあえず p値 0.3 から写像を始めて50世代(?)計算する。
- 51世代目から100世代までのp値をプロットする。
k値が小さい間は、さっさとアトラクタに収束しますが、k値が大きくなるとカオスな挙動を示すようになる(ストレンジ・アトラクタ?、知らんけど。)
良く分かっていないのだけれども、お手軽に短いコードで書けて、カオスの深淵を覗き込める(ホントか?)ので、嬉しいかもです。
やっつけな日常(4) スマホでGo!まだfmt.Printしか使えないけれど画像出力はできる へ戻る
やっつけな日常(6) スマホでGo!fmt.Printで今度はフルカラー画像出力 へ進む
// Based on chaos.c by Okumura 1991 ISBN4-87408-414-1 package main import ( "flag" "fmt" ) type pbmImage struct { width int height int BUF []int } func newPbmImage(w int, h int) *pbmImage { p := pbmImage{width: w, height: h} buf := make([]int, w*h) p.BUF = buf return &p } func dotXY(img *pbmImage, x int, y int, v int) bool { if x > (img.width - 1) { return false } else if y > (img.height - 1) { return false } else { img.BUF[y*img.width + x] = v } return true } func writePBM(img *pbmImage) bool { fmt.Println("P1") fmt.Println(img.width, " ", img.height) for y := 0; y < img.height; y++ { for x := 0; x < img.width; x++ { fmt.Print(img.BUF[y*img.width + x], " ") } fmt.Println() } return true } func bifur(kmin float64, kmax float64, pmin float64, pmax float64) { XMAX := 640 YMAX := 480 var x float64; var y float64; img := newPbmImage(XMAX, YMAX) dk := (kmax - kmin) / float64(XMAX -1) for k := kmin; k <= kmax; k = k + dk { p := 0.3 for i:= 1; i <= 50; i++ { p = p + (k * p * (1.0 - p)) } for i:= 51; i <= 100; i++ { if p >= pmin && p <= pmax { x = ((k-kmin)/(kmax - kmin))*float64(XMAX) y = ((p-pmin)/(pmax - pmin))*float64(YMAX) dotXY(img, int(x), int(y),1) } p = p + (k * p * (1.0 - p)) } } writePBM(img) } func chaos(k float64, p float64) { for i:=1; i <= 100; i++ { fmt.Printf("%10.3f", p) if i % 4 == 0 { fmt.Println() } p = p + (k * p * (1.0 - p)) } } func main() { kPtr := flag.Float64("k", 1.5, "Parameter(min), float64") pPtr := flag.Float64("p", 0, "Initial value(min), float64") kxPtr := flag.Float64("kmax", 3.0, "Parameter(max), float64") pxPtr := flag.Float64("pmax", 1.5, "Initial value(max), float64") grPtr := flag.Bool("graph", false, "a bool") flag.Parse() if ! *grPtr { chaos(*kPtr, *pPtr) } else { bifur(*kPtr, *kxPtr, *pPtr, *pxPtr) } }