library(systemfonts)library(showtext)## Clean the slatesystemfonts::clear_local_fonts()systemfonts::clear_registry()##showtext_opts(dpi =96)# set DPI for showtextsysfonts::font_add( family ="Alegreya", regular ="../../../../../../fonts/Alegreya-Regular.ttf", bold ="../../../../../../fonts/Alegreya-Bold.ttf", italic ="../../../../../../fonts/Alegreya-Italic.ttf", bolditalic ="../../../../../../fonts/Alegreya-BoldItalic.ttf")sysfonts::font_add( family ="Roboto Condensed", regular ="../../../../../../fonts/RobotoCondensed-Regular.ttf", bold ="../../../../../../fonts/RobotoCondensed-Bold.ttf", italic ="../../../../../../fonts/RobotoCondensed-Italic.ttf", bolditalic ="../../../../../../fonts/RobotoCondensed-BoldItalic.ttf")showtext_auto(enable =TRUE)# enable showtext##theme_custom<-function(){font<-"Alegreya"# assign font family up front"%+replace%"<-ggplot2::"%+replace%"# nolinttheme_classic(base_size =14, base_family =font)%+replace%# replace elements we want to changetheme( text =element_text(family =font), # set base font family# text elements plot.title =element_text(# title family =font, # set font family size =24, # set font size face ="bold", # bold typeface hjust =0, # left align margin =margin(t =5, r =0, b =5, l =0)), # margin plot.title.position ="plot", plot.subtitle =element_text(# subtitle family =font, # font family size =14, # font size hjust =0, # left align margin =margin(t =5, r =0, b =10, l =0)), # margin plot.caption =element_text(# caption family =font, # font family size =9, # font size hjust =1), # right align plot.caption.position ="plot", # right align axis.title =element_text(# axis titles family ="Roboto Condensed", # font family size =12), # font size axis.text =element_text(# axis text family ="Roboto Condensed", # font family size =9), # font size axis.text.x =element_text(# margin for axis text margin =margin(5, b =10))# since the legend often requires manual tweaking# based on plot content, don't define it here)}## Use available fonts in ggplot text geoms too!ggplot2::update_geom_defaults(geom ="text", new =list( family ="Roboto Condensed", face ="plain", size =3.5, color ="#2b2b2b"))## Set the themeggplot2::theme_set(new =theme_custom())
Error in theme_classic(base_size = 14, base_family = font): could not find function "theme_classic"
Inspiration
This is a mathematically created fern! It uses, (gasp!) repeated matrix multiplication and addition!
We’ll see.
Introduction
The self-similarity of fractals suggests that we could create new fractals from a basic shape using the following procedure:
Start with a basic shape, e.g. a rectangle
Define a set of transformations: scaling / mirroring / translation / combination (say n scaled+rotated replicates)
Run these transformations on the basic shape
Feed the output back to the input ( Classic IFR )
Wait for the pattern to emerge.
See the figure below to get an idea of this process.
Figure 1: Emerging Fractal
Well, this works, provided the transformations include significant amounts of scaling (i.e. reduction in size). You can imagine that if the basic shape does not shrink fast enough, the pattern converges very slowly and would remain chunky even after a large number of iterations.
Secondly, the number of operations quickly becomes exponentially high, as each stage creates n-fold computation increase. Indeed, if we run \(d\) iterations, then the computations scale as \(n^d\), which can very quickly become out of hand!!
So what to do? Just like with the DeepSeek-R1 algorithm that simplified a great deal of AI algorithms, we have recourse to what is called the Barnsley Algorithm. NOTE: especially note the terrific pictures on this stackexchange page!
First let us understand what are Affine Transformations and then build our fractals.
What is an Affine Transformation?
Affine Transformations are defined as a transformations of a space that are:
linear (no nonlinear functions of an x-coordinate, say \(e^x\))
reversible.
Affine transformations can be represented by matrices which multiply the coordinates of a shape in space. Multiple transformations can be understood a series of matrix multiplications, and can indeed be collapsed into a SINGLE matrix multiplication of the coordinates of the shape.
Here are some short videos of affine transformations:
Figure 2: Scaling Along X
Figure 3: Scaling Along Y
Figure 4: Shearing Along X
Figure 5: Shearing Along Y
Figure 6: Translation Along X
Figure 7: Translation Along Y
Designing with Affine Transformations
So how do we use these Affine Transformations? Let us paraphrase what Gary William Flake says in his book The Computational Beauty of Nature:
If \(p\) is a point in space, and its affine transformation(s) is \(L(p\)), then:
If \(p\) is on the final fractal, then so is \(L(p)\);
If \(p\) is not part of the final fractal, then \(L(p)\) will be atleast closer to the final fractal than \(p\) itself.
These ideas give us our final algorithm for designing a fractal with affine transformations.
Start with any point \(p\)
Pick a (set of) Affine transformations \(L_i(p)\) that allow us to imagine the final shape
Take the affine transformation\(L_i(p)\) of point \(p\). Choose \(i\) at random!
Use an IFR: pipe the result back into the input
Make a large number of iterations
Plot all intermediate points that come out of the IFR
With this approach, the points rapidly land up on the fractal which builds up over multiple iterations. We can start anywhere in space and it will still converge.
The additional feature of the Barnsley algorithm is the randomness: since most fractals use not one but several affine transformations to create a multiplicity of forms, at each iteration we can randomly choose between them!
The block diagram of the Barnsley Algorithm looks like this:
How to understand this sketch? Here is Dan Shiffman again!
In the code below, the Affine transformations \(Af_i\) are of the form
\[
AF_i = A_i * X + B_i, ~ i = 1...4
\tag{1}\]
with four options each for matrix \(A\) and matrix \(B\), and \(X = (x,y)\), the current point coordinates (seed input, then output feedback for recursion). There are 50000 iterations performed and at each interation, a random A and a random B are picked to provide the Affine Transformation for that iteration.
The starting “seed point” is simply \(X = (0,0)\).
The probabilities with which each affine transformation is chosen are not all equal; these can be tweaked to see the effect on the fractal.
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
Show the Code
as_sym(b[[2]])
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
Show the Code
as_sym(b[[3]])
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
Show the Code
as_sym(b[[4]])
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
By randomly choosing any of the \(16\) resulting transformations, with different but fixed probablilities, we compute and render the Barnsley fern:
Show the Code
# Iteratively build the ferntheme_set(theme_custom())#n<-50000x<-numeric(n)y<-numeric(n)x[1]<-0y[1]<-0# Starting point (0,0). Can be anything!for(iin1:(n-1)){# Randomly sample the 4 + 4 translations based on a probability# Change these to try different kinds of fernstrans<-sample(1:4, prob =c(.02, .9, .09, .08), size =1)# Translate **current** xy based on the selected translation# Apply one of 16 possible affine transformationsxy<-A[[trans]]%*%c(x[i], y[i])+b[[trans]]x[i+1]<-xy[1]# Save x componenty[i+1]<-xy[2]# Save y component}# Plot this baby# plot(y,x,col= "pink",cex=0.1)gf_point(y~x, colour ="lightgreen", size =0.02, title ="Barnsley Fern")
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
Show the Code
as_sym(X)
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
Show the Code
as_sym(X)
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
Show the Code
as_sym(X)
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
Show the Code
as_sym(X)
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
Error in ensure_sympy(): Both Python3 and 'SymPy' >= 1.4 must be available.
Please verify Python version with 'reticulate::py_config()'.
Remember to configure reticulate (e.g. 'reticulate::use_condaenv("anaconda3")') before loading caracas.
To install SymPy, please run this command:
caracas::install_sympy()
The second transformation is the one most commonly used!! The others are relatively rarely used! So the points slowly slope to the right and do now get squashed up close to the start: they retain sufficient size in (x,y) coordinates for the fern to slowly spread to the right.
So we can design the affine transformations based on an intuition of how we might draw the fractal by hand, say larger strokes to the right, smaller to the left etc, and and decide on the frequency of strokes based on how often these strokes might be used in drawing.
Friendly, Michael, John Fox, and Phil Chalmers. 2024. matlib: Matrix Functions for Teaching and Learning Linear Algebra and Multivariate Statistics. https://doi.org/10.32614/CRAN.package.matlib.