Belousov-Zhabotinsky sun catcher
Belousov-Zhabotinsky sun catcher
Generated in a Python script with a hexagonal neighborhood and 120° symmetry, die-cut in two colors of acetate, and embedded in clear resin. I realized after making a similar one that I could get three colored bands by cutting filters for adjacent bands and using the combination as the third color. This makes it possible to cut the filters at a higher level of detail. This is an 86mm disk and would be quite fragile otherwise and hard to remove from the cutting mat without breaking it. I made a small mistake and left islands in the red filter. I will add bridges for them when I make another. Here you can see them casting a colored shadow. It does that in resin as well, but with less clarity.
Re: Belousov-Zhabotinsky sun catcher
Do you have .rle 's for any of your Patterns ?
They must look great as colorful C. A. 's ...
Thank You !
They must look great as colorful C. A. 's ...
Thank You !
"One picture is worth 1000 words; but one thousand words, carefully crafted, can paint an infinite number of pictures."
- autonomic writing
forFUN : http://gol.jct.onl
ArtGallery : http://cgolart.onfav.net
VideoWS : http://conway.life
- autonomic writing
forFUN : http://gol.jct.onl
ArtGallery : http://cgolart.onfav.net
VideoWS : http://conway.life
Re: Belousov-Zhabotinsky sun catcher
I generated them with floating point calculations on a 2D array so they're inherently dense. The same code could generate a series of images to animate, I think, but I never tried. I'll see if I can find it and post it.
Re: Belousov-Zhabotinsky sun catcher
Here's what I was able come up with. First, the Python code, which is mostly taken from elsewhere (with acknowledgments).
Code: Select all
import math
import numpy as np
from scipy.ndimage import convolve
from sys import argv
from PIL import Image
# Simulation method modified from https://scipython.com/blog/simulating-the-belousov-zhabotinsky-reaction/
# Side length of base rhombus (stored as an nXn grid)
n = 200 if len(argv) < 2 else int(argv[1])
# Value for all Belousov-Zhabotinsky reaction coefficients.
param = 1 if len(argv) < 3 else float(argv[2])
# Number of interations to run.
niter = 500 if len(argv) < 4 else int(argv[3])
# Reaction parameters.
alpha, beta, gamma = param, param, param
# Shape of hexagonal neighborhood out to radius 3.
FILTER_STR = '''
1111000
1111100
1111110
1111111
0111111
0011111
0001111
'''
FILTER_INT = [[int(c) for c in row] for row in FILTER_STR.split()]
FILTER = np.array(FILTER_INT)/sum(sum(row) for row in FILTER_INT)
def update(p,arr):
"""Update arr[p] to arr[q] by evolving in time."""
# Count the average amount of each species in the neighborhood around each cell
# by convolution with FILTER
q = (p+1) % 2
s = np.zeros((3, arr.shape[2], arr.shape[3]))
for k in range(3):
s[k] = convolve(arr[p,k], FILTER, mode='wrap')
# Apply the reaction equations
arr[q,0] = s[0] + s[0]*(alpha*s[1] - gamma*s[2])
arr[q,1] = s[1] + s[1]*(beta*s[2] - alpha*s[0])
arr[q,2] = s[2] + s[2]*(gamma*s[0] - beta*s[1])
# Ensure the species concentrations are kept within [0,1].
np.clip(arr[q], 0, 1, arr[q])
return arr
def equivalence(i, j, n):
"""Find the equivalent coordinates in the base rhombus by rotating through symmetries."""
rot = 0
while True:
if ((i // n) + (j //n)) % 3 == 0:
return i % n, j % n
i, j = -j - 1, i - j
rot += 1
# Initialize the base rhombus with random amounts of A, B and C.
data = np.random.random(size=(2, 3, n, n))
# Build a 3nX3n patch based on 120-degree rhombic packing.
# This can be tiled with simple toroidal wrapping at the boundaries.
arr = np.zeros((2, 3, n * 3, n * 3))
for i in range(arr.shape[2]):
for j in range(arr.shape[2]):
ti, tj = equivalence(i, j, n)
arr[:, :, i, j] = data[:, :, ti, tj]
# run the iteration
for i in range(0, niter):
arr = update(i % 2, arr)
# Create an image from just the top nXn square, equivalent to the base rhombus
disp = np.concatenate((arr[(i + 1) % 2].transpose(2, 1, 0)[:n, :n, :], np.full((n, n, 1), 1, dtype=np.uint8)), axis=2)
img = Image.fromarray((disp * 255).astype(np.uint8), mode='RGBA')
w, h = img.size
img = img.transform((int(w * 3), int(h * math.sqrt(3))),
Image.Transform.AFFINE, (0.5, 0.5 / math.sqrt(3), -0.5 * w, 0, 1 / math.sqrt(3), 0),
resample=Image.Resampling.BICUBIC)
img.save('/Users/calla/Downloads/zb3a.png')
img.show()
You can generate a series of images like this to animate and I believe it will show the reaction dynamically. I was only interested in an image to use as a suncatcher so I never tried that.
Re: Belousov-Zhabotinsky sun catcher
This is not a suncatcher yet. Here is a 6cm 3D-print of a symmetric Belousov-Zhabotinsky pattern. It captures the intensity as a height map but does not vary in color.
I could print one or maybe something larger, mold it and cast it in resin (maybe smoothed out or maybe with the ridges to see what the optical effect is like). With a little work I could probably figure out a way to mask out the three colors. I am not sure I am that ambitious.
Here is the intensity map it's based on The code hasn't changed much and you can use GIMP or similar to separate and recombine the colors.
Here is the intensity map it's based on The code hasn't changed much and you can use GIMP or similar to separate and recombine the colors.
Re: Belousov-Zhabotinsky sun catcher
Printable for anyone interested: https://www.printables.com/model/175446 ... ative-disk
I have plans to make this more general using tiles and other runs of the pattern.
I have plans to make this more general using tiles and other runs of the pattern.
Re: Belousov-Zhabotinsky sun catcher
I'm still working on this, but I can now set boundary conditions for 60-120 rhombus that tile both rotated and flipped. The continuity is sometimes a little off but on the whole the effect is believable. I will make some 3d-printed snap tiles when I get a chance.
Re: Belousov-Zhabotinsky sun catcher
Seamless Belousov-Zhabotinsky rhombus tiles. They tile both rotated and flipped and the boundary conditions were tricky to get right. Still a work in progress but moving along. The continuity on boundaries could be better and there might be way. I've also learned a little about how these oscillations actually work over time (nothing new I'm sure) and might post something in a real CA thread if I have any interesting observations. One thing worth doing other than physical tiles is making a looping animation.
Re: Belousov-Zhabotinsky sun catcher
Resin cast of symmetric periodic hexagon.
PLA original embedded as a coaster.
Re: Belousov-Zhabotinsky sun catcher
pcallahan wrote: August 1st, 2023, 4:51 pm Generated in a Python script with a hexagonal neighborhood and 120° symmetry, die-cut in two colors of acetate, and embedded in clear resin. 20230801_113853.jpg I realized after making a similar one that I could get three colored bands by cutting filters for adjacent bands and using the combination as the third color. This makes it possible to cut the filters at a higher level of detail. This is an 86mm disk and would be quite fragile otherwise and hard to remove from the cutting mat without breaking it. I made a small mistake and left islands in the red filter. I will add bridges for them when I make another. 20230731_093949.jpg Here you can see them casting a colored shadow. It does that in resin as well, but with less clarity. 20230731_093721.jpg
Re: Belousov-Zhabotinsky sun catcher
This won't be catching any sun but it's a different aesthetic. It is cast in AC730, a stone-like material, and I painted the grooves for contrast. Maybe I should make some cellular automata patterns like this. Either single generations of CGOL or rows of Rule 110.