Proofreading

This commit is contained in:
2024-12-16 21:29:03 -05:00
parent a6194763d1
commit 37e2992865
4 changed files with 99 additions and 107 deletions

View File

@ -22,15 +22,14 @@ import banner from '../banner.png'
<!-- truncate -->
I don't remember exactly when I first learned about fractal flames, but I do remember being entranced by the images they created.
I don't remember when exactly I first learned about fractal flames, but I do remember being 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 Algorithm paper](https://flam3.com/flame_draves.pdf) describing their structure was too much
The [Fractal Flame Algorithm paper](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, I want to revisit it
and try to make some progress.
But the desire to understand it stuck around. Now, with a graduate degree under my belt, I wanted to revisit it.
This guide is my attempt to explain how fractal flames work in a way that younger me &mdash; and others interested in the art &mdash;
This guide is my attempt to explain how fractal flames work so that younger me &mdash; and others interested in the art &mdash;
can understand without too much prior knowledge.
---
@ -42,7 +41,7 @@ This post covers section 2 of the Fractal Flame Algorithm paper
:::
As mentioned, fractal flames are a type of "[iterated function system](https://en.wikipedia.org/wiki/Iterated_function_system),"
or IFS. The formula for an IFS is short, but takes some time to unpack:
or IFS. The formula for an IFS is short, but takes some time to work through:
$$
S = \bigcup_{i=0}^{n-1} F_i(S)
@ -52,7 +51,7 @@ $$
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 to our equation.
Our goal is to find all points in $S$, plot them, and display that image.
Our goal is to find all the points in $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:
@ -67,20 +66,17 @@ export const simpleData = [
<VictoryScatter data={simpleData} size={5} style={{data: {fill: "blue"}}}/>
</VictoryChart>
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 the functions change, the solution also changes, and we get a new picture.
With fractal flames, rather than listing individual points, we use functions to describe the solution.
This means there are an infinite number of points, but if we find _enough_ points to plot, we get a nice picture.
And if the functions change, the solution also changes, and we get something new.
### Transform functions
Second, the $F_i(S)$ functions, also known as "transforms."
At their most basic, each $F_i$ takes in a 2-dimensional point and gives back a new point
Each transform takes in a 2-dimensional point and gives a new point back
(in math terms, $F_i \in \mathbb{R}^2 \rightarrow \mathbb{R}^2$).
While you could theoretically use any function, we'll start with a specific kind of function
called an "[affine transformation](https://en.wikipedia.org/wiki/Affine_transformation)."
The general form of an affine transformation is:
While you could theoretically use any function, we'll focus on a specific kind of function
called an "[affine transformation](https://en.wikipedia.org/wiki/Affine_transformation)." Every transform uses the same formula:
$$
F_i(a_i x + b_i y + c_i, d_i x + e_i y + f_i)
@ -91,8 +87,8 @@ import CodeBlock from '@theme/CodeBlock'
<CodeBlock language="typescript">{transformSource}</CodeBlock>
The parameters ($a_i$, $b_i$, etc.) are values we get to choose.
For example, we can represent a "shift" function like this:
The parameters ($a_i$, $b_i$, etc.) are values we choose.
For example, we can define a "shift" function like this:
$$
\begin{align*}
@ -106,7 +102,7 @@ F_{shift}(x, y) &= (1 \cdot x + 0.5, 1 \cdot y + 1.5)
\end{align*}
$$
Applying this function to our original points will give us a new set of points:
Applying this transform to the original points gives us a new set of points:
import {applyCoefs} from "../src/transform"
@ -139,7 +135,7 @@ $$
S = \bigcup_{i=0}^{n-1} F_i(S)
$$
Or, to put it in English, we might say:
Or, in English, we might say:
> Our solution, $S$, is the union of all sets produced by applying each function, $F_i$,
> to points in the solution.
@ -153,17 +149,13 @@ defining the mathematics of iterated function systems:
> Furthermore, $S$ is compact and is the closure of the set of fixed points $s_{i_1...i_p}$
> of finite compositions $F_{i_1...i_p}$ of members of $F$.
:::note
I've tweaked the conventions of that paper slightly to match the Fractal Flame paper
:::
Before your eyes glaze over, let's unpack this:
- **Furthermore, $S$ is [compact](https://en.wikipedia.org/wiki/Compact_space)...**: All points in our solution will be in a finite range
- **...and is the [closure](https://en.wikipedia.org/wiki/Closure_(mathematics)) of the set of [fixed points](https://en.wikipedia.org/wiki/Fixed_point_(mathematics))**:
Applying our functions to points in the solution will give us other points that are in the solution
- **...of finite compositions $F_{i_1...i_p}$ of members of $F$**: By composing our functions (that is,
using the output of one function as input to the next function), we will arrive at the points in the solution
using the output of one function as input to the next), we will arrive at the points in the solution
Thus, by applying the functions to fixed points of our system, we will find the other points we care about.
@ -177,11 +169,11 @@ Thus, by applying the functions to fixed points of our system, we will find the
algorithm demonstrates, we only need functions to be contractive _on average_. At worst, the system will
degenerate and produce a bad image.
Second, we're focused on $\mathbb{R}^2$ because we're generating images, but the Hutchinson paper
Second, we're focused on $\mathbb{R}^2$ because we're generating images, but the math
allows for arbitrary dimensions; you could also have 3-dimensional fractal flames.
Finally, there's a close relationship between fractal flames and [attractors](https://en.wikipedia.org/wiki/Attractor).
Specifically, the fixed points of $S$ act as attractors for the chaos game (explained in more detail below).
Specifically, the fixed points of $S$ act as attractors for the chaos game (explained below).
</details>
This is still a bit vague, so let's work through an example.
@ -200,7 +192,7 @@ $$
### The chaos game
Next, how do we find the "fixed points" mentioned earlier? The paper lays out an algorithm called the "[chaos game](https://en.wikipedia.org/wiki/Chaos_game)"
Now, how do we find the "fixed points" mentioned earlier? The paper lays out an algorithm called the "[chaos game](https://en.wikipedia.org/wiki/Chaos_game)"
that gives us points in the solution:
$$
@ -220,8 +212,8 @@ The chaos game algorithm is effectively the "finite compositions of $F_{i_1..i_p
Let's turn this into code, one piece at a time.
First, we need to generate some random numbers. The "bi-unit square" is the range $[-1, 1]$,
so we generate a random point using an existing API:
To start, we need to generate some random numbers. The "bi-unit square" is the range $[-1, 1]$,
and we can do this using an existing API:
import biunitSource from '!!raw-loader!../src/randomBiUnit'
@ -235,14 +227,14 @@ import randintSource from '!!raw-loader!../src/randomInteger'
### Plotting
Finally, implementing the `plot` function. This blog series is designed to be interactive,
Finally, implementing the `plot` function. This blog series is interactive,
so everything displays directly in the browser. As an alternative,
software like `flam3` and Apophysis can "plot" by saving an image to disk.
To show the results, we'll use the [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API).
This allows us to manipulate individual pixels in an image and display it on screen.
To see the results, we'll use the [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API).
This allows us to manipulate individual pixels in an image and show it on screen.
First, we need to convert from Fractal Flame coordinates to pixel coordinates.
First, we need to convert from fractal flame coordinates to pixel coordinates.
To simplify things, we'll assume that we're plotting a square image
with range $[0, 1]$ for both $x$ and $y$:
@ -251,7 +243,7 @@ import cameraSource from "!!raw-loader!./cameraGasket"
<CodeBlock language="typescript">{cameraSource}</CodeBlock>
Next, we'll store the pixel data in an [`ImageData` object](https://developer.mozilla.org/en-US/docs/Web/API/ImageData).
Each pixel in the image on screen has a corresponding index in the `data` array.
Each pixel on screen has a corresponding index in the `data` array.
To plot a point, we set that pixel to be black:
import plotSource from '!!raw-loader!./plot'
@ -277,25 +269,22 @@ import chaosGameSource from '!!raw-loader!./chaosGame'
### Weights
There's one last step before we finish the introduction. So far, each function $F_i$ has
the same chance of being chosen. We can change that by introducing a "weight" ($w_i$)
to each transform in the chaos game:
There's one last step before we finish the introduction. So far, each transform has
the same chance of being picked in the chaos game.
We can change that by giving them a "weight" ($w_i$) instead:
import randomChoiceSource from '!!raw-loader!../src/randomChoice'
<CodeBlock language={'typescript'}>{randomChoiceSource}</CodeBlock>
If we let the chaos game run forever, these weights wouldn't matter.
But because the iteration count is limited, changing the weights
means we don't plot some parts of the image:
import chaosGameWeightedSource from "!!raw-loader!./chaosGameWeighted";
<CodeBlock language={'typescript'}>{chaosGameWeightedSource}</CodeBlock>
For Sierpinski's Gasket, we start with equal weighting,
but changing the transform weights affects how often
the chaos game "visits" parts of the image. If the chaos
were to run forever, we'd get the same image;
but because the iteration count is limited,
some parts may be missing below.
:::tip
Double-click the image if you want to save a copy!
:::
@ -308,7 +297,7 @@ import {SquareCanvas} from "../src/Canvas";
## Summary
Studying the foundations of fractal flames is challenging,
but we now have an understanding of both the mathematics
and implementations of iterated function systems.
but we now have an understanding of the mathematics
and the implementation of iterated function systems.
In the next post, we'll study the first innovation of fractal flames: variations.
In the next post, we'll look at the first innovation of fractal flame algorithm: variations.