# `lsys`
Create and visualize lindenmayer systems.
<p align="center">
<a href="https://github.com/austinorr/lsys/actions" target="_blank">
<img src="https://github.com/austinorr/lsys/actions/workflows/test.yml/badge.svg?branch=master" alt="Build Status">
</a>
<a href="https://codecov.io/gh/austinorr/lsys" target="_blank">
<img src="https://codecov.io/gh/austinorr/lsys/branch/master/graph/badge.svg" alt="Coverage">
</a>
</p>
## Getting Started
`lsys` is a library for creating Lindenmayer systems inspired by Flake's **The Computational Beauty of Nature**.
The graphics in that book are extraordinary, and this little tool helps make similar graphics with matplotlib.
From the text, an L-system consists of a special seed, an axiom, from which the fractal growth follows according to certain production rules.
For example, if 'F' is move foward and "+-" are left and right, we can make the well-known Dragon curve using the following axiom and production rules:
```python
import matplotlib.pyplot as plt
import lsys
from lsys import Lsys, Fractal
axiom = "FX"
rule = {"X": "X+YF+", "Y": "-FX-Y"}
dragon = Lsys(axiom=axiom, rule=rule, ignore="XY")
for depth in range(4):
dragon.depth = depth
print(depth, dragon.string)
```
0 FX
1 FX+YF+
2 FX+YF++-FX-YF+
3 FX+YF++-FX-YF++-FX+YF+--FX-YF+
Note how the production rules expand on the axiom, expanding it at each depth according to the characters in the string.
If we interpret the string as a turtle graphics instruction set and move forward each time we see 'F' and left or right each time we see '-' or '+' we can visualize the curve.
```python
dragon.depth = 3
_ = dragon.plot(lw=5)
```
![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_5_0.png)
```python
dragon.depth = 12
_ = dragon.plot(lw=1)
```
![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_6_0.png)
The `Lsys` object exposes multiple options for interacting with the results of the L-system expansion, including the xy coordinates, depths of each segment, and even functions for forming bezier curves to transition between vertices of the fractal.
This allows for easier visulaization of the path that the fractal takes when the vertices of the expansion start to overlap.
For the Dragon curve, this can lead to some satisfying results.
```python
dragon.depth = 4
fig, axes = plt.subplots(1, 2, figsize=(6, 3))
_ = dragon.plot(ax=axes[0], lw=5, c="k", square=True)
_ = dragon.plot(ax=axes[1], lw=5, square=True, as_bezier=True)
```
![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_8_0.png)
```python
dragon.depth = 12
_ = dragon.plot(lw=1, as_bezier=True)
```
![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_9_0.png)
It's also possible to use a colormap to show the path.
The most efficient way to do this in `matplotlib` uses the `PathCollection` with each segment as a cubic bezier curve.
By default, the curves are approximately circular, but the weight of the control points can be adjusted.
```python
dragon.depth = 4
fig, axes = plt.subplots(1, 4, figsize=(12, 5))
for ax, weight in zip(axes, [0.3, None, 0.8, 1.5]):
_ = dragon.plot_bezier(ax=ax, bezier_weight=weight, lw=3, square=True)
```
![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_11_0.png)
The bezier functionality also allows for applying a color map, which is useful for uncovering how the path unfolds, especially for large depths of the fractal
```python
fig, axes = plt.subplots(1, 2, figsize=(6, 3))
for ax, depth in zip(axes, [4, 13]):
dragon.depth = depth
_ = dragon.plot_bezier(ax=ax, lw=1.5, square=True, cmap="viridis")
```
![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_13_0.png)
```python
hilbert = Lsys(**Fractal["Hilbert"])
fig, axes = plt.subplots(1, 2, figsize=(6, 3))
for ax, depth in zip(axes, [2, 7]):
hilbert.depth = depth
_ = hilbert.plot_bezier(ax=ax, lw=1, square=True, cmap="viridis")
```
![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_14_0.png)
The plotting features allow for a fast and deep rendering, as well as a slower rendering algorithm that allows the user to choose the number of bezier segments per segment in the line collection.
This feature allows for either high fidelity (many segments) color rendering of the smooth bezier path, or low fidelity
```python
dragon.depth = 4
fig, axes = plt.subplots(1, 5, figsize=(15, 3))
# Default renderer for bezier, peak bezier rendering performance for colormapped renderings, noticably
# low color fidelity per curve at low fractal depths
_ = dragon.plot_bezier(ax=axes[0], lw=10, square=True, cmap="magma")
# line collection with custom n-segments, slower rendering due to many lines, customizably
# high or low color fidelity per curve
_ = dragon.plot_bezier(
ax=axes[1], lw=10, square=True, cmap="magma", segs=10, as_lc=True
)
_ = dragon.plot_bezier(ax=axes[2], lw=10, square=True, cmap="magma", segs=1, as_lc=True)
# High rendering performance, but rendered as single path with a single color.
# This is the default render if `segs` is not None and `as_lc` is not set True (default is False)
_ = dragon.plot_bezier(ax=axes[3], lw=10, square=True, segs=10, c="C2")
_ = dragon.plot_bezier(ax=axes[4], lw=10, square=True, segs=1, c="C0")
```
![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_16_0.png)
## Exploring Other Fractals
```python
Serpinski_Maze = {
"name": "Serpinski Maze",
"axiom": "F",
"rule": "F=[-G+++F][-G+F][GG--F],G=GG",
"da": 60,
"a0": 0,
"ds": 0.5,
"depth": 4,
}
_ = Lsys(**Serpinski_Maze).plot()
```
![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_18_0.png)
```python
def build_computational_beauty_of_nature_plot(lsystem: Lsys, depths=None, **fig_kwargs):
if depths is None:
depths = [0, 1, 4]
assert len(depths) == 3, "`depths` must be length 3"
fig_kwargs_default = dict(
figsize=(9, 3.5),
gridspec_kw={"wspace": 0, "hspace": 0.01, "height_ratios": [1, 10]},
)
fig_kwargs_default.update(fig_kwargs)
lsystem.depth = depths[-1]
xlim, ylim = lsys.viz.get_coord_lims(lsystem.coords, pad=5, square=True)
fig, axes = plt.subplot_mosaic([[1, 1, 1], [2, 3, 4]], **fig_kwargs_default)
for i, (l, ax) in enumerate(axes.items()):
ax.set_xticks([])
ax.set_yticks([])
plot_text = (
f"{lsystem.name} "
r"$\bf{Angle:}$ "
f"{lsystem.da} "
r"$\bf{Axiom:}$ "
r"$\it{" + lsystem.axiom + "}$ "
r"$\bf{Rule(s):}$ "
r"$\it{" + lsystem.rule + "}$ "
)
axes[1].text(
0.01,
0.5,
plot_text,
math_fontfamily="dejavuserif",
fontfamily="serif",
va="center",
size=8,
)
plot_axes = [axes[i] for i in [2, 3, 4]]
for ax, depth in zip(plot_axes, depths):
lsystem.depth = depth
lsystem.plot(ax=ax, lw=0.5, c="k")
ax.set_xlim(xlim)
ax.set_ylim(ylim)
_ = ax.set_aspect("equal")
return fig, axes
```
```python
_ = build_computational_beauty_of_nature_plot(
lsystem=Lsys(**Serpinski_Maze),
depths=[0, 1, 7],
)
```
![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_20_0.png)
## Additional Rendering Options
The `lsys` library has a few rendering helpers, like one to build up custom color maps.
Here is one of my favorites:
```python
dragon.depth = 6
cmap = lsys.viz.make_colormap(
[
"midnightblue",
"blue",
"cyan",
"lawngreen",
"yellow",
"orange",
"red",
"firebrick",
]
)
_ = dragon.plot(lw=5, square=True, as_lc=True, cmap=cmap)
```
![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_23_0.png)
This colormap helper can also assist with non-hideous abuses of colormaps, like when rendering a tree-like fractal.
```python
Fractal["Tree2"]
```
{'depth': 4,
'axiom': 'F',
'rule': 'F = |[5+F][7-F]-|[4+F][6-F]-|[3+F][5-F]-|F',
'da': 8,
'a0': 82,
'ds': 0.65}
```python
tree = Lsys(**Fractal["Tree2"])
tree.depth = 5
_ = tree.plot(c="k", lw=0.3)
```
![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_26_0.png)
We can add some color by creating a colormap that transitions from browns to greens.
```python
cmap = lsys.viz.make_colormap(
["saddlebrown", "saddlebrown", "sienna", "darkgreen", "yellowgreen"]
)
_ = tree.plot(as_lc=True, cmap=cmap)
```
![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_28_0.png)
This has rendered each of our line segments in the order that the string expansion of the axiom and rules defined.
It's interesting to see when each part of the tree appears in the linear order of the string expansion, but it's not really tree-like and it's not yet 'non-hideous'.
We can do better.
The `Lsys` objects store an array of the depth of each line segment.
This depth changes when the string expansion algorithm encounters a push character ("[") or a pop character ("]").
Not every fractal has push and pop characters, but for those that do, the depth array can be useful for rendering.
```python
cmap = lsys.viz.make_colormap(
["saddlebrown", "saddlebrown", "sienna", "darkgreen", "yellowgreen"]
)
_ = tree.plot(as_lc=True, array=tree.depths, cmap=cmap)
```
![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_30_0.png)
This is somewhat closer to the intention.
Now the colors are mapped correctly to each segments fractal depth and trunk/stem segments are brown while branch and leaf segments are green.
Even still, we can do better.
If we render each depth in separate line collections and in order of depth rather than in order of the string expansion, we can improve our tree-like rendering.
```python
import numpy
from matplotlib.collections import LineCollection
```
```python
tree = Lsys(**Fractal["Tree2"])
for d in range(5):
tree.depth = d
print(set(tree.depths))
```
{0}
{1}
{1, 2}
{1, 2, 3}
{1, 2, 3, 4}
_*Sidenote:*_ The string expansion rules for this fractal nuke the first depth (0th) on the first expansion with the "|[" character combo.
We'll account for this when we render things.
```python
tree = Lsys(**Fractal["Tree2"])
tree.depth = 5
fig, ax = plt.subplots(figsize=(7, 7))
cmap = lsys.viz.make_colormap(
["saddlebrown", "saddlebrown", "sienna", "darkgreen", "yellowgreen"]
)
_ = lsys.viz.pretty_format_ax(ax=ax, coords=tree.coords)
for depth in range(tree.depth):
# each depth will have a single value for color, lineweight, and alpha.
color = cmap((depth + 1) / tree.depth)
lw = 10 / (depth + 2)
alpha = 0.5 if depth + 2 >= tree.depth else 1
lc = LineCollection(
tree.coords[tree.depths == (depth + 1)],
color=color,
lw=lw,
alpha=alpha,
capstyle="round",
)
ax.add_collection(lc)
```
![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_35_0.png)
## Rendering Sequences
It can be fun to see how each of these fractals evolve, so here are a few examples of watching how the dragon fractal 'winds' itself up.
```python
d = Lsys(**Fractal["Dragon"])
d.a0 = 0
depths = range(12)
rows = int(numpy.ceil(len(depths) / 4))
fig_width = 12
fig_height = int(fig_width / 4 * rows)
fig, axes = plt.subplots(rows, 4, figsize=(fig_width, fig_height))
for ax, depth in zip(axes.flatten(), depths):
d.depth = depth
ax = d.plot_bezier(ax=ax, lw=3, square=True, cmap="viridis", segs=10)
```
![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_37_0.png)
Sequences like this lend themselves nicely to creating animations.
Here's one showing another way this fractal 'winds' in on itself.
For this one to work, we've got to do some math to scale each plot and change the start angle for each depth.
```python
from matplotlib import animation
from matplotlib import rc
rc("animation", html="html5")
```
```python
d = Lsys(**Fractal["Dragon"])
# The difference between depth 0 and depth 1 shows where the sqrt(2) comes from
# as the line shifts into a right triangle.
d.ds = 1 / numpy.sqrt(2)
# start with bearing to the right and find all bearings for our depths
# by adding 45 deg to the start bearing for each depth
d.a0 = 0
depths = list(range(12))
a0s = [d.a0 + 45 * i for i in depths]
fig, ax = plt.subplots(figsize=(6, 6))
# set axes lims to enclose the final wound up dragon using a helper function
# that takes the coordinates of the fractal.
d.depth = depths[-1]
d.a0 = a0s[-1]
ax = lsys.viz.pretty_format_ax(ax, coords=d.coords, pad=10, square=True)
frames = []
for i in depths:
d.depth = i
d.a0 = a0s[i]
# helper function makes the bezier paths for us given the fractal
# coordinates and the interior angle to span with the bezier curve.
paths = lsys.viz.construct_bezier_path_collection(
d.coords, angle=d.da, keep_ends=True
)
pc = ax.add_collection(paths)
frames.append([pc])
anim = animation.ArtistAnimation(fig, frames, blit=True, interval=500)
plt.close()
```
![Animated L-System Dragon Sequence](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/Animation.gif)
## Built-in L-System Fractals
Though you may definately define your L-Systems, and are encouraged to do so, there are a number of them provided by `lsys.Fractal` for convenience.
```python
fractals = sorted(Fractal.keys())
rows = len(fractals)
fig, axes = plt.subplots(rows, 4, figsize=(12, 3 * rows))
depths = [0, 1, 2, 4]
for i, fractal in enumerate(fractals):
f = Lsys(**Fractal[fractal])
f.unoise = 0 # This is an exciting paramter that you are encouraged to explore.
for j, (ax, depth) in enumerate(zip(axes[i].flatten(), depths)):
f.depth = depth
ax = f.plot(ax=ax, as_lc=True, color="k", lw=0.5, square=True)
name = f"{fractal} [depth={depth}]" if j == 0 else f"depth={depth}"
ax.set_title(name)
```
![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_43_0.png)
Raw data
{
"_id": null,
"home_page": "https://github.com/austinorr/lsys",
"name": "lsys",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": "",
"keywords": "l-systems,lindenmayer,fractal",
"author": "Austin Orr",
"author_email": "austinmartinorr@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/65/89/2277833205204b8e42ad434489fe1d3ace8de7b9dadc5b818347ba2cb8ce/lsys-0.2.0.tar.gz",
"platform": null,
"description": "# `lsys`\n\nCreate and visualize lindenmayer systems.\n\n\n<p align=\"center\">\n <a href=\"https://github.com/austinorr/lsys/actions\" target=\"_blank\">\n <img src=\"https://github.com/austinorr/lsys/actions/workflows/test.yml/badge.svg?branch=master\" alt=\"Build Status\">\n </a>\n <a href=\"https://codecov.io/gh/austinorr/lsys\" target=\"_blank\">\n <img src=\"https://codecov.io/gh/austinorr/lsys/branch/master/graph/badge.svg\" alt=\"Coverage\">\n </a>\n</p>\n\n## Getting Started\n\n`lsys` is a library for creating Lindenmayer systems inspired by Flake's **The Computational Beauty of Nature**.\nThe graphics in that book are extraordinary, and this little tool helps make similar graphics with matplotlib.\n\nFrom the text, an L-system consists of a special seed, an axiom, from which the fractal growth follows according to certain production rules.\nFor example, if 'F' is move foward and \"+-\" are left and right, we can make the well-known Dragon curve using the following axiom and production rules:\n\n\n\n```python\nimport matplotlib.pyplot as plt\n\nimport lsys\nfrom lsys import Lsys, Fractal\n\n\naxiom = \"FX\"\nrule = {\"X\": \"X+YF+\", \"Y\": \"-FX-Y\"}\n\ndragon = Lsys(axiom=axiom, rule=rule, ignore=\"XY\")\n\nfor depth in range(4):\n dragon.depth = depth\n print(depth, dragon.string)\n\n```\n\n 0 FX\n 1 FX+YF+\n 2 FX+YF++-FX-YF+\n 3 FX+YF++-FX-YF++-FX+YF+--FX-YF+\n \n\nNote how the production rules expand on the axiom, expanding it at each depth according to the characters in the string.\nIf we interpret the string as a turtle graphics instruction set and move forward each time we see 'F' and left or right each time we see '-' or '+' we can visualize the curve.\n\n\n\n```python\ndragon.depth = 3\n_ = dragon.plot(lw=5)\n\n```\n\n\n \n![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_5_0.png)\n \n\n\n\n```python\ndragon.depth = 12\n_ = dragon.plot(lw=1)\n\n```\n\n\n \n![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_6_0.png)\n \n\n\nThe `Lsys` object exposes multiple options for interacting with the results of the L-system expansion, including the xy coordinates, depths of each segment, and even functions for forming bezier curves to transition between vertices of the fractal.\nThis allows for easier visulaization of the path that the fractal takes when the vertices of the expansion start to overlap.\nFor the Dragon curve, this can lead to some satisfying results.\n\n\n\n```python\ndragon.depth = 4\n\nfig, axes = plt.subplots(1, 2, figsize=(6, 3))\n\n_ = dragon.plot(ax=axes[0], lw=5, c=\"k\", square=True)\n_ = dragon.plot(ax=axes[1], lw=5, square=True, as_bezier=True)\n\n```\n\n\n \n![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_8_0.png)\n \n\n\n\n```python\ndragon.depth = 12\n_ = dragon.plot(lw=1, as_bezier=True)\n\n```\n\n\n \n![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_9_0.png)\n \n\n\nIt's also possible to use a colormap to show the path.\nThe most efficient way to do this in `matplotlib` uses the `PathCollection` with each segment as a cubic bezier curve.\nBy default, the curves are approximately circular, but the weight of the control points can be adjusted.\n\n\n\n```python\ndragon.depth = 4\nfig, axes = plt.subplots(1, 4, figsize=(12, 5))\n\nfor ax, weight in zip(axes, [0.3, None, 0.8, 1.5]):\n _ = dragon.plot_bezier(ax=ax, bezier_weight=weight, lw=3, square=True)\n\n```\n\n\n \n![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_11_0.png)\n \n\n\nThe bezier functionality also allows for applying a color map, which is useful for uncovering how the path unfolds, especially for large depths of the fractal\n\n\n\n```python\nfig, axes = plt.subplots(1, 2, figsize=(6, 3))\n\nfor ax, depth in zip(axes, [4, 13]):\n dragon.depth = depth\n _ = dragon.plot_bezier(ax=ax, lw=1.5, square=True, cmap=\"viridis\")\n\n```\n\n\n \n![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_13_0.png)\n \n\n\n\n```python\nhilbert = Lsys(**Fractal[\"Hilbert\"])\nfig, axes = plt.subplots(1, 2, figsize=(6, 3))\n\nfor ax, depth in zip(axes, [2, 7]):\n hilbert.depth = depth\n _ = hilbert.plot_bezier(ax=ax, lw=1, square=True, cmap=\"viridis\")\n\n```\n\n\n \n![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_14_0.png)\n \n\n\nThe plotting features allow for a fast and deep rendering, as well as a slower rendering algorithm that allows the user to choose the number of bezier segments per segment in the line collection.\nThis feature allows for either high fidelity (many segments) color rendering of the smooth bezier path, or low fidelity\n\n\n\n```python\ndragon.depth = 4\n\nfig, axes = plt.subplots(1, 5, figsize=(15, 3))\n\n# Default renderer for bezier, peak bezier rendering performance for colormapped renderings, noticably\n# low color fidelity per curve at low fractal depths\n_ = dragon.plot_bezier(ax=axes[0], lw=10, square=True, cmap=\"magma\")\n\n# line collection with custom n-segments, slower rendering due to many lines, customizably\n# high or low color fidelity per curve\n_ = dragon.plot_bezier(\n ax=axes[1], lw=10, square=True, cmap=\"magma\", segs=10, as_lc=True\n)\n_ = dragon.plot_bezier(ax=axes[2], lw=10, square=True, cmap=\"magma\", segs=1, as_lc=True)\n\n# High rendering performance, but rendered as single path with a single color.\n# This is the default render if `segs` is not None and `as_lc` is not set True (default is False)\n_ = dragon.plot_bezier(ax=axes[3], lw=10, square=True, segs=10, c=\"C2\")\n_ = dragon.plot_bezier(ax=axes[4], lw=10, square=True, segs=1, c=\"C0\")\n\n```\n\n\n \n![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_16_0.png)\n \n\n\n## Exploring Other Fractals\n\n\n\n```python\nSerpinski_Maze = {\n \"name\": \"Serpinski Maze\",\n \"axiom\": \"F\",\n \"rule\": \"F=[-G+++F][-G+F][GG--F],G=GG\",\n \"da\": 60,\n \"a0\": 0,\n \"ds\": 0.5,\n \"depth\": 4,\n}\n\n_ = Lsys(**Serpinski_Maze).plot()\n\n```\n\n\n \n![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_18_0.png)\n \n\n\n\n```python\ndef build_computational_beauty_of_nature_plot(lsystem: Lsys, depths=None, **fig_kwargs):\n\n if depths is None:\n depths = [0, 1, 4]\n\n assert len(depths) == 3, \"`depths` must be length 3\"\n\n fig_kwargs_default = dict(\n figsize=(9, 3.5),\n gridspec_kw={\"wspace\": 0, \"hspace\": 0.01, \"height_ratios\": [1, 10]},\n )\n\n fig_kwargs_default.update(fig_kwargs)\n\n lsystem.depth = depths[-1]\n xlim, ylim = lsys.viz.get_coord_lims(lsystem.coords, pad=5, square=True)\n\n fig, axes = plt.subplot_mosaic([[1, 1, 1], [2, 3, 4]], **fig_kwargs_default)\n\n for i, (l, ax) in enumerate(axes.items()):\n ax.set_xticks([])\n ax.set_yticks([])\n\n plot_text = (\n f\"{lsystem.name} \"\n r\"$\\bf{Angle:}$ \"\n f\"{lsystem.da} \"\n r\"$\\bf{Axiom:}$ \"\n r\"$\\it{\" + lsystem.axiom + \"}$ \"\n r\"$\\bf{Rule(s):}$ \"\n r\"$\\it{\" + lsystem.rule + \"}$ \"\n )\n\n axes[1].text(\n 0.01,\n 0.5,\n plot_text,\n math_fontfamily=\"dejavuserif\",\n fontfamily=\"serif\",\n va=\"center\",\n size=8,\n )\n\n plot_axes = [axes[i] for i in [2, 3, 4]]\n\n for ax, depth in zip(plot_axes, depths):\n lsystem.depth = depth\n lsystem.plot(ax=ax, lw=0.5, c=\"k\")\n\n ax.set_xlim(xlim)\n ax.set_ylim(ylim)\n _ = ax.set_aspect(\"equal\")\n\n return fig, axes\n\n```\n\n\n```python\n_ = build_computational_beauty_of_nature_plot(\n lsystem=Lsys(**Serpinski_Maze),\n depths=[0, 1, 7],\n)\n\n```\n\n\n \n![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_20_0.png)\n \n\n\n## Additional Rendering Options\n\n\nThe `lsys` library has a few rendering helpers, like one to build up custom color maps.\nHere is one of my favorites:\n\n\n\n```python\ndragon.depth = 6\ncmap = lsys.viz.make_colormap(\n [\n \"midnightblue\",\n \"blue\",\n \"cyan\",\n \"lawngreen\",\n \"yellow\",\n \"orange\",\n \"red\",\n \"firebrick\",\n ]\n)\n_ = dragon.plot(lw=5, square=True, as_lc=True, cmap=cmap)\n\n```\n\n\n \n![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_23_0.png)\n \n\n\nThis colormap helper can also assist with non-hideous abuses of colormaps, like when rendering a tree-like fractal.\n\n\n\n```python\nFractal[\"Tree2\"]\n\n```\n\n\n\n\n {'depth': 4,\n 'axiom': 'F',\n 'rule': 'F = |[5+F][7-F]-|[4+F][6-F]-|[3+F][5-F]-|F',\n 'da': 8,\n 'a0': 82,\n 'ds': 0.65}\n\n\n\n\n```python\ntree = Lsys(**Fractal[\"Tree2\"])\ntree.depth = 5\n_ = tree.plot(c=\"k\", lw=0.3)\n\n```\n\n\n \n![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_26_0.png)\n \n\n\nWe can add some color by creating a colormap that transitions from browns to greens.\n\n\n\n```python\ncmap = lsys.viz.make_colormap(\n [\"saddlebrown\", \"saddlebrown\", \"sienna\", \"darkgreen\", \"yellowgreen\"]\n)\n_ = tree.plot(as_lc=True, cmap=cmap)\n\n```\n\n\n \n![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_28_0.png)\n \n\n\nThis has rendered each of our line segments in the order that the string expansion of the axiom and rules defined.\nIt's interesting to see when each part of the tree appears in the linear order of the string expansion, but it's not really tree-like and it's not yet 'non-hideous'.\nWe can do better.\n\nThe `Lsys` objects store an array of the depth of each line segment.\nThis depth changes when the string expansion algorithm encounters a push character (\"[\") or a pop character (\"]\").\nNot every fractal has push and pop characters, but for those that do, the depth array can be useful for rendering.\n\n\n\n```python\ncmap = lsys.viz.make_colormap(\n [\"saddlebrown\", \"saddlebrown\", \"sienna\", \"darkgreen\", \"yellowgreen\"]\n)\n_ = tree.plot(as_lc=True, array=tree.depths, cmap=cmap)\n\n```\n\n\n \n![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_30_0.png)\n \n\n\nThis is somewhat closer to the intention.\nNow the colors are mapped correctly to each segments fractal depth and trunk/stem segments are brown while branch and leaf segments are green.\nEven still, we can do better.\n\nIf we render each depth in separate line collections and in order of depth rather than in order of the string expansion, we can improve our tree-like rendering.\n\n\n\n```python\nimport numpy\nfrom matplotlib.collections import LineCollection\n\n```\n\n\n```python\ntree = Lsys(**Fractal[\"Tree2\"])\n\nfor d in range(5):\n tree.depth = d\n print(set(tree.depths))\n\n```\n\n {0}\n {1}\n {1, 2}\n {1, 2, 3}\n {1, 2, 3, 4}\n \n\n_*Sidenote:*_ The string expansion rules for this fractal nuke the first depth (0th) on the first expansion with the \"|[\" character combo.\nWe'll account for this when we render things.\n\n\n\n```python\ntree = Lsys(**Fractal[\"Tree2\"])\ntree.depth = 5\n\nfig, ax = plt.subplots(figsize=(7, 7))\ncmap = lsys.viz.make_colormap(\n [\"saddlebrown\", \"saddlebrown\", \"sienna\", \"darkgreen\", \"yellowgreen\"]\n)\n_ = lsys.viz.pretty_format_ax(ax=ax, coords=tree.coords)\n\nfor depth in range(tree.depth):\n # each depth will have a single value for color, lineweight, and alpha.\n color = cmap((depth + 1) / tree.depth)\n lw = 10 / (depth + 2)\n alpha = 0.5 if depth + 2 >= tree.depth else 1\n\n lc = LineCollection(\n tree.coords[tree.depths == (depth + 1)],\n color=color,\n lw=lw,\n alpha=alpha,\n capstyle=\"round\",\n )\n\n ax.add_collection(lc)\n\n```\n\n\n \n![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_35_0.png)\n \n\n\n## Rendering Sequences\n\nIt can be fun to see how each of these fractals evolve, so here are a few examples of watching how the dragon fractal 'winds' itself up.\n\n\n\n```python\nd = Lsys(**Fractal[\"Dragon\"])\nd.a0 = 0\ndepths = range(12)\nrows = int(numpy.ceil(len(depths) / 4))\nfig_width = 12\nfig_height = int(fig_width / 4 * rows)\nfig, axes = plt.subplots(rows, 4, figsize=(fig_width, fig_height))\n\nfor ax, depth in zip(axes.flatten(), depths):\n d.depth = depth\n ax = d.plot_bezier(ax=ax, lw=3, square=True, cmap=\"viridis\", segs=10)\n\n```\n\n\n \n![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_37_0.png)\n \n\n\nSequences like this lend themselves nicely to creating animations.\nHere's one showing another way this fractal 'winds' in on itself.\nFor this one to work, we've got to do some math to scale each plot and change the start angle for each depth.\n\n\n\n```python\nfrom matplotlib import animation\nfrom matplotlib import rc\n\nrc(\"animation\", html=\"html5\")\n\n```\n\n\n```python\nd = Lsys(**Fractal[\"Dragon\"])\n# The difference between depth 0 and depth 1 shows where the sqrt(2) comes from\n# as the line shifts into a right triangle.\nd.ds = 1 / numpy.sqrt(2)\n\n# start with bearing to the right and find all bearings for our depths\n# by adding 45 deg to the start bearing for each depth\nd.a0 = 0\ndepths = list(range(12))\na0s = [d.a0 + 45 * i for i in depths]\n\nfig, ax = plt.subplots(figsize=(6, 6))\n\n# set axes lims to enclose the final wound up dragon using a helper function\n# that takes the coordinates of the fractal.\nd.depth = depths[-1]\nd.a0 = a0s[-1]\nax = lsys.viz.pretty_format_ax(ax, coords=d.coords, pad=10, square=True)\n\nframes = []\nfor i in depths:\n d.depth = i\n d.a0 = a0s[i]\n\n # helper function makes the bezier paths for us given the fractal\n # coordinates and the interior angle to span with the bezier curve.\n paths = lsys.viz.construct_bezier_path_collection(\n d.coords, angle=d.da, keep_ends=True\n )\n\n pc = ax.add_collection(paths)\n\n frames.append([pc])\n\nanim = animation.ArtistAnimation(fig, frames, blit=True, interval=500)\nplt.close()\n\n```\n\n![Animated L-System Dragon Sequence](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/Animation.gif)\n\n\n## Built-in L-System Fractals\n\nThough you may definately define your L-Systems, and are encouraged to do so, there are a number of them provided by `lsys.Fractal` for convenience.\n\n\n\n```python\nfractals = sorted(Fractal.keys())\nrows = len(fractals)\nfig, axes = plt.subplots(rows, 4, figsize=(12, 3 * rows))\ndepths = [0, 1, 2, 4]\n\nfor i, fractal in enumerate(fractals):\n f = Lsys(**Fractal[fractal])\n f.unoise = 0 # This is an exciting paramter that you are encouraged to explore.\n for j, (ax, depth) in enumerate(zip(axes[i].flatten(), depths)):\n f.depth = depth\n ax = f.plot(ax=ax, as_lc=True, color=\"k\", lw=0.5, square=True)\n name = f\"{fractal} [depth={depth}]\" if j == 0 else f\"depth={depth}\"\n ax.set_title(name)\n\n```\n\n\n \n![png](https://raw.githubusercontent.com/austinorr/lsys/master/docs/_all_docs_source/readme/readme_files/readme_43_0.png)\n \n\n",
"bugtrack_url": null,
"license": "BSD license",
"summary": "Create and visualize Lindenmayer systems",
"version": "0.2.0",
"split_keywords": [
"l-systems",
"lindenmayer",
"fractal"
],
"urls": [
{
"comment_text": "",
"digests": {
"md5": "b1018d06e45a45a98a1213b17d674d72",
"sha256": "9dc8497a6dafef313d5d744f86f92df319ea9e572957a173b134d22191d05889"
},
"downloads": -1,
"filename": "lsys-0.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "b1018d06e45a45a98a1213b17d674d72",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 28673,
"upload_time": "2022-12-02T01:58:44",
"upload_time_iso_8601": "2022-12-02T01:58:44.827477Z",
"url": "https://files.pythonhosted.org/packages/66/05/55ce92259e49839b9edf903494f7b209393ed98e169e440ad778d1a43e21/lsys-0.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "d8a89dc9e2046cf2c4adba36e74ccbe6",
"sha256": "351e483719ab0fc3cf7cc638b6ba9521ad153d889f9c6e75b46e70077ed1b812"
},
"downloads": -1,
"filename": "lsys-0.2.0.tar.gz",
"has_sig": false,
"md5_digest": "d8a89dc9e2046cf2c4adba36e74ccbe6",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 2495543,
"upload_time": "2022-12-02T01:58:47",
"upload_time_iso_8601": "2022-12-02T01:58:47.258807Z",
"url": "https://files.pythonhosted.org/packages/65/89/2277833205204b8e42ad434489fe1d3ace8de7b9dadc5b818347ba2cb8ce/lsys-0.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2022-12-02 01:58:47",
"github": true,
"gitlab": false,
"bitbucket": false,
"github_user": "austinorr",
"github_project": "lsys",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"lcname": "lsys"
}