Pixels2GenAI
Path i Foundations
M 04 · 4.3.1 · hybrid

4.3.1 Plant Generation (L-Systems)

Lindenmayer systems — a string-rewriting grammar interpreted as turtle commands. One axiom, one rule, four iterations: a fractal plant. Same paradigm spans algae, ferns, and entire forests.

Duration22–28 min
Levelintermediate
Load3 core concepts
Prereqs4.1.2 (turtle graphics interpreter), 4.2.1 (fractal trees / branching)

Overview

In 1968, biologist Aristid Lindenmayer needed a formalism to describe how multicellular organisms develop, cell by cell, from a single founding cell [1]. The result — Lindenmayer systems, or L-systems — is a parallel string-rewriting grammar in which every symbol is rewritten simultaneously according to a fixed set of production rules. Iterate the rules; the string grows. Interpret each symbol as a turtle command; the string draws.

The architecture is the same separation we have seen since the dragon curve (4.1.2): generation (the string) and interpretation (the turtle) are independent, and you can swap either one without touching the other. What is genuinely new in this lesson is that the production rules can branch. Two extra symbols — [ and ] — push and pop the turtle’s state on a stack. That tiny stack-machine addition is what lets a one-dimensional string describe a two-dimensional branching structure. With it, the same algorithm draws ferns, weeds, bushes, trees, and entire shrubbery families. Prusinkiewicz and Lindenmayer’s The Algorithmic Beauty of Plants (1990) showed that real botanical species can be reproduced from L-systems with just a handful of rules each [2].

Learning objectives

  1. Implement parallel string rewriting: iterate an axiom + production-rule set $n$ times.
  2. Implement a turtle interpreter with a push/pop stack for the bracket symbols [ and ].
  3. Design rules to produce different plant shapes (simple weed vs. fern vs. branching bush) by tuning the rule set and the branching angle.
  4. Recognise L-systems as the string-encoded IFS dialect — the same paradigm as the chaos game (4.1.5) and fractal trees (4.2.1), expressed as a grammar.

Quick start — a weed in four iterations

python · quick_start.py
import math
from PIL import Image, ImageDraw

AXIOM = "F"
RULES = {"F": "F[+F]F[-F]F"}
ITERATIONS = 4
ANGLE = 25.7

# Apply rules ITERATIONS times — every F becomes the rule's expansion
s = AXIOM
for _ in range(ITERATIONS):
    s = "".join(RULES.get(c, c) for c in s)

# Turtle interpretation
img = Image.new("RGB", (800, 600), (10, 20, 30))
draw = ImageDraw.Draw(img)
x, y, angle = 400, 550, -math.pi / 2          # start centre-bottom, facing up
stack = []
step = 5

for symbol in s:
    if symbol == "F":
        nx = x + step * math.cos(angle)
        ny = y + step * math.sin(angle)
        draw.line([(x, y), (nx, ny)], fill=(100, 180, 100))
        x, y = nx, ny
    elif symbol == "+": angle -= math.radians(ANGLE)
    elif symbol == "-": angle += math.radians(ANGLE)
    elif symbol == "[": stack.append((x, y, angle))
    elif symbol == "]": x, y, angle = stack.pop()

img.save("plant_basic.png")
A fractal plant generated by an L-system, with a vertical green stem branching outward at regular intervals against a dark background
Fig. 1 Iteration 4 of `F[+F]F[-F]F` — a recognisable weed. The single rule produces alternating left/right side branches at every recursion level.

Core concepts

Concept 1 — Parallel string rewriting

An L-system has three components:

  • Alphabet — the set of symbols the strings can contain (here: F, +, -, [, ]).
  • Axiom — the starting string. Often a single symbol like "F".
  • Production rules — a map from one symbol to a replacement string. F → F[+F]F[-F]F means “every F in the string becomes this longer expansion.”

At each iteration, every symbol in the current string is replaced simultaneously. Symbols that have no rule keep themselves. That parallelism — every cell rewrites at the same time — is what makes L-systems biologically motivated: a developing organism has many cells all dividing in step, not one at a time.

Concretely:

python · rewrite.py
def apply_rules(axiom, rules, iterations):
    s = axiom
    for _ in range(iterations):
        s = "".join(rules.get(c, c) for c in s)
    return s

