fractals.barnsley_fern

The Barnsley fern is a fractal that resembles the black spleenwort fern. It was described by the British mathematician Michael Barnsley in his 1988 book Fractals Everywhere and is a classic example of an iterated function system (IFS).

An IFS builds a fractal by repeatedly applying a small set of affine transformations, each chosen at random with a fixed probability. Starting from the point (0, 0) the fern uses four transformations:

Transformation

Effect

Probability

Stem

collapse onto the y-axis

1%

Successive leaf

the main self-similar copy of the fern

85%

Left leaflet

a smaller rotated/reflected copy

7%

Right leaflet

another smaller rotated/reflected copy

7%

Because the whole picture is produced by chance the doctests below seed Python’s random generator so that the results are reproducible. Plotting the points with matplotlib is optional and only happens when the module is run directly.

Reference: https://en.wikipedia.org/wiki/Barnsley_fern

Attributes

CUMULATIVE_PROBABILITIES

TRANSFORMATIONS

fern_points

Functions

choose_transformation(→ int)

Map a value sample from [0, 1) to a transformation index using the

generate_fern(→ list[tuple[float, float]])

Generate iterations points of the Barnsley fern, starting at (0, 0).

transform(→ tuple[float, float])

Apply the affine transformation index to point and return the image.

Module Contents

fractals.barnsley_fern.choose_transformation(sample: float) int

Map a value sample from [0, 1) to a transformation index using the cumulative probabilities of the fern.

>>> choose_transformation(0.0)
0
>>> choose_transformation(0.5)
1
>>> choose_transformation(0.9)
2
>>> choose_transformation(0.97)
3
fractals.barnsley_fern.generate_fern(iterations: int, seed: int | None = None) list[tuple[float, float]]

Generate iterations points of the Barnsley fern, starting at (0, 0).

Passing a seed makes the (otherwise random) output reproducible, which is what keeps the doctests deterministic.

>>> points = generate_fern(5, seed=0)
>>> len(points)
5
>>> points[0]
(0.0, 0.0)
>>> points
[(0.0, 0.0), (0.0, 1.6), (0.064, 2.96),
 (0.1728, 4.11344), (0.3114176, 5.089512)]

Every fern point lives inside the well known bounding box.

>>> cloud = generate_fern(2000, seed=42)
>>> all(-2.182 <= x <= 2.6558 for x, _ in cloud)
True
>>> all(0.0 <= y <= 9.9984 for _, y in cloud)
True
>>> generate_fern(0)
Traceback (most recent call last):
    ...
ValueError: iterations must be positive, got 0
fractals.barnsley_fern.transform(point: tuple[float, float], index: int) tuple[float, float]

Apply the affine transformation index to point and return the image.

>>> transform((0.0, 0.0), 0)
(0.0, 0.0)
>>> transform((1.0, 1.0), 1)
(0.89, 2.41)
>>> transform((2.0, 3.0), 3)
(0.54, 1.68)
>>> transform((0.0, 0.0), 4)
Traceback (most recent call last):
    ...
IndexError: index must be in range 0..3, got 4
fractals.barnsley_fern.CUMULATIVE_PROBABILITIES: tuple[float, ...] = (0.01, 0.86, 0.93, 1.0)
fractals.barnsley_fern.TRANSFORMATIONS: tuple[tuple[float, float, float, float, float, float], ...]
fractals.barnsley_fern.fern_points