Making a Lorenz Attractor in go
What
A Lorenz Attractor is a chaotic system developed by Edward Lorenz. It’s related to the butterfly effect as the image it produces resembles a butterfly and the idea behind it gives rise to the idea that a butterfly flapping it’s wings in Brazil can cause a hurricane in the US. More accurately, it’s an example of a system where tiny changes or errors in the process cause completely different outcomes. In the absence of perfect knowledge of the initial conditions, it’s impossible to predict the systems future.
Fun Fact #1
My original motiviation for coding this was to get a Lorenz Attractor tattoo generated by myself. It turns out Lorenz Attractors don’t tattoo too well - too many lines, bleeding into one another. C’est la vie.
History
The Lorenz Attractor is basically a simplified weather model. It came about by Edwards Lorenz study of meteorology. He wanted to reduce the complex problems of weather into a simple system so he could understand why predicting the weather was so difficult. He reduced it down to a set of 3 equations modelling fluid convection. He came across the chaotic nature when we tried to start his simulation from a previous point and realised the starting point he was providing was slightly different. The print out he was using had truncated the values so instead of 1.001, example, he used 1.00 and got a wildly different output.
The Math (but it’s actually code)
The system is 3 differential equations (or, one 3 dimensional equation 🙃) that we will express in code as:
dx := sigma * (old_p.y - old_p.x)
dy := old_p.x * (rho - old_p.z) - old_p.y
dz := old_p.x * old_p.y - beta * old_p.z
sigma, rho and beta are 3 parameters that control the system. In my code, I set them to the values:
sigma := 10.0
beta := 8.0/3.0
rho := 28.0
which are fairly commonly chosen and produce the classic butterfly image.
These equations represent the rate of convection, the temperature difference and the distortion of the vertical temperature. I’m just a simple hyper chicken from a backwoods asteroid, but in my mind these state:
- the convection is related to the change in temperature
- the temperature is related to the change in convection and the vertical temperature distortion
- the vertical temperature distortion is related to the convection and the temperature
The Simplest Code
We can use the above equations to generate a new point. Then all we need to do is run a simulation, starting from an initial point. We generate X number of points stepping through our simulation by a predefined timestep amount.
package main
type point struct {
x float64
y float64
z float64
}
func step(old_p point, dt float64) point {
sigma := 10.0
beta := 8.0/3.0
rho := 28.0
dx := sigma * (old_p.y - old_p.x)
dy := old_p.x * (rho - old_p.z) - old_p.y
dz := old_p.x * old_p.y - beta * old_p.z
new_p := point{old_p.x + dx * dt,old_p.y + dy * dt, old_p.z + dz * dt}
return new_p;
}
func main() {
point := point{0,0.1,0};
timestep := 0.01
for x := 0; x < 10000; x++ {
point = step(point, timestep);
}
}
If you run this you’re probably blown away by the output.
Generating an Image
Ok so the output is nothing. It’s really boring. And I watch grass grow for fun so that’s saying something. Let’s render the points out to an image.
I used gg because it’s a really simple and straightforward immediate mode graphics library (look ma no buffers). Plus it was the first one I found and it just worked.
import "github.com/fogleman/gg"
...the other bits of code....
func main() {
width := 800
height := 600
dc := gg.NewContext(width, height)
dc.SetRGBA(1, 1, 1, 1.0)
point := point{0,0.1,0};
timestep := 0.01
dc.Translate(float64(width)/2.0, 50.0)
scale := 0.1;
for x := 0; x < 10000; x++ {
old_p := point;
point = step(point, t);
dc.DrawLine(old_p.x/scale, old_p.z/scale, p.x/scale, p.z/scale);
dc.Stroke()
}
dc.SavePNG("Lorenz.png")
}
Here we set the image height and width (NewContext), set the colour of our line (SetRGBA) and move the whole image to the centre-ish (Translate)… I basically move it horizontally central and down a bit for purely aesthetic reasons.
Then simulate as normal, drawing a line from the old point to the newly calculated point. I scale these points, again for aesthetic reasons, because my lorenz attractor’s need to slay. You can omit the scaling if you like, it renders as so:
Fun Fact #2
Originally, I generated an image I liked but was using too large of a step value causing a not so smooth, janky looking image. I knew what to do to fix this: just make the step size smaller! that’ll smooth it out! Unfortunately, the whole point of a chaotic systems is that small changes… It was a very different image.
Github
You can see the whole thing on my Github: Gorenz Attractor.
Feedback
Any feedback is appreciated, feel free to contact me on any of the socials on the side.
Fun Fact #3
Chaos theory is connected to fractals, another area of maths known for it’s pretty pictures.