今回は、Pythonで「バーンスタインのシダの葉っぱ」実装します。
すごい有名なやつで、色んなプログラム言語の実装が紹介されてます。
ソースコードと補足説明です
パラメータはひとつだけです。
単純なので、ソースコードの補足説明でまとめます。
まずは、クラスのソースコードです。
gr_fern.py
import random class Fern(): def __init__(self): self.tx = [0] self.ty = [0] def tran_1(self,p): x = p[0] y = p[1] x1 = 0.85*x + 0.04*y y1 = -0.04*x + 0.85*y + 1.6 return x1, y1 def tran_2(self,p): x = p[0] y = p[1] x1 = 0.2*x - 0.26*y y1 = 0.23*x + 0.22*y + 1.6 return x1, y1 def tran_3(self,p): x = p[0] y = p[1] x1 = -0.15*x + 0.28*y y1 = 0.26*x + 0.24*y + 0.44 return x1, y1 def tran_4(self,p): x = p[0] y = p[1] x1 = 0 y1 = 0.16*y return x1, y1 def get_index(self): prob = [0.85,0.07,0.07,0.01] r = random.random() c = 0 sump = [] for p in prob: c += p sump.append(c) for item,sp in enumerate(sump): if(r <= sp): return item return len(prob)-1 def tran(self,p): trans = [self.tran_1,self.tran_2,self.tran_3,self.tran_4] tindex = self.get_index() t = trans[tindex] x,y = t(p) return x, y def draw(self, n=5000): x1 = 0 y1 = 0 for i in range(n): x1,y1 = self.tran((x1,y1)) self.tx.append(x1) self.ty.append(y1) return self.tx,self.ty
補足説明です。
メインの処理は「draw()」です。
def draw(self, n=10000):
x1 = 0
y1 = 0
for i in range(n):
x1,y1 = self.tran*1
self.tx.append(x1)
self.ty.append(y1)
return self.tx,self.ty
座標を示す「x1、y1」を更新しながら、「n」で指定した回数繰り返します。
デフォルトでは10000回です。
x1,y1を更新する処理は「tran()」です。
def tran(self,p):
trans = [self.tran_1,self.tran_2,self.tran_3,self.tran_4]
tindex = self.get_index()
t = trans[tindex]
x,y = t(p)
return x, y
pというタプルで(x1,y1)を受け取ります。
それを引数に使って、「tran_1」「tran_2」「tran_3」「tran_4」のいずれかのメソッドをよびだします。
どのメソッドを呼び出すかを決めるのは「get_index()」です。
def get_index(self):
prob = [0.85,0.07,0.07,0.01]
r = random.random()
c = 0
sump = []
for p in prob:
c += p
sump.append(c)
for item,sp in enumerate(sump):
if(r <= sp):
return item
return len(prob)-1
[0.85,0.07,0.07,0.01]のリストを順番に加算した値と、乱数を比較して、乱数が合計値よりも小さかったら、そのインデックスを返します。
この数字を変更すると、できあがる図形の形が変わります。
とりあえず、シダの葉っぱのような図形にするには、この値が適切だというのは経験的に知ってるのですが、なぜかを数学的に証明しろとか言われたらできません。
数式の苦手な文系ですから・・。
まあ。
言い訳ですが(笑)
あとは、入力された「x1とy1」の変換の仕方が4パターン。
1パターン目
x1 = 0.85*x + 0.04*y
y1 = -0.04*x + 0.85*y + 1.6
2パターン目
x1 = 0.2*x - 0.26*y
y1 = 0.23*x + 0.22*y + 1.6
3パターン目
x1 = -0.15*x + 0.28*y
y1 = 0.26*x + 0.24*y + 0.44
4パターン目
x1 = 0
y1 = 0.16*y
このパターンをランダムに繰り返していくことで、図形を描き出していくわけです。
さて、実行してみます
上記のクラスを、fern.pyという名前で保存した前提で、それを使って図形を描くコードです。
gr_fern_do.py
import matplotlib.pyplot as plt import gr_fern as fn f = fn.Fern() x,y = f.draw() plt.plot(x,y,'.') plt.title('バーンスタインのシダ') plt.show()
インポートして、Fernクラスオブジェクトを生成して、draw()を呼ぶだけです。
それを、matplotlibでプロットします。
なお、タイトルは日本語にしていますけど、matplotlibはデフォルトだと日本語表示できません。
まだの場合は以下の記事を参考に、日本語化します。
これで、描かれる図形は・・・まさしく「シダ」です。
凄いです。
これも数学の力なんですかね。
でも。
これもパラメータで形が変わるわけではないんですよね。
ちなみに
上記の「get_index()」のprobのリストの値を、ちょっと変更してみます。
これは図形のプロットのポイントや集積度が変わります。
例えば。
prob = [0.55,0.35,0.35,0.01]
とかにすると、こんな感じ。
シダの茎にあたる部分のプロットが減って、なんとなく、雪の積もった林を上から眺めているような感じです。
自分は結構好きですね。
こちらの記事と見比べてもらうとわかると思うのですが.
今回の「シダの葉」を描くクラスと、「シェルピンスキーのギャスケット」を描くクラスは、x1・y1の変換する部分の比率と、get_index()のリストの数値が違うだけで、ほぼ、構造は同じです。
なので。
この構造のままで、いろいろと数値を変えていくと、いろんな図形が描けるみたいなので、いろいろ試してはいますが・・・。
いやあ。
なかなか、これだってものはできません。
まあ。
ぼちぼとやって・・面白いのができたら、また記事にします。
いつになるかはわからないですけどね(笑)
*1:x1,y1