The Barnsley Fern: Ferns Seen as Fractals (not only as plants)
Ferns are beautiful plants that exhibit a self-similar structure: the entire plant is similar to a part of itself. This property makes them interesting from a mathematical point of view. The fern pattern can be described as a fractal that can be mathematically generated, thus being reproducible at any scale. We can zoom-in indefinitely and never run out of ferns.
The generation of a fern fractal might seem like a complicated task, but in fact it is quite simple. It conforms to the simplicity of the fractal geometry: cranking the same formula over and over again.
In this visualization guide, we will learn how the generation of a fern is mathematically defined coupled with a Python implementation. Then we will see how to utilize this fern generation system to produce and plot different types of ferns using Matplotlib.
Definition
The British mathematician Michael Barnsley was the first to describe and formalize the fern fractal. For this reason the fern fractal is called the Barnsley Fern.
The fern fractal can be generated using four instances (different sets of coefficients) of the following affine transformation:
\[f\left(x_{n+1}, y_{n+1}\right) = \begin{bmatrix} a & b \\ c & d \end{bmatrix} * \begin{bmatrix} x_{n} \\ y_{n} \end{bmatrix} + \begin{bmatrix} e \\ f \end{bmatrix}\]s.t. each instance is having a certain probability of being chosen. The original Barnsley Fern is generated using the following coefficients and probabilities:
w | a | b | c | d | e | f | p |
---|---|---|---|---|---|---|---|
f1 | 0 | 0 | 0 | 0.16 | 0 | 0 | 0.01 |
f2 | 0.85 | 0.04 | -0.04 | 0.85 | 0 | 1.60 | 0.85 |
f3 | 0.20 | -0.26 | 0.23 | 0.22 | 0 | 1.60 | 0.07 |
f4 | -0.15 | 0.28 | 0.26 | 0.24 | 0 | 0.44 | 0.07 |
Python Implementation
In this section we will see how to implement in Python the fern fractal generation defined above.
First we define the data structures we need as namedtuples
:
1
2
3
4
5
from collections import namedtuple
transformations = namedtuple("transformations", ["f1", "f2", "f3", "f4"])
coefficients = namedtuple("coefficients", ["a", "b", "c", "d", "e", "f"])
fern_settings = namedtuple("fern_settings", ["transformations", "probabilities"])
The namedtuples
hold all the coefficients for the affine transformations as well as their assigned probabilities.
Consequently, we instantiate the settings we need to generate a Barnsley Fern:
1
2
3
4
5
6
7
8
9
10
11
barnsley_fern_coefficients = transformations(
f1=coefficients(.0, .0, .0, .16, .0, .0),
f2=coefficients(.85, .04, -.04, .85, 0, 1.6),
f3=coefficients(.20, -.26, .23, .22, .0, 1.6),
f4=coefficients(-.15, .28, .26, .24, .0, .44),
)
barnsley_fern_probabilities = [.01, .85, .07, .07]
barnsley_fern = fern_settings(
transformations=barnsley_fern_coefficients,
probabilities=barnsley_fern_probabilities,
)
In this way the 4 affine transformations are indexed with indexes between 0 and 3. Finally, we pass the barnsley_fern
namedtuple
in the following
function to generate all the points:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import numpy as np
def generate_fern(selected_fern_settings: namedtuple, num_points: int) -> list:
rng = np.random.default_rng() # Generator object
# generate `num_points` indexes from 0 to 3 according to the probability
indexes = rng.choice(
a=len(selected_fern_settings.probabilities),
size=num_points,
p=selected_fern_settings.probabilities,
shuffle=False
)
# x_0 and y_0 at time step 0
x, y = .0, .0
fern_points = []
for idx in indexes:
fern_points.append((x, y)) # save x_{n} and y_{n}
coeff = selected_fern_settings.transformations[idx]
# calculate x_{n + 1} and y_{n + 1} using x_{n} and y_{n}
x, y = coeff.a * x + coeff.b * y + coeff.e, coeff.c * x + coeff.d * y + coeff.f
return fern_points
As the affine transformations are already indexed from 0 to 3, we randomly sample num_points
indexes between 0 and 3 using
the assigned probabilities. For this purpose we use the NumPy choice
method from the
Generator
class.
In the iteration process that follows, we map the sampled indexes back to the affine transformation they point to and calculate the coordinates. We start from the initial coordinates (0, 0) and calculate the next coordinates using the ones calculated in the previous iteration.
If we plot the points generated by the function above using Matplotlib, we get a nice figure that resembles a fern:
The source code for this work can be found in this Jupyter Notebook. It would be very helpful to star the repo to get more easily noticed. For more information, please follow me on LinkedIn or Twitter.
If you like this content you can subscribe to the mailing list below to get similar updates from time to time.
Appendix: Other types of fern fractals
By playing with the coefficients of the four affine transformation defined above, we can get different interesting results. Some of these results are shown in the figures below.
Fishbone Fern
The Fishbone Fern Fractal can be obtained using the following transformations and probabilities:
w | a | b | c | d | e | f | p |
---|---|---|---|---|---|---|---|
f1 | 0 | 0 | 0 | 0.25 | 0 | -0.4 | 0.02 |
f2 | 0.95 | 0.002 | -0.002 | 0.93 | -0.002 | 0.5 | 0.84 |
f3 | 0.035 | -0.11 | 0.27 | 0.01 | -0.05 | 0.005 | 0.07 |
f4 | -0.04 | 0.11 | 0.27 | 0.01 | 0.047 | 0.06 | 0.07 |
By cranking the iterative formula many times we get the following result:
Fractal Tree
Using the Barnsley Fern technique we can “mutate” the ferns into fractal trees. If we fit the following coefficients and probabilities
w | a | b | c | d | e | f | p |
---|---|---|---|---|---|---|---|
f1 | 0 | 0 | 0 | 0.5 | 0 | 0 | 0.04 |
f2 | 0.42 | -0.42 | 0.42 | 0.42 | 0 | 0.2 | 0.4 |
f3 | 0.42 | 0.42 | -0.42 | 0.42 | 0 | 0.2 | 0.4 |
f4 | 0.1 | 0 | 0 | 0.1 | 0 | 0.2 | 0.15 |
we get an interesting result that resembles a tree as shown below:
Leave a comment