Skip to content

Commit f180a62

Browse files
committed
Add all code from original repo
1 parent e311bfb commit f180a62

22 files changed

+1116
-3
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ pom.xml
22
*jar
33
/lib/
44
/classes/
5+
.lein-failures
56
.lein-deps-sum
7+
.#*
8+
.DS_Store

README.md

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,134 @@
1-
clojure-cise-2012
2-
=================
1+
# Clojure examples for Computing in Science and Engineering article, 2012
2+
3+
Wherein we investigate parallelism in Clojure to pique the interests of Fortran/C++ coders.
4+
5+
## Installation
6+
7+
We recommend installing Leiningen. Leiningen will help install Clojure and it make it easy to run tests and start a REPL. For more information on Leiningen, including installation instructions,
8+
go to [http://github.com/technomancy/leiningen].
9+
10+
Buttons to download a .zip or a .tar.gz of the contents can be found [the download page](https://github.com/dmiller/clojure-cise-2012/downloads) of our Github repository. Download and unzip to somewhere. Start a terminal/cmd/whatever you call it and cd to where you unzipped the code.
11+
12+
To run the tests,
13+
14+
$ lein test
15+
16+
To start a REPL,
17+
18+
$ lein repl
19+
20+
Once you have the REPL going:
21+
22+
user=> (load "cise/core")
23+
nil
24+
user=> (in-ns 'cise.core)
25+
#<Namespace cise.core>
26+
cise.core=>
27+
28+
To test the _stats_ program:
29+
30+
cise.core=> (test-sum 1000)
31+
[1000 500.34570874019175 0.5003457087401918]
32+
33+
To test the _poisson_ program:
34+
35+
cise.core=> (def a (double-array 100)) ; double array, size 100, all 0
36+
#'cise.core/a
37+
38+
cise.core=> (aset a 50 1.0) ; let's get at least one non-zero value in there
39+
1.0
40+
41+
cise.core=> (def r (agent-solver a 5 200)) ; 5 slices, 200 iterations
42+
#'cise.core/r
43+
44+
cise.core=> (aget r 50)
45+
5.634847900925116
46+
47+
cise.core=> (aget r 49)
48+
5.163022140429744
49+
50+
Time to get serious:
51+
52+
cise.core=> (def a (double-array 1000000))
53+
#'cise.core/a
54+
55+
cise.core=> (aset a 500000 1.0)
56+
1.0
57+
58+
cise.core=> (time (agent-solver a 1 1000)) ; 1,000,000 points, 1 thread, 1000 iterations
59+
"Elapsed time: 6271.475 msecs"
60+
61+
cise.core=> (time (agent-solver a 2 1000)) ; 2 threads
62+
"Elapsed time: 4152.594 msecs"
63+
64+
cise.core=> (time (agent-solver a 4 1000))
65+
"Elapsed time: 3212.367 msecs"
66+
67+
cise.core=> (time (agent-solver a 5 1000))
68+
"Elapsed time: 3272.189 msecs"
69+
70+
cise.core=> (time (agent-solver a 6 1000))
71+
"Elapsed time: 3130.349 msecs"
72+
73+
cise.core=> (time (agent-solver a 7 1000))
74+
"Elapsed time: 3124.874 msecs"
75+
76+
cise.core=> (time (agent-solver a 8 1000))
77+
"Elapsed time: 3130.097 msecs"
78+
79+
cise.core=> (time (agent-solver a 16 1000))
80+
"Elapsed time: 3132.442 msecs"
81+
82+
On a four-core machine, max speedup occurs at around 6 or 7 slices -- NumCores+2 is not unexpected. The thread pooler throttles us from there.
83+
84+
To run the _sim_ program, it is best to move to the cise.sim namespace.
85+
86+
cise.core=> (in-ns 'cise.sim)
87+
#<Namespace cise.sim>
88+
89+
We have left in some print statements so that you can see the ants behaving. After you start the simulation running, you stop it by evaluating
90+
91+
(def running false)
92+
93+
However, you will be trying to type that while the output is scrolling, so good luck! After you've typed, all running agents will do one more iteration and shut down. You can then ask for report on the status of all the ants.
94+
95+
cise.sim=> (start)
96+
"Creating ant at " [4 5]
97+
"Creating ant at " [5 1]
98+
"Creating ant at " [3 2]
99+
...
100+
nil
101+
102+
cise.sim=>
103+
5 ": Behaving at " [7 4]
104+
0 ": Behaving at " [4 5]
105+
4 ": Behaving at " [9 0]
106+
5 ": From " [7 4] " to " [7 5] ": " [7 5]
107+
4 ": From " [9 0] " to " [0 0] ": " [0 0]
108+
0 ": From " [4 5] " to " [3 4] ": " [3 4]
109+
2 ": Behaving at " [3 2]
110+
2 ": From " [3 2] " to " [2 3] ": " [2 3]
111+
3 ": Behaving at " [5 1]
112+
3 ": From " [5 1] " to " [6 0] ": " [6 0]
113+
6 ": From " [9 4] " to " [9 3] ": " [9 3]
114+
7 ": From " [0 0] " to " [9 1] ": " [9 1]
115+
...
116+
117+
The number on the left of each line is the ant id.
118+
Somewhere along the way, I typed (def running false), and eventually output ceased.
119+
120+
cise.sim=>
121+
cise.sim=> (report)
122+
({:moves 21, :stays 2, :id 2} {:moves 18, :stays 4, :id 8}
123+
{:moves 23, :stays 0, :id 6} {:moves 22, :stays 1, :id 3}
124+
...)
125+
126+
127+
Type Ctrl-C to get out of the REPL.
128+
129+
## License
130+
131+
Copyright (C) 2012 Marty Kalin and David Miller
132+
133+
Distributed under the Eclipse Public License, the same as Clojure.
3134

4-
Code for Computing in Science &amp; Engineering article on Clojure, Vol 14, No 6 (Nov/Dec 2012)

project.clj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
(defproject cise2012 "1.0.0-SNAPSHOT"
2+
:description "Sample solver for 1D Poisson problems"
3+
:url "https://github.com/dmiller/clojure-cise-2012"
4+
:dependencies [[org.clojure/clojure "1.3.0"]])

src/cise/agents.clj

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
(ns cise.agents
2+
(:use [cise.simple :only (step-slice)]
3+
[cise.poisson :only (slice-sizes)]))
4+
5+
(set! *warn-on-reflection* true)
6+
7+
;; Agent-based approach #1
8+
;; Requires two copies of the array, current and next
9+
;; Works one iteration at a time
10+
;;
11+
;; Agent state is a map with the following
12+
;; :agent-idx -- index of the agent for this slice, for debugging primarily
13+
;; :start-idx -- index of first position of slice106
14+
;; :end-idx -- index of first position of next slice
15+
;; :src -- array with current values
16+
;; :dest -- array to put next iteration in
17+
;; :rhos -- yep
18+
;;
19+
;; The src, dest, and rhos array could be passed in def'd vars,
20+
;; but I'd rather put them in the state.
21+
;;
22+
;; This will be a case-2 solution -- we will put the end values in position 0,n-1.
23+
;; Assume we are handed the initial array with this satisfied. Rhos also.
24+
;; Thus the first slice will have :start-idx == 1
25+
;; And the last slice will have :end-idx == n-1
26+
;;
27+
;; We will swap src & dest at each iteration.
28+
29+
(defn step-agent1-slice [slice]
30+
(let [src (:src slice)
31+
dest (:dest slice)]
32+
(step-slice src
33+
dest
34+
(:rhos slice)
35+
(:start-idx slice)
36+
(:end-idx slice))
37+
(assoc slice :src dest :dest src)))
38+
39+
(defn agent1-solver [init-array rhos num-slices num-iters ]
40+
(let [n (count init-array)
41+
arr2 (double-array n)
42+
sizes (slice-sizes (- n 2) num-slices)
43+
start-indexes (reductions + 1 sizes)
44+
make-slice (fn [i start-idx end-idx]
45+
{:agt-idx i ; for debugging
46+
:start-idx start-idx
47+
:end-idx end-idx
48+
:src init-array
49+
:dest arr2
50+
:rhos rhos})
51+
slices (map make-slice (range num-slices) start-indexes (rest start-indexes))
52+
agts (map agent slices)]
53+
(dotimes [i num-iters]
54+
(doseq [a agts] (send a step-agent1-slice))
55+
(apply await agts))
56+
(:src @(first agts))))
57+

src/cise/core.clj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
(ns cise.core
2+
(use [cise stats poisson sim agents futures simple]))
3+
4+
;; This namespace just pulls in all the other namespaces to play with.
5+
6+

src/cise/futures.clj

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
(ns cise.futures
2+
(:use [cise.poisson :only (slice-sizes)]
3+
[cise.simple :only (step-slice)]))
4+
5+
(set! *warn-on-reflection* true)
6+
7+
;; iterative solves 1-D Poisson problem
8+
;; rhos is a (Java) array of doubles.
9+
;; requires dummy values in first and last position
10+
;; Does num-iters iteration to solve
11+
;; Number of threads = num-slices
12+
;; On each iteration
13+
;; create a future on each slice
14+
;; Wait for each future to be computed
15+
;; Swap source and destination arrays for next iteration
16+
17+
(defn future-solver [rhos num-slices num-iters]
18+
(let [n (count rhos)
19+
dest1 (double-array n)
20+
dest2 (double-array n)
21+
sizes (slice-sizes (- n 2) num-slices)
22+
start-indexes (reductions + 1 sizes)
23+
end-indexes (rest start-indexes)]
24+
(loop [i 0 src dest1 dest dest2]
25+
(if (< i num-iters)
26+
(let [fs (doall (map #(future (step-slice src dest rhos %1 %2)) start-indexes end-indexes))]
27+
(doseq [f fs] @f)
28+
(recur (inc i) dest src))
29+
src))))

src/cise/poisson.clj

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
(ns cise.poisson)
2+
3+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
4+
;;
5+
;; Agent example
6+
;;
7+
;; Compute a solution to 1D Poisson problem.
8+
;;
9+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
10+
11+
12+
(set! *warn-on-reflection* true)
13+
14+
;; Compute the next value at a location (unhinted version)
15+
(defn next-value-no-hint [rho left right]
16+
(* 0.5 (+ (+ left right) rho)))
17+
18+
;; Compute the next value at a location (unhinted version)
19+
(defn next-value
20+
^double [^double rho ^double left ^double right]
21+
(* 0.5 (+ (+ left right) rho)))
22+
23+
;; Update the slice [start-index, end-index) in array arr
24+
(defn step-slice-in-place [^doubles arr ^doubles rhos start-index end-index lval rval]
25+
(let [last-idx (unchecked-dec (int end-index))]
26+
(loop [old-val (double lval)
27+
i (int start-index)]
28+
(if (< i last-idx)
29+
(let [cur-val (aget arr i)]
30+
(aset arr i (next-value (aget rhos i) old-val (aget arr (inc i))))
31+
(recur cur-val (unchecked-inc i)))
32+
(aset arr last-idx (next-value (aget rhos i) old-val (double rval)))))))
33+
34+
;; Agent state is a map with the following:
35+
;; :agent-idx -- index of the agent for this slice
36+
;; :start-idx -- index of first position of slice
37+
;; :end-idx -- index of first position of next slice
38+
;; :niters -- the number of iterations to perform
39+
;; :iter -- current iteration number
40+
;; :lq -- queue of values coming from the slice to the left
41+
;; :rq -- queue of values coming from the slice to the right
42+
;;
43+
;; Careful analysis shows that no slice can be more than one ahead of any other slice,
44+
;; so a queue never gets larger than 2. But it can be two.
45+
46+
47+
(def agents (ref nil)) ; a vector of agents
48+
(def rhos (ref nil)) ; an array of doubles
49+
(def arr (ref nil)) ; an array of doubles, the work vector
50+
(def latch (ref nil)) ; a countdown latch
51+
52+
53+
(defn agt-at [i]
54+
(get @agents i))
55+
56+
(defn left-agt [i]
57+
(agt-at (dec i)))
58+
59+
(defn right-agt [i]
60+
(agt-at (inc i)))
61+
62+
63+
(defn step-slice-data [slice lval rval]
64+
;; (:key map) is equivalent to (get map :key)
65+
(step-slice-in-place @arr @rhos (:start-idx slice) (:end-idx slice) lval rval))
66+
67+
(declare update-slice)
68+
69+
(defn enqueue-left [slice value]
70+
(update-slice (assoc slice :lq (conj (:lq slice) value))))
71+
72+
(defn enqueue-right [slice value]
73+
(update-slice (assoc slice :rq (conj (:rq slice) value))))
74+
75+
(defn notify-neighbors [i lval rval]
76+
(let [lagt (left-agt i)
77+
ragt (right-agt i)
78+
agt (agt-at i)]
79+
(if (nil? lagt)
80+
(send agt enqueue-left 0.0)
81+
(send lagt enqueue-right lval))
82+
(if (nil? ragt)
83+
(send agt enqueue-right 0.0)
84+
(send ragt enqueue-left rval))))
85+
86+
87+
(defn update-slice [slice]
88+
(let [{:keys [lq rq iter niters agt-idx start-idx end-idx]} slice
89+
lval (peek lq)
90+
rval (peek rq)]
91+
(if (or (nil? lval) (nil? rval) (>= iter niters))
92+
slice
93+
(do
94+
(step-slice-data slice lval rval)
95+
(notify-neighbors agt-idx
96+
(aget ^doubles @arr start-idx)
97+
(aget ^doubles @arr (dec end-idx)))
98+
(when (= (inc iter) niters)
99+
(swap! @latch dec))
100+
(assoc slice :iter (inc iter) :lq (pop lq) :rq (pop rq))))))
101+
102+
103+
(defn slice-sizes [n num-slices]
104+
(let [r (rem n num-slices)
105+
d (quot n num-slices)]
106+
(concat (repeat r (inc d)) (repeat (- num-slices r) d))))
107+
108+
109+
(defn create-agents [nvals nslices niters]
110+
(let [start-indexes (reductions + 0 (slice-sizes nvals nslices))
111+
q0 (conj clojure.lang.PersistentQueue/EMPTY 0.0) ;; immutable, so all can share
112+
make-slice (fn [i start-idx end-idx]
113+
{:agt-idx i
114+
:start-idx start-idx
115+
:end-idx end-idx
116+
:niters niters
117+
:iter 0
118+
:lq q0
119+
:rq q0})
120+
slices (map make-slice (range nvals) start-indexes (rest start-indexes))]
121+
(into [] (map agent slices))))
122+
123+
124+
(defn start-agents [agts]
125+
(doseq [a agts]
126+
(send a update-slice)))
127+
128+
(defn agent-solver [rs nslices niters]
129+
(let [nvals (count rs)
130+
agts (create-agents nvals nslices niters)
131+
p (promise)
132+
a-watch (fn [key a old-val new-val]
133+
(when (zero? new-val)
134+
(deliver p true)))]
135+
(dosync
136+
(ref-set agents agts)
137+
(ref-set arr (double-array nvals))
138+
(ref-set rhos rs)
139+
(ref-set latch (atom nslices)))
140+
(add-watch @latch :zero a-watch)
141+
(start-agents agts)
142+
@p
143+
@arr))

0 commit comments

Comments
 (0)