Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

ch11 mandelbrot project tweaks

  • Loading branch information...
commit 158d6da726b232e84b02f1be61d80a84d1ea0282 1 parent fb44c29
@cemerick cemerick authored
View
17 ch11-mandelbrot/README.asciidoc
@@ -1,17 +0,0 @@
-You can obtain a nearly order-of-magnitude improvement in the running time of `clojureprogramming.mandlebrot/mandlebrot` by replacing its helper `escape` function with this implementation:
-
-----
-(defn- escape
- [^double a0 ^double b0 depth]
- (loop [a a0
- b b0
- iteration 0]
- (cond
- (< 4 (+ (* a a) (* b b))) iteration
- (>= iteration depth) -1
- :else (recur (+ a0 (- (* a a) (* b b)))
- (+ b0 (* 2 (* a b)))
- (inc iteration)))))
-----
-
-Note the `^double` type declarations for the `a0` and `b0` arguments in our revised implementation of `escape`, which is otherwise unchanged.
View
73 ch11-mandelbrot/README.md
@@ -0,0 +1,73 @@
+## _Clojure Programming_, Chapter 11
+
+### Visualizing the Mandelbrot Set in Clojure
+
+This project contains a Mandelbrot Set implementation in Clojure that
+demonstrates the usage and impact of primitive type declarations on the
+runtime of numerically-intensive algorithms.
+
+#### Running
+
+A canonical rendering of the Mandelbrot Set can be obtained by running
+the `-main` entry point in the
+[`com.clojurebook.mandelbrot`](src/com/clojurebook/mandelbrot.clj)
+namespace using Leiningen:
+
+```
+$ lein run mandelbrot.png -2.25 0.75 -1.5 1.5 :width 800 :height 800
+```
+
+After running this, you'll see this in `mandelbrot.png`:
+
+![](https://github.com/clojurebook/ClojureProgramming/raw/master/ch11-mandelbrot/mandelbrot.png)
+
+The run arguments correspond exactly to those required by
+`com.clojurebook.mandelbrot/mandelbrot`.
+
+You can change the view you get by modifying the coordinates provided to
+that function:
+
+```
+$ lein run mandelbrot-zoomed.png -1.5 -1.3 -0.1 0.1 :width 800 :height 800
+```
+
+![](https://github.com/clojurebook/ClojureProgramming/raw/master/ch11-mandelbrot/mandelbrot-zoomed.png)
+
+Of course, if you're going to do a bunch of exploration of the
+Mandelbrot Set using this implementation, you'll be _way_ better off
+working from the REPL rather than paying the JVM and Leiningen startup
+cost repeatedly. Refer to the book or the sources here for
+REPL-oriented examples.
+
+#### Optimization via primitive type declarations
+A nearly order-of-magnitude improvement in the running time of
+`com.clojurebook.mandelbrot/mandelbrot` can be had by replacing its
+helper `escape` function with this implementation:
+
+```clojure
+(defn- fast-escape
+ [^double a0 ^double b0 depth]
+ (loop [a a0
+ b b0
+ iteration 0]
+ (cond
+ (< 4 (+ (* a a) (* b b))) iteration
+ (>= iteration depth) -1
+ :else (recur (+ a0 (- (* a a) (* b b)))
+ (+ b0 (* 2 (* a b)))
+ (inc iteration)))))
+```
+
+Aside from the `^double` type declarations for the `a0` and `b0`
+arguments, this implemenation is otherwise unchanged compared to the
+default (boxing) `escape` function.
+
+An alternative `lein run` alias — called `:fast` — is set up to use `fast-escape`:
+
+```
+$ lein run :fast mandelbrot.png -2.25 0.75 -1.5 1.5 :width 800 :height 800
+```
+
+The above will run far faster than the first `lein run` invocation
+above.
+
View
BIN  ch11-mandelbrot/mandelbrot-zoomed.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  ch11-mandelbrot/mandelbrot.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
11 ch11-mandelbrot/project.clj
@@ -1,2 +1,9 @@
-(defproject mandlebrot "1.0.0-SNAPSHOT"
- :dependencies [[org.clojure/clojure "1.3.0"]])
+(defproject com.clojurebook.mandelbrot "1.0.0-SNAPSHOT"
+ :description "A Mandelbrot Set implementation in Clojure that
+demonstrates the usage and impact of primitive type declarations on the
+runtime of numerically-intensive algorithms. From chapter 11 of 'Clojure
+Programming' by Emerick, Carper, and Grand."
+ :url "http://github.com/clojurebook/ClojureProgramming"
+ :dependencies [[org.clojure/clojure "1.3.0"]]
+ :main ^:skip-aot com.clojurebook.mandelbrot
+ :run-aliases {:fast com.clojurebook.mandelbrot/-fast-main})
View
85 ...rot/src/clojureprogramming/mandlebrot.clj → ...elbrot/src/com/clojurebook/mandelbrot.clj
@@ -1,11 +1,11 @@
-(ns clojureprogramming.mandlebrot
+(ns com.clojurebook.mandelbrot
(:import java.awt.image.BufferedImage
(java.awt Color RenderingHints)))
(defn- escape
"Returns an integer indicating how many iterations were required
before the value of z (using the components `a` and `b`) could
- be determined to have escaped the Mandlebrot set. If z
+ be determined to have escaped the Mandelbrot set. If z
will not escape, -1 is returned."
[a0 b0 depth]
(loop [a a0
@@ -18,9 +18,23 @@
(+ b0 (* 2 (* a b)))
(inc iteration)))))
-(defn mandlebrot
+(defn- fast-escape
+ "A primitive-hinted variant of `escape` that can result in an
+ order-of-magnitude performance improvement when used instead."
+ [^double a0 ^double b0 depth]
+ (loop [a a0
+ b b0
+ iteration 0]
+ (cond
+ (< 4 (+ (* a a) (* b b))) iteration
+ (>= iteration depth) -1
+ :else (recur (+ a0 (- (* a a) (* b b)))
+ (+ b0 (* 2 (* a b)))
+ (inc iteration)))))
+
+(defn mandelbrot
"Calculates membership within and number of iterations to escape
- from the Mandlebrot set for the region defined by `rmin`, `rmax`
+ from the Mandelbrot set for the region defined by `rmin`, `rmax`
`imin` and `imax` (real and imaginary components of z, respectively).
Optional kwargs include `:depth` (maximum number of iterations
@@ -32,7 +46,7 @@
the corresponding point escaped from the set. -1 indicates points
that did not escape in fewer than `depth` iterations, i.e. they
belong to the set. These integers can be used to drive most common
- Mandlebrot set visualizations."
+ Mandelbrot set visualizations."
[rmin rmax imin imax & {:keys [width height depth]
:or {width 80 height 40 depth 1000}}]
(let [rmin (double rmin)
@@ -51,26 +65,26 @@
depth)))))))
(defn render-text
- "Prints a basic textual rendering of mandlebrot set membership,
- as returned by a call to `mandlebrot`."
- [mandlebrot-grid]
- (doseq [row mandlebrot-grid]
+ "Prints a basic textual rendering of mandelbrot set membership,
+ as returned by a call to `mandelbrot`."
+ [mandelbrot-grid]
+ (doseq [row mandelbrot-grid]
(doseq [escape-iter row]
(print (if (neg? escape-iter) \* \space)))
(println)))
(defn render-image
- "Given a mandlebrot set membership grid as returned by a call to
- `mandlebrot`, returns a BufferedImage with the same resolution as the
+ "Given a mandelbrot set membership grid as returned by a call to
+ `mandelbrot`, returns a BufferedImage with the same resolution as the
grid that uses a discrete grayscale color palette."
- [mandlebrot-grid]
+ [mandelbrot-grid]
(let [palette (vec (for [c (range 500)]
(Color/getHSBColor 0.0 0.0 (/ (Math/log c) (Math/log 500)))))
- height (count mandlebrot-grid)
- width (count (first mandlebrot-grid))
+ height (count mandelbrot-grid)
+ width (count (first mandelbrot-grid))
img (BufferedImage. width height BufferedImage/TYPE_INT_RGB)
^java.awt.Graphics2D g (.getGraphics img)]
- (doseq [[y row] (map-indexed vector mandlebrot-grid)
+ (doseq [[y row] (map-indexed vector mandelbrot-grid)
[x escape-iter] (map-indexed vector row)]
(.setColor g (if (neg? escape-iter)
(palette 0)
@@ -78,3 +92,44 @@
(.drawRect g x y 1 1))
(.dispose g)
img))
+
+(defn- coerce-mandelbrot-args
+ [args]
+ (for [x args]
+ (if (= \: (first x))
+ (keyword (subs x 1))
+ (try
+ (if (.contains x ".")
+ (Double/parseDouble x)
+ (Long/parseLong x))
+ (catch NumberFormatException e
+ (println "Invalid number" x))))))
+
+(defn- print-usage []
+ (println "Mandelbrot set visualization from 'Clojure Programming', chapter 11.")
+ (println "Please refer to documentation for com.clojurebook.mandelbrot/mandelbrot for information on what rmin, rmax, etc. mean.")
+ (println)
+ (println "Usage: lein run [:fast] output-path rmin rmax imin imax [:width XXX] [:height YYY] [:depth DDD]")
+ (println " e.g.: lein run mandelbrot.png -2.25 0.75 -1.5 1.5 :width 800 :height 800 :depth 500")
+ (println)
+ (println "Using the :fast option will result in the primitive-optimized `fast-escape` function being used instead of the default (boxing) `escape`."))
+
+(defn -main
+ [& [output-path & opts]]
+ (let [args (coerce-mandelbrot-args opts)]
+ (when (or (not output-path)
+ (seq (filter nil? args))
+ (not (even? (count args))))
+ (print-usage)
+ (System/exit 1))
+ (javax.imageio.ImageIO/write
+ (render-image (apply mandelbrot args))
+ "png" (java.io.File. output-path))
+ (System/exit 0)))
+
+(defn -fast-main
+ "Same as -main, but uses `with-redefs` to replace `escape` with its
+ optimized variant `fast-escape`."
+ [& args]
+ (with-redefs [escape fast-escape]
+ (apply -main args)))
Please sign in to comment.
Something went wrong with that request. Please try again.