"".join(...) is Python’s idiom for building a string from a generator: faster than += in a loop because it allocates once and avoids the quadratic copying that += causes on long strings.

Concept 2 — Turtle interpretation with a stack

The string by itself is just symbols. To draw, walk through it once and dispatch each symbol to a turtle command:

SymbolAction
Fmove forward one step, drawing a line
+rotate left by ANGLE
-rotate right by ANGLE
[push the current (x, y, angle) onto a stack
]pop the stack back into (x, y, angle)

The bracket symbols are what enable branching. When the interpreter hits [, the turtle’s current state is saved. The turtle then wanders off — drawing a branch, turning, drawing again. When it hits ], it teleports back to the saved position and continues with the next symbol in the trunk. A stack is the right data structure because branches nest: a side branch can itself have side branches, and pushing/popping handles arbitrary depth.

python · turtle.py
for symbol in s:
    if symbol == "F":
        nx = x + step * math.cos(angle)
        ny = y + step * math.sin(angle)
        draw.line([(x, y), (nx, ny)], fill=(100, 180, 100))
        x, y = nx, ny
    elif symbol == "+":   angle -= math.radians(ANGLE)
    elif symbol == "-":   angle += math.radians(ANGLE)
    elif symbol == "[":   stack.append((x, y, angle))
    elif symbol == "]":   x, y, angle = stack.pop()
A diagram showing the L-system rewriting and turtle interpretation process — left side shows the axiom transforming through three iterations into a longer string, right side shows the turtle drawing branching segments
Fig. 2 The two-stage architecture of L-systems: parallel rewriting builds the string, turtle interpretation walks it. The two halves are independent.

The same turtle interpreter draws any L-system; the same rewriter handles any rule set. Once you have both, generating a new species is just writing a new rule.

Concept 3 — Designing rules

The vocabulary of L-system rules grew naturally from describing real plants. A few classical patterns:

  • F → F[+F]F[-F]F — alternating left/right side branches, simple weed silhouette. The default in plant_basic.png.
  • X → F+[[X]-X]-F[-FX]+X, F → FF — Prusinkiewicz’s “fern” rule [2]. Uses two symbols: X controls branching structure (X is invisible to the turtle), and F controls line drawing (which doubles in length each iteration). Hugely more organic.
  • F → FF+[+F-F-F]-[-F+F+F] — symmetric three-branch bush, fills out more like an azalea.
A grid showing the same L-system plant at iterations one, two, three, and four — the first iteration is a stick figure Y shape, the fourth is a dense bush
Fig. 3 Iteration sweep of `F[+F]F[-F]F`: 1, 2, 3, 4. Each iteration replaces every F with the rule expansion; complexity doubles fast.

The branching angle ANGLE is the second design parameter. With the same rule:

  • 15° → narrow, columnar plant (cypress, conifer).
  • 25.7° → natural-looking branching (the value used in the quick-start is the so-called “golden angle minus the angle of nature”).
  • 45° → wide, spreading branches (oak).
  • 90° → geometric, cross-like patterns — visually arresting but not botanical.

Exercises

Three exercises in Execute → Modify → Create order: run the simple plant, sweep angles and rules, then design a fern from scratch.

EXECUTE I.

Grow the basic plant

Run plant_lsystem.py to render F[+F]F[-F]F at iteration 4.

plant_lsystem.py — reference implementation

