completely breaking the graph stuff out of the main code
[propagator.git] / propagator.org
blob86c54d0fb01f94a60fdbdba470d3ca39fcefac3f
1 #+TITLE: Concurrent Propagator System
2 #+OPTIONS: num:nil ^:nil
3 #+LaTeX_CLASS: normal
5 * code
6   :PROPERTIES:
7   :tangle:   src/propagator.clj
8   :END:
9 #+begin_src clojure
10   (ns
11       #^{:author "Eric Schulte",
12          :license "GPLV3",
13          :doc "Simple concurrent propagator system."}
14     propagator
15     (:use clojure.contrib.repl-utils clojure.contrib.math))
16   
17   (defmacro cell "Define a new cell."
18     [name state]
19     `(def ~name (agent ~state)))
20   
21   (defn set-cell "Set the value of a cell" [cell value]
22     (send cell (fn [_] value)))
23   
24   (defmacro propagator "Define a new propagator."
25     [name in-cells out-cells & body]
26     `(do
27        (defn ~(with-meta name
28                 (assoc (meta name)
29                   :in-cells in-cells :out-cells out-cells))
30          ~in-cells ~@body)
31        (doseq [cell# ~in-cells] (add-neighbor cell# ~name))
32        ~name))
33   
34   (defmacro run-propagator
35     "Run a propagator, first collect the most recent values from all
36   cells associated with the propagator, then evaluate."
37     [propagator]
38     `(let [results# (apply ~propagator (map deref (:in-cells ^#'~propagator)))]
39        (doseq [cell# (:out-cells ^#'~propagator)]
40          (when (not (= @cell# results#))
41            (send cell# (fn [_#] results#))))
42        results#))
43   
44   (defmacro add-neighbor "Add a neighbor to the given cell."
45     [cell neighbor]
46     `(add-watcher
47       ~cell :send
48       (agent nil :validator (fn [_#] (do (future (run-propagator ~neighbor)) true)))
49       (fn [_# _#])))
50 #+end_src
51 * extensions
52 ** graphs of the propagator network
53 draw a graph in a JFrame
54 #+begin_src clojure :tangle src/graphs.clj
55   (in-ns 'propagator)
56   (use 'vijual)
57   (import '(javax.swing JFrame JPanel)
58           '(java.awt Color Graphics)
59           '(java.awt.image BufferedImage))
60   
61   ;; saving graph information
62   ;; record keeping for graphing propagators
63   (def prop-graph {})
64   (defmacro remember-prop [name in-cells out-cells]
65     `(def prop-graph
66           (assoc prop-graph
67             (quote ~name)
68             {:in-cells (map (fn [x#] (name x#)) (quote ~in-cells))
69              :out-cells (map (fn [x#] (name x#)) (quote ~out-cells))})))
70   
71   (defmacro propagator "Define a new propagator."
72     [name in-cells out-cells & body]
73     `(do
74        (remember-prop ~name ~in-cells ~out-cells)
75        (defn ~(with-meta name
76                 (assoc (meta name)
77                   :in-cells in-cells :out-cells out-cells))
78          ~in-cells ~@body)
79        (doseq [cell# ~in-cells] (add-neighbor cell# ~name))
80        ~name))
81   
82   ;; stuff for graphing
83   (def dim [300 300])
84   
85   (def frame (JFrame.))
86   
87   (defn view "Display a graph generated by vijual" [img]
88     (let [mult 1.5
89           width (* (.getWidth img) mult)
90           height (* (.getHeight img) mult)
91           offset 50
92           panel (doto (proxy [JPanel] []
93                         (paint [g]
94                                (.drawImage g img offset offset nil))))]
95       (doto frame (.add panel) .pack (.setSize (+ offset width) (+ offset height)).show)))
96   
97   (defn graph-propagators []
98     (vec (set
99           (apply concat
100                  (map (fn [key]
101                         (let [hsh (get prop-graph key)]
102                           (concat
103                            (map #(vec (list % (name key))) (get hsh :in-cells))
104                            (map #(vec (list (name key) %)) (get hsh :out-cells)))))
105                       (keys prop-graph))))))
106   
107   (defn view-network []
108     (view (draw-directed-graph-image (graph-propagators))))
109   
110   (defn clear-network []
111     (def prop-graph {})
112     (def frame (JFrame.)))
113 #+end_src
115 * usage
116 ** doubling a number -- simplest
117 #+begin_src clojure
118   (cell in-c 0)
119   (cell out-c 0)
120   (propagator doubler [in-c] [out-c] (* 2 in))
121   ;; then any updates to in
122   (set-cell in-c 1)
123   ;; will propagate to out
124   @out-c
125 #+end_src
127 ** square roots -- heron
128 #+begin_src clojure :tangle src/heron.clj
129   (in-ns 'propagator)
130   (cell guess 1)
131   (cell x 9)
132   (cell done false)
133   (cell margin 0.1)
134   
135   (propagator enough [x guess] [done]
136     (Thread/sleep 1000)
137     (if (< (abs (- (* guess guess) x)) @margin) true false))
138   
139   (propagator heron [x done guess] [guess]
140     (Thread/sleep 1000)
141     (if done
142       guess
143       (/ (+ guess (/ x guess)) 2.0)))
144 #+end_src
146 ** web server
147 Alright, this will use Clojure's [[http://github.com/mmcgrana/ring][Ring]] web server middle-ware.
149 So, I guess the best demo here would be some reading/writing through a
150 MVC setup.
152 The =app= will dispatch the incoming data to input cell by the route
153 at the end of the url, then there can be a couple of output cells
154 which will render different views of the related data.
156 #+begin_src clojure :tangle src/web.clj
157   (load-file "/home/eschulte/src/propagator/src/propagator.clj")
158   (in-ns 'propagator)
159   (use 'ring.adapter.jetty)
160   (use 'clojure.contrib.seq-utils)
161   (import 'java.util.Date 'java.text.SimpleDateFormat)
162   
163   ;; cells
164   (cell names '())
165   (cell input "")
166   
167   (propagator adder [input names] [names]
168               (when (> (count (seq input)) 0)
169                 (set (cons (str input " :1") names))))
170   
171   (defn app [req]
172     (or
173      ;; page to accept input
174      (when (= "/" (:uri req))
175        {:status 200
176         :headers {"Content-Type" "text/html"
177                   "Title" "3000"}
178         :body (apply str
179                      "<form name=\"\" action=\"add\" method=\"get\">"
180                      "Word: <input type=\"type\" name=\"word\" />"
181                      "<input type=\"submit\" value=\"Submit\" />"
182                      "</form>")})
183      ;; dump value into "input" cell
184      (when (re-matches #"/add" (:uri req))
185        (set-cell input (second (re-matches #".+=(.+)" (:query-string req))))
186        {:status 303 :headers {"Location" "../list"}})
187      ;; render the value of the "list" cell
188      (when-let [matched (re-matches #"/list" (:uri req))]
189        {:status  200
190         :headers {"Content-Type" "text/html"}
191         :body    (apply str (flatten (list "<ul>"
192                                            (map #(str "<li>"%"</li>") @names)
193                                            "</ul>"
194                                            "<p><a href=\"/\">another word</a></p>")))})))
195   
196   (run-jetty #'app {:port 3000})
197 #+end_src
199 * notes
200 ** look at mutable data stores
201 - http://clojure.org/agents
202 - http://clojure.org/atoms
203 - http://clojure.org/refs
205 most definitely will use agents, functions to set their values are
206 applied to them with send (or send-off if potentially I/O bound), they
207 support validators, and they can be set to run actions (i.e. alert
208 propagators) as part of their update cycle (with add-watcher).
210 ** design to permit distribution across multiple machines
211 it should be possible to wrap these mutable items including
212 - network connections from other machines
213 - hardware (timers, I/O devices, etc...)
215 in cells, so that the propagator system remains "pure"