Gravner-Griffeath snowflake simulation.
For better results increase the image size and number of growth steps and prepare to wait!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170  | # Gravner-Griffeath Snowflake Simulation
# http://psoup.math.wisc.edu/Snowfakes.htm
# "A typical simulated crystal reaches a final diameter of 400-600 cells
# over 10,000-100,000 updates (growth steps)..."
# FB36 - 20130526
import math; import random
from PIL import Image
imgx = 300; imgy = 300 # image size
imgx1 = imgx - 1; imgy1 = imgy - 1
image = Image.new("RGB", (imgx, imgy))
pixels = image.load()
ver = "deterministic" # random.choice(["deterministic", "randomized"])
print "Version: " + ver
maxIt = 3000 # 10000 - 100000 # of growth steps
p = 0.58 # random.random() * 0.6 + 0.3 # rho: homogeneous vapor density
k = random.random() * 0.05 # kappa
b = 2.0 # random.random() * 1.95 + 1.05 # beta
a = random.random() * 0.3 # alpha
t = random.random() * 0.5595 + 0.02 # theta
m = random.random() * 0.01 # mu
g = 0.0000515 # gamma
s = random.random() * -0.5 # sigma
print "Parameters:"
print "rho = ", p
print "kappa = ", k
print "beta = ", b
print "alpha = ", a
print "theta = ", t
print "mu = ", m
print "gamma = ", g
print "sigma = ", s
print
mx = imgx; my = imgy # width and height of 2DCA
dx = [-1, 0, -1, 1, 0, 1]; dy = [-1, -1, 0, 0, 1, 1] # 6 directions to grow
# set initial state
# ice cells belong to crystal
at = [[[0 for x in range(mx)] for y in range(my)] for z in range(2)]
# boundary masses of cells
bt = [[[0.0 for x in range(mx)] for y in range(my)] for z in range(2)]
# crystal masses of cells
ct = [[[0.0 for x in range(mx)] for y in range(my)] for z in range(2)]
# diffusive masses of cells
dt = [[[p for x in range(mx)] for y in range(my)] for z in range(2)]
# set ice seed
ox = (mx - 1) / 2; oy = (my - 1) / 2
at[0][oy][ox] = 1
ct[0][oy][ox] = 1.0
dt[0][oy][ox] = 0.0
def isBoundary(x, y):
    global dx, dy, at, za
    flag = False
    if at[za][y][x] == 0:
        for j in range(6): # neighbors
            jx = x + dx[j]; jy = y + dy[j]
            if jx >= 0 and jx < mx and jy >= 0 and jy < my:
                if at[za][jy][jx] == 1: flag = True; break
    return flag
def numIce(x, y): # of ice neighbors of boundary cell
    global dx, dy, at, za
    ni = 0
    if at[za][y][x] == 0:
        for j in range(6): # neighbors
            jx = x + dx[j]; jy = y + dy[j]
            if jx >= 0 and jx < mx and jy >= 0 and jy < my:
                if at[za][jy][jx] == 1: ni += 1
    return ni
# total diffusive mass
def difMass(x, y):
    global dx, dy, dt, zd
    wsum = dt[zd][y][x]
    for j in range(6): # neighbors
        jx = x + dx[j]; jy = y + dy[j]
        if jx >= 0 and jx < mx and jy >= 0 and jy < my:
            wsum += dt[zd][jy][jx]
    return wsum
