ホーム
/
作ったもの
/
考えたこと
/
きろく

アステカダイヤモンドのビジュアライザ

使い方・計算量

タイリング

 アステカダイヤモンドのドミノタイリング及び、色付けを行うコードです。 当初は非交差経路を構成してから、それに応じてタイリングを行っていましたが、ドミノシャッフリングを用いた方法へと変更しました。

タイリング + 色付け

用途: 北極圏定理の視覚化

計算量: nをアステカダイヤモンドの次数として、 $O(n^3)$ です。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
import random

def generate_aztec_diamond(n):
    size = 2 * n
    # 0: 空白(背景), 1: North(上), 2: South(下), 3: West(左), 4: East(右)
    grid = np.zeros((size, size), dtype=int)

    for k in range(1, n + 1):
        current_parity = (k - 1) % 2

        for r in range(size - 1):
            for c in range(size - 1):
                if (r + c) % 2 == current_parity:
                    if grid[r, c] == 2 and grid[r, c+1] == 2 and \
                       grid[r+1, c] == 1 and grid[r+1, c+1] == 1:
                        grid[r, c] = 0; grid[r, c+1] = 0
                        grid[r+1, c] = 0; grid[r+1, c+1] = 0

                    elif grid[r, c] == 4 and grid[r+1, c] == 4 and \
                         grid[r, c+1] == 3 and grid[r+1, c+1] == 3:
                        grid[r, c] = 0; grid[r+1, c] = 0
                        grid[r, c+1] = 0; grid[r+1, c+1] = 0

        next_grid = np.zeros((size, size), dtype=int)
        for r in range(size):
            for c in range(size):
                val = grid[r, c]
                if val == 1:   next_grid[r - 1, c] = 1
                elif val == 2: next_grid[r + 1, c] = 2
                elif val == 3: next_grid[r, c - 1] = 3
                elif val == 4: next_grid[r, c + 1] = 4
        grid = next_grid

        for r in range(size - 1):
            for c in range(size - 1):
                if (r + c) % 2 == current_parity:
                    dr = r - n + 1
                    dc = c - n + 1
                    if abs(dr) + abs(dc) < k:
                        if grid[r, c] == 0 and grid[r+1, c] == 0 and \
                           grid[r, c+1] == 0 and grid[r+1, c+1] == 0:

                            if random.random() < 0.5:
                                grid[r, c] = 1;     grid[r, c+1] = 1     # 上にNorth
                                grid[r+1, c] = 2;   grid[r+1, c+1] = 2   # 下にSouth
                            else:
                                grid[r, c] = 3;     grid[r+1, c] = 3     # 左にWest
                                grid[r, c+1] = 4;   grid[r+1, c+1] = 4   # 右にEast

    return grid

def plot_aztec_diamond(n):
    grid = generate_aztec_diamond(n)

    # 0:白, 1:赤, 2:青, 3:緑, 4:黄
    cmap = ListedColormap(['#e1e9ee', '#FF595E', '#1982C4', '#8AC926', '#FFCA3A'])

    plt.figure(figsize=(8, 8), facecolor='#e1e9ee')
    plt.imshow(grid, cmap=cmap, interpolation='nearest')
    plt.axis('off')
    plt.show()

plot_aztec_diamond(100)

非交差経路

タイリング + 色付け + 経路描画

用途: 北極圏定理の視覚化

計算量: nをアステカダイヤモンドの次数として、 $O(n^3)$ です。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
import random
import matplotlib.patheffects as pe

