--- slug: 2024/11/playing-with-fire title: "Playing with fire: The fractal flame algorithm" date: 2024-11-15 12:00:00 authors: [bspeice] tags: [] --- Wikipedia describes fractal flames fractal flames as: > a member of the iterated function system class of fractals It's a bit tedious, but technically correct. I choose to think of them a different way: beauty in mathematics. import isDarkMode from '@site/src/isDarkMode' import banner from '../banner.png'
I don't remember exactly when I first learned about fractal flames, but I do remember becoming entranced by the images they created. I also remember their unique appeal to my young engineering mind; this was an art form I could participate in. The original [Fractal Flame](https://flam3.com/flame_draves.pdf) describing their structure was too much for me to handle at the time (I was ~12 years old), so I was content to play around and enjoy the pictures. But the desire to understand it stuck around. Now, with a graduate degree under my belt, maybe I can make some progress. This guide is my attempt to explain fractal flames in a way that younger me — and others interested in the art — can understand without too much prior knowledge. --- ## Iterated function systems As mentioned above, fractal flames are a type of "[iterated function system](https://en.wikipedia.org/wiki/Iterated_function_system)," or IFS. Their mathematical foundations come from a paper written by [John E. Hutchinson](https://maths-people.anu.edu.au/~john/Assets/Research%20Papers/fractals_self-similarity.pdf), but reading that paper isn't critical for our purposes. Instead, we'll focus on building a practical understanding of how they work. The formula for an IFS is short, but will take some time to unpack: $$ S = \bigcup_{i=0}^{n-1} F_i(S) $$ ### Fixed set First, $S$. $S$ is the set of points in two dimensions (in math terms, $S \in \mathbb{R}^2$) that represent a "solution" of some kind. Our goal is to find all points in the set $S$, plot them, and display that image. For example, if we say $S = \{(0,0), (1, 1), (2, 2)\}$, there are three points to plot: import {VictoryChart, VictoryTheme, VictoryScatter, VictoryLegend} from "victory"; export const simpleData = [ {x: 0, y: 0}, {x: 1, y: 1}, {x: 2, y: 2} ] However, this is a pretty boring image. With fractal flames, rather than listing individual points, we use functions to describe which points are part of the solution. This means there are an infinite number of points, but if we find _enough_ points to plot, we'll end up with a nice picture. And if we choose different functions to start with, our solution set changes, and we'll end up with a new picture. However, it's not clear which points belong in the solution just by staring at the functions. We'll need a computer to figure it out. TODO: Other topics worth covering in this section? Maybe in a `details` block?: - Fixed sets: https://en.wiktionary.org/wiki/fixed_set - Compact sets ### Transformation functions Second, $F_i(S)$. At their most basic, each $F_i$ is a function that takes in a 2-dimensional point and transforms it into a new 2-dimensional point: $F_i \in \mathbb{R}^2 \rightarrow \mathbb{R}^2$. It's worth discussing these functions, but not critical, so **this section is optional**. In mathematical terms, each $F_i$ is a special kind of function called an [affine transformation](https://en.wikipedia.org/wiki/Affine_transformation). We can think of them like mapping from one coordinate system to another. For example, we can define a coordinate system where everything is shifted over: $$ F_{shift}(x, y) = (x + 1, y) $$ That is, for an input point $(x, y)$, the output point will be $(x + 1, y)$: export const shiftData = simpleData.map(({x, y}) => { return {x: x + 1, y} }) This is a simple example designed to illustrate the principle. In general, $F_i$ functions have the form: $$ F_i(x,y) = (a_i \cdot x + b_i \cdot y + c_i, d_i \cdot x + e_i \cdot y + f_i) $$ The parameters ($a_i$, $b_i$, etc.) are values we get to choose. In the example above, we can represent our shift function using these parameters: $$ a_i = 1 \hspace{0.5cm} b_i = 0 \hspace{0.5cm} c_i = 1 \\ d_i = 0 \hspace{0.5cm} e_i = 1 \hspace{0.5cm} f_i = 0 \\ $$ $$ \begin{align*} F_{shift}(x,y) &= (1 \cdot x + 0 \cdot y + 1, 0 \cdot x + 1 \cdot y + 0) \\ F_{shift}(x,y) &= (x + 1, y) \end{align*} $$ Fractal flames use more complex functions to produce a wide variety of images, but all follow this same format. ## Sierpinski's gasket Using these definitions, we can build the first image. The paper defines a function system for us: $$ F_0(x, y) = \left({x \over 2}, {y \over 2} \right) \\ ~\\ F_1(x, y) = \left({{x + 1} \over 2}, {y \over 2} \right) \\ ~\\ F_2(x, y) = \left({x \over 2}, {{y + 1} \over 2} \right) $$ ### The chaos game import CodeBlock from '@theme/CodeBlock' Next, how do we find out all the points in $S$? The paper lays out an algorithm called the "chaos game": $$ \begin{align*} &(x, y) = \text{random point in the bi-unit square} \\ &\text{iterate } \{ \\ &\hspace{1cm} i = \text{random integer from 0 to } n - 1 \\ &\hspace{1cm} (x,y) = F_i(x,y) \\ &\hspace{1cm} \text{plot}(x,y) \text{ if iterations} > 20 \\ \} \end{align*} $$ Let's turn this into code, one piece at a time. First, the "bi-unit square" is the range $[-1, 1]$. We can pick a random point like this: import biunitSource from '!!raw-loader!../src/randomBiUnit' {biunitSource} Next, we need to choose a random integer from $0$ to $n - 1$: import randintSource from '!!raw-loader!../src/randomInteger' {randintSource} Finally, implementing the `plot` function. Web browsers have a [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) we can use for 2D graphics. In our case, the plot function will take an $(x,y)$ coordinate and plot it by coloring the corresponding pixel in an [ImageData](https://developer.mozilla.org/en-US/docs/Web/API/ImageData): import plotSource from '!!raw-loader!./plot' {plotSource} import Playground from '@theme/Playground' import Scope from './scope' import chaosGameSource from '!!raw-loader!./chaosGame' {chaosGameSource}
Note: The image here is slightly different than the fractal flame paper; I think the paper has an error, so I'm choosing to plot the image in a way that's consistent with [`flam3` itself](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/rect.c#L440-L441). ## Weights Finally, we'll introduce a "weight" parameter ($w_i$) assigned to each function, which controls how often that function is used: import randomChoiceSource from '!!raw-loader!../src/randomChoice' {randomChoiceSource} import chaosGameWeightedSource from "!!raw-loader!./chaosGameWeighted"; {chaosGameWeightedSource} import GasketWeighted from "./GasketWeighted"; import {SquareCanvas} from "../src/Canvas";