Skip to content

Commit 158d6da

Browse files
committed
ch11 mandelbrot project tweaks
1 parent fb44c29 commit 158d6da

6 files changed

Lines changed: 152 additions & 34 deletions

File tree

ch11-mandelbrot/README.asciidoc

Lines changed: 0 additions & 17 deletions
This file was deleted.

ch11-mandelbrot/README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
## _Clojure Programming_, Chapter 11
2+
3+
### Visualizing the Mandelbrot Set in Clojure
4+
5+
This project contains a Mandelbrot Set implementation in Clojure that
6+
demonstrates the usage and impact of primitive type declarations on the
7+
runtime of numerically-intensive algorithms.
8+
9+
#### Running
10+
11+
A canonical rendering of the Mandelbrot Set can be obtained by running
12+
the `-main` entry point in the
13+
[`com.clojurebook.mandelbrot`](src/com/clojurebook/mandelbrot.clj)
14+
namespace using Leiningen:
15+
16+
```
17+
$ lein run mandelbrot.png -2.25 0.75 -1.5 1.5 :width 800 :height 800
18+
```
19+
20+
After running this, you'll see this in `mandelbrot.png`:
21+
22+
![](https://github.com/clojurebook/ClojureProgramming/raw/master/ch11-mandelbrot/mandelbrot.png)
23+
24+
The run arguments correspond exactly to those required by
25+
`com.clojurebook.mandelbrot/mandelbrot`.
26+
27+
You can change the view you get by modifying the coordinates provided to
28+
that function:
29+
30+
```
31+
$ lein run mandelbrot-zoomed.png -1.5 -1.3 -0.1 0.1 :width 800 :height 800
32+
```
33+
34+
![](https://github.com/clojurebook/ClojureProgramming/raw/master/ch11-mandelbrot/mandelbrot-zoomed.png)
35+
36+
Of course, if you're going to do a bunch of exploration of the
37+
Mandelbrot Set using this implementation, you'll be _way_ better off
38+
working from the REPL rather than paying the JVM and Leiningen startup
39+
cost repeatedly. Refer to the book or the sources here for
40+
REPL-oriented examples.
41+
42+
#### Optimization via primitive type declarations
43+
A nearly order-of-magnitude improvement in the running time of
44+
`com.clojurebook.mandelbrot/mandelbrot` can be had by replacing its
45+
helper `escape` function with this implementation:
46+
47+
```clojure
48+
(defn- fast-escape
49+
[^double a0 ^double b0 depth]
50+
(loop [a a0
51+
b b0
52+
iteration 0]
53+
(cond
54+
(< 4 (+ (* a a) (* b b))) iteration
55+
(>= iteration depth) -1
56+
:else (recur (+ a0 (- (* a a) (* b b)))
57+
(+ b0 (* 2 (* a b)))
58+
(inc iteration)))))
59+
```
60+
61+
Aside from the `^double` type declarations for the `a0` and `b0`
62+
arguments, this implemenation is otherwise unchanged compared to the
63+
default (boxing) `escape` function.
64+
65+
An alternative `lein run` alias — called `:fast` — is set up to use `fast-escape`:
66+
67+
```
68+
$ lein run :fast mandelbrot.png -2.25 0.75 -1.5 1.5 :width 800 :height 800
69+
```
70+
71+
The above will run far faster than the first `lein run` invocation
72+
above.
73+
151 KB
Loading

ch11-mandelbrot/mandelbrot.png

54.8 KB
Loading

ch11-mandelbrot/project.clj

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
1-
(defproject mandlebrot "1.0.0-SNAPSHOT"
2-
:dependencies [[org.clojure/clojure "1.3.0"]])
1+
(defproject com.clojurebook.mandelbrot "1.0.0-SNAPSHOT"
2+
:description "A Mandelbrot Set implementation in Clojure that
3+
demonstrates the usage and impact of primitive type declarations on the
4+
runtime of numerically-intensive algorithms. From chapter 11 of 'Clojure
5+
Programming' by Emerick, Carper, and Grand."
6+
:url "http://github.com/clojurebook/ClojureProgramming"
7+
:dependencies [[org.clojure/clojure "1.3.0"]]
8+
:main ^:skip-aot com.clojurebook.mandelbrot
9+
:run-aliases {:fast com.clojurebook.mandelbrot/-fast-main})

ch11-mandelbrot/src/clojureprogramming/mandlebrot.clj renamed to ch11-mandelbrot/src/com/clojurebook/mandelbrot.clj

Lines changed: 70 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
(ns clojureprogramming.mandlebrot
1+
(ns com.clojurebook.mandelbrot
22
(:import java.awt.image.BufferedImage
33
(java.awt Color RenderingHints)))
44

55
(defn- escape
66
"Returns an integer indicating how many iterations were required
77
before the value of z (using the components `a` and `b`) could
8-
be determined to have escaped the Mandlebrot set. If z
8+
be determined to have escaped the Mandelbrot set. If z
99
will not escape, -1 is returned."
1010
[a0 b0 depth]
1111
(loop [a a0
@@ -18,9 +18,23 @@
1818
(+ b0 (* 2 (* a b)))
1919
(inc iteration)))))
2020

21-
(defn mandlebrot
21+
(defn- fast-escape
22+
"A primitive-hinted variant of `escape` that can result in an
23+
order-of-magnitude performance improvement when used instead."
24+
[^double a0 ^double b0 depth]
25+
(loop [a a0
26+
b b0
27+
iteration 0]
28+
(cond
29+
(< 4 (+ (* a a) (* b b))) iteration
30+
(>= iteration depth) -1
31+
:else (recur (+ a0 (- (* a a) (* b b)))
32+
(+ b0 (* 2 (* a b)))
33+
(inc iteration)))))
34+
35+
(defn mandelbrot
2236
"Calculates membership within and number of iterations to escape
23-
from the Mandlebrot set for the region defined by `rmin`, `rmax`
37+
from the Mandelbrot set for the region defined by `rmin`, `rmax`
2438
`imin` and `imax` (real and imaginary components of z, respectively).
2539
2640
Optional kwargs include `:depth` (maximum number of iterations
@@ -32,7 +46,7 @@
3246
the corresponding point escaped from the set. -1 indicates points
3347
that did not escape in fewer than `depth` iterations, i.e. they
3448
belong to the set. These integers can be used to drive most common
35-
Mandlebrot set visualizations."
49+
Mandelbrot set visualizations."
3650
[rmin rmax imin imax & {:keys [width height depth]
3751
:or {width 80 height 40 depth 1000}}]
3852
(let [rmin (double rmin)
@@ -51,30 +65,71 @@
5165
depth)))))))
5266

