Last active
June 19, 2022 10:04
-
-
Save bbbradsmith/2408dc2a2fcf68f9390b206f2ac46ba5 to your computer and use it in GitHub Desktop.
Example renderings of the chaotic Circle Map. - Details: https://www.patreon.com/posts/24826459
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# | |
# circlemap.py | |
# Brad Smith, 2019 | |
# http://rainwarrior.ca | |
# | |
# Example renderings of the chaotic Circle Map. | |
# http://mathworld.wolfram.com/CircleMap.html | |
import sys | |
assert sys.version_info[0] == 3, "Python 3 required." | |
import math | |
import PIL.Image | |
def lerp(a,b,t): | |
return a + (b-a)*t | |
def circle_map(x, o, k): | |
return (x + o - k * math.sin(2.0 * math.pi * x) / (2.0 * math.pi)) % 1.0 | |
def circle_map_row(o,k,width,iters): | |
# accumulated brightness everywhere the iteration lands | |
row = [0] * (width + 1) # +1 deals with occasional rounding error overflow | |
for xi in range(width): | |
x = xi / width | |
for i in range(iters): | |
x = circle_map(x, o, k) | |
row[int(x * width)] += 1 | |
row[0] += row[width] # recover the overflow | |
return row[0:width] | |
def circle_map_rows(o,k0,k1,width,height,iters): | |
rows = [] | |
for y in range(height): | |
k = lerp(k0,k1,y/height) | |
rows.append(circle_map_row(o,k,width,iters)) | |
return rows | |
def inv_circle_map_row(x,k,width,iters): | |
# counts iterations to recurrence for each value of o | |
row = [0] * width | |
for oi in range(width): | |
o = oi / width | |
xi = int(x*width) | |
i = 0 | |
while i < iters: | |
x = circle_map(x,o,k) | |
if int(x*width) == xi: | |
break | |
i += 1 | |
row[oi] = i | |
return row[0:width] | |
def inv_circle_map_rows(x,k0,k1,width,height,iters): | |
rows = [] | |
for y in range(height): | |
k = lerp(k0,k1,y/height) | |
rows.append(inv_circle_map_row(x,k,width,iters)) | |
return rows | |
def power_rows(rows,power): | |
# adjust scale of values exponentially (better visual appearance) | |
for y in range(len(rows)): | |
for x in range(len(rows[y])): | |
rows[y][x] = math.pow(rows[y][x],power) | |
def image_rows(rows,invert=False): | |
# find range of values | |
vmin = rows[0][0] | |
vmax = rows[0][0] | |
for y in range(len(rows)): | |
for x in range(len(rows[y])): | |
v = rows[y][x] | |
vmin = min(v,vmin) | |
vmax = max(v,vmax) | |
if invert: | |
(vmax,vmin) = (vmin,vmax) | |
vrange = vmax-vmin | |
# create image | |
img = PIL.Image.new("L",(len(rows[0]),len(rows))) | |
pixels = img.load() | |
for y in range(len(rows)): | |
for x in range(len(rows[y])): | |
p = int(((rows[y][x] - vmin)/vrange)*255) | |
pixels[x,y] = p | |
return img | |
# main | |
dims = (360,640) | |
# regular circle map, tracking accumulated travels of iterations | |
# this will take a few seconds | |
rows = circle_map_rows(0,0,4*math.pi,dims[0],dims[1],10) | |
power_rows(rows,0.4) | |
img = image_rows(rows) | |
img.save("circlemap.png") | |
print("saved: circlemap.png") | |
# "inverted" circle map, measuring recurrence lengths | |
# this requires more iterations to resolve and will take much longer | |
rows = inv_circle_map_rows(0,0,4*math.pi,dims[0],dims[1],100) | |
power_rows(rows,0.5) | |
img = image_rows(rows,True) | |
img.save("inv_circlemap.png") | |
print("saved: inv_circlemap.png") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment