diff --git a/project.clj b/project.clj index 6924a0b..cdd79dd 100644 --- a/project.clj +++ b/project.clj @@ -7,19 +7,34 @@ [org.clojure/core.async "0.2.374"] [org.clojure/data.int-map "0.2.1"] [org.clojure/test.check "0.9.0"] + [org.clojure/clojurescript "1.9.36"] [clj-http "2.0.0"] [cljs-http "0.1.38"]] :jvm-opts ^:replace ["-server" "-Xmx2500m"] - :cljsbuild {:builds [{:jar true - :compiler {:output-to "target/testable.js" - :optimizations :advanced}}]} + :plugins [[lein-cljsbuild "1.1.3"]] - :profiles {:dev {:dependencies [[org.clojure/clojurescript "1.7.189"] - [criterium "0.4.3"]] - :plugins [[lein-cljsbuild "1.1.0"] - [com.cemerick/clojurescript.test "0.3.3"] - ;[lein-marginalia "0.8.1-SNAPSHOT"] - ]} + :clean-targets ["public/comportex.js" "public/out"] + + :cljsbuild {:builds + {:main + {:source-paths ["src"] + :jar true + :compiler {:output-dir "public/out" + :output-to "public/comportex.js"}}}} + + :profiles {:dev {:dependencies [[criterium "0.4.3"]] + ;:plugins [[lein-marginalia "0.9.0"]] + :cljsbuild {:builds + {:main + {:compiler + {:optimizations :none + :source-map true}}}}} + ;; Use: "lein with-profile +prod cljsbuild once" + :prod {:cljsbuild {:builds + {:main + {:compiler + {:optimizations :advanced + :source-map "public/comportex.js.map"}}}}} :repl {:source-paths ["dev" "src"]}}) diff --git a/public/comportexjs.html b/public/comportexjs.html new file mode 100644 index 0000000..a9705f5 --- /dev/null +++ b/public/comportexjs.html @@ -0,0 +1,35 @@ + + + + + + +

Comportex from Javascript. Look at the js source!