5367
(defn render-text
54-
"Prints a basic textual rendering of mandlebrot set membership,
55-
as returned by a call to `mandlebrot`."
56-
[mandlebrot-grid]
57-
(doseq [row mandlebrot-grid]
68+
"Prints a basic textual rendering of mandelbrot set membership,
69+
as returned by a call to `mandelbrot`."
70+
[mandelbrot-grid]
71+
(doseq [row mandelbrot-grid]
5872
(doseq [escape-iter row]
5973
(print (if (neg? escape-iter) \* \space)))
6074
(println)))
6175

6276
(defn render-image
63-
"Given a mandlebrot set membership grid as returned by a call to
64-
`mandlebrot`, returns a BufferedImage with the same resolution as the
77+
"Given a mandelbrot set membership grid as returned by a call to
78+
`mandelbrot`, returns a BufferedImage with the same resolution as the
6579
grid that uses a discrete grayscale color palette."
66-
[mandlebrot-grid]
80+
[mandelbrot-grid]
6781
(let [palette (vec (for [c (range 500)]
6882
(Color/getHSBColor 0.0 0.0 (/ (Math/log c) (Math/log 500)))))
69-
height (count mandlebrot-grid)
70-
width (count (first mandlebrot-grid))
83+
height (count mandelbrot-grid)
84+
width (count (first mandelbrot-grid))
7185
img (BufferedImage. width height BufferedImage/TYPE_INT_RGB)
7286
^java.awt.Graphics2D g (.getGraphics img)]
73-
(doseq [[y row] (map-indexed vector mandlebrot-grid)
87+
(doseq [[y row] (map-indexed vector mandelbrot-grid)
7488
[x escape-iter] (map-indexed vector row)]
7589
(.setColor g (if (neg? escape-iter)
7690
(palette 0)
7791
(palette (mod (dec (count palette)) (inc escape-iter)))))
7892
(.drawRect g x y 1 1))
7993
(.dispose g)
8094
img))
95+
96+
(defn- coerce-mandelbrot-args
97+
[args]
98+
(for [x args]
99+
(if (= \: (first x))
100+
(keyword (subs x 1))
101+
(try
102+
(if (.contains x ".")
103+
(Double/parseDouble x)
104+
(Long/parseLong x))
105+
(catch NumberFormatException e
106+
(println "Invalid number" x))))))
107+
108+
(defn- print-usage []
109+
(println "Mandelbrot set visualization from 'Clojure Programming', chapter 11.")
110+
(println "Please refer to documentation for com.clojurebook.mandelbrot/mandelbrot for information on what rmin, rmax, etc. mean.")
111+
(println)
112+
(println "Usage: lein run [:fast] output-path rmin rmax imin imax [:width XXX] [:height YYY] [:depth DDD]")
113+
(println " e.g.: lein run mandelbrot.png -2.25 0.75 -1.5 1.5 :width 800 :height 800 :depth 500")
114+
(println)
115+
(println "Using the :fast option will result in the primitive-optimized `fast-escape` function being used instead of the default (boxing) `escape`."))
116+
117+
(defn -main
118+
[& [output-path & opts]]
119+
(let [args (coerce-mandelbrot-args opts)]
120+
(when (or (not output-path)
121+
(seq (filter nil? args))
122+
(not (even? (count args))))
123+
(print-usage)
124+
(System/exit 1))
125+
(javax.imageio.ImageIO/write
126+
(render-image (apply mandelbrot args))
127+
"png" (java.io.File. output-path))
128+
(System/exit 0)))
129+
130+
(defn -fast-main
131+
"Same as -main, but uses `with-redefs` to replace `escape` with its
132+
optimized variant `fast-escape`."
133+
[& args]
134+
(with-redefs [escape fast-escape]
135+
(apply -main args)))

0 commit comments

Comments
 (0)