"BOKU"のITな日常

還暦越えの文系システムエンジニアの”BOKU”は新しいことが大好きです。

Pythonでグラフィックス:有名な「バーンスタインのシダの葉」を描くクラスを実装してみました

今回は、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はデフォルトだと日本語表示できません。

まだの場合は以下の記事を参考に、日本語化します。

arakan-pgm-ai.hatenablog.com

これで、描かれる図形は・・・まさしく「シダ」です。

f:id:arakan_no_boku:20181206223429j:plain

凄いです。

これも数学の力なんですかね。

でも。

これもパラメータで形が変わるわけではないんですよね。

 

ちなみに

 

上記の「get_index()」のprobのリストの値を、ちょっと変更してみます。

これは図形のプロットのポイントや集積度が変わります。

例えば。

prob = [0.55,0.35,0.35,0.01]

とかにすると、こんな感じ。

f:id:arakan_no_boku:20181206230556j:plain

シダの茎にあたる部分のプロットが減って、なんとなく、雪の積もった林を上から眺めているような感じです。

自分は結構好きですね。

こちらの記事と見比べてもらうとわかると思うのですが.

arakan-pgm-ai.hatenablog.com

今回の「シダの葉」を描くクラスと、「シェルピンスキーのギャスケット」を描くクラスは、x1・y1の変換する部分の比率と、get_index()のリストの数値が違うだけで、ほぼ、構造は同じです。

なので。

この構造のままで、いろいろと数値を変えていくと、いろんな図形が描けるみたいなので、いろいろ試してはいますが・・・。

いやあ。

なかなか、これだってものはできません。

まあ。

ぼちぼとやって・・面白いのができたら、また記事にします。

いつになるかはわからないですけどね(笑)

*1:x1,y1