diff --git a/contents/barnsley/barnsley.md b/contents/barnsley/barnsley.md index c879d7bbd..9c353f8b4 100644 --- a/contents/barnsley/barnsley.md +++ b/contents/barnsley/barnsley.md @@ -24,7 +24,7 @@ In this chapter, I hope to provide a slightly more satisfying answer by introduc | Hutchinson Operator | Attractor | | ------------------- | --------- | -| $$\begin{align} f_1(P) &= \begin{bmatrix} 0 &0 \\ 0 &0.16 \end{bmatrix}P + \begin{bmatrix} 0 \\ 0 \end{bmatrix} \\ f_2(P) &= \begin{bmatrix} 0.85 &0.04 \\ -0.04 &0.85 \end{bmatrix}P + \begin{bmatrix} 0 \\ 1.6 \end{bmatrix} \\ f_3(P) &= \begin{bmatrix} 0.2 &-0.26 \\ 0.23 &022 \end{bmatrix}P + \begin{bmatrix} 0 \\ 1.6 \end{bmatrix} \\ f_4(P) &= \begin{bmatrix} -0.15 &0.28 \\ 0.26 &0.24 \end{bmatrix}P + \begin{bmatrix} 0 \\ 0.44 \end{bmatrix} \end{align}$$ | Barnsley Chaos Game | +| $$\begin{align} f_1(P) &= \begin{bmatrix} 0 &0 \\ 0 &0.16 \end{bmatrix}P + \begin{bmatrix} 0 \\ 0 \end{bmatrix} \\ f_2(P) &= \begin{bmatrix} 0.85 &0.04 \\ -0.04 &0.85 \end{bmatrix}P + \begin{bmatrix} 0 \\ 1.6 \end{bmatrix} \\ f_3(P) &= \begin{bmatrix} 0.2 &-0.26 \\ 0.23 &0.22 \end{bmatrix}P + \begin{bmatrix} 0 \\ 1.6 \end{bmatrix} \\ f_4(P) &= \begin{bmatrix} -0.15 &0.28 \\ 0.26 &0.24 \end{bmatrix}P + \begin{bmatrix} 0 \\ 0.44 \end{bmatrix} \end{align}$$ | Barnsley Chaos Game | At first glance, this set of functions looks like an incomprehensible mess of magic numbers to create a specific result, and in a sense, that is precisely correct. That said, we will go through each function and explain how it works, while also providing a simple chaos game implementation in code. @@ -54,7 +54,7 @@ Now let's hop into disecting the Barnsley fern by seeing how each transform affe | -------- | --------- | | $$f_1(P) = \begin{bmatrix} 0 &0 \\ 0 &0.16 \end{bmatrix}P + \begin{bmatrix} 0 \\ 0 \end{bmatrix}$$

This operation moves every point to a single line. |

| | $$f_2(P) = \begin{bmatrix} 0.85 &0.04 \\ -0.04 &0.85 \end{bmatrix}P + \begin{bmatrix} 0 \\ 1.6 \end{bmatrix}$$

This operation moves every point up and to the right. |

| -| $$f_3(P) = \begin{bmatrix} 0.2 &-0.26 \\ 0.23 &022 \end{bmatrix}P + \begin{bmatrix} 0 \\ 1.6 \end{bmatrix}$$

This operation rotates every point to the left. |

| +| $$f_3(P) = \begin{bmatrix} 0.2 &-0.26 \\ 0.23 &0.22 \end{bmatrix}P + \begin{bmatrix} 0 \\ 1.6 \end{bmatrix}$$

This operation rotates every point to the left. |

| | $$f_4(P) = \begin{bmatrix} -0.15 &0.28 \\ 0.26 &0.24 \end{bmatrix}P + \begin{bmatrix} 0 \\ 0.44 \end{bmatrix}$$

This operation flips every point and rotates to the right.|

| At this stage, it *might* be clear what is going on, but it's not exactly obvious. @@ -71,7 +71,7 @@ The easiest way to make sense of this is to show the operations on the Barnsley | -------- | --------- | | $$f_1(P) = \begin{bmatrix} 0 &0 \\ 0 &0.16 \end{bmatrix}P + \begin{bmatrix} 0 \\ 0 \end{bmatrix}$$ |
| | $$f_2(P) = \begin{bmatrix} 0.85 &0.04 \\ -0.04 &0.85 \end{bmatrix}P + \begin{bmatrix} 0 \\ 1.6 \end{bmatrix}$$ |
| -| $$f_3(P) = \begin{bmatrix} 0.2 &-0.26 \\ 0.23 &022 \end{bmatrix}P + \begin{bmatrix} 0 \\ 1.6 \end{bmatrix}$$ |
| +| $$f_3(P) = \begin{bmatrix} 0.2 &-0.26 \\ 0.23 &0.22 \end{bmatrix}P + \begin{bmatrix} 0 \\ 1.6 \end{bmatrix}$$ |
| | $$f_4(P) = \begin{bmatrix} -0.15 &0.28 \\ 0.26 &0.24 \end{bmatrix}P + \begin{bmatrix} 0 \\ 0.44 \end{bmatrix}$$ |
| Here, the self-similar nature of the fern becomes apparent. @@ -86,7 +86,7 @@ To account for this, each function is also given a probability of being chosen: | -------- | ----------- | | $$f_1(P) = \begin{bmatrix} 0 &0 \\ 0 &0.16 \end{bmatrix}P + \begin{bmatrix} 0 \\ 0 \end{bmatrix}$$ | 0.01 | | $$f_2(P) = \begin{bmatrix} 0.85 &0.04 \\ -0.04 &0.85 \end{bmatrix}P + \begin{bmatrix} 0 \\ 1.6 \end{bmatrix}$$ | 0.85 | -| $$f_3(P) = \begin{bmatrix} 0.2 &-0.26 \\ 0.23 &022 \end{bmatrix}P + \begin{bmatrix} 0 \\ 1.6 \end{bmatrix}$$ | 0.07 | +| $$f_3(P) = \begin{bmatrix} 0.2 &-0.26 \\ 0.23 &0.22 \end{bmatrix}P + \begin{bmatrix} 0 \\ 1.6 \end{bmatrix}$$ | 0.07 | | $$f_4(P) = \begin{bmatrix} -0.15 &0.28 \\ 0.26 &0.24 \end{bmatrix}P + \begin{bmatrix} 0 \\ 0.44 \end{bmatrix}$$ | 0.07 | ## Playing around a bit... @@ -98,7 +98,7 @@ Here are a few examples of ferns that can be generated by modifying constituent | -------- | --------- | | $$f_1(P) = \begin{bmatrix} \tau &0 \\ 0 &0.16 \end{bmatrix}P + \begin{bmatrix} 0 \\ 0 \end{bmatrix}$$

