Skip to content

Commit 3947494

Browse files
committed
[math/fractals/dragon/complex-bits] First cut
1 parent 054eac4 commit 3947494

2 files changed

Lines changed: 120 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)