Reflection questions

  • After 4 iterations the string has 1,561 characters. Where does that number come from?
  • Why is the ] symbol useless without a corresponding [ somewhere earlier? What happens if you forget to push before popping?
  • Walking the turtle through the same instruction string twice would produce two identical-looking plants. What changes if you swap the order of two iterations — i.e. interpret the iteration-3 string with the iteration-4 rule? (Hint: this question is a trap.)
MODIFY II.

Sweep angle and rule

Edit plant_lsystem.py to produce three plant variants. Keep iterations = 4 throughout for fair comparison.

Goals

  1. Narrow conifer. Change ANGLE to 12°. Same rule, same iterations.
  2. Bushy oak. Change ANGLE to 45° with the rule F → FF+[+F-F-F]-[-F+F+F]. This rule has three sub-branches per node.
  3. Triple fork. Use the rule F → F[+F][-F]F. Each F becomes three sub-Fs: one straight, one left, one right.
CREATE III.

Design a fern

Implement a fern using two-symbol L-systems. The trick: introduce a non-drawing symbol X that controls the branching topology, and let F handle the actual line drawing. The rules:

X → F+[[X]-X]-F[-FX]+X
F → FF

X is invisible to the turtle — the interpreter just skips it. But because every X expands to a long string containing more Xs, the structure of the fern is encoded in X’s rule. Meanwhile, F → FF doubles every drawn segment each iteration, so the stems grow proportionally as the structure refines.

python · exercise3_starter.py
import math
from PIL import Image, ImageDraw

AXIOM = "X"
RULES = {
    # TODO 1: define the X and F rules above
}
ANGLE = 25
ITERATIONS = 5
STEP = 3

def apply_rules(axiom, rules, n):
    s = axiom
    for _ in range(n):
        s = "".join(rules.get(c, c) for c in s)
    return s

def draw_lsystem(s, angle_deg, step, size):
    img = Image.new("RGB", size, (10, 20, 30))
    draw = ImageDraw.Draw(img)
    x, y, angle = size[0] // 4, size[1] - 40, -math.pi / 2
    stack = []
    for c in s:
        # TODO 2: handle F, +, -, [, ] correctly
        # Remember: X is silent.
        pass
    return img

s = apply_rules(AXIOM, RULES, ITERATIONS)
draw_lsystem(s, ANGLE, STEP, (800, 600)).save("my_fern.png")

Make it your own

  • Depth-based colour. Track the current stack depth in the interpreter. Set the line colour to a brown → green gradient based on depth: thick brown stems near the trunk fading to bright green twigs at the tips.
  • Stochastic L-system. Pick from multiple rule expansions per symbol with weighted probability. F could expand to one of F[+F]F, F[-F]F, or FF with probabilities 0.4/0.4/0.2. Plants come out asymmetric and organic — closer to real botany.
  • Animate growth. Render each iteration’s instruction string separately, save them as a sequence, and compose into a GIF. The plant visibly grows from a seed to a fern over 5–6 frames.

Downloads

plant_lsystem.py — reference implementation

Summary

Common pitfalls to avoid

  • Sequential rewriting. Walking through the string and replacing one symbol at a time produces wrong results because earlier replacements interfere with later ones. Build the new string from the old one as a whole with a list comprehension or "".join(generator).
  • Stack underflow. Rules must produce balanced [ and ] pairs. Unbalanced brackets crash the interpreter mid-string.
  • Too many iterations. Every iteration multiplies string length by roughly the rule’s expansion factor. 6 iterations of an “F → FF[+F][-F]F” rule produces ~16,000-character strings; 7 produces ~80,000; 8 a million. The plant doesn’t get visibly more detailed past 5–6 iterations on a 800×600 canvas — stop before things slow down.
  • Step size and canvas mismatch. Long iterations need shorter step sizes. If the plant extends off-canvas, halve STEP instead of cropping.

References

  1. [1] Lindenmayer, A. (1968). Mathematical models for cellular interactions in development I. Filaments with one-sided inputs. Journal of Theoretical Biology, 18(3), 280–299. doi:10.1016/0022-5193(68)90079-9
  2. [2] Prusinkiewicz, P., & Lindenmayer, A. (1990). The Algorithmic Beauty of Plants. Springer-Verlag. ISBN 978-0-387-97297-8.
  3. [3] Rozenberg, G., & Salomaa, A. (1980). The Mathematical Theory of L Systems. Academic Press. ISBN 978-0-12-597140-8.
  4. [4] Smith, A. R. (1984). Plants, fractals, and formal languages. ACM SIGGRAPH Computer Graphics, 18(3), 1–10. doi:10.1145/964965.808571
  5. [5] Abelson, H., & diSessa, A. A. (1986). Turtle Geometry: The Computer as a Medium for Exploring Mathematics. MIT Press. ISBN 978-0-262-51037-0.
  6. [6] Prusinkiewicz, P., & Runions, A. (2012). Computational models of plant development and form. New Phytologist, 193(3), 549–569. doi:10.1111/j.1469-8137.2011.04009.x
  7. [7] Mandelbrot, B. B. (1982). The Fractal Geometry of Nature. W. H. Freeman. ISBN 978-0-7167-1186-5.