where $$-0.5 < \tau < 0.5 $$

Turning stems to leaves |

| | $$f_2(P) = \begin{bmatrix} 0.85 & \tau \\ -0.04 &0.85 \end{bmatrix}P + \begin{bmatrix} 0 \\ 1.6 \end{bmatrix}$$

where $$ -0.01 < \tau < 0.09 $$

Changing fern tilt |

| -| $$f_3(P) = \begin{bmatrix} 0.2 &-0.26 \\ 0.23 &022 \end{bmatrix}P + \begin{bmatrix} \tau \\ 1.6 \end{bmatrix}$$

where $$-0.5 < \tau < 0.5$$

Plucking left leaves |

| +| $$f_3(P) = \begin{bmatrix} 0.2 &-0.26 \\ 0.23 &0.22 \end{bmatrix}P + \begin{bmatrix} \tau \\ 1.6 \end{bmatrix}$$

where $$-0.5 < \tau < 0.5$$

Plucking left leaves |

| | $$f_4(P) = \begin{bmatrix} -0.15 &0.28 \\ 0.26 &0.24 \end{bmatrix}P + \begin{bmatrix} \tau \\ 0.44 \end{bmatrix}$$

where $$-0.5 < \tau < 0.5$$

Plucking right leaves |

| As an important note: the idea of modifying a resulting image by twiddling the knobs of an affine transform is the heart of many interesting methods, including fractal image compression where a low resolution version of an image is stored along with a reconstructing function set to generate high-quality images on-the-fly {{ "fractal-compression" | cite }}{{ "saupe1994review" | cite }}. @@ -135,6 +135,8 @@ The biggest differences between the two code implementations is that the Barnsle [import, lang:"java"](code/java/Barnsley.java) {% sample lang="coco" %} [import, lang:"coconut"](code/coconut/barnsley.coco) +{% sample lang="hs" %} +[import, lang:"haskell"](code/haskell/Barnsley.hs) {% endmethod %} ### Bibliography diff --git a/contents/barnsley/code/haskell/Barnsley.hs b/contents/barnsley/code/haskell/Barnsley.hs new file mode 100644 index 000000000..bf7024200 --- /dev/null +++ b/contents/barnsley/code/haskell/Barnsley.hs @@ -0,0 +1,40 @@ +import Data.Array (Array, bounds, elems, listArray, (!)) +import Data.List (intercalate) +import System.Random + +data Point = Point Double Double + +chaosGame :: RandomGen g => g -> Int -> Array Int (Double, (Point -> Point)) -> [Point] +chaosGame g n hutchinson = take n points + where + (x, g') = random g + (y, g'') = random g' + + cumulProbabilities = scanl1 (+) $ map fst $ elems hutchinson + to_choice x = length $ takeWhile (x >) cumulProbabilities + + picks = map to_choice $ randomRs (0, 1) g'' + step = fmap snd hutchinson + + points = Point x y : zipWith (step !) picks points + +affine :: (Double, Double, Double, Double) -> (Double, Double) -> Point -> Point +affine (xx, xy, yx, yy) (a, b) (Point x y) = Point (a + xx * x + xy * y) (b + yx * x + yy * y) + +showPoint :: Point -> String +showPoint (Point x y) = show x ++ "\t" ++ show y + +main :: IO () +main = do + g <- newStdGen + let barnsley = + listArray + (0, 3) + [ (0.01, affine (0, 0, 0, 0.16) (0, 0)), + (0.85, affine (0.85, 0.04, -0.04, 0.85) (0, 1.6)), + (0.07, affine (0.2, -0.26, 0.23, 0.22) (0, 1.6)), + (0.07, affine (-0.15, 0.28, 0.26, 0.24) (0, 0.44)) + ] + points = chaosGame g 100000 barnsley + + writeFile "out.dat" $ intercalate "\n" $ map showPoint points