Show code cell content
import mmf_setup; mmf_setup.nbinit()
import os
from pathlib import Path
FIG_DIR = Path(mmf_setup.ROOT) / '../Docs/_build/figures/'
os.makedirs(FIG_DIR, exist_ok=True)
import logging; logging.getLogger("matplotlib").setLevel(logging.CRITICAL)
%matplotlib inline
import numpy as np, matplotlib.pyplot as plt
import mpld3
# mpld3.enable_notebook() # Makes loading really slow - big?
try: from myst_nb import glue
except: glue = None
This cell adds /home/docs/checkouts/readthedocs.org/user_builds/iscimath-583-fractals/checkouts/latest/src to your path, and contains some definitions for equations and some CSS for styling the notebook. If things look a bit strange, please try the following:
- Choose "Trust Notebook" from the "File" menu.
- Re-execute this cell.
- Reload the notebook.
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[1], line 9
7 get_ipython().run_line_magic('matplotlib', 'inline')
8 import numpy as np, matplotlib.pyplot as plt
----> 9 import mpld3
10 # mpld3.enable_notebook() # Makes loading really slow - big?
11 try: from myst_nb import glue
ModuleNotFoundError: No module named 'mpld3'
L-Systems#
Here we implement the L-system (named after Lindenmayer) discussed at the end of
Chapter 11 in [Schroeder, 1991]. These are reminiscent of the turtle language
Logo, and are expressed as strings of characters F, f, +, -, and subroutines
denoted by [/] pairs. We generalize the description there slightly to allow the use
of multiple letters (not just f and F). Capital letters will draw, while lowercase
letters will only move.
def walk(prog, z0=0, dz0=1.0, delta=90):
z = z0 + 0j # Just in case z0 is mutable.
dz = dz0 + 0j
turn = np.exp(delta*1j/180*np.pi)
paths = []
path = [z]
stack = []
for char in prog:
if char == '[':
stack.append((z, dz))
elif char == ']':
if not stack:
raise ValueError("Unbalanced ']'")
z, dz = stack.pop()
if len(path) > 1:
paths.append(path)
path = [z]
elif char == '+':
dz *= turn
elif char == '-':
dz /= turn
else:
z += dz
if char == char.upper():
# Upper characters draw
path.append(z)
else:
# Lower case move
if len(path) > 1:
paths.append(path)
path = [z]
if len(path) > 1:
paths.append(path)
return z, dz, paths
class Lindenmayer:
z0 = 0 # Initial position
dz0 = 1.0 # Initial direction
d = 1.0 # Walk distance
delta = 90 # Turn angle (degrees)
axiom = 'F' # Initial string
# Rules: capital letters draw, lowercase letters move.
rules = dict(
f='f',
F='F'
)
N = 1 # Levels
def __init__(self, **kw):
for key in kw:
if not hasattr(self, key):
raise ValueError(f"Unknown {key=}")
setattr(self, key, kw[key])
self.init()
def init(self):
s = self.axiom
for n in range(self.N):
s = ''.join([self.rules.get(c, c) for c in s])
self.s = s
z, dz, self.paths = walk(s, z0=self.z0, dz0=self.dz0, delta=self.delta)
def draw(self, ax=None, ls='-', c='k', **kw):
if ax is None:
ax = plt.gca()
ax.set(aspect=1);
for path in self.paths:
zs = np.asarray(path)
ax.plot(zs.real, zs.imag, ls=ls, c=c, **kw)
return ax
# Figure 12 from Chapter 11 of Schroeder:1991
ax = Lindenmayer(
d=2,
delta=90,
axiom='F-F-F-F',
rules=dict(f='ffffff',
F='F-f+FF-F-FF-Ff-FF+f-FF+F+FF+Ff+FFF'),
N=2).draw()
# Figure 14 from Chapter 11 of Schroeder:1991
ax = Lindenmayer(
dz0=1j,
d=4,
delta=-22.5,
axiom='F',
rules=dict(F='FF+[+F-F-F]-[-F+F+F]'),
N=5).draw(lw=0.3)
# Koch snowflake
ax = Lindenmayer(
d=1,
delta=60,
axiom='F--F--F',
rules=dict(f='f',
F='F+F--F+F'),
N=10).draw(lw=0.3)
# Sirpinski's gasket
ax = Lindenmayer(
d=1,
delta=120,
axiom='F+F+F',
rules=dict(f='ff', F='F+f-F-f+F'),
N=5).draw()
# Sirpinski's gasket 2
ax = Lindenmayer(
d=1,
delta=60,
axiom='A',
rules=dict(A='B+A+B', B='A-B-A'),
N=7).draw()
# Dragon curve
ax = Lindenmayer(
d=1,
delta=90,
#axiom='F+F',
axiom='F',
rules=dict(F='F+G',
G='F-G'),
N=10).draw()
# Gosper curve (flowsnake)
ax = Lindenmayer(
d=1,
delta=60,
axiom='A',
rules=dict(A='A-B--B+A++AA+B-',
B='+A-BB--B-A++A+B'),
N=4).draw(lw=0.3)
ax.set(title="Can you find the path to the middle?");