def generate_aztec_diamond(n):
    size = 2 * n
    grid = np.zeros((size, size), dtype=int)

    for k in range(1, n + 1):
        current_parity = (k - 1) % 2

        for r in range(size - 1):
            for c in range(size - 1):
                if (r + c) % 2 == current_parity:
                    if grid[r, c] == 2 and grid[r, c+1] == 2 and \
                       grid[r+1, c] == 1 and grid[r+1, c+1] == 1:
                        grid[r, c] = 0; grid[r, c+1] = 0
                        grid[r+1, c] = 0; grid[r+1, c+1] = 0
                    elif grid[r, c] == 4 and grid[r+1, c] == 4 and \
                         grid[r, c+1] == 3 and grid[r+1, c+1] == 3:
                        grid[r, c] = 0; grid[r+1, c] = 0
                        grid[r, c+1] = 0; grid[r+1, c+1] = 0

        next_grid = np.zeros((size, size), dtype=int)
        for r in range(size):
            for c in range(size):
                val = grid[r, c]
                if val == 1:   next_grid[r - 1, c] = 1
                elif val == 2: next_grid[r + 1, c] = 2
                elif val == 3: next_grid[r, c - 1] = 3
                elif val == 4: next_grid[r, c + 1] = 4
        grid = next_grid

        for r in range(size - 1):
            for c in range(size - 1):
                if (r + c) % 2 == current_parity:
                    dr = r - n + 1
                    dc = c - n + 1
                    if abs(dr) + abs(dc) < k:
                        if grid[r, c] == 0 and grid[r+1, c] == 0 and \
                           grid[r, c+1] == 0 and grid[r+1, c+1] == 0:
                            if random.random() < 0.5:
                                grid[r, c] = 1;     grid[r, c+1] = 1
                                grid[r+1, c] = 2;   grid[r+1, c+1] = 2
                            else:
                                grid[r, c] = 3;     grid[r+1, c] = 3
                                grid[r, c+1] = 4;   grid[r+1, c+1] = 4

    return grid

def get_non_intersecting_paths(grid, n):
    size = 2 * n
    paths = []

    vertical_halves = np.zeros((size, size), dtype=int)
    for c in range(size):
        r = 0
        while r < size:
            if grid[r, c] == 3:
                vertical_halves[r, c] = 1      # 上半分
                vertical_halves[r+1, c] = -1   # 下半分
                r += 2
            else:
                r += 1

        r = 0
        while r < size:
            if grid[r, c] == 4:
                vertical_halves[r, c] = 1      # 上半分
                vertical_halves[r+1, c] = -1   # 下半分
                r += 2
            else:
                r += 1

    for i in range(n):
        r = n + i
        c = i

        path_x = [c - 0.5]
        path_y = [r]

        while c < size:
            if r < 0 or r >= size:
                break

            val = grid[r, c]
            if val == 0:
                break

            elif val == 1 or val == 2:
                path_x.append(c + 1.5)
                path_y.append(r)
                c += 2

            elif val == 3 or val == 4:
                half = vertical_halves[r, c]
                if half == -1:
                    path_x.append(c + 0.5)
                    path_y.append(r - 1)
                    r -= 1
                    c += 1
                elif half == 1:
                    path_x.append(c + 0.5)
                    path_y.append(r + 1)
                    r += 1
                    c += 1
                else:
                    break

        paths.append((path_x, path_y))

    return paths

def plot_aztec_diamond_with_paths(n):
    grid = generate_aztec_diamond(n)
    paths = get_non_intersecting_paths(grid, n)

    # 0:白, 1:赤, 2:青, 3:緑, 4:黄
    cmap = ListedColormap(['#e1e9ee', '#FF595E', '#1982C4', '#8AC926', '#FFCA3A'])

    plt.figure(figsize=(10, 10), facecolor='#e1e9ee')

    plt.imshow(grid, cmap=cmap, interpolation='nearest')

    for path_x, path_y in paths:
        plt.plot(path_x, path_y,
                 color='#111111',
                 linewidth=1.8,
                 alpha=1.0,
                 path_effects=[pe.Stroke(linewidth=3.5, foreground='white'), pe.Normal()])
    plt.axis('off')
    plt.tight_layout()
    plt.show()

plot_aztec_diamond_with_paths(25)

出力結果

1つ目のコードの実行結果
図1.1つ目のコードの実行結果"
2つ目のコードの実行結果
図1.2つ目のコードの実行結果"