+ +
+
+        
+    
+
diff --git a/src/org/nfrac/comportex/js.cljc b/src/org/nfrac/comportex/js.cljc
deleted file mode 100644
index e87efb7..0000000
--- a/src/org/nfrac/comportex/js.cljc
+++ /dev/null
@@ -1,303 +0,0 @@
-(ns comportex.js
-  (:require
-    [org.nfrac.comportex.core :as core]
-    [org.nfrac.comportex.encoders :as encoders]
-    [org.nfrac.comportex.protocols :as protocols]
-  ))
-
-;; Public API
-
-;; Converters from js->clj and clj->js
-
-;; See protocols:
-;; PHTM, PRegion, PFeedForward, PFeedBack, PFeedForwardMotor, PLayerOfCells
-;; PSynapseGraph, PSegments, PSense, PSelector, PEncoder, PRestartable, PInterruptable
-;; PTemporal, PParameterised, PTopological, PTopology
-
-;; Convert JavaScript object or array into ClojureScript data structure. 
-;; We can do this by using js->clj function that:
-;; "Recursively transforms JavaScript arrays into ClojureScript vectors, 
-;; and JavaScript objects into ClojureScript maps. With
-;; option ‘:keywordize-keys true’ will convert object fields from
-;; strings to keywords.
-
-;;  `spec` is the parameter specification map. 
-
-(defn js->spec
-  [spec]
-  (js->clj spec :keywordize-keys true))
-
-(defn js->htm
-  [htm]
-  (js->clj htm))
-
-(defn js->motor
-  [motor]
-  (js->clj motor))
-
-(defn js->sensory
-  [sensory]
-  (js->clj sensory))
-
-(defn js->topo
-  [topo]
-  (js->clj topo))
-
-(defn js->region
-  [region]
-  (js->clj region))
-
-
-;; .core
-
-;; ===================================================================
-
-(defn ^:export sensory-region
-  "Constructs a cortical region with one layer.
-
-  `spec` is the parameter specification map. See documentation on
-  `cells/parameter-defaults` for possible keys. Any keys given here
-  will override those default values."
-
-  [spec]
-  (clj->js (core/sensory-region (js->spec spec))))
-
-
-;; ===================================================================
-
-(defn ^:export sensorimotor-region
-  "Constructs a cortical region with two layers. `spec` can contain
-  nested maps under :layer-3 and :layer-4 that are merged in for
-  specific layers.
-
-  This sets `:lateral-synapses? false` in Layer 4, and true in Layer
-  3."
-
-  [spec]
-  (clj->js (core/sensorimotor-region (js->spec spec))))
-
-
-;; ===================================================================
-
-(defn ^:export sense-node
-  "Creates a sense node with given topology, matching the encoder that
-  will generate its bits."
-
-  [topo sensory motor]
-  (clj->js (core/sense-node (js->clj [topo sensory motor]))))
-
-;; ===================================================================
-
-
-(defn ^:export region-network
-  "Builds a network of regions and senses from the given dependency
-  map. The keywords used in the dependency map are used to look up
-  region-building functions, parameter specifications, and sensors in
-  the remaining argments.
-
-  Sensors are defined to be the form `[selector encoder]`, satisfying
-  protocols PSelector and PEncoder respectively. Sensors in the
-  `main-sensors` map can make activating (proximal) connections while
-  those in the `motor-sensors` map can make depolarising (distal)
-  connections. The same sensor may also be included in both maps.
-
-  For each node, the combined dimensions of its feed-forward sources
-  is calculated and used to set the `:input-dimensions` parameter in
-  its `spec`. Also, the combined dimensions of feed-forward motor
-  inputs are used to set the `:distal-motor-dimensions` parameter, and
-  the combined dimensions of its feed-back superior regions is used to
-  set the `:distal-topdown-dimensions` parameter. The updated spec is
-  passed to a function (typically `sensory-region`) to build a
-  region. The build function is found by calling `region-builders`
-  with the region id keyword.
-
-  For example to build the network `inp -> v1 -> v2`:
-
-   `
-   (region-network
-    {:v1 [:input]
-     :v2 [:v1]}
-    {:v1 sensory-region
-     :v2 sensory-region}
-    {:v1 spec
-     :v2 spec}
-    {:input sensor}
-    nil)`"
-  [ff-deps region-builders region-specs main-sensors motor-sensors]
-  (clj->js (core/region-network (js->clj [ff-deps region-builders region-specs main-sensors]motor-sensors))))
-
-;; ===================================================================
-
-(defn ^:export regions-in-series
-  "Constructs an HTM network consisting of n regions in a linear
-  series. The regions are given keys :rgn-0, :rgn-1, etc. Senses feed
-  only to the first region. Their sensors are given in a map with
-  keyword keys. Sensors are defined to be the form `[selector encoder]`.
-
-  This is a convenience wrapper around `region-network`."
-
-  [n build-region specs sensors]
-  (clj->js (core/regions-in-series (js->clj [n build-region specs sensors]))))
-
-;; ===================================================================
-
-(defn ^:export column-state-freqs
-  "Returns a map with the frequencies of columns in states
-  `:active` (bursting), `:predicted`, `:active-predicted`. Note that
-  these are distinct categories. The names are possibly misleading.
-  Argument `layer-fn` is called on the region to obtain a layer of
-  cells; if omitted it defaults to the output layer."
-  [rgn]
-  (clj->js (core/column-state-freqs (js->clj rgn))))
-
-;; ===================================================================
-
-(defn ^:export predicted-bit-votes
-  [rgn]
-  (clj->js (core/predicted-bit-votes (js->clj rgn))))
-
-;; ===================================================================
-
-(defn cells-proximal-bit-votes
-  "For decoding. Given a set of cells in the layer, returns a map from
-  incoming bit index to the number of connections to that bit from the
-  cells' columns."
-  [lyr cells]
-  (clj->js (core/cells-proximal-bit-votes (js->clj [lyr cells]))))
-
-;; ===================================================================
-
-(defn ^:export predictions
-  [htm sense-id n-predictions]
-  (clj->js (core/predictions (js->clj [htm sense-id n-predictions]))))
-
-;; ===================================================================
-
-(defn ^:export cell-excitation-breakdowns
-  "Calculates the various sources contributing to total excitation
-  level of each of the `cell-ids` in the given layer. Returns a map
-  keyed by these cell ids. Each cell value is a map with keys
-
-  * :total - number.
-  * :proximal-unstable - a map keyed by source region/sense id.
-  * :proximal-stable - a map keyed by source region/sense id.
-  * :distal - a map keyed by source region/sense id.
-  * :boost - number.
-  "
-
-  [htm prior-htm rgn-id lyr-id cell-ids]
-  (clj->js (core/cell-excitation-breakdowns (js->clj [htm prior-htm rgn-id lyr-id cell-ids]))))
-
-;; ===================================================================
-
-(defn ^:export update-excitation-breakdown
-  "Takes an excitation breakdown such as returned under one key from
-  cell-excitation-breakdowns, and updates each numeric component with
-  the function f. Key :total will be updated accordingly. The default
-  is to scale the values to a total of 1.0. To aggregate breakdowns,
-  use `(util/deep-merge-with +)`."
-
-  [breakdown]
-  (clj->js (core/update-excitation-breakdown (js->clj breakdown))))
-
-;; protocols
-;; mostly just protocols (ie. interfaces)
-
-(defn ^:export htm-step
-  [htm inval]
-  (clj->js (protocols/htm-step (js->clj [htm inval]))))
-
-
-;; encoders
-
-(defn ^:export vec-selector
-  [& selectors]
-  (clj->js (encoders/vec-selector (js->clj selectors))))
-
-(defn ^:export prediction-stats
-  [x-bits bit-votes total-votes]
-  (clj->js (encoders/prediction-stats (js->clj [x-bits bit-votes total-votes]))))
-
-(defn ^:export decode-by-brute-force
-  [e try-values bit-votes]
-  (clj->js (encoders/decode-by-brute-force (js->clj [e try-values bit-votes]))))
-
-(defn ^:export encat
-  "Returns an encoder for a sequence of values, where each is encoded
-  separately before the results are concatenated into a single
-  sense. Each value by index is passed to the corresponding index of
-  `encoders`."
-
-  [encoders]
-  (clj->js (encoders/encat (js->clj encoders))))
-
-(defn ^:export ensplat
-  "Returns an encoder for a sequence of values. The given encoder will
-  be applied to each value, and the resulting encodings
-  overlaid (splatted together), taking the union of the sets of bits."
-
-  [encoders]
-  (encoders/ensplat (js->clj encoders)))
-
-(defn ^:export linear-encoder
-  [dimensions n-active lower upper]
-  (clj->js (encoders/linear-encoder (js->clj [dimensions n-active [lower upper] ]))))
-
-(defn ^:export category-encoder
-  [dimensions values]
-  (clj->js (encoders/category-encoder (js->clj [dimensions values]))))
-
-(defn ^:export no-encoder
-  [dimensions]
-  (clj->js (encoders/no-encoder (js->clj dimensions))))
-
-(defn ^:export unique-sdr
-  [x n-bits n-active]
-  (clj->js (encoders/unique-sdr (js->clj [x n-bits n-active]))))
-
-(defn ^:export unique-encoder
-  "This encoder generates a unique bit set for each distinct value,
-  based on its hash. `dimensions` is given as a vector."
-
-  [dimensions n-active]
-  (encoders/unique-encoder (js->clj [dimensions n-active])))
-
-(defn ^:export linear-2d-encoder
-  "Returns a simple encoder for a tuple of two numbers representing a
-  position in rectangular bounds. The encoder maps input spatial
-  positions to boxes of active bits in corresponding spatial positions
-  of the encoded sense. So input positions close in both coordinates
-  will have overlapping bit sets.
-
-  * `dimensions` - of the encoded bits, given as a vector [nx ny].
-
-  * `n-active` is the number of bits to be active.
-
-  * `[x-max y-max]` gives the numeric range of input space to
-  cover. The numbers will be clamped to this range, and below by
-  zero."
-
-  [dimensions n-active x-max y-max]
-  (clj->js (encoders/linear-2d-encoder (js->clj [dimensions n-active [x-max y-max]]))))
-
-(defn ^:export coordinate-encoder
-  "Coordinate encoder for integer coordinates, unbounded, with one,
-  two or three dimensions. Expects a coordinate, i.e. a sequence of
-  numbers with 1, 2 or 3 elements. These raw values will be multiplied
-  by corresponding `scale-factors` to obtain integer grid
-  coordinates. Each dimension has an associated radius within which
-  there is some similarity in encoded SDRs."
-
-  [dimensions n-active scale-factors radii]
-  (clj->js (encoders/coordinate-encoder (js->clj [dimensions n-active scale-factors radii]))))
-
-(defn ^:export sampling-linear-encoder
-  [dimensions n-active lower upper radius]
-  (clj->js (encoders/sampling-linear-encoder (js->clj [dimensions n-active [lower upper] radius]))))
-
-(defn ^:export sensor-cat
-  [& sensors]
-  (clj->js (encoders/sensor-cat (js->clj sensors))))
-
-
-
diff --git a/src/org/nfrac/comportex/js.cljs b/src/org/nfrac/comportex/js.cljs
new file mode 100644
index 0000000..febfaa7
--- /dev/null
+++ b/src/org/nfrac/comportex/js.cljs
@@ -0,0 +1,166 @@
+(ns org.nfrac.comportex.js
+  (:require
+    [org.nfrac.comportex.core :as core]
+    [org.nfrac.comportex.encoders :as encoders]
+    [org.nfrac.comportex.protocols :as p]))
+
+;; Minimal public API
+
+;; Complex clojurescript objects such as encoders and HTM models and their
+;; constituent regions and layers are not converted to native javascript types.
+;; They are left as black box values intended to be used with other API fns.
+
+;;  `spec` is the parameter specification map.
+
+(defn js->spec
+  [spec]
+  ;; TODO: handle keyword values such as for :spatial-pooling key
+  (js->clj spec :keywordize-keys true))
+
+(defn js->selector
+  [selector]
+  (cond
+   (satisfies? p/PSelector selector) selector
+   (string? selector) (keyword selector)
+   (array? selector) (mapv js->selector selector)
+   :else (throw (js/Error. (str "unknown selector " selector)))))
+
+;; .core
+
+;; ===================================================================
+
+(defn ^:export regions-in-series
+  "Constructs an HTM network consisting of n regions in a linear
+  series. The regions are given keys :rgn-0, :rgn-1, etc. Senses feed
+  only to the first region. Their sensors are given in a map with
+  keyword keys. Sensors are defined to be the form `[selector encoder]`.
+  Encoders should be passed as clojure objects (as from encoder fns).
+  Selectors should be passed as strings or arrays of strings, which
+  select the value at that key or nested path of keys in an input value."
+  [n specs sensors]
+  (let [build-region core/sensory-region
+        specs (map js->spec specs)
+        sensors (into {} (for [k (js-keys sensors)]
+                           (let [[selector encoder] (aget sensors k)]
+                             [(keyword k)
+                              [(js->selector selector)
+                               ;; do not convert encoders
+                               encoder]])))]
+    (core/regions-in-series n build-region specs sensors)))
+
+;; ===================================================================
+
+(defn ^:export region-seq
+  "Returns a js array of regions, each a clojure object."
+  [htm]
+  (let [arr (array)]
+    (doseq [x (core/region-seq htm)]
+      (.push arr x))
+    arr))
+
+;; ===================================================================
+
+(defn ^:export column-state-freqs
+  "Returns a map with the frequencies of columns in states
+  `active` (bursting), `predicted`, `active-predicted`. Note that
+  these are distinct categories. The names are possibly misleading."
+  [rgn]
+  (clj->js (core/column-state-freqs rgn)))
+
+;; ===================================================================
+
+(defn ^:export predictions
+  [htm sense-id n-predictions]
+  (clj->js (core/predictions htm (keyword sense-id) n-predictions)))
+
+;; protocols
+;; mostly just protocols (ie. interfaces)
+
+(defn ^:export htm-step
+  "Compute the next time step. Pass `htm` as a clojure object, but `inval` as
+  a js value which will be converted to a clojurescript value, including
+  keywordizing keys."
+  [htm inval]
+  (p/htm-step htm (js->clj inval :keywordize-keys true)))
+
+(defn ^:export encode
+  [encoder x]
+  (clj->js (p/encode encoder (js->clj x))))
+
+(defn ^:export decode
+  [encoder bit-votes n]
+  (clj->js (p/decode encoder (js->clj bit-votes) n)))
+
+(defn ^:export timestep
+  [htm]
+  (p/timestep htm))
+
+;; encoders
+
+(defn ^:export encat
+  "Returns an encoder for a sequence of values, where each is encoded
+  separately before the results are concatenated into a single
+  sense. Each value by index is passed to the corresponding index of
+  `encoders`."
+  [encoders]
+  (encoders/encat encoders))
+
+(defn ^:export ensplat
+  "Returns an encoder for a sequence of values. The given encoder will
+  be applied to each value, and the resulting encodings
+  overlaid (splatted together), taking the union of the sets of bits."
+  [encoders]
+  (encoders/ensplat encoders))
+
+(defn ^:export linear-encoder
+  [dimensions n-active lower upper]
+  (encoders/linear-encoder (vec dimensions) n-active [lower upper]))
+
+(defn ^:export category-encoder
+  [dimensions values]
+  (encoders/category-encoder (vec dimensions) (js->clj values)))
+
+(defn ^:export no-encoder
+  [dimensions]
+  (encoders/no-encoder (vec dimensions)))
+
+(defn ^:export unique-encoder
+  "This encoder generates a unique bit set for each distinct value,
+  based on its hash. `dimensions` is given as a vector."
+  [dimensions n-active]
+  (encoders/unique-encoder (vec dimensions) n-active))
+
+(defn ^:export linear-2d-encoder
+  "Returns a simple encoder for a tuple of two numbers representing a
+  position in rectangular bounds. The encoder maps input spatial
+  positions to boxes of active bits in corresponding spatial positions
+  of the encoded sense. So input positions close in both coordinates
+  will have overlapping bit sets.
+
+  * `dimensions` - of the encoded bits, given as a vector [nx ny].
+
+  * `n-active` is the number of bits to be active.
+
+  * `[x-max y-max]` gives the numeric range of input space to
+  cover. The numbers will be clamped to this range, and below by
+  zero."
+  [dimensions n-active x-max y-max]
+  (encoders/linear-2d-encoder (vec dimensions) n-active [x-max y-max]))
+
+(defn ^:export coordinate-encoder
+  "Coordinate encoder for integer coordinates, unbounded, with one,
+  two or three dimensions. Expects a coordinate, i.e. a sequence of
+  numbers with 1, 2 or 3 elements. These raw values will be multiplied
+  by corresponding `scale-factors` to obtain integer grid
+  coordinates. Each dimension has an associated radius within which
+  there is some similarity in encoded SDRs."
+  [dimensions n-active scale-factors radii]
+  (encoders/coordinate-encoder (vec dimensions) n-active (vec scale-factors) (vec radii)))
+
+(defn ^:export sampling-linear-encoder
+  [dimensions n-active lower upper radius]
+  (encoders/sampling-linear-encoder (vec dimensions) n-active [lower upper] radius))
+
+(defn ^:export sensor-cat
+  [& sensors]
+  (encoders/sensor-cat sensors))