|
| 1 | +^{:kindly/hide-code true |
| 2 | + :clay {:title "Dragon Curve Fractal - Complex & Bits" |
| 3 | + :quarto {:type :post |
| 4 | + :author [:harold] |
| 5 | + :date "2025-09-10" |
| 6 | + :draft true |
| 7 | + :description "A familiar image is re-discovered in a sequence of complex numbers and the binary representation of the natural numbers." |
| 8 | + :image "dragon-curve.png" |
| 9 | + :category :math |
| 10 | + :tags [:fractals :dragon :curve :complex :imaginary :binary] |
| 11 | + :keywords [:fractals :dragon :curve :complex :imaginary :binary]}}} |
| 12 | +(ns math.fractals.dragon.complex-bits |
| 13 | + (:require [clojure.string :as s] |
| 14 | + [fastmath.complex :as c] |
| 15 | + [scicloj.kindly.v4.kind :as kind])) |
| 16 | + |
| 17 | +;; ### Part 1: Complex Numbers |
| 18 | + |
| 19 | +;; Clojure's [fastmath](https://generateme.github.io/fastmath/notebooks/notebooks/complex_quaternion/index.html) library provides [complex numbers](https://en.wikipedia.org/wiki/Complex_number). |
| 20 | + |
| 21 | +;; For example, if we let `a=b=1`, then |
| 22 | + |
| 23 | +^:kindly/hide-code |
| 24 | +(kind/tex "a+bi=1+1i") |
| 25 | + |
| 26 | + |
| 27 | +(c/complex 1 1) |
| 28 | + |
| 29 | +;; Here's a fun fact about powers of one more than the imaginary unit. |
| 30 | + |
| 31 | +^:kindly/hide-code |
| 32 | +(kind/tex "(1+i)^k=\\sqrt{2}^ke^{ik\\pi/4}") |
| 33 | + |
| 34 | +;; One does not need to be Euler to sense spinning, |
| 35 | + |
| 36 | +;; and `k` from `(0 1 2 3 ...)` gives a sequence of points. |
| 37 | + |
| 38 | +(def pts |
| 39 | + (for [k (range)] |
| 40 | + (c/pow (c/complex 1 1) |
| 41 | + (c/complex k 0)))) |
| 42 | + |
| 43 | +;; The first few of which are... |
| 44 | + |
| 45 | +(take 10 pts) |
| 46 | + |
| 47 | +;; I plot, therefore I am: |
| 48 | + |
| 49 | +(kind/hiccup |
| 50 | + (into [:svg {:width "256" :height "256" :viewBox "-24 -24 48 48"} |
| 51 | + [:rect {:x -24 :y -24 :width 48 :height 48 :fill :#f8f8f8}]] |
| 52 | + (concat (for [[x y] (take 10 pts)] |
| 53 | + [:line {:x1 0 :y1 0 :x2 x :y2 y :stroke :#222}]) |
| 54 | + (for [[x y] (take 10 pts)] |
| 55 | + [:circle {:cx x :cy y :r 1.5 :fill :#88f}])))) |
| 56 | + |
| 57 | +;; Spinning indeed. |
| 58 | + |
| 59 | +;; --- |
| 60 | + |
| 61 | +;; ### Part 2: Binary Bits |
| 62 | + |
| 63 | +;; In computers, numbers are made of bits. |
| 64 | + |
| 65 | +(defn bit |
| 66 | + [n pos] |
| 67 | + (bit-and (int (/ n (Math/pow 2 pos))) 1)) |
| 68 | + |
| 69 | +(for [pos (reverse (range 8))] |
| 70 | + (bit 42 pos)) |
| 71 | + |
| 72 | +;; Hidden in the bits is a draconiform structure, we use them to carefully sum the `pts`. |
| 73 | + |
| 74 | +(defn dragon-by-bits |
| 75 | + ([n] (dragon-by-bits n (count (Integer/toBinaryString n)))) |
| 76 | + ([n pos] |
| 77 | + (letfn [(a [n pos] |
| 78 | + ;; We move according to pts, occasionally turning |
| 79 | + (if (zero? (bit n pos)) |
| 80 | + c/ZERO |
| 81 | + (let [turn? (if (zero? (bit n (inc pos))) |
| 82 | + c/ONE |
| 83 | + c/I) |
| 84 | + pt (nth pts pos)] ;; (1+i)^pos |
| 85 | + (c/mult turn? pt)))) |
| 86 | + (m [n pos] |
| 87 | + ;; When the bits flip, we turn |
| 88 | + (if (= (bit n pos) (bit n (inc pos))) |
| 89 | + c/ONE |
| 90 | + c/I))] |
| 91 | + (if (>= pos 0) |
| 92 | + (c/add (a n pos) |
| 93 | + (c/mult (m n pos) |
| 94 | + (dragon-by-bits n (dec pos)))) |
| 95 | + c/ZERO)))) |
| 96 | + |
| 97 | +;; --- |
| 98 | + |
| 99 | +;; ### Part 3: A Glimpse |
| 100 | + |
| 101 | +;; > "It does not do to leave a live dragon out of your calculations, if you live near him." |
| 102 | +;; > |
| 103 | +;; > -- J.R.R. Tolkien |
| 104 | + |
| 105 | +(let [dragon-pts (for [n (range 512)] (dragon-by-bits n)) |
| 106 | + furthest (* 1.15 (apply max (map Math/abs (flatten dragon-pts)))) |
| 107 | + [l t w h] [(- furthest) (- furthest) (* 2 furthest) (* 2 furthest)]] |
| 108 | + (kind/hiccup |
| 109 | + [:svg {:width "512" :height "512" :viewBox (s/join " " [l t w h]) |
| 110 | + :shape-rendering :crispEdges} |
| 111 | + [:rect {:x l :y t :width w :height h :fill :#f8f8f8}] |
| 112 | + [:path {:d (reduce (fn [eax [x y]] |
| 113 | + (str eax " L" x " " y)) |
| 114 | + (let [[x y] (first dragon-pts)] |
| 115 | + (str "M" x " " y)) |
| 116 | + (rest dragon-pts)) |
| 117 | + :vector-effect "non-scaling-stroke" |
| 118 | + :stroke :#222 |
| 119 | + :fill :none}]])) |
| 120 | + |
| 121 | +;; Inspired by this [lovely rosettacode gnuplot code](https://rosettacode.org/wiki/Dragon_curve#Version_#1.). |
0 commit comments