見出し画像

Processing でグラフを描く⑭ ストレンジアトラクター

Processing でグラフを描く 第14回目です。
いま Twitter上でひそかに人気になっているストレンジアトラクターを紹介します。今回の内容は @KojiSaito様の記事を参考に書かれています。@KojiSaito様の許可のもと、creativival (筆者)がコードを一部改造して公開するものです。ストレンジアトラクターの詳しい説明は元記事をご参照ください。

ストレンジアトラクター

ID: JGWMNTTGLLCDID
ID: VHBPDVLMNXMI
ID: HJWLTLYHVFBI
ID: SYHLDECORFNV

ストレンジアトラクターをいくつか生成してみました。読者の皆様もぜひご自分のパソコンでストレンジアトラクターを生成してみてください。Sprott 式ストレンジアトラクタ発見機 に記載のコードを Processing3 (Python mode) に貼り付けて、実行ボタンを押すだけで、この魅惑的な図形が手に入ります。

@KojiSaito 様は、元記事の中で「見つけた面白いストレンジアトラクタの画像と ID をハッシュタグ #StrAttrCat と共に tweet して頂けたら幸いである」と仰っています。一緒に #StrAttrCat を盛り上げていきましょう!

ストレンジアトラクター発見機と探索機

$$
\begin{equation*}
\left\{ \,
\begin{aligned}
    & x' =a_1 + a_2 x + a_3 x^2 + a_4 xy +a_5 y + a_6 y^2  \\
    & y' =a_7 + a_8 x + a_9 x^2 + a_{10} xy +a_{11} y + a_{12} y^2 \\
\end{aligned}
\right.
\end{equation*}
$$

@KojiSaito様は2種類のストレンジアトラクターを作成するコードを公開されています。ストレンジアトラクター発見機は、ストレンジアトラクターを新しく発見するコードのことです。
上記の媒介変数x, y で表される x', y' を画面上にプロットしていくと、ストレンジアトラクターを描くことができます。面白い図形にするには、適切な12個の係数 $${a_1, a_2, a_3…a_{12}}$$ を見つける必要があり、それを自動化したものが「発見機」の役割といえるでしょう。

対して、ストレンジアトラクター探索機は、発見されたストレンジアトラクターの IDからその周辺に面白い図形がないか調べるコードです。「Sprott のコードとストレンジアトラクタ探索機」の中で紹介されているので、元記事をご覧ください。探索機の目的はストレンジアトラクターのカタログを作成するため、ストレンジアトラクターを網羅的に調べることです。@KojiSaito様は「得られた ID をほんの少しだけ変更し、それが魅力的な図形を生成するか否かを調べていかなければストレンジアトラクタのカタログは完成しない。」と仰っておられます。

「発見機」で見つけて、「探索機」でその ID周辺を調べ上げるという2段構えでストレンジアトラクターの宇宙に旅立っていく作戦です。

ストレンジアトラクター発見機の改造版(25枚同時作成)

# strange attractor generator with Sprott's algorithm
# program by KojiSaito ( Twitter @KojiSaito )
# modified to generate 25 attractors at the same time by creativival
import uuid

# If you set the ID, only one character will be different. If it is empty, a new one will be created.
# ID = ''
ID ='JGWMNTTGLLCD'
InitX = InitY = .05
N = 5000
points_list = []
character_num = 0


def init():
    global XE, YE, Lsum, Xmin, Xmax, Ymin, Ymax
    YE = .05
    XE = InitX + .000001
    Lsum = 0
    Xmin = Ymin = 1000000
    Xmax = Ymax = -Xmin
    id = [int(random(25)) for i in range(12)]
    coef = [(t - 12) * .1 for t in id]
    idStr = ""
    for c in id:
        idStr += chr(ord('A') + c)
    return coef, idStr


def next(coef, x, y):
    xNew = coef[0] + x * (coef[1] + coef[2] * x + coef[3] * y) + y * (coef[4] + coef[5] * y)
    yNew = coef[6] + x * (coef[7] + coef[8] * x + coef[9] * y) + y * (coef[10] + coef[11] * y)
    return xNew, yNew


def updateBound(x, y):
    global Xmin, Xmax, Ymin, Ymax
    Xmin = min(x, Xmin)
    Xmax = max(x, Xmax)
    Ymin = min(y, Ymin)
    Ymax = max(y, Ymax)


def LyapunovExponent(coef, x, y, n):
    global XE, YE, Lsum
    tx, ty = next(coef, XE, YE)
    dLx = tx - x
    dLy = ty - y
    dL2 = dLx * dLx + dLy * dLy
    df = 1e12 * dL2
    rs = 1 / sqrt(df)
    XE = x + rs * (tx - x)
    YE = y + rs * (ty - y)
    Lsum += log(df)
    return .721347 * Lsum / n  # L=Lyapunov exponent


def search(i):
    if ID == '':
        while True:
            coef, idStr = init()
            x, y = InitX, InitY
            found = True
            for i in range(1, 11000):
                x, y = next(coef, x, y)
                updateBound(x, y)
                l = LyapunovExponent(coef, x, y, i)
                if abs(x) + abs(y) > 1e6 or (i > 1000 and l < .002):  # Sprott 's original is .005
                    found = False
                    break
            if found:
                break
        print('ID="' + idStr + '"', " L=", l)
        return coef, idStr
    else:
        ord_num = ord(ID[character_num]) + i
        if ord_num > 89:
            ord_num = 64 + ord_num % 89
        idStr = ID[0:character_num] + chr(ord_num) + ID[character_num + 1:]
        coef = [(ord(id) - 77) * .1 for id in idStr]
        print('ID="' + idStr)
        return coef, idStr


def calcScale(coef, w, h):
    init()
    x, y = InitX, InitY
    for i in range(1000):
        x, y = next(coef, x, y)
        updateBound(x, y)
    dx = .1 * (Xmax - Xmin)
    dy = .1 * (Ymax - Ymin)
    xl = Xmin - dx
    xh = Xmax + dx
    yl = Ymin - dy
    yh = Ymax + dy
    sx = w / (xh - xl)
    sy = h / (yh - yl)
    return sx, sy, xl, yl


def setColor(idStr):
    colorH = 538
    for c in idStr:
        colorH = ord(c) + colorH * 33
    colorH %= 251
    s = [0] * 3
    t = colorH % 3
    s[t] = 3
    s[(t + 1) % 3] = 1.5
    s[(t + 2) % 3] = .8
    colorMode(HSB, 250)
    c = color(colorH, 190, 200)  # make color here
    colorMode(RGB, 255)
    blendMode(ADD)
    plotColor = (s[0] * (red(c) + 40), s[1] * (green(c) + 40), s[2] * (blue(c) + 40))
    return plotColor


def drawSA(w, h, i):
    clear()
    Coef, IdStr = search(i)
    PlotColor = setColor(IdStr)
    sx, sy, tx, ty = calcScale(Coef, w, h)
    x, y = InitX, InitY
    points = [IdStr, PlotColor]
    for i in range(N):
        x, y = next(Coef, x, y)
        points.append(((x - tx) * sx, (y - ty) * sy))
    return points


def set_points():
    global points_list
    points_list = []
    for i in range(25):
        points_list.append(
            drawSA(width / 5, height / 5, i)
        )


def setup():
    size(1250, 1250)
    frameRate(1)
    set_points()


def draw():
    background(0)
    for i in range(25):
        points = points_list[i]
        IdStr = points[0]
        PlotColor = points[1]
        stroke(PlotColor[0], PlotColor[1], PlotColor[2])
        x0 = i % 5 * 250
        y0 = int(i / 5) * 250
        for p in points[2:]:
            point(x0 + p[0], y0 + p[1])
        text(IdStr, x0 + 5, y0 + 15)


def keyPressed():
    global character_num
    if key == ' ':
        character_num = (character_num + 1) % 12
        set_points()
    if key == 's':
        if ID:
            file_name = '{}_position{}_explorer.png'.format(ID, str(character_num))
        else:
            file_name = str(uuid.uuid4())[:6] + '_generator.png'
        save(file_name)
        print("Done.")

こちらのコードは、@KojiSaito様の「ストレンジアトラクター発見機」を一部改造して、25枚の画像を同時に作成するものです。

25枚のストレンジアトラクターを発見する方法

変数 ID を空にしたときは、@KojiSitao様のストレンジアトラクター発見機と同様に新たにストレンジアトラクターを発見してくれます。元のコードとの違いは、一度に発見できる枚数を増やして効率化できるということです。ただし画質は低下するため、先ほど紹介した「ストレンジアトラクター探索機」を使って、高画質の画像を作成する必要があります。

使用方法
Processing3 (Python mode) にコードを貼り付けて実行してください。面白い図形が表示されたら「s」キーを押して画像を保存できます。「スペース」キーを押すと、新たに25枚の図形を発見します。

以下、「改造版(25枚同時作成)」で作成した画像を掲載します。

mult generator1
mult generator2
mult generator3

25枚のストレンジアトラクターを探索する方法

変数 ID を設定した場合は、そのIDの中で一文字だけ変更した画像を探索します。

使用方法
Processing3 (Python mode) にコードを貼り付けてください。探索したい ID を変数に代入します。実行すると、ID の最初の一文字だけ変更した25枚の画像が表示されます。
面白い図形が表示されたら「s」キーを押して画像を保存できます。「スペース」キーを押すと、ID の次の文字だけを変更した25枚の図形を探索します。ID は12文字ですから、12種類の画像を生成でき、周辺を網羅的に調べ上げることが可能です。

以下は、1文字目を変更したもの、2文字目を変更したもの、3文字目を変更したもの、そして12文字目を変更したものを掲載します(12枚)。12 x 24枚の周辺のストレンジアトラクターのカタログが完成しました。

JGWMNTTGLLCD_position0_explorer
JGWMNTTGLLCD_position1_explorer
JGWMNTTGLLCD_position2_explorer
JGWMNTTGLLCD_position3_explorer
JGWMNTTGLLCD_position4_explorer
JGWMNTTGLLCD_position5_explorer
JGWMNTTGLLCD_position6_explorer
JGWMNTTGLLCD_position7_explorer
JGWMNTTGLLCD_position8_explorer
JGWMNTTGLLCD_position9_explorer
JGWMNTTGLLCD_position10_explorer
JGWMNTTGLLCD_position11_explorer

ストレンジアトラクターの宇宙へ

ストレンジアトラクターは面白い。マンデルブロ集合と並んで、私の大好きな図形が増えました。J. C. Sprott氏と @KojiSaito様に、素晴らしい世界を開いてくれたことに感謝いたします。私の「改造版のコード」がストレンジアトラクター研究の一助になればうれしく思います。


前の記事
Processing でグラフを描く⑬ 行列による3次元グラフの変換
次の記事
Processing でグラフを描く⑮ ストレンジアトラクターのアニメーション

その他のタイトルはこちら


この記事が気に入ったらサポートをしてみませんか?