├── .gitignore ├── LICENSE ├── README.md ├── assets ├── circle-res128.jpg ├── circle-res32.jpg ├── circle-res64.jpg ├── noise-res128.jpg ├── noise-res32.jpg └── noise-res64.jpg ├── src ├── contours.org ├── core.org ├── index.org └── setup.org ├── tangle.sh └── test └── core.org /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # thi.ng/ndarray 2 | 3 | ![2d contour lines example](assets/noise-res128.jpg) 4 | 5 | [example](src/contours.org#examples) 6 | 7 | This library is a Clojure/Clojurescript port of Mikola Lysenko's 8 | [ndarray core](https://github.com/scijs/ndarray) JS library for 9 | multidimensional arrays with almost zero-cost views, re-ordering, 10 | skipping and extended features and to make more sense in a Clojure 11 | context. 12 | 13 | Please see [index.org](src/index.org) for further information & examples. 14 | 15 | ## Current Leiningen coordinates 16 | 17 | ```clj 18 | [thi.ng/ndarray "0.3.3"] 19 | ``` 20 | -------------------------------------------------------------------------------- /assets/circle-res128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thi-ng/ndarray/e619f48d5dec20e5001c92de916d32004271d0d9/assets/circle-res128.jpg -------------------------------------------------------------------------------- /assets/circle-res32.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thi-ng/ndarray/e619f48d5dec20e5001c92de916d32004271d0d9/assets/circle-res32.jpg -------------------------------------------------------------------------------- /assets/circle-res64.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thi-ng/ndarray/e619f48d5dec20e5001c92de916d32004271d0d9/assets/circle-res64.jpg -------------------------------------------------------------------------------- /assets/noise-res128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thi-ng/ndarray/e619f48d5dec20e5001c92de916d32004271d0d9/assets/noise-res128.jpg -------------------------------------------------------------------------------- /assets/noise-res32.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thi-ng/ndarray/e619f48d5dec20e5001c92de916d32004271d0d9/assets/noise-res32.jpg -------------------------------------------------------------------------------- /assets/noise-res64.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thi-ng/ndarray/e619f48d5dec20e5001c92de916d32004271d0d9/assets/noise-res64.jpg -------------------------------------------------------------------------------- /src/contours.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: setup.org 2 | 3 | * Contents :toc_3_gh: 4 | - [[#namespace-thingndarraycontours][Namespace: thi.ng.ndarray.contours]] 5 | - [[#2d-contour-extraction][2D contour extraction]] 6 | - [[#examples][Examples]] 7 | - [[#implementation][Implementation]] 8 | - [[#todo-3d-contour-extraction][TODO 3D contour extraction]] 9 | - [[#level-crossings][Level crossings]] 10 | - [[#examples][Examples]] 11 | - [[#implementation][Implementation]] 12 | - [[#complete-namespace-definition][Complete namespace definition]] 13 | 14 | * Namespace: thi.ng.ndarray.contours 15 | 16 | ** 2D contour extraction 17 | 18 | This section implements the =find-contours2d= function, which accepts 19 | a 2D ndarray and a scalar. It computes a list of point sequences 20 | representing all interpolated level crossings for the given value. 21 | These contour lines can then be further processed or visualized, as 22 | shown in the example below... 23 | 24 | *** Examples 25 | 26 | The following demo utilizes [[http://thi.ng/geom][thi.ng/geom]], [[http://thi.ng/math][thi.ng/math]] and [[http://thi.ng/color][thi.ng/color]] 27 | libs to create SVG based contour maps of generated dummy data. To try 28 | it out yourself, simply add the following dependency (in addition to 29 | this (ndarray) library) - geom itself depends on math & color so they 30 | don't need to be specified in your project: 31 | 32 | #+BEGIN_SRC clojure 33 | [thi.ng/geom "0.0.815"] 34 | #+END_SRC 35 | 36 | These images were generated with the demo code below and show the 37 | impact of different matrix resolutions on the precision and quality of 38 | the resulting visualizations. 39 | 40 | *Note:* Click on images to view in bigger resolution 41 | 42 | **** Simplex noise 43 | 44 | | [[../assets/noise-res32.jpg]] | [[../assets/noise-res64.jpg]] | [[../assets/noise-res128.jpg]] | 45 | | 32 x 32 | 64 x 64 | 128 x 128 | 46 | | 8916 vertices | 19172 vertices | 39702 vertices | 47 | 48 | **** Radial distance (modulated) 49 | 50 | | [[../assets/circle-res32.jpg]] | [[../assets/circle-res64.jpg]] | [[../assets/circle-res128.jpg]] | 51 | | 32 x 32 | 64 x 64 | 128 x 128 | 52 | | 7020 vertices | 14652 vertices | 29874 vertices | 53 | 54 | **** Demo code 55 | 56 | #+BEGIN_SRC clojure :tangle ../babel/examples/contours.clj :mkdirp yes :padline no 57 | (require 58 | '[thi.ng.ndarray.core :as nd] 59 | '[thi.ng.ndarray.contours :as contours] 60 | '[thi.ng.geom.core :as g] 61 | '[thi.ng.geom.core.vector :as v] 62 | '[thi.ng.geom.svg.core :as svg] 63 | '[thi.ng.math.core :as m] 64 | '[thi.ng.math.simplexnoise :as n] 65 | '[thi.ng.color.gradients :as grad]) 66 | 67 | (def res 128) 68 | (def width 640.0) 69 | (def scale (/ width (- res 2))) 70 | (def clipped (- width (* 2.0 scale))) 71 | (def n-scale 0.03) 72 | (def num-contours 60) 73 | 74 | (defn contour->svg 75 | "Takes a single seq of contour coordinates and converts it into an 76 | SVG polygon (hiccup format)." 77 | [contour] 78 | (-> (map #(-> % v/vec2 v/yx (g/scale scale)) contour) 79 | (svg/polygon))) 80 | 81 | (defn noise-matrix 82 | "Creates a new 2D matrix of size res and populates it with simplex 83 | noise, then sets border cells to 1.0 and returns matrix" 84 | [res ns] 85 | (let [mat (nd/ndarray :float32 (float-array (* res res)) [res res])] 86 | (dorun 87 | (for [[y x] (nd/position-seq mat)] 88 | (nd/set-at mat x y (+ 0.5 (* 0.5 (n/noise2 (+ 101 (* x ns)) (* y ns))))))) 89 | (contours/set-border-2d mat 1))) 90 | 91 | (defn circle-matrix 92 | "Creates new 2D matrix of size res and populates it w/ modulated 93 | normalized distance values from center. `spikes` arg is number of 94 | oscillations used to modulate. `amp` is modulation strength. Sets 95 | border cells to 1.0 and returns matrix." 96 | [res spikes amp] 97 | (let [mat (nd/ndarray :float32 (float-array (* res res)) [res res]) 98 | c (/ res 2.0) 99 | dmax (* m/SQRT2 0.5 res)] 100 | (dorun 101 | (for [[y x] (nd/position-seq mat) 102 | :let [dx (- c x) 103 | dy (- c y) 104 | t (Math/atan2 dy dx) 105 | d (Math/sqrt (* (+ 0.5 (* amp (Math/sin (* t spikes)))) 106 | (+ (* dx dx) (* dy dy))))]] 107 | (nd/set-at mat x y (/ d dmax)))) 108 | (contours/set-border-2d mat 1))) 109 | 110 | (def palette 111 | (apply grad/cosine-gradient num-contours (:orange-blue grad/cosine-schemes))) 112 | 113 | ;; choose one... 114 | (def mat (noise-matrix res n-scale)) 115 | ;;(def mat (circle-matrix res 8 0.25)) 116 | 117 | (->> (m/norm-range num-contours) 118 | (rest) 119 | (map 120 | #(svg/group 121 | {:stroke (palette (int (* % (dec num-contours)))) 122 | :fill "none"} 123 | (map contour->svg (contours/find-contours-2d mat %)))) 124 | (svg/svg 125 | {:width width 126 | :height width 127 | :viewBox (format "%1.2f %1.2f %1.2f %1.2f" scale scale clipped clipped)}) 128 | (svg/serialize) 129 | (spit "iso.svg")) 130 | #+END_SRC 131 | 132 | *** Implementation 133 | 134 | Loosely based on Marching Squares/Cubes implementations by Paul Bourke (C) & Murphy Stein (Java): 135 | 136 | - http://paulbourke.net/geometry/polygonise/ 137 | - https://github.com/murphydactyl/JavaKinectFingerTracker/blob/master/imageprocessor/FindIsolines.java 138 | 139 | #+BEGIN_SRC clojure :noweb-ref contours2d 140 | (def edge-index-2d 141 | [nil [2 0] [1 0] [1 0] 142 | [0 0] nil [0 0] [0 0] 143 | [3 0] [2 0] nil [1 0] 144 | [3 0] [2 0] [3 0] nil]) 145 | 146 | (def next-edges-2d 147 | [[-1 0] [0 1] [1 0] [0 -1]]) 148 | 149 | (defn set-border-2d 150 | [mat x] 151 | (let [[h w] (nd/shape mat) 152 | h' (dec h) 153 | w' (dec w) 154 | l (nd/pick mat nil 0) 155 | r (nd/pick mat nil w') 156 | t (nd/pick mat 0 nil) 157 | b (nd/pick mat h' nil)] 158 | (loop [i w'] 159 | (when (>= i 0) 160 | (nd/set-at t i x) 161 | (nd/set-at b i x) 162 | (recur (dec i)))) 163 | (loop [i h'] 164 | (when (>= i 0) 165 | (nd/set-at l i x) 166 | (nd/set-at r i x) 167 | (recur (dec i)))) 168 | mat)) 169 | 170 | (defn encode-crossings-2d 171 | [src isoval] 172 | (let [out (nd/ndarray :int8 (#?(:clj byte-array :cljs a/int8) (nd/size src)) (nd/shape src)) 173 | iso? (fn [y x m] (if (< (nd/get-at src y x) isoval) m 0))] 174 | (loop [pos (nd/position-seq (nd/truncate-h src -1 -1))] 175 | (if pos 176 | (let [[y x] (first pos) 177 | x' (inc x) 178 | y' (inc y)] 179 | (nd/set-at 180 | out y x 181 | (-> (iso? y x 0x08) 182 | (bit-or (iso? y x' 0x04)) 183 | (bit-or (iso? y' x' 0x02)) 184 | (bit-or (iso? y' x 0x01)))) 185 | (recur (next pos))) 186 | out)))) 187 | 188 | (defn mean-cell-value-2d 189 | [src y x] 190 | (* (+ (+ (nd/get-at src y x) (nd/get-at src y (inc x))) 191 | (+ (nd/get-at src (inc y) x) (nd/get-at src (inc y) (inc x)))) 192 | 0.25)) 193 | 194 | (defn process-saddle5 195 | [src y x iso from] 196 | (if (> (mean-cell-value-2d src y x) iso) 197 | (if (== 3 from) [2 0x04] [0 0x01]) 198 | (if (== 3 from) [0 0x0d] [2 0x07]))) 199 | 200 | (defn process-saddle10 201 | [src y x iso from] 202 | (if (> (mean-cell-value-2d src y x) iso) 203 | (if (== 0 from) [3 0x02] [1 0x08]) 204 | (if (== 2 from) [3 0x0b] [1 0x0e]))) 205 | 206 | (defn mix2d 207 | [src y1 x1 y2 x2 iso] 208 | (let [a (nd/get-at src y1 x1) 209 | b (nd/get-at src y2 x2)] 210 | (if (== a b) 0 (/ (- a iso) (- a b))))) 211 | 212 | (defn contour-vertex-2d 213 | [src y x to iso] 214 | (let [x' (inc x) y' (inc y)] 215 | (case (int to) 216 | 0 [y (+ x (mix2d src y x y x' iso))] 217 | 1 [(+ y (mix2d src y x' y' x' iso)) x'] 218 | 2 [y' (+ x (mix2d src y' x y' x' iso))] 219 | 3 [(+ y (mix2d src y x y' x iso)) x] 220 | nil))) 221 | 222 | (defn find-contours-2d 223 | [src isoval] 224 | (let [[h' w'] (nd/shape src) 225 | h' (dec h') 226 | w' (dec w') 227 | coded (encode-crossings-2d src isoval) 228 | contours (volatile! (transient []))] 229 | (loop [pos (nd/position-seq coded) 230 | curr (transient []) 231 | to nil 232 | p nil] 233 | (if pos 234 | (let [from to 235 | [y x] (if p p (first pos))] 236 | (if (or (>= x w') (>= y h')) 237 | (recur (next pos) curr to nil) 238 | (let [id (nd/get-at coded y x) 239 | [to clear] (case (int id) 240 | 5 (process-saddle5 src y x isoval from) 241 | 10 (process-saddle10 src y x isoval from) 242 | (edge-index-2d (int id))) 243 | curr (if (and (nil? from) to (pos? (count curr))) 244 | (do (vswap! contours conj! (persistent! curr)) 245 | (transient [])) 246 | curr)] 247 | (when clear 248 | (nd/set-at coded y x clear)) 249 | (if (and to (>= to 0)) 250 | (let [vertex (contour-vertex-2d src y x to isoval) 251 | [oy ox] (next-edges-2d to)] 252 | (recur (next pos) (conj! curr vertex) to [(+ y oy) (+ x ox)])) 253 | (recur (next pos) curr to nil))))) 254 | (persistent! (conj! @contours (persistent! curr))))))) 255 | #+END_SRC 256 | 257 | ** TODO 3D contour extraction 258 | 259 | This functionality will be ported from the [[https://github.com/thi-ng/geom/blob/master/geom-voxel/src/index.org][thi.ng/geom-voxel module]]... 260 | 261 | ** Level crossings 262 | 263 | This section contains somewhat less high level operations to find 264 | level crossings in 1D, 2D and 3D ndarrays. Unlike the contour 265 | extractions above, which procude a logical sequence of connected 266 | points/segments/facets in the grid, these functions here merely 267 | produce a sequence of (potentially) unconnected cell positions where 268 | thresholds are crossed and are intended for more analytical use cases. 269 | The functions all take an ndarray and contour level value and assume 270 | the array to be in major shape order (the default order), i.e. in 2D 271 | row-major (YX), in 3D slice-row-major (ZYX). The functions return 272 | interpolated grid positions where the given contour level is crossed 273 | between cells. If the array doesn't conform to this ordering, use the 274 | =nd/transpose= method to create a properly ordered view before using 275 | the functions below. 276 | 277 | *** Examples 278 | 279 | #+BEGIN_SRC clojure 280 | (level-crossings1d (nd/ndarray :float32 [0 0 1 0]) 4 0.25) 281 | ;; (1.25 2.75) 282 | 283 | (let [a [0 0 0 284 | 0 1 0 285 | 0 0 0] 286 | a (nd/ndarray :float32 a [3 3])] 287 | {:x (level-crossings2d-x a 0.25) 288 | :y (level-crossings2d-y a 0.25) 289 | :all (level-crossings2d a 0.25)}) 290 | ;; {:x ([1 0.25] [1 1.75]) 291 | ;; :y ([0.25 1] [1.75 1]) 292 | ;; :all ([1 0.25] [1 1.75] [0.25 1] [1.75 1])} 293 | 294 | (let [a (nd/ndarray :float32 (float-array 27) [3 3 3])] 295 | (nd/set-at a 1 1 1 1) 296 | {:x (level-crossings3d-x a 0.25) 297 | :y (level-crossings3d-y a 0.25) 298 | :z (level-crossings3d-z a 0.25) 299 | :all (level-crossings3d a 0.25)}) 300 | ;; {:x ((1 1 0.25) (1 1 1.75)) 301 | ;; :y ((1 0.25 1) (1 1.75 1)) 302 | ;; :z ([0.25 1 1] [1.75 1 1]) 303 | ;; :all ((1 1 0.25) (1 1 1.75) (1 0.25 1) (1 1.75 1) [0.25 1 1] [1.75 1 1])} 304 | #+END_SRC 305 | 306 | *** Implementation 307 | 308 | #+BEGIN_SRC clojure :noweb-ref level-crossings 309 | (defn level-crossing 310 | [offset a b level] 311 | (let [da (- a level) 312 | db (- b level)] 313 | (if-not (= (>= da 0.0) (>= db 0.0)) 314 | (+ offset (+ 0.5 (* 0.5 (/ (+ da db) (- da db)))))))) 315 | 316 | (defn level-crossings1d 317 | [mat shape level] 318 | (for [x (range (dec (if (number? shape) shape (first shape)))) 319 | :let [x' (level-crossing x (nd/get-at mat x) (nd/get-at mat (inc x)) level)] 320 | :when x'] 321 | x')) 322 | 323 | (defn level-crossings2d-x 324 | ([mat level] 325 | (level-crossings2d-x mat (nd/shape mat) level)) 326 | ([mat [sy sx] level] 327 | (mapcat 328 | (fn [y] (map #(vector y %) (level-crossings1d (nd/pick mat y nil) sx level))) 329 | (range sy)))) 330 | 331 | (defn level-crossings2d-y 332 | ([mat level] 333 | (level-crossings2d-y mat (nd/shape mat) level)) 334 | ([mat [sy sx] level] 335 | (mapcat 336 | (fn [x] (map #(vector % x) (level-crossings1d (nd/pick mat nil x) sy level))) 337 | (range sx)))) 338 | 339 | (defn level-crossings2d 340 | ([mat level] 341 | (level-crossings2d mat (nd/shape mat) level)) 342 | ([mat shape level] 343 | (concat 344 | (level-crossings2d-x mat shape level) 345 | (level-crossings2d-y mat shape level)))) 346 | 347 | (defn level-crossings3d-x 348 | ([mat level] 349 | (level-crossings3d-x mat (nd/shape mat) level)) 350 | ([mat [sz sy sx] level] 351 | (mapcat 352 | (fn [z] (map #(cons z %) (level-crossings2d-x (nd/pick mat z nil nil) [sy sx] level))) 353 | (range sz)))) 354 | 355 | (defn level-crossings3d-y 356 | ([mat level] 357 | (level-crossings3d-y mat (nd/shape mat) level)) 358 | ([mat [sz sy sx] level] 359 | (mapcat 360 | (fn [z] (map #(cons z %) (level-crossings2d-y (nd/pick mat z nil nil) [sy sx] level))) 361 | (range sz)))) 362 | 363 | (defn level-crossings3d-z 364 | ([mat level] 365 | (level-crossings3d-z mat (nd/shape mat) level)) 366 | ([mat [sz sy sx] level] 367 | (mapcat 368 | (fn [x] (map #(conj % x) (level-crossings2d-y (nd/pick mat nil nil x) [sz sy] level))) 369 | (range sx)))) 370 | 371 | (defn level-crossings3d 372 | ([mat level] 373 | (level-crossings3d mat (nd/shape mat) level)) 374 | ([mat shape level] 375 | (concat 376 | (level-crossings3d-x mat shape level) 377 | (level-crossings3d-y mat shape level) 378 | (level-crossings3d-z mat shape level)))) 379 | #+END_SRC 380 | 381 | ** Complete namespace definition 382 | 383 | #+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/ndarray/contours.cljc :noweb yes :mkdirp yes :padline no 384 | (ns thi.ng.ndarray.contours 385 | (:require 386 | [thi.ng.ndarray.core :as nd] 387 | #?(:cljs [thi.ng.typedarrays.core :as a]))) 388 | 389 | <> 390 | 391 | <> 392 | #+END_SRC 393 | -------------------------------------------------------------------------------- /src/core.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: setup.org 2 | 3 | * Contents :toc_3_gh: 4 | - [[#namespace-thingndarraycore][Namespace: thi.ng.ndarray.core]] 5 | - [[#protocol-public-api-core-operations][Protocol (Public API core operations)]] 6 | - [[#constructors][Constructors]] 7 | - [[#supported-type-ids][Supported type IDs]] 8 | - [[#implementations][Implementations]] 9 | - [[#ndarray-generator-macro--registry][NDArray generator macro & registry]] 10 | - [[#macro-helper-functions][Macro helper functions]] 11 | - [[#collreduce--ireduce-implementations][CollReduce / IReduce implementations]] 12 | - [[#clojure--clojurscript-protocols][Clojure / Clojurscript protocols]] 13 | - [[#main-macro][Main macro]] 14 | - [[#helper-functions][Helper functions]] 15 | - [[#complete-namespace-definitions][Complete namespace definitions]] 16 | 17 | * Namespace: thi.ng.ndarray.core 18 | 19 | This library defines a datatype providing n-dimensional views over 1D 20 | arrays and a number of operations to work with these views 21 | efficiently. Optimized =NDArray= types are provided for 1-4 dimensions 22 | and for all Java primitive array types (or in ClojureScript using JS 23 | typed arrays via the [[http://thi.ng/typedarrays][thi.ng/typedarrays]] library). Furthermore, generic 24 | (non-typehinted) implementations are available for each dimension too. 25 | 26 | ** Protocol (Public API core operations) 27 | 28 | In addition to the protocol based API below, the NDArray views also 29 | implement Clojure's =ISeqable= interface/protocol and can therefore be 30 | directly used with the usual sequence processing fns (=map=, =reduce=, 31 | =take= etc.) 32 | 33 | See [[./index.org][index.org]] for further usage example of some of these operations... 34 | 35 | #+BEGIN_SRC clojure :noweb-ref api 36 | (defprotocol PNDArray 37 | (data [_] 38 | "Returns the backing data array.") 39 | (data-type [_] 40 | "Returns the ndarray's type id (keyword).") 41 | (dimension [_] 42 | "Returns the ndarray's dimension.") 43 | (shape [_] 44 | "Returns a vector of the ndarray's sizes in each dimension.") 45 | (stride [_] 46 | "Returns a vector of the ndarray's strides in each dimension.") 47 | (offset [_] 48 | "Returns the ndarray view's start index in the backing data array") 49 | (size [_] 50 | "Returns the element count of an ndarray view") 51 | (extract [_] 52 | "Creates a new backing array of only values in given ndarray view 53 | and returns new ndarray of same shape, but with strides reset to 54 | defaults order.") 55 | (index-at [_ x] [_ x y] [_ x y z] [_ x y z w] 56 | "Returns the global index into the backing array for given 57 | position in an ndarray view.") 58 | (index-pos [_ i] 59 | "Computes relative position in an ndarray view from given absolute 60 | array index.") 61 | (index-seq [_] 62 | "Returns a lazy seq of all array indices in an ndarray view.") 63 | (position-seq [_] 64 | "Returns a lazy seq of all position vectors in an ndarray view.") 65 | (get-at [_ x] [_ x y] [_ x y z] [_ x y z w] 66 | "Returns value at given position in an ndarray view (without bounds check, 67 | assumes position is safe).") 68 | (get-at-safe [_ x] [_ x y] [_ x y z] [_ x y z w] 69 | "Returns value at given position in an ndarray view (with bounds 70 | check)") 71 | (get-at-index [_ i] 72 | "Returns value at given global index in an ndarray view's backing 73 | array.") 74 | (set-at [_ x v] [_ x y v] [_ x y z v] [_ x y z w v] 75 | "Sets backing data array at given position in an ndarray view to 76 | new value v (without bounds check). Returns same NDArray instance.") 77 | (set-at-safe [_ x v] [_ x y v] [_ x y z v] [_ x y z w v] 78 | "Sets backing array at given position in an ndarray view to 79 | new value v (with bounds check). Returns same NDArray instance.") 80 | (set-at-index [_ i v] 81 | "Sets backing data array at given global index to new value 82 | v (without bounds check). Returns same NDArray instance.") 83 | (update-at [_ x f] [_ x y f] [_ x y z f] [_ x y z w f] 84 | "Applies function f to given position in an ndarray view and sets 85 | backing array at same position to the return value of f. The 86 | function f itself accepts m args: first the n coordinates of the 87 | position and the value at this position") 88 | (update-at-safe [_ x f] [_ x y f] [_ x y z f] [_ x y z w f] 89 | "Same as `update-at` but with bounds check.") 90 | (update-at-index [_ i f] 91 | "Applies function f to given global index in an ndarray's view 92 | backing array and sets it to the return value of f. The 93 | function f itself accepts 2 args: the supplied array index 94 | and the array's value at this index") 95 | (truncate-h [_ x] [_ x y] [_ x y z] [_ x y z w] 96 | "Returns a new ndarray of same type with its shape truncated at 97 | top end. Negative args are considered offsets from current shape. 98 | nil values keep shape in that dimension. Does not mutate backing 99 | array.") 100 | (truncate-l [_ x] [_ x y] [_ x y z] [_ x y z w] 101 | "Returns a new ndarray of same type with its shape truncated at 102 | lower end, effectively shifting its start index/offset towards the 103 | center of the view. Does not mutate backing array.") 104 | (transpose [_ x] [_ x y] [_ x y z] [_ x y z w] 105 | "Returns a new ndarray view with stride order/axes swapped as per 106 | given vector. Does not mutate backing array.") 107 | (step [_ x] [_ x y] [_ x y z] [_ x y z w] 108 | "Returns new ndarray view with stride steps/direction changed as 109 | per given vector. Values >1 result in skipping of items in that 110 | dimension, negative value flip direction, `nil` keeps current 111 | stride for that dimension. Does not mutate backing array.") 112 | (pick [_ x] [_ x y] [_ x y z] [_ x y z w] 113 | "Generalized getter. Accepts n args (e.g. 3 for a 3D ndarray), 114 | each selecting a dimension slice (nil skips a dimension). Returns 115 | new ndarray view of selection, or if selecting in all dimensions, 116 | returns array value at that point. Does not mutate backing array.")) 117 | #+END_SRC 118 | 119 | ** Constructors 120 | 121 | NDArrays can be created via the =ndarray= function, which accepts the 122 | following arguments: 123 | 124 | - =type= - a keyword to choose a typed implementation 125 | - =data= - a pre-existing data array or Clojure sequence 126 | - =shape= - a vector of =n= elements defining the shape of the data in each dimension 127 | 128 | *Note:* When supplying an array, make sure it's of the same type as 129 | the requested NDArray type id or else will cause casting errors when 130 | attempting to set array values. 131 | 132 | #+BEGIN_SRC clojure 133 | ;; example = 3D array of 16 x 16 x 4 items 134 | (nd/ndarray :float32 (float-array 1024) [16 16 4]) 135 | 136 | ;; or 137 | (nd/ndarray :float32 (repeat 1024 0) [16 16 4]) 138 | #+END_SRC 139 | 140 | *** Supported type IDs 141 | 142 | The table below lists all currently supported type IDs and their 143 | availability in Clojure & Clojurescript. 144 | 145 | | *ID* | *CLJ?* | *CLJS?* | *Description* | 146 | |------------------+--------+---------+----------------------------------------| 147 | | =:generic= | ✓ | ✓ | untyped generic backing array | 148 | | =:int8= | ✓ | ✓ | signed byte | 149 | | =:uint8= | | ✓ | unsigned byte | 150 | | =:uint8-clamped= | | ✓ | unsigned byte (clamped to 0x00 - 0xff) | 151 | | =:int16= | ✓ | ✓ | signed short | 152 | | =:uint16= | | ✓ | unsigned short | 153 | | =:int32= | ✓ | ✓ | signed int | 154 | | =:uint32= | ✓ | ✓ | unsigned int | 155 | | =:int64= | ✓ | | signed long | 156 | | =:float32= | ✓ | ✓ | float | 157 | | =:float64= | ✓ | ✓ | double | 158 | 159 | *** Implementations 160 | 161 | #+BEGIN_SRC clojure :noweb-ref ctors 162 | (def ctor-registry (atom {})) 163 | 164 | #?(:clj 165 | (do 166 | (def-ndarray 1 nil "objects" :generic to-array aget aset true) 167 | (def-ndarray 1 boolean "booleans" :boolean boolean-array aget aset true) 168 | (def-ndarray 1 byte "bytes" :int8 byte-array aget aset true) 169 | (def-ndarray 1 short "shorts" :int16 short-array aget aset true) 170 | (def-ndarray 1 int "ints" :int32 int-array aget aset true) 171 | (def-ndarray 1 long "longs" :int64 long-array aget aset true) 172 | (def-ndarray 1 float "floats" :float32 float-array aget aset true) 173 | (def-ndarray 1 double "doubles" :float64 double-array aget aset true) 174 | 175 | (def-ndarray 2 nil "objects" :generic to-array aget aset true) 176 | (def-ndarray 2 boolean "booleans" :boolean boolean-array aget aset true) 177 | (def-ndarray 2 byte "bytes" :int8 byte-array aget aset true) 178 | (def-ndarray 2 short "shorts" :int16 short-array aget aset true) 179 | (def-ndarray 2 int "ints" :int32 int-array aget aset true) 180 | (def-ndarray 2 long "longs" :int64 long-array aget aset true) 181 | (def-ndarray 2 float "floats" :float32 float-array aget aset true) 182 | (def-ndarray 2 double "doubles" :float64 double-array aget aset true) 183 | 184 | (def-ndarray 3 nil "objects" :generic to-array aget aset true) 185 | (def-ndarray 3 boolean "booleans" :boolean boolean-array aget aset true) 186 | (def-ndarray 3 byte "bytes" :int8 byte-array aget aset true) 187 | (def-ndarray 3 short "shorts" :int16 short-array aget aset true) 188 | (def-ndarray 3 int "ints" :int32 int-array aget aset true) 189 | (def-ndarray 3 long "longs" :int64 long-array aget aset true) 190 | (def-ndarray 3 float "floats" :float32 float-array aget aset true) 191 | (def-ndarray 3 double "doubles" :float64 double-array aget aset true) 192 | 193 | (def-ndarray 4 nil "objects" :generic to-array aget aset true) 194 | (def-ndarray 4 boolean "booleans" :boolean boolean-array aget aset true) 195 | (def-ndarray 4 byte "bytes" :int8 byte-array aget aset true) 196 | (def-ndarray 4 short "shorts" :int16 short-array aget aset true) 197 | (def-ndarray 4 int "ints" :int32 int-array aget aset true) 198 | (def-ndarray 4 long "longs" :int64 long-array aget aset true) 199 | (def-ndarray 4 float "floats" :float32 float-array aget aset true) 200 | (def-ndarray 4 double "doubles" :float64 double-array aget aset true)) 201 | :cljs 202 | (do 203 | (def-ndarray 1 nil nil :generic to-array aget aset false) 204 | (def-ndarray 1 nil nil :uint8 a/uint8 aget aset false) 205 | (def-ndarray 1 nil nil :uint8-clamped a/uint8-clamped aget aset false) 206 | (def-ndarray 1 nil nil :uint16 a/uint16 aget aset false) 207 | (def-ndarray 1 nil nil :uint32 a/uint32 aget aset false) 208 | (def-ndarray 1 nil nil :int8 a/int8 aget aset false) 209 | (def-ndarray 1 nil nil :int16 a/int16 aget aset false) 210 | (def-ndarray 1 nil nil :int32 a/int32 aget aset false) 211 | (def-ndarray 1 nil nil :float32 a/float32 aget aset false) 212 | (def-ndarray 1 nil nil :float64 a/float64 aget aset false) 213 | 214 | (def-ndarray 2 nil nil :generic to-array aget aset false) 215 | (def-ndarray 2 nil nil :uint8 a/uint8 aget aset false) 216 | (def-ndarray 2 nil nil :uint8-clamped a/uint8-clamped aget aset false) 217 | (def-ndarray 2 nil nil :uint16 a/uint16 aget aset false) 218 | (def-ndarray 2 nil nil :uint32 a/uint32 aget aset false) 219 | (def-ndarray 2 nil nil :int8 a/int8 aget aset false) 220 | (def-ndarray 2 nil nil :int16 a/int16 aget aset false) 221 | (def-ndarray 2 nil nil :int32 a/int32 aget aset false) 222 | (def-ndarray 2 nil nil :float32 a/float32 aget aset false) 223 | (def-ndarray 2 nil nil :float64 a/float64 aget aset false) 224 | 225 | (def-ndarray 3 nil nil :generic to-array aget aset false) 226 | (def-ndarray 3 nil nil :uint8 a/uint8 aget aset false) 227 | (def-ndarray 3 nil nil :uint8-clamped a/uint8-clamped aget aset false) 228 | (def-ndarray 3 nil nil :uint16 a/uint16 aget aset false) 229 | (def-ndarray 3 nil nil :uint32 a/uint32 aget aset false) 230 | (def-ndarray 3 nil nil :int8 a/int8 aget aset false) 231 | (def-ndarray 3 nil nil :int16 a/int16 aget aset false) 232 | (def-ndarray 3 nil nil :int32 a/int32 aget aset false) 233 | (def-ndarray 3 nil nil :float32 a/float32 aget aset false) 234 | (def-ndarray 3 nil nil :float64 a/float64 aget aset false) 235 | 236 | (def-ndarray 4 nil nil :generic to-array aget aset false) 237 | (def-ndarray 4 nil nil :uint8 a/uint8 aget aset false) 238 | (def-ndarray 4 nil nil :uint8-clamped a/uint8-clamped aget aset false) 239 | (def-ndarray 4 nil nil :uint16 a/uint16 aget aset false) 240 | (def-ndarray 4 nil nil :uint32 a/uint32 aget aset false) 241 | (def-ndarray 4 nil nil :int8 a/int8 aget aset false) 242 | (def-ndarray 4 nil nil :int16 a/int16 aget aset false) 243 | (def-ndarray 4 nil nil :int32 a/int32 aget aset false) 244 | (def-ndarray 4 nil nil :float32 a/float32 aget aset false) 245 | (def-ndarray 4 nil nil :float64 a/float64 aget aset false) 246 | )) 247 | 248 | (defn ndarray 249 | ([type data] 250 | (ndarray type data [(count data)])) 251 | ([type data shape] 252 | (let [{:keys [ctor data-ctor]} (get-in @ctor-registry [(count shape) type])] 253 | (if ctor 254 | (ctor (if (sequential? data) (data-ctor data) data) 0 (shape->stride shape) shape) 255 | (throw (new #?(:clj IllegalArgumentException :cljs js/Error) 256 | (str "Can't create ndarray for: " type " " data))))))) 257 | #+END_SRC 258 | 259 | ** NDArray generator macro & registry 260 | 261 | The NDArray types are all generated by the macro =def-ndarray= defined 262 | in this section. Due to CLJS restrictions, this macro (and its helper 263 | fns) is actually defined in its own namespace =thi.ng.ndarray.macro=, 264 | but this namespace should be considered opaque for user applications 265 | (only =ndarray.core= is required for userland apps). 266 | 267 | For each generated =NDArray= type, the macro also produces a custom 268 | factory function and registers it in the 269 | =thi.ng.ndarray.core/ctor-registry= atom. This registry is used by the 270 | public =ndarray= constructor fn to choose the correct type 271 | implementation for the given arguments. 272 | 273 | *** Macro helper functions 274 | 275 | #+BEGIN_SRC clojure :noweb-ref macros 276 | (defn- type-hinted 277 | [type x] (if type (with-meta x {:tag (name type)}) x)) 278 | 279 | (defn- do-cast 280 | [cast body] 281 | (if cast `(~cast ~body) body)) 282 | 283 | (defn- make-symbols 284 | [id n] (mapv #(symbol (str id %)) (range n))) 285 | 286 | (defn- pair-fn 287 | [f coll] 288 | (let [coll (->> coll 289 | (partition-all 2) 290 | (map #(if (< 1 (count %)) (cons f %) (first %))))] 291 | (if (> (count coll) 2) (recur f coll) coll))) 292 | 293 | (defn- make-indexer 294 | [dim ->st p] 295 | `(int (+ ~@(->> (range dim) 296 | (map #(list '* (->st %) `(int (~p ~%)))) 297 | (cons '_offset) 298 | (pair-fn '+))))) 299 | 300 | (defn- make-indexer-syms 301 | [dim ->st ->p] 302 | `(int (+ ~@(->> (range dim) 303 | (map #(list '* (->st %) `(int ~(->p %)))) 304 | (cons '_offset) 305 | (pair-fn '+))))) 306 | 307 | (defmacro and* 308 | "Like clojure.core/and, but avoids intermediate let bindings and 309 | only ever returns either result of last form (if all previous 310 | succeeded) or nil." 311 | ([x] x) 312 | ([x & more] `(if ~x (and* ~@more)))) 313 | 314 | (defn- with-bounds-check 315 | [dim psyms shapes clj? & body] 316 | `(if (and* 317 | ~@(mapcat 318 | #(let [p (symbol (psyms %))] 319 | (list `(>= ~p 0) `(< ~p ~(symbol (shapes %))))) 320 | (range dim))) 321 | (do ~@body) 322 | (throw 323 | (new ~(if clj? 'IndexOutOfBoundsException 'js/Error) 324 | (str "Invalid index: " (pr-str [~@psyms])))))) 325 | 326 | (defn- for* 327 | [->a ->sh rdim body] 328 | `(for [~@(mapcat #(vector (->a %) `(range ~(->sh %))) rdim)] ~body)) 329 | #+END_SRC 330 | 331 | *** CollReduce / IReduce implementations 332 | 333 | #+BEGIN_SRC clojure :noweb-ref macros 334 | (defn- reduce-1 335 | [f acc ->p ->sh ->st get data rdim clj? init?] 336 | (let [x (->p 0) 337 | sx (->sh 0) 338 | st (->st 0) 339 | idx (gensym) 340 | acc0 `(~get ~data ~'_offset) 341 | body `(if (reduced? ~acc) 342 | @~acc 343 | (if (< ~x ~sx) 344 | (recur (~f ~acc (~get ~data ~idx)) (unchecked-inc-int ~x) (unchecked-add-int ~idx ~st)) 345 | (if (reduced? ~acc) 346 | @~acc ~acc)))] 347 | (if init? 348 | `(if (zero? ~sx) 349 | ~acc 350 | (if (== 1 ~sx) 351 | (~f ~acc ~acc0) 352 | (loop [~acc ~acc ~x 0 ~idx ~'_offset] 353 | ~body))) 354 | `(if (zero? ~sx) 355 | (~f) 356 | (if (== 1 ~sx) 357 | ~acc0 358 | (loop [~acc (~f ~acc0 (~get ~data (unchecked-add-int ~'_offset ~st))) 359 | ~x 2 360 | ~idx (unchecked-add-int ~'_offset (bit-shift-left ~st 1))] 361 | ~body)))))) 362 | 363 | (defn- reduce-2 364 | [f acc ->p ->sh ->st get data rdim clj? init?] 365 | (let [[y x] (map ->p rdim) 366 | [sy sx] (map ->sh rdim) 367 | [sty stx] (map ->st rdim) 368 | [idx sy'] (repeatedly 2 gensym) 369 | acc0 `(~get ~data ~'_offset) 370 | body `(if (reduced? ~acc) 371 | @~acc 372 | (if (< ~x ~sx) 373 | (recur (~f ~acc (~get ~data ~idx)) 374 | ~y 375 | (unchecked-inc-int ~x) 376 | (unchecked-add-int ~idx ~stx)) 377 | (if (< ~y ~sy') 378 | (let [~idx (unchecked-add-int 379 | ~'_offset 380 | (unchecked-multiply-int ~sty (unchecked-inc-int ~y)))] 381 | (recur (~f ~acc (~get ~data ~idx)) 382 | (unchecked-inc-int ~y) 383 | 1 384 | (unchecked-add-int ~idx ~stx))) 385 | (if (reduced? ~acc) 386 | @~acc ~acc))))] 387 | (if init? 388 | `(if (zero? (* ~sy ~sx)) 389 | ~acc 390 | (if (== 1 (* ~sy ~sx)) 391 | (~f ~acc ~acc0) 392 | (let [~sy' (dec ~sy)] 393 | (loop [~acc ~acc ~y 0 ~x 0 ~idx ~'_offset] 394 | ~body)))) 395 | `(if (zero? (* ~sy ~sx)) 396 | (~f) 397 | (if (== 1 (* ~sy ~sx)) 398 | ~acc0 399 | (let [~sy' (dec ~sy)] 400 | (loop [~acc (~f ~acc0 (~get ~data (unchecked-add-int ~'_offset ~stx))) 401 | ~y 0 402 | ~x 2 403 | ~idx (unchecked-add-int ~'_offset (bit-shift-left ~stx 1))] 404 | ~body))))))) 405 | 406 | (defn- reduce-3 407 | [f acc ->p ->sh ->st get data rdim clj? init?] 408 | (let [[z y x] (map ->p rdim) 409 | [sz sy sx] (map ->sh rdim) 410 | [stz sty stx] (map ->st rdim) 411 | [idx sz' sy'] (repeatedly 3 gensym) 412 | acc0 `(~get ~data ~'_offset) 413 | body `(if (reduced? ~acc) 414 | @~acc 415 | (if (< ~x ~sx) 416 | (recur (~f ~acc (~get ~data ~idx)) 417 | ~z 418 | ~y 419 | (unchecked-inc-int ~x) 420 | (unchecked-add-int ~idx ~stx)) 421 | (if (< ~y ~sy') 422 | (let [~idx (+ ~'_offset (+ (* ~stz ~z) (* ~sty (unchecked-inc-int ~y))))] 423 | (recur (~f ~acc (~get ~data ~idx)) 424 | ~z 425 | (unchecked-inc-int ~y) 426 | 1 427 | (unchecked-add-int ~idx ~stx))) 428 | (if (< ~z ~sz') 429 | (let [~idx (+ ~'_offset (* ~stz (unchecked-inc-int ~z)))] 430 | (recur (~f ~acc (~get ~data ~idx)) 431 | (unchecked-inc-int ~z) 432 | 0 433 | 1 434 | (unchecked-add-int ~idx ~stx))) 435 | (if (reduced? ~acc) 436 | @~acc ~acc)))))] 437 | (if init? 438 | `(if (zero? (* (* ~sz ~sy) ~sx)) 439 | ~acc 440 | (if (== 1 (* (* ~sz ~sy) ~sx)) 441 | (~f ~acc ~acc0) 442 | (let [~sz' (dec ~sz) ~sy' (dec ~sy)] 443 | (loop [~acc ~acc ~z 0 ~y 0 ~x 0 ~idx ~'_offset] 444 | ~body)))) 445 | `(if (zero? (* (* ~sz ~sy) ~sx)) 446 | (~f) 447 | (if (== 1 (* (* ~sz ~sy) ~sx)) 448 | ~acc0 449 | (let [~sz' (dec ~sz) ~sy' (dec ~sy)] 450 | (loop [~acc (~f ~acc0 (~get ~data (unchecked-add-int ~'_offset ~stx))) 451 | ~z 0 452 | ~y 0 453 | ~x 2 454 | ~idx (unchecked-add-int ~'_offset (bit-shift-left ~stx 1))] 455 | ~body))))))) 456 | 457 | (defn- reduce-4 458 | [f acc ->p ->sh ->st get data rdim clj? init?] 459 | (let [[w z y x] (map ->p rdim) 460 | [sw sz sy sx] (map ->sh rdim) 461 | [stw stz sty stx] (map ->st rdim) 462 | [idx sw' sz' sy'] (repeatedly 4 gensym) 463 | acc0 `(~get ~data ~'_offset) 464 | body `(if (reduced? ~acc) 465 | @~acc 466 | (if (< ~x ~sx) 467 | (recur (~f ~acc (~get ~data ~idx)) 468 | ~w 469 | ~z 470 | ~y 471 | (unchecked-inc-int ~x) 472 | (unchecked-add-int ~idx ~stx)) 473 | (if (< ~y ~sy') 474 | (let [~idx (+ ~'_offset 475 | (+ (* ~stw ~w) 476 | (+ (* ~stz ~z) (* ~sty (unchecked-inc-int ~y)))))] 477 | (recur (~f ~acc (~get ~data ~idx)) 478 | ~w 479 | ~z 480 | (unchecked-inc-int ~y) 481 | 1 482 | (unchecked-add-int ~idx ~stx))) 483 | (if (< ~z ~sz') 484 | (let [~idx (+ ~'_offset (+ (* ~stw ~w) (* ~stz (unchecked-inc-int ~z))))] 485 | (recur (~f ~acc (~get ~data ~idx)) 486 | ~w 487 | (unchecked-inc-int ~z) 488 | 0 489 | 1 490 | (unchecked-add-int ~idx ~stx))) 491 | (if (< ~w ~sw') 492 | (let [~idx (+ ~'_offset (* ~stw (unchecked-inc-int ~w)))] 493 | (recur (~f ~acc (~get ~data ~idx)) 494 | (unchecked-inc-int ~w) 495 | 0 496 | 0 497 | 1 498 | (unchecked-add-int ~idx ~stx))) 499 | (if (reduced? ~acc) 500 | @~acc ~acc))))))] 501 | (if init? 502 | `(if (zero? (* (* (* ~sw ~sz) ~sy) ~sx)) 503 | ~acc 504 | (if (== 1 (* (* (* ~sw ~sz) ~sy) ~sx)) 505 | (~f ~acc ~acc0) 506 | (let [~sw' (dec ~sw) ~sz' (dec ~sz) ~sy' (dec ~sy)] 507 | (loop [~acc ~acc ~w 0 ~z 0 ~y 0 ~x 0 ~idx ~'_offset] 508 | ~body)))) 509 | `(if (zero? (* (* (* ~sw ~sz) ~sy) ~sx)) 510 | (~f) 511 | (if (== 1 (* (* (* ~sw ~sz) ~sy) ~sx)) 512 | ~acc0 513 | (let [~sw' (dec ~sw) ~sz' (dec ~sz) ~sy' (dec ~sy)] 514 | (loop [~acc (~f ~acc0 (~get ~data (unchecked-add-int ~'_offset ~stx))) 515 | ~w 0 516 | ~z 0 517 | ~y 0 518 | ~x 2 519 | ~idx (unchecked-add-int ~'_offset (bit-shift-left ~stx 1))] 520 | ~body))))))) 521 | #+END_SRC 522 | 523 | *** Clojure / Clojurscript protocols 524 | 525 | #+BEGIN_SRC clojure :noweb-ref macros 526 | (def ^:private reduce-impls 527 | [nil reduce-1 reduce-2 reduce-3 reduce-4]) 528 | 529 | (defn- inject-clj-protos 530 | [clj? get data ->a ->sh ->st idx rdim] 531 | (let [[f init] (repeatedly gensym) 532 | r-impl (reduce-impls (count rdim)) 533 | reduce* (partial r-impl f init ->a ->sh ->st get data rdim clj?)] 534 | (if clj? 535 | (list 536 | 'clojure.lang.Seqable 537 | `(~'seq 538 | [_#] 539 | ~(for* ->a ->sh rdim `(~get ~data ~idx))) 540 | 'clojure.core.protocols/CollReduce 541 | `(~'coll-reduce 542 | [_# ~f] ~(reduce* false)) 543 | `(~'coll-reduce 544 | [_# ~f ~init] ~(reduce* true))) 545 | (list 546 | 'ISeqable 547 | `(~'-seq 548 | [_#] 549 | ~(for* ->a ->sh rdim `(~get ~data ~idx))) 550 | 'IReduce 551 | `(~'-reduce 552 | [_# ~f] ~(reduce* false)) 553 | `(~'-reduce 554 | [_# ~f ~init] ~(reduce* true)))))) 555 | #+END_SRC 556 | 557 | *** Main macro 558 | 559 | #+BEGIN_SRC clojure :noweb-ref macros 560 | (defmacro def-ndarray 561 | [dim cast type-hint type-id data-ctor get set & [clj?]] 562 | (let [type-name (symbol (str "NDArray" dim (name type-id))) 563 | raw-name (symbol (str "make-raw-ndarray" dim "-" (name type-id))) 564 | strides (make-symbols "_stride" dim) 565 | shapes (make-symbols "_shape" dim) 566 | asyms (make-symbols "a" dim) 567 | bsyms (make-symbols "b" dim) 568 | psyms (make-symbols "p" dim) 569 | [->st ->sh ->a ->b ->p] (map #(comp symbol %) [strides shapes asyms bsyms psyms]) 570 | [c d f p o] (repeatedly gensym) 571 | idx (make-indexer dim ->st p) 572 | idx-syms (make-indexer-syms dim ->st ->p) 573 | data (type-hinted type-hint '_data) 574 | rdim (range dim)] 575 | `(do 576 | (deftype ~type-name 577 | [~data ~'_offset ~@strides ~@shapes] 578 | ~@(inject-clj-protos clj? get data ->a ->sh ->st (make-indexer-syms dim ->st ->a) rdim) 579 | ~'thi.ng.ndarray.core/PNDArray 580 | (~'data 581 | [_#] ~data) 582 | (~'data-type 583 | [_#] ~type-id) 584 | (~'dimension 585 | [_#] ~dim) 586 | (~'stride 587 | [_#] [~@strides]) 588 | (~'shape 589 | [_#] [~@shapes]) 590 | (~'offset 591 | [_#] ~'_offset) 592 | (~'size 593 | [_#] (* ~@(pair-fn '* shapes))) 594 | (~'extract 595 | [_#] 596 | (let [buf# ~(type-hinted type-hint `(~data-ctor (* ~@(pair-fn '* shapes)))) 597 | [~@asyms] (thi.ng.ndarray.core/shape->stride [~@shapes]) 598 | arr# (new ~type-name buf# 0 ~@asyms ~@shapes)] 599 | (loop [~c (thi.ng.ndarray.core/index-seq _#) 600 | ~d (thi.ng.ndarray.core/index-seq arr#)] 601 | (when ~c 602 | (~set buf# (int (first ~d)) ~(do-cast cast `(~get ~data (int (first ~c))))) 603 | (recur (next ~c) (next ~d)))) 604 | arr#)) 605 | (~'index-at 606 | [_# ~@psyms] ~idx-syms) 607 | (~'index-pos 608 | [_# ~p] 609 | (let [~p (int ~p) 610 | ~c (- ~p ~'_offset) 611 | ~@(drop-last 612 | 2 (mapcat 613 | #(let [a (->a %) s (->st %)] 614 | (list a `(int (/ ~c ~s)) 615 | c `(- ~c (* ~a ~s)))) 616 | rdim))] 617 | [~@asyms])) 618 | (~'index-seq 619 | [_#] 620 | ~(let [idx (make-indexer-syms dim ->st ->a)] 621 | (for* ->a ->sh rdim idx))) 622 | (~'position-seq 623 | [_#] ~(for* ->a ->sh rdim `[~@asyms])) 624 | (~'get-at 625 | [_# ~@psyms] (~get ~data ~idx-syms)) 626 | (~'get-at-safe 627 | [_# ~@psyms] 628 | ~(with-bounds-check dim psyms shapes clj? 629 | `(~get ~data ~idx-syms))) 630 | (~'get-at-index 631 | [_# i#] (~get ~data (int i#))) 632 | (~'set-at 633 | [_# ~@psyms ~c] (~set ~data ~idx-syms ~(do-cast cast c)) _#) 634 | (~'set-at-safe 635 | [_# ~@psyms ~c] 636 | ~(with-bounds-check dim psyms shapes clj? 637 | `(~set ~data ~idx-syms ~(do-cast cast c))) 638 | _#) 639 | (~'set-at-index 640 | [_# i# ~c] (~set ~data (int i#) ~(do-cast cast c)) _#) 641 | (~'update-at 642 | [_# ~@psyms ~f] 643 | (let [~c ~idx-syms] 644 | (~set ~data ~c ~(do-cast cast `(~f ~@psyms (~get ~data ~c))))) 645 | _#) 646 | (~'update-at-safe 647 | [_# ~@psyms ~f] 648 | ~(with-bounds-check dim psyms shapes clj? 649 | `(let [~c ~idx-syms] 650 | (~set ~data ~c ~(do-cast cast `(~f ~@psyms (~get ~data ~c)))))) 651 | _#) 652 | (~'update-at-index 653 | [_# ~c ~f] (~set ~data ~c ~(do-cast cast `(~f ~c (~get ~data (int ~c))))) _#) 654 | (~'truncate-h 655 | [_# ~@psyms] 656 | (new ~type-name ~data ~'_offset ~@strides 657 | ~@(map 658 | #(let [p (->p %) s (->sh %)] 659 | `(if (number? ~p) 660 | (if (neg? ~p) 661 | (+ ~s (int ~p)) 662 | (int ~p)) 663 | ~s)) 664 | rdim))) 665 | (~'truncate-l 666 | [_# ~@psyms] 667 | (let [~@(mapcat 668 | #(let [p (->p %) sh (->sh %) st (->st %)] 669 | (list 670 | [(->a %) (->b %)] 671 | `(if (pos? ~p) 672 | [(- ~sh (int ~p)) 673 | (* ~st (int ~p))] 674 | [~sh 0]))) 675 | rdim) 676 | ~o (+ ~@(->> rdim (map ->b) (cons '_offset) (pair-fn '+)))] 677 | (new ~type-name ~data ~o ~@strides ~@asyms))) 678 | (~'transpose 679 | [_# ~@psyms] 680 | (let [~@(mapcat #(let [p (->p %)] (list p `(if ~p (int ~p) ~%))) rdim) 681 | ~c [~@strides] 682 | ~d [~@shapes]] 683 | (new ~type-name ~data ~'_offset 684 | ~@(map #(list c (->p %)) rdim) 685 | ~@(map #(list d (->p %)) rdim)))) 686 | (~'step 687 | [_# ~@psyms] 688 | (let [~o ~'_offset 689 | ~@(mapcat 690 | #(let [p (->p %) sh (->sh %) st (->st %) 691 | stride' `(* ~st (int ~p))] 692 | (list 693 | [(->a %) (->b %) o] 694 | `(if (number? ~p) 695 | (if (neg? ~p) 696 | [(int (~'Math/ceil (/ (- ~sh) (int ~p)))) 697 | ~stride' 698 | (+ ~o (* ~st (dec ~sh)))] 699 | [(int (~'Math/ceil (/ ~sh (int ~p)))) 700 | ~stride' 701 | ~o]) 702 | [~sh ~st ~o]))) 703 | rdim)] 704 | (new ~type-name ~data ~o ~@bsyms ~@asyms))) 705 | (~'pick 706 | [_# ~@psyms] 707 | (let [~o ~'_offset, ~c [], ~d [] 708 | ~@(mapcat 709 | #(let [p (->p %) sh (->sh %) st (->st %)] 710 | (list 711 | [c d o] 712 | `(if (and (number? ~p) (>= ~p 0)) 713 | [~c ~d (+ ~o (* ~st (int ~p)))] 714 | [(conj ~c ~sh) (conj ~d ~st) ~o]))) 715 | rdim) 716 | cnt# (count ~c)] 717 | (if (pos? cnt#) 718 | ((get-in @~'thi.ng.ndarray.core/ctor-registry [cnt# ~type-id :ctor]) ~data ~o ~d ~c) 719 | (~get ~data (int ~o))))) 720 | ~'Object 721 | (~'toString 722 | [_#] 723 | (pr-str 724 | {:data ~data :type ~type-id 725 | :size (* ~@(pair-fn '* shapes)) :total (count (seq _#)) :offset ~'_offset 726 | :shape [~@shapes] :stride [~@strides]}))) 727 | 728 | (defn ~(with-meta raw-name {:export true}) 729 | [data# o# [~@strides] [~@shapes]] 730 | (new ~type-name data# o# ~@strides ~@shapes)) 731 | 732 | (swap! 733 | ~'thi.ng.ndarray.core/ctor-registry 734 | assoc-in [~dim ~type-id] 735 | {:ctor ~raw-name 736 | :data-ctor ~data-ctor})))) 737 | #+END_SRC 738 | 739 | ** Helper functions 740 | 741 | #+BEGIN_SRC clojure :noweb-ref ops 742 | (defn order 743 | [coll] (->> coll (map vector (range)) (sort-by peek) (mapv first))) 744 | 745 | (defn shape->stride 746 | [shape] 747 | (->> shape 748 | reverse 749 | (reduce #(conj % (* %2 (first %))) '(1)) 750 | (drop 1) 751 | (vec))) 752 | #+END_SRC 753 | 754 | ** Complete namespace definitions 755 | 756 | #+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/ndarray/core.cljc :noweb yes :mkdirp yes :padline no 757 | (ns thi.ng.ndarray.core 758 | #?(:cljs 759 | (:require-macros 760 | [thi.ng.math.macros :as mm] 761 | [thi.ng.ndarray.macros :refer [def-ndarray]])) 762 | (:require 763 | #?@(:clj 764 | [[thi.ng.math.macros :as mm] 765 | [thi.ng.ndarray.macros :refer [def-ndarray]]] 766 | :cljs 767 | [[thi.ng.typedarrays.core :as a]]) 768 | [thi.ng.math.core :as m])) 769 | 770 | <> 771 | 772 | <> 773 | 774 | <> 775 | #+END_SRC 776 | 777 | #+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/ndarray/macros.clj :noweb yes :mkdirp yes :padline no 778 | (ns thi.ng.ndarray.macros) 779 | 780 | <> 781 | #+END_SRC 782 | -------------------------------------------------------------------------------- /src/index.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: ./setup.org 2 | #+TITLE: thi.ng/ndarray 3 | #+AUTHOR: Karsten Schmidt 4 | #+EMAIL: k@thi.ng 5 | 6 | * Contents :toc_3_gh: 7 | - [[#about-the-project][About the project]] 8 | - [[#description][Description]] 9 | - [[#example-usage][Example usage]] 10 | - [[#transpose-matrix][Transpose matrix]] 11 | - [[#reverse-direction-in-any-dimension][Reverse direction in any dimension]] 12 | - [[#skip-items-in-any-dimension][Skip items in any dimension]] 13 | - [[#extract-slices-of-a-volume][Extract slices of a volume]] 14 | - [[#fill-subsection-of-an-array][Fill subsection of an array]] 15 | - [[#contour-extraction][Contour extraction]] 16 | - [[#undirected-graph-queries-via-adjacency-matrix][Undirected graph queries via adjacency matrix]] 17 | - [[#namespaces][Namespaces]] 18 | - [[#tests][Tests]] 19 | - [[#project-definition][Project definition]] 20 | - [[#injected-properties][Injected properties]] 21 | - [[#dependencies][Dependencies]] 22 | - [[#runtime][Runtime]] 23 | - [[#development][Development]] 24 | - [[#leiningen-coordinates][Leiningen coordinates]] 25 | - [[#building-this-project][Building this project]] 26 | - [[#testing][Testing]] 27 | - [[#leiningen-project-file][Leiningen project file]] 28 | - [[#clojurescript-html-harness][ClojureScript HTML harness]] 29 | - [[#accessing-library-version-during-runtime][Accessing library version during runtime]] 30 | - [[#version-namespace][Version namespace]] 31 | - [[#release-history][Release history]] 32 | - [[#contributors][Contributors]] 33 | - [[#license][License]] 34 | 35 | * About the project 36 | 37 | ** Description 38 | 39 | This library is a Clojure/Clojurescript port of Mikola Lysenko's 40 | [[https://github.com/scijs/ndarray][ndarray]] core JS library with extended features to make more sense in a 41 | Clojure context. 42 | 43 | ** Example usage 44 | 45 | #+BEGIN_SRC clojure 46 | (require '[thi.ng.ndarray.core :as nd]) 47 | #+END_SRC 48 | 49 | *** Transpose matrix 50 | 51 | The =ndarray= constructor automatically coerces a Clojure seq into the 52 | correct array type (if not given as array already). =transpose= allows 53 | swapping the axis order without any copying. 54 | 55 | #+BEGIN_SRC clojure 56 | (def a (nd/ndarray :float64 (range 16) [4 4])) 57 | (seq a) 58 | ;; => (0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0 14.0 15.0) 59 | (seq (nd/transpose a 1 0)) 60 | ;; => (0.0 4.0 8.0 12.0 1.0 5.0 9.0 13.0 2.0 6.0 10.0 14.0 3.0 7.0 11.0 15.0) 61 | #+END_SRC 62 | 63 | *** Reverse direction in any dimension 64 | 65 | Likewise, step order can be independently flipped/manipulated in any dimension... 66 | 67 | #+BEGIN_SRC clojure 68 | (def a (nd/ndarray :float64 (range 16) [4 4])) 69 | (seq (nd/step a -1 nil)) 70 | ;; => (12.0 13.0 14.0 15.0 8.0 9.0 10.0 11.0 4.0 5.0 6.0 7.0 0.0 1.0 2.0 3.0) 71 | (seq (nd/step a nil -1)) 72 | ;; => (3.0 2.0 1.0 0.0 7.0 6.0 5.0 4.0 11.0 10.0 9.0 8.0 15.0 14.0 13.0 12.0) 73 | #+END_SRC 74 | 75 | *** Skip items in any dimension 76 | 77 | #+BEGIN_SRC clojure 78 | (def a (nd/ndarray :float64 (range 16) [4 4])) 79 | (seq (nd/step a 2 2)) 80 | ;; => (0.0 2.0 8.0 10.0) 81 | 82 | ;; ...optionally with offset 83 | (-> a (nd/truncate-l 0 1) (nd/step 2 2) seq) 84 | ;; => (1.0 3.0 9.0 11.0) 85 | #+END_SRC 86 | 87 | *** Extract slices of a volume 88 | 89 | #+BEGIN_SRC clojure 90 | (def a (nd/ndarray :float32 (range 27) [3 3 3])) 91 | (seq (nd/pick a 2 nil nil)) 92 | ;; => (18.0 19.0 20.0 21.0 22.0 23.0 24.0 25.0 26.0) 93 | (seq (nd/pick a nil 2 nil)) 94 | ;; => (6.0 7.0 8.0 15.0 16.0 17.0 24.0 25.0 26.0) 95 | (seq (nd/pick a nil nil 2)) 96 | ;; => (2.0 5.0 8.0 11.0 14.0 17.0 20.0 23.0 26.0) 97 | (seq (nd/pick a nil 0 0)) 98 | ;; (0.0 9.0 18.0) 99 | #+END_SRC 100 | 101 | *** Fill subsection of an array 102 | 103 | #+BEGIN_SRC clojure 104 | (def a (nd/ndarray :int8 (byte-array 25) [5 5])) 105 | (dorun 106 | (for [i (-> a (nd/truncate-h 4 4) (nd/truncate-l 1 1) nd/index-seq)] 107 | (nd/set-at-index a i 1))) 108 | (seq a) 109 | 110 | ;; (0 0 0 0 0 111 | ;; 0 1 1 1 0 112 | ;; 0 1 1 1 0 113 | ;; 0 1 1 1 0 114 | ;; 0 0 0 0 0) 115 | #+END_SRC 116 | 117 | *** Contour extraction 118 | 119 | [[../assets/circle-res64.jpg]] 120 | 121 | Code for this example is in the [[./contours.org][contours namespace]]. 122 | 123 | *** Undirected graph queries via adjacency matrix 124 | 125 | A slightly larger example to demonstrate connected component queries on a graph: 126 | 127 | #+BEGIN_SRC clojure 128 | (defn adjacency-mat 129 | [ids] 130 | (let [n (count ids)] 131 | (nd/ndarray :boolean (repeat (* n n) false) [n n]))) 132 | 133 | (defn add-edge 134 | [index mat [a b]] 135 | (-> mat 136 | (nd/set-at (index a) (index b) true) 137 | (nd/set-at (index b) (index a) true))) 138 | 139 | (defn edge? 140 | [index mat [a b]] 141 | (nd/get-at mat (index a) (index b))) 142 | 143 | (defn neighbors 144 | [index rev-index mat x] 145 | (sequence 146 | (comp 147 | (map-indexed (fn [i rel] (if rel (rev-index i)))) 148 | (filter identity)) 149 | (nd/pick mat (index x) nil))) 150 | 151 | (defn all-connected 152 | ([index rev-index mat q] 153 | (all-connected index rev-index mat #{} #{} [q])) 154 | ([index rev-index mat acc seen q] 155 | (let [n (->> q 156 | (filter (complement seen)) 157 | (mapcat #(neighbors index rev-index graph %))) 158 | acc' (into acc n)] 159 | (if (= acc acc') 160 | acc 161 | (recur index rev-index mat acc' (into seen q) n))))) 162 | 163 | (def index {:a 0 :b 1 :c 2 :d 3 :e 4 :f 5}) 164 | (def rev-index (reduce-kv #(assoc % %3 %2) {} index)) 165 | 166 | (def graph 167 | (reduce 168 | (partial add-edge index) 169 | (adjacency-mat index) 170 | [[:a :b] [:b :d] [:d :c] [:e :f]])) 171 | 172 | (edge? index graph [:b :a]) ;; => true 173 | (edge? index graph [:a :c]) ;; => false 174 | (neighbors index rev-index graph :c) ;; => (:a :d) 175 | 176 | (all-connected index rev-index graph :a) ;; => #{:c :b :d :a} 177 | (all-connected index rev-index graph :f) ;; => #{:e :f} 178 | #+END_SRC 179 | 180 | * Namespaces 181 | 182 | - [[./core.org][thi.ng.ndarray.core]] 183 | - [[./contours.org][thi.ng.ndarray.contours]] 184 | 185 | * Tests 186 | 187 | - [[../test/core.org][thi.ng.ndarray.test.core]] 188 | 189 | * Project definition 190 | 191 | ** Injected properties :noexport: 192 | 193 | #+BEGIN_SRC clojure :noweb-ref version 194 | 0.3.3 195 | #+END_SRC 196 | 197 | #+BEGIN_SRC clojure :exports none :noweb-ref project-url 198 | http://thi.ng/ndarray 199 | #+END_SRC 200 | 201 | #+BEGIN_SRC clojure :exports none :noweb yes :noweb-ref cljs-artefact-path 202 | target/ndarray-<>.js 203 | #+END_SRC 204 | 205 | ** Dependencies 206 | *** Runtime 207 | **** [[https://github.com/clojure/clojure][Clojure]] 208 | #+BEGIN_SRC clojure :noweb-ref dep-clj 209 | [org.clojure/clojure "1.11.1"] 210 | #+END_SRC 211 | **** [[https://github.com/clojure/clojurescript][ClojureScript]] 212 | #+BEGIN_SRC clojure :noweb-ref dep-cljs 213 | [org.clojure/clojurescript "1.11.4"] 214 | #+END_SRC 215 | **** [[http://thi.ng/typedarrays][thi.ng/typedarrays]] 216 | #+BEGIN_SRC clojure :noweb-ref dep-arrays 217 | [thi.ng/typedarrays "0.1.7"] 218 | #+END_SRC 219 | **** [[http://thi.ng/math][thi.ng/math]] 220 | #+BEGIN_SRC clojure :noweb-ref dep-math 221 | [thi.ng/math "0.3.1"] 222 | #+END_SRC 223 | *** Development 224 | **** [[https://github.com/cemerick/clojurescript.test][clojurescript.test]] 225 | #+BEGIN_SRC clojure :noweb-ref dep-cljs-test 226 | [com.cemerick/clojurescript.test "0.3.3"] 227 | #+END_SRC 228 | **** [[https://github.com/emezeske/lein-cljsbuild][Cljsbuild]] 229 | #+BEGIN_SRC clojure :noweb-ref dep-cljsbuild 230 | [lein-cljsbuild "1.1.8"] 231 | #+END_SRC 232 | **** [[https://github.com/weavejester/lein-auto][lein-auto]] 233 | #+BEGIN_SRC clojure :noweb-ref dep-autotest 234 | [lein-auto "0.1.2"] 235 | #+END_SRC 236 | **** [[https://github.com/hugoduncan/criterium][Criterium]] 237 | #+NAME: dep-criterium 238 | #+BEGIN_SRC clojure 239 | [criterium "0.4.6"] 240 | #+END_SRC 241 | 242 | ** Leiningen coordinates 243 | 244 | #+BEGIN_SRC clojure :noweb yes :noweb-ref lein-coords 245 | [thi.ng/ndarray "0.3.3"] 246 | #+END_SRC 247 | 248 | ** Building this project 249 | 250 | This project is written in a literate programming format and requires 251 | [[https://www.gnu.org/software/emacs/][Emacs]] & [[http://orgmode.org][Org-mode]] to generate usable source code. Assuming both tools 252 | are installed, the easiest way to generate a working project is via 253 | command line (make sure =emacs= is on your path or else edit its path 254 | in =tangle.sh=): 255 | 256 | #+BEGIN_SRC bash 257 | git clone https://github.com/thi.ng/ndarray.git 258 | cd ndarray 259 | ./tangle.sh src/*.org test/*.org 260 | #+END_SRC 261 | 262 | Tangling is the process of extracting & combining source blocks from 263 | =.org= files into an actual working project/source tree. Once tangling 264 | is complete, you can =cd= into the generated project directory 265 | (=babel=) and then use =lein= as usual. 266 | 267 | *** Testing 268 | 269 | The =project.clj= file defines an alias to trigger a complete build & 270 | tests for both Clojure & Clojurescript. Since PhantomJS (the usual 271 | test runner for other thi.ng projects) doesn't support typed arrays, 272 | the =cleantest= command will run the Clojurescript tests in your 273 | default browser and test results are displayed in the dev console. 274 | 275 | #+BEGIN_SRC bash 276 | cd babel 277 | lein cleantest 278 | 279 | ;; or use auto test runner via lein-auto 280 | lein auto test 281 | #+END_SRC 282 | 283 | Tangling this file will also generate a small HTML harness for the 284 | resulting JS file and will be placed in the main folder 285 | (=babel/index.html=), allowing for further experimentation in the 286 | browser. 287 | 288 | ** Leiningen project file :noexport: 289 | 290 | #+BEGIN_SRC clojure :tangle ../babel/project.clj :noweb yes :mkdirp yes :padline no 291 | (defproject thi.ng/ndarray "<>" 292 | :description "ndarray for Clojure/Clojurescript" 293 | :url "<>" 294 | :license {:name "Apache Software License 2.0" 295 | :url "http://www.apache.org/licenses/LICENSE-2.0" 296 | :distribution :repo} 297 | :scm {:name "git" 298 | :url "git@github.com:thi-ng/ndarray.git"} 299 | 300 | :min-lein-vesion "2.4.0" 301 | 302 | :dependencies [<> 303 | <> 304 | <> 305 | <>] 306 | 307 | :plugins [<>] 308 | 309 | :profiles {:dev {:dependencies [<>] 310 | :plugins [<> 311 | <>] 312 | :global-vars {*warn-on-reflection* true} 313 | :jvm-opts ^:replace [] 314 | :aliases {"cleantest" ["do" "clean," "test," "cljsbuild" "test"]}}} 315 | 316 | :auto {:default {:file-pattern #"\.(clj|cljs|cljc)$"}} 317 | 318 | :cljsbuild {:builds [{:id "simple" 319 | :source-paths ["src" "test"] 320 | :compiler {:output-to "<>" 321 | :optimizations :whitespace 322 | :pretty-print true}}] 323 | :test-commands {"unit-tests" ["open" :runner "index.html"]}} 324 | 325 | :pom-addition [:developers [:developer 326 | [:name "Karsten Schmidt"] 327 | [:url "https://thi.ng"] 328 | [:timezone "1"]]]) 329 | #+END_SRC 330 | 331 | ** ClojureScript HTML harness :noexport: 332 | 333 | #+BEGIN_SRC html :tangle ../babel/index.html :noweb yes :mkdirp yes :padline no 334 | 335 | 336 | 337 | <<lein-coords>> test 338 | 339 | 340 | 341 | 342 | 343 | #+END_SRC 344 | 345 | ** Accessing library version during runtime 346 | 347 | The autogenerated namespace =thi.ng.ndarray.version= contains a single 348 | symbol =version= holding the version string defined above: 349 | 350 | #+BEGIN_SRC clojure :noweb yes 351 | (use '[thi.ng.ndarray.version]) 352 | 353 | (prn version) 354 | ; "<>" 355 | #+END_SRC 356 | 357 | *** Version namespace :noexport: 358 | 359 | #+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/ndarray/version.cljc :noweb yes :mkdirp yes :padline no :exports none 360 | (ns thi.ng.ndarray.version) 361 | 362 | (def version "<>") 363 | #+END_SRC 364 | 365 | ** Release history 366 | 367 | | *Version* | *Released* | *Description* | *Lein coordinates* | *Tagged Github URL* | 368 | |-----------+------------+---------------------------------------------------------+----------------------------+---------------------| 369 | | 0.3.3 | 2022-04-23 | update deps | =[thi.ng/ndarray "0.3.3"]= | [[https://github.com/thi-ng/typedarrays/tree/0.3.3][0.3.3]] | 370 | | 0.3.2 | 2016-03-19 | update thi.ng/math dep | =[thi.ng/ndarray "0.3.2"]= | [[https://github.com/thi-ng/typedarrays/tree/0.3.2][0.3.2]] | 371 | | 0.3.1 | 2016-03-19 | fix refl. warning, refactor reduce impls, update deps | =[thi.ng/ndarray "0.3.1"]= | [[https://github.com/thi-ng/typedarrays/tree/0.3.1][0.3.1]] | 372 | | 0.3.0 | 2015-06-20 | CollReduce support, cljs bugfix contour ns, update deps | =[thi.ng/ndarray "0.3.0"]= | [[https://github.com/thi-ng/typedarrays/tree/0.3.0][0.3.0]] | 373 | | 0.2.0 | 2015-06-14 | add contour ns, bugfixes, updated deps | =[thi.ng/ndarray "0.2.0"]= | [[https://github.com/thi-ng/typedarrays/tree/0.2.0][0.2.0]] | 374 | | 0.1.0 | 2015-05-31 | initial test release | =[thi.ng/ndarray "0.1.0"]= | [[https://github.com/thi-ng/typedarrays/tree/0.1.0][0.1.0]] | 375 | 376 | ** Contributors 377 | 378 | | *Name* | *Role* | *Website* | 379 | |-----------------+---------------------------------+----------------------------| 380 | | [[k@thi.ng][Karsten Schmidt]] | initiator & principal developer | https://thi.ng | 381 | 382 | I've got a fairly detailed roadmap and task list to implement over the 383 | coming months, but am always happy to receive feedback & suggestions 384 | and have issues filed. Once the core engine is more refined I'll be 385 | gladly welcoming other contributions. Thanks for understanding! 386 | 387 | ** License 388 | 389 | This project is open source and licensed under the [[http://www.apache.org/licenses/LICENSE-2.0][Apache Software License 2.0]]. 390 | -------------------------------------------------------------------------------- /src/setup.org: -------------------------------------------------------------------------------- 1 | #+SEQ_TODO: TODO(t) INPROGRESS(i) WAITING(w@) | DONE(d) CANCELED(c@) 2 | #+TAGS: write(w) fix(f) verify(v) noexport(n) template(t) usetemplate(u) 3 | #+EXPORT_EXCLUDE_TAGS: noexport 4 | #+AUTHOR: Karsten Schmidt 5 | #+EMAIL: k@thi.ng 6 | #+LANGUAGE: en 7 | #+OPTIONS: toc:3 h:4 html-postamble:auto html-preamble:t tex:t 8 | #+HTML_CONTAINER: div 9 | #+HTML_DOCTYPE: 10 | #+HTML_HEAD: 11 | #+HTML_HEAD: 12 | -------------------------------------------------------------------------------- /tangle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DIR=`pwd` 4 | FILES="" 5 | 6 | # wrap each argument in the code required to call tangle on it 7 | for i in $@; do 8 | FILES="$FILES \"$i\"" 9 | done 10 | 11 | emacs -Q --batch \ 12 | --eval \ 13 | "(progn 14 | (require 'org)(require 'ob)(require 'ob-tangle)(require 'ob-lob) 15 | (org-babel-lob-ingest \"src/library-of-babel.org\") 16 | (org-babel-lob-ingest \"src/config.org\") 17 | (setq org-confirm-babel-evaluate nil) 18 | (mapc (lambda (file) 19 | (find-file (expand-file-name file \"$DIR\")) 20 | (org-babel-tangle) 21 | (kill-buffer)) '($FILES)))" \ 22 | #2>&1 | grep Tangled 23 | -------------------------------------------------------------------------------- /test/core.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: ../src/setup.org 2 | 3 | * Contents :toc_4_gh: 4 | - [[#thingndarraytestcore][thi.ng.ndarray.test.core]] 5 | - [[#main-tests][Main tests]] 6 | - [[#complete-namespace-definition][Complete namespace definition]] 7 | - [[#scratch-ignore][Scratch (ignore)]] 8 | 9 | * thi.ng.ndarray.test.core 10 | 11 | ** Main tests 12 | 13 | #+BEGIN_SRC clojure :noweb-ref test 14 | (deftest test-3d 15 | (let [shape [3 3 3] 16 | raw (range (apply * shape)) 17 | a (nd/ndarray :int8 raw shape)] 18 | (is (satisfies? nd/PNDArray a)) 19 | #?(:cljs (is (a/typed-array? (nd/data a)))) 20 | (is (= shape (nd/shape a))) 21 | (is (= [9 3 1] (nd/stride a))) 22 | (is (= (apply * shape) (nd/size a))) 23 | (is (= 3 (nd/dimension a))) 24 | (is (= raw (seq a))) 25 | (is (= [2 2 2] 26 | (-> a (nd/truncate-h 2 2 2) nd/shape))) 27 | (is (= [0 1 3 4 9 10 12 13] 28 | (-> a (nd/truncate-h 2 2 2) seq))) 29 | (is (= (-> a (nd/truncate-h 2 2 2) seq) 30 | (-> a (nd/truncate-h -1 -1 -1) seq))) 31 | (is (= [2 2 2] 32 | (-> a (nd/truncate-l 1 1 1) nd/shape))) 33 | (is (= [13 14 16 17 22 23 25 26] 34 | (-> a (nd/truncate-l 1 1 1) seq))) 35 | (is (= [13] 36 | (-> a (nd/truncate-h 2 2 2) (nd/truncate-l 1 1 1) seq))) 37 | (is (= [0 1 2 3 4 5 6 7 8 18 19 20 21 22 23 24 25 26] 38 | (-> a (nd/step 2 nil nil) seq))) 39 | (is (= [0 1 2 6 7 8 9 10 11 15 16 17 18 19 20 24 25 26] 40 | (-> a (nd/step nil 2 nil) seq))) 41 | (is (= [0 2 3 5 6 8 9 11 12 14 15 17 18 20 21 23 24 26] 42 | (-> a (nd/step nil nil 2) seq))) 43 | (is (= [9 11 12 14 15 17 18 20 21 23 24 26] 44 | (-> a (nd/truncate-l 1 0 0) (nd/step nil nil 2) seq))) 45 | (is (= [3 5 6 8 12 14 15 17 21 23 24 26] 46 | (-> a (nd/truncate-l 0 1 0) (nd/step nil nil 2) seq))) 47 | (is (= [1 4 7 10 13 16 19 22 25] 48 | (-> a (nd/truncate-l 0 0 1) (nd/step nil nil 2) seq))) 49 | (is (= [18 19 20 21 22 23 24 25 26 9 10 11 12 13 14 15 16 17 0 1 2 3 4 5 6 7 8] 50 | (-> a (nd/step -1 nil nil) seq))) 51 | (is (= [6 7 8 3 4 5 0 1 2 15 16 17 12 13 14 9 10 11 24 25 26 21 22 23 18 19 20] 52 | (-> a (nd/step nil -1 nil) seq))) 53 | (is (= [2 1 0 5 4 3 8 7 6 11 10 9 14 13 12 17 16 15 20 19 18 23 22 21 26 25 24] 54 | (-> a (nd/step nil nil -1) seq))) 55 | (is (= [0 9 18 3 12 21 6 15 24 1 10 19 4 13 22 7 16 25 2 11 20 5 14 23 8 17 26] 56 | (-> a (nd/transpose 2 1 0) seq))) 57 | (is (= [0 3 6 9 12 15 18 21 24 1 4 7 10 13 16 19 22 25 2 5 8 11 14 17 20 23 26] 58 | (-> a (nd/transpose 2 0 1) seq))) 59 | (is (= (reduce + (seq a)) (reduce + a))) 60 | (is (= (reduce + 0 (seq a)) (reduce + 0 a))) 61 | (is (= (seq (nd/truncate-l a 1 1 1)) (reduce conj [] (nd/truncate-l a 1 1 1)))) 62 | (is (= (seq (nd/transpose a 2 1 0)) (reduce conj [] (nd/transpose a 2 1 0)))) 63 | (is (= (seq (nd/step a 2 nil -1)) (reduce conj [] (nd/step a 2 nil -1)))) 64 | )) 65 | #+END_SRC 66 | 67 | #+BEGIN_SRC clojure 68 | ;; :noweb-ref test 69 | (deftest bench1 70 | (time 71 | (dotimes [i 16] 72 | (let [shape [64 64 64] 73 | [nx ny nz] (map dec shape) 74 | a (nd/ndarray :float64 (double-array (apply * shape)) shape) 75 | b (nd/ndarray :float64 (double-array (apply * shape)) shape) 76 | fa (fn [i v] (+ (+ v (nd/get-at-index b i)) 0.1)) 77 | fb (fn [i v] (- v (* (nd/get-at-index a i) 0.5)))] 78 | (time 79 | (loop [idx (nd/index-seq a)] 80 | (if idx 81 | (let [i (first idx)] 82 | (nd/update-at-index a i fa) 83 | (nd/update-at-index b i fb) 84 | (recur (next idx)))))))))) 85 | #+END_SRC 86 | 87 | ** Complete namespace definition 88 | 89 | #+BEGIN_SRC clojure :tangle ../babel/test/thi/ng/ndarray/test/core.cljc :noweb yes :mkdirp yes :padline no 90 | (ns thi.ng.ndarray.test.core 91 | (:require 92 | [thi.ng.ndarray.core :as nd] 93 | #?@(:clj 94 | [[clojure.test :refer :all]] 95 | :cljs 96 | [[thi.ng.typedarrays.core :as a] 97 | [cemerick.cljs.test :as t :refer-macros [is deftest]]]))) 98 | 99 | #?(:cljs (enable-console-print!)) 100 | 101 | <> 102 | 103 | #?(:cljs 104 | (if (a/typed-arrays-supported?) 105 | (t/run-all-tests) 106 | (prn "Can't test - typed arrays not supported!"))) 107 | #+END_SRC 108 | 109 | *** Scratch (ignore) 110 | 111 | #+BEGIN_SRC clojure 112 | (use 'criterium.core) 113 | (set! *unchecked-math* true) 114 | 115 | ;; 937.259675 µs [16 16 16] 116 | ;; 51.299623 ms [64 64 64] 117 | ;; 1.418434 ms [2 2 2048] 118 | (with-progress-reporting 119 | (quick-bench 120 | (let [shape [2 2 2048] 121 | [nx ny nz] (map dec shape) 122 | ^doubles ad (double-array (apply * shape)) 123 | ^doubles bd (double-array (apply * shape)) 124 | a (nd/ndarray :float64 ad shape) 125 | b (nd/ndarray :float64 bd shape)] 126 | (loop [idx (nd/index-seq a)] 127 | (if idx 128 | (let [i (first idx)] 129 | (aset ad i (+ (+ (aget ad i) (aget bd i)) 0.1)) 130 | (aset bd i (- (aget bd i) (* (aget ad i) 0.5))) 131 | (recur (next idx)))))) 132 | :verbose)) 133 | 134 | (with-progress-reporting 135 | (quick-bench 136 | (let [shape [16 16 16] 137 | a (nd/ndarray :float64 (double-array (apply * shape)) shape) 138 | b (nd/ndarray :float64 (double-array (apply * shape)) shape) 139 | fa (fn [i v] (+ (+ v (nd/get-at-index b i)) 0.1)) 140 | fb (fn [i v] (- v (* (nd/get-at-index a i) 0.5)))] 141 | (loop [idx (nd/index-seq a)] 142 | (if idx 143 | (let [i (first idx)] 144 | (nd/update-at-index a i fa) 145 | (nd/update-at-index b i fb) 146 | (recur (next idx)))))) 147 | :verbose)) 148 | #+END_SRC 149 | --------------------------------------------------------------------------------