mirror of
https://github.com/dunyakirkali/letterpress.git
synced 2025-01-23 12:14:04 +00:00
277 lines
8.5 KiB
Plaintext
277 lines
8.5 KiB
Plaintext
== Chapter 5: Programming Fractals
|
||
|
||
=== Introduction
|
||
|
||
Fractals are complex patterns that are self-similar across different scales. They are fascinating both in their mathematical properties and their visual appeal. In this chapter, we will explore how to program fractals using Elixir, BQN, and Haskell, focusing on some of the most famous fractal patterns such as the Mandelbrot set and the Sierpinski triangle.
|
||
|
||
=== The Mandelbrot Set
|
||
|
||
The Mandelbrot set is a set of complex numbers that produces a distinctive and famous fractal shape. The set is defined by iterating the function:
|
||
|
||
[stem]
|
||
++++
|
||
z_{n+1} = z_n^2 + c
|
||
++++
|
||
|
||
==== Plotting the Mandelbrot Set in Elixir
|
||
|
||
Let's start with a basic Elixir program to plot the Mandelbrot set.
|
||
|
||
[source,elixir]
|
||
----
|
||
defmodule Mandelbrot do
|
||
def mandelbrot(c, max_iter) do
|
||
iterate(c, 0, 0, max_iter)
|
||
end
|
||
|
||
defp iterate(_c, _z, n, max_iter) when n >= max_iter, do: n
|
||
defp iterate(c, z, n, max_iter) when abs(z) > 2, do: n
|
||
defp iterate(c, z, n, max_iter) do
|
||
iterate(c, z*z + c, n + 1, max_iter)
|
||
end
|
||
|
||
def mandelbrot_set(xmin, xmax, ymin, ymax, width, height, max_iter) do
|
||
for x <- 0..(width-1), y <- 0..(height-1) do
|
||
r = xmin + (xmax - xmin) * x / width
|
||
i = ymin + (ymax - ymin) * y / height
|
||
c = Complex.new(r, i)
|
||
{x, y, mandelbrot(c, max_iter)}
|
||
end
|
||
end
|
||
end
|
||
|
||
{:ok, _} = :application.ensure_all_started(:gnuplot)
|
||
|
||
plot = Mandelbrot.mandelbrot_set(-2.0, 1.0, -1.5, 1.5, 800, 800, 256)
|
||
:Gnuplot.plot(plot)
|
||
----
|
||
|
||
==== Plotting the Mandelbrot Set in BQN
|
||
|
||
Here's a BQN program to plot the Mandelbrot set.
|
||
|
||
[source,bqn]
|
||
----
|
||
mandelbrot ← {
|
||
z ← 0
|
||
for n ≤ ⍟ do
|
||
if ∨/2≤´|z then n else
|
||
z +← z×z + ⍟
|
||
end
|
||
end
|
||
n
|
||
}
|
||
|
||
mandelbrotSet ← {
|
||
r ← (⍟1+⌊○)∧!⌊○1,1⊸−⍟
|
||
⌽⊏ { ⊏⍟(⍟1×⍟1)⊣mandelbrot¨r¨r}¨⊸∾¨⊸∾
|
||
}
|
||
|
||
xmin xmax ymin ymax width height max_iter ← ⊏-2 1 ¯1.5 1.5 800 800 256
|
||
plot ← mandelbrotSet xmin xmax ymin ymax width height max_iter
|
||
plot
|
||
----
|
||
|
||
==== Plotting the Mandelbrot Set in Haskell
|
||
|
||
Here's a Haskell program to plot the Mandelbrot set.
|
||
|
||
[source,haskell]
|
||
----
|
||
import Data.Complex
|
||
import Codec.Picture
|
||
|
||
mandelbrot :: Complex Double -> Int -> Int
|
||
mandelbrot c maxIter = length . takeWhile ((<= 2) . magnitude) . take maxIter $ iterate (\z -> z*z + c) 0
|
||
|
||
mandelbrotSet :: Double -> Double -> Double -> Double -> Int -> Int -> Int -> Image Pixel8
|
||
mandelbrotSet xmin xmax ymin ymax width height maxIter = generateImage pixelRenderer width height
|
||
where
|
||
pixelRenderer x y = let
|
||
real = xmin + (xmax - xmin) * fromIntegral x / fromIntegral width
|
||
imag = ymin + (ymax - ymin) * fromIntegral y / fromIntegral height
|
||
c = real :+ imag
|
||
iter = mandelbrot c maxIter
|
||
in fromIntegral (255 * iter `div` maxIter)
|
||
|
||
main :: IO ()
|
||
main = savePngImage "mandelbrot.png" (ImageY8 $ mandelbrotSet (-2.0) 1.0 (-1.5) 1.5 800 800 256)
|
||
----
|
||
|
||
=== The Sierpinski Triangle
|
||
|
||
The Sierpinski triangle is a fractal that is formed by recursively subdividing an equilateral triangle into smaller equilateral triangles.
|
||
|
||
==== Plotting the Sierpinski Triangle in Elixir
|
||
|
||
Here is an Elixir program to plot the Sierpinski triangle using recursion.
|
||
|
||
[source,elixir]
|
||
----
|
||
defmodule Sierpinski do
|
||
def draw_triangle(x, y, size, depth, canvas) when depth == 0 do
|
||
triangle = [{x, y}, {x + size / 2, y + size * :math.sqrt(3) / 2}, {x + size, y}]
|
||
:egd.filled_polygon(canvas, triangle, :egd.color(:black))
|
||
end
|
||
|
||
def draw_triangle(x, y, size, depth, canvas) do
|
||
draw_triangle(x, y, size / 2, depth - 1, canvas)
|
||
draw_triangle(x + size / 2, y, size / 2, depth - 1, canvas)
|
||
draw_triangle(x + size / 4, y + size * :math.sqrt(3) / 4, size / 2, depth - 1, canvas)
|
||
end
|
||
end
|
||
|
||
{:ok, canvas} = :egd.create(800, 800)
|
||
Sierpinski.draw_triangle(0, 0, 800, 5, canvas)
|
||
:image.create(:png, canvas)
|
||
----
|
||
|
||
==== Plotting the Sierpinski Triangle in BQN
|
||
|
||
Here is a BQN program to plot the Sierpinski triangle using recursion.
|
||
|
||
[source,bqn]
|
||
----
|
||
sierpinski ← {
|
||
size depth canvas ← ⍟
|
||
if depth = 0 then canvas
|
||
else
|
||
sierpinski size/2 depth-1 canvas ∨ (size/2) size
|
||
sierpinski size/2 depth-1 canvas ⊹⌽(size/2) size
|
||
sierpinski size/2 depth-1 canvas ⊹⌽(size/4) (size*√3/4)
|
||
end
|
||
}
|
||
|
||
sierpinski 800 5 []
|
||
----
|
||
|
||
==== Plotting the Sierpinski Triangle in Haskell
|
||
|
||
Here is a Haskell program to plot the Sierpinski triangle using recursion.
|
||
|
||
[source,haskell]
|
||
----
|
||
import Codec.Picture
|
||
|
||
drawTriangle :: Int -> Int -> Int -> Int -> Image Pixel8 -> Image Pixel8
|
||
drawTriangle x y size depth img
|
||
| depth == 0 = drawFilledPolygon img [(x, y), (x + size `div` 2, y + round (fromIntegral size * sqrt 3 / 2)), (x + size, y)] 0
|
||
| otherwise = drawTriangle x y (size `div` 2) (depth - 1) .
|
||
drawTriangle (x + size `div` 2) y (size `div` 2) (depth - 1) .
|
||
drawTriangle (x + size `div` 4) (y + round (fromIntegral size * sqrt 3 / 4)) (size `div` 2) (depth - 1) $ img
|
||
|
||
main :: IO ()
|
||
main = savePngImage "sierpinski.png" (ImageY8 $ drawTriangle 0 0 800 5 (generateImage (\_ _ -> 255) 800 800))
|
||
----
|
||
|
||
=== Julia Sets
|
||
|
||
Julia sets are another type of fractal, closely related to the Mandelbrot set. They are generated using a similar iterative function but with a fixed complex parameter.
|
||
|
||
==== Plotting a Julia Set in Elixir
|
||
|
||
Here's an Elixir program to plot a Julia set.
|
||
|
||
[source,elixir]
|
||
----
|
||
defmodule Julia do
|
||
def julia(c, z, max_iter) do
|
||
iterate(c, z, 0, max_iter)
|
||
end
|
||
|
||
defp iterate(_c, _z, n, max_iter) when n >= max_iter, do: n
|
||
defp iterate(c, z, n, max_iter) when abs(z) > 2, do: n
|
||
defp iterate(c, z, n, max_iter) do
|
||
iterate(c, z*z + c, n + 1, max_iter)
|
||
end
|
||
|
||
def julia_set(c, xmin, xmax, ymin, ymax, width, height, max_iter) do
|
||
for x <- 0..(width-1), y <- 0..(height-1) do
|
||
r = xmin + (xmax - xmin) * x / width
|
||
i = ymin + (ymax - ymin) * y / height
|
||
z = Complex.new(r, i)
|
||
{x, y, julia(c, z, max_iter)}
|
||
end
|
||
end
|
||
end
|
||
|
||
c = Complex.new(-0.7, 0.27015)
|
||
plot = Julia.julia_set(c, -1.5, 1.5, -1.5, 1.5, 800, 800, 256)
|
||
:Gnuplot.plot(plot)
|
||
----
|
||
|
||
==== Plotting a Julia Set in BQN
|
||
|
||
Here's a BQN program to plot a Julia set.
|
||
|
||
[source,bqn]
|
||
----
|
||
julia ← { z c max_iter ← ⍟
|
||
iterate ← { z +← z×z + c}
|
||
for n ≤ max_iter do
|
||
if ∨/2≤´|z then n else iterate z
|
||
end
|
||
}
|
||
|
||
juliaSet ← { c xmin xmax ymin ymax width height max_iter ← ⍟
|
||
r1 ← (⍟1+⌊○)∧!⌊○1,1⊸−⍟
|
||
⌽⊏ { ⊏⍟(⍟1×⍟1)⊣julia¨r1¨r1¨c¨max_iter}¨⊸∾¨⊸∾
|
||
}
|
||
|
||
c = 0.27015J¯0.7
|
||
xmin xmax ymin ymax width height max_iter ← ⊏-1.5 1.5 ¯1.5 1.5 800 800 256
|
||
plot ← juliaSet c xmin xmax ymin ymax width height max_iter
|
||
plot
|
||
----
|
||
|
||
==== Plotting a Julia Set in Haskell
|
||
|
||
Here's a Haskell program to plot a Julia set.
|
||
|
||
[source,haskell]
|
||
----
|
||
import Data.Complex
|
||
import Codec.Picture
|
||
|
||
julia :: Complex Double -> Complex Double -> Int -> Int
|
||
julia c z0 maxIter = length . takeWhile ((<= 2) . magnitude) . take maxIter $ iterate (\z -> z*z + c) z0
|
||
|
||
juliaSet :: Complex Double -> Double -> Double -> Double -> Double -> Int -> Int -> Int -> Image Pixel8
|
||
juliaSet c xmin xmax ymin ymax width height maxIter = generateImage pixelRenderer width height
|
||
where
|
||
pixelRenderer x y = let
|
||
real = xmin + (xmax - xmin) * fromIntegral x / fromIntegral width
|
||
imag = ymin + (ymax - ymin) * fromIntegral y / fromIntegral height
|
||
z0 = real :+ imag
|
||
iter = julia c z0 maxIter
|
||
in fromIntegral (255 * iter `div` maxIter)
|
||
|
||
main :: IO ()
|
||
main = savePngImage "julia.png" (ImageY8 $ juliaSet (0.27015 :+ (-0.7)) (-1.5) 1.5 (-1.5) 1.5 800 800 256)
|
||
----
|
||
|
||
=== Conclusion
|
||
|
||
Fractals are an excellent way to understand the beauty and complexity of mathematical patterns. By programming these patterns, we not only appreciate their aesthetic appeal but also gain insights into their mathematical properties.
|
||
|
||
=== Further Reading
|
||
|
||
For more information on fractals and programming, check out the following resources:
|
||
|
||
* https://en.wikipedia.org/wiki/Mandelbrot_set[Mandelbrot Set - Wikipedia]
|
||
* https://en.wikipedia.org/wiki/Sierpinski_triangle[Sierpinski Triangle - Wikipedia]
|
||
* https://en.wikipedia.org/wiki/Julia_set[Julia Set - Wikipedia]
|
||
|
||
=== References
|
||
|
||
1. Mandelbrot, B. B. (1982). _The Fractal Geometry of Nature_. New York: W.H. Freeman and Company.
|
||
2. Peitgen, H.-O., Jürgens, H., & Saupe, D. (1992). _Chaos and Fractals: New Frontiers of Science_. New York: Springer.
|
||
|
||
=== Appendix
|
||
|
||
==== Image and Code Credits
|
||
|
||
* Mandelbrot Set: Code adapted from various fractal tutorials in Elixir, BQN, and Haskell.
|
||
* Sierpinski Triangle: Code adapted from various fractal tutorials in Elixir, BQN, and Haskell.
|
||
* Julia Set: Code adapted from various fractal tutorials in Elixir, BQN, and Haskell.
|