za = 0; wa = 1
zb = 0; wb = 1
zc = 0; wc = 1
zd = 0; wd = 1
for i in range(maxIt): # growth steps
    print "Growth Step: " + str(i + 1) + " of " + str(maxIt)
    # step 1: diffusion
    for iy in range(my):
        for ix in range(mx):
            if not(at[za][iy][ix] == 1 or isBoundary(ix, iy)):
                dt[wd][iy][ix] = difMass(ix, iy) / 7.0
            elif isBoundary(ix, iy):
                wsum = dt[zd][iy][ix]
                for j in range(6): # neighbors
                    jx = ix + dx[j]; jy = iy + dy[j]
                    if jx >= 0 and jx < mx and jy >= 0 and jy < my:
                        if at[za][jy][jx] == 1:
                            wsum += dt[zd][iy][ix]
                        else:
                            wsum += dt[zd][jy][jx]                            
                dt[wd][iy][ix] = wsum / 7.0                
    zd = 1 - zd; wd = 1 - wd # switch planes
    # step 2: freezing
    for iy in range(my):
        for ix in range(mx):
            if isBoundary(ix, iy):
                bt[wb][iy][ix] = bt[zb][iy][ix] + (1.0 - k) * dt[zd][iy][ix]
                ct[wc][iy][ix] = ct[zc][iy][ix] + k * dt[zd][iy][ix]
                dt[wd][iy][ix] = 0.0    
    zb = 1 - zb; wb = 1 - wb # switch planes
    zc = 1 - zc; wc = 1 - wc # switch planes
    zd = 1 - zd; wd = 1 - wd # switch planes
    # step 3: attachment
    for iy in range(my):
        for ix in range(mx):
            nIce = numIce(ix, iy)
            if nIce > 0:
                if (nIce == 1 or nIce == 2) and bt[zb][iy][ix] >= b:
                    at[wa][iy][ix] = 1
                if nIce == 3:
                    if bt[zb][iy][ix] >= 1.0:
                        at[wa][iy][ix] = 1
                    elif bt[zb][iy][ix] >= a:
                        if difMass(ix, iy) < t: at[wa][iy][ix] = 1                        
                if nIce >= 4: at[wa][iy][ix] = 1
                if at[wa][iy][ix] == 1:
                    ct[wc][iy][ix] = bt[zb][iy][ix] + ct[zc][iy][ix];
                    bt[zb][iy][ix] = 0.0    
    za = 1 - za; wa = 1 - wa # switch planes
    zb = 1 - zb; wb = 1 - wb # switch planes
    zc = 1 - zc; wc = 1 - wc # switch planes
    # step 4: melting
    for iy in range(my):
        for ix in range(mx):
            if isBoundary(ix, iy):
                bt[wb][iy][ix] = bt[zb][iy][ix] * (1.0 - m)
                ct[wc][iy][ix] = ct[zc][iy][ix] * (1.0 - g)
                dt[wd][iy][ix] = dt[zd][iy][ix] + bt[zb][iy][ix] * m + ct[zc][iy][ix] * g    
    zb = 1 - zb; wb = 1 - wb # switch planes
    zc = 1 - zc; wc = 1 - wc # switch planes
    zd = 1 - zd; wd = 1 - wd # switch planes
    # step 5: noise
    if ver == "randomized":
        for iy in range(my):
            for ix in range(mx):
                if at[wa][iy][ix] == 0: # if not ice
                    dt[wd][iy][ix] = dt[zd][iy][ix] * (1.0 + s * random.choice([1, -1]))    
    zd = 1 - zd; wd = 1 - wd # switch planes
# paint final state of the snowflake
an45 = - math.pi / 4.0
sn45 = math.sin(an45); cs45 = math.cos(an45)
scale = math.sqrt(3.0); ox = imgx1 / 2.0; oy = imgy1 / 2.0
for ky in range(imgy):
    for kx in range(imgx):
        # apply geometric transformation (scaling and rotation)
        tx = kx - ox; ty = (ky - oy) * scale
        tx0 = tx * cs45 - ty * sn45 + ox
        ty = tx * sn45 + ty * cs45 + oy; tx = tx0
        if tx >= 0 and tx <= imgx1 and ty >= 0 and ty <= imgy1:
            c = at[wa][int((my - 1) * ty / imgy1)][int((mx - 1) * tx / imgx1)]
            pixels[kx, ky] = (c * 255, c * 255, c * 255)
image.save("Gravner-Griffeath_Snowfake_Simulation.png", "PNG")
 | 
Download
Copy to clipboard