Skip to content

Instantly share code, notes, and snippets.

@bbbradsmith
Last active June 19, 2022 10:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bbbradsmith/2408dc2a2fcf68f9390b206f2ac46ba5 to your computer and use it in GitHub Desktop.
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
#!/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