This repository has been archived by the owner on May 14, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
operations.lisp
382 lines (333 loc) · 13.7 KB
/
operations.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
(in-package :array-operations)
;;;; !!! HUGE FIXES ARE NEEDED
;;;; - remove find-or-displace-to-flat-array
;;;; - make result elemet type automatic where possible
;;;; - multiparam elementwise operations, inline for speed
(defun ignoring-nil (function)
"From a bivariate function, create a function that calls the
original if both arguments are non-nil, otherwise returns the argument
which is non-nil, or nil when both are. Useful for ignoring nil's
when calling reduce."
#'(lambda (a b)
(cond
((and a b) (funcall function a b))
(a a)
(b b)
(t nil))))
(defun array-reduce (function array &key key ignore-p all-ignored)
"Apply (reduce function ...) to the flattened array. If ignore-p is
given, it behaves as if elements which satisfy ignore-p (ie return
non-nil for (funcall ignore-p element) were removed from the array.
When all elements are ignored, the value is all-ignored. ignore-p is
called before key."
(declare (optimize (debug 3)))
(bind (((:values original start) (find-original-array array))
(size (array-total-size array))
(end (+ start size)))
(when (zerop size)
(return-from array-reduce all-ignored))
(unless key
(setf key #'identity))
(if ignore-p
(let ((val all-ignored))
;; bit of a hole in here, should keep track of whether there
;; has been a non-nil variable separately, instead of using
;; equal on val and all-ignored
(iter
(for i :from start :below end)
(for v := (row-major-aref original i))
(unless (funcall ignore-p v)
(let ((v-key (funcall key v)))
(setf val (if (equal val all-ignored)
v-key
(funcall function val v-key))))))
val)
(let ((val (funcall key (row-major-aref array start))))
(iter
(for i :from (1+ start) :below end)
(setf val (funcall function val
(funcall key (row-major-aref original i)))))
val))))
(defun array-max (array &key key ignore-p)
"Find the maximum in array."
(array-reduce #'max array :key key :ignore-p ignore-p))
(defun array-min (array &key key ignore-p)
"Find the minimum in array."
(array-reduce #'min array :key key :ignore-p ignore-p))
(defun array-sum (array &key key ignore-p)
"Sum of the elements in array."
(array-reduce #'+ array :key key :ignore-p ignore-p))
(defun array-product (array &key key ignore-p)
"Product of the elements in array."
(array-reduce #'* array :key key :ignore-p ignore-p))
(defun array-count (array predicate)
"Count elements in array satisfying predicate."
(array-reduce #'+ array :key (lambda (x) (if (funcall predicate x) 1 0))))
(defun array-range (array &key key ignore-p)
"Minimum and maximum of an array, returned as a two-element list.
In case all elements are nil, return (nil nil)."
(let ((range (array-reduce (lambda (x y)
(if (atom x)
(list (min x y) (max x y))
(list (min (first x) y) (max (second x) y))))
array :key key :ignore-p ignore-p)))
(cond
((null range) nil) ; all are nil
((atom range) (list range range)) ; single non element
(t range))))
(defun array-abs-range (array &key key ignore-p)
"Maximum of the absolute values of the elements of an array."
(array-reduce (lambda (x y) (max (abs x) (abs y))) array
:key key :ignore-p ignore-p))
(defun array-mean (array &key key ignore-p)
"Calculate the mean of the elements in array."
(/ (array-sum array :key key :ignore-p ignore-p)
(if ignore-p
(array-count array #'not)
(length array))))
(defun dot-product (x y &optional (function #'*))
"Calculate the (generalized) dot product of vectors x and y."
(let* ((n (length x))
(sum 0))
(assert (= (length y) n))
(dotimes (i n)
(incf sum (funcall function (aref x i) (aref y i))))
sum))
(defun outer-product (x y &key (function #'*) (element-type (array-element-type x)))
"Calculate the (generalized) outer product of vectors x and y. When
not specified, element-type will be the element-type of x."
(declare (type (vector * *) x y))
(let* ((x-length (array-dimension x 0))
(y-length (array-dimension y 0))
(result (make-array (list x-length y-length) :element-type element-type)))
(dotimes (i x-length)
(dotimes (j y-length)
(setf (aref result i j) (funcall function (aref x i) (aref y j)))))
result))
(defun array-elementwise-operation (operator a b element-type)
"Apply a bivariate operator on two arrays of the same dimension
elementwise, returning the resulting array, which has the given
element-type."
(let ((dimensions (array-dimensions a)))
(assert (equal dimensions (array-dimensions b)))
(bind (((:values a-original a-index-offset) (find-original-array a))
((:values b-original b-index-offset) (find-original-array b))
(length (array-total-size a-original))
(result (make-array dimensions :element-type element-type)))
(assert (= length (array-total-size b-original)))
(iter
(for index :from 0 :below length)
(for a-index :from a-index-offset)
(for b-index :from b-index-offset)
(setf (row-major-aref result index)
(funcall operator
(row-major-aref a-original a-index)
(row-major-aref b-original b-index))))
result)))
;; elementwise operations
(defun array+ (a b &optional (element-type 'double-float))
(array-elementwise-operation #'+ a b element-type))
(defun array- (a b &optional (element-type 'double-float))
(array-elementwise-operation #'- a b element-type))
(defun array* (a b &optional (element-type 'double-float))
(array-elementwise-operation #'* a b element-type))
(defun array/ (a b &optional (element-type 'double-float))
(array-elementwise-operation #'/ a b element-type))
(defmacro define-array-scalar-binary-operation (name operator)
`(defun ,name (a b &optional (element-type 'double-float))
(array-map (lambda (x) (,operator x b)) a element-type)))
(define-array-scalar-binary-operation array-scalar+ +)
(define-array-scalar-binary-operation array-scalar- -)
(define-array-scalar-binary-operation array-scalar* *)
(define-array-scalar-binary-operation array-scalar/ /)
(defun array-reciprocal (a &optional b (element-type 'double-float))
"For each element x of a, map to (/ x) or (/ b x) (if b is given)."
(if b
(array-map (lambda (x) (/ b x)) a element-type)
(array-map (lambda (x) (/ x)) a element-type)))
(defun array-negate (a &optional b (element-type 'double-float))
"For each element x of a, map to (- x) or (- b x) (if b is given)."
(if b
(array-map (lambda (x) (- b x)) a element-type)
(array-map (lambda (x) (- x)) a element-type)))
(defun array-map-list (function array n
&optional (element-type (array-element-type array)))
"Apply function (which is supposed to return a list of length n) to
each element of array, returning the results in an array which has an
extra last dimension of n."
(let* ((dimensions (array-dimensions array))
(total-size (array-total-size array))
(result (make-array (append dimensions (list n))
:element-type element-type))
(result-matrix (displace-array result (list total-size n) 0)))
(dotimes (i total-size result)
(let ((value (funcall function (row-major-aref array i))))
(assert (= (length value) n))
(iter
(for elt :in value)
(for j :from 0)
(setf (aref result-matrix i j) elt))))))
(defun array-map-values (function array n
&optional (element-type (array-element-type array)))
"Apply function (which is supposed to return n values) to each
element of array, returning the results in an array which has an extra
last dimension of n."
(flet ((list-function (x)
(multiple-value-list (funcall function x))))
(array-map-list #'list-function array n element-type)))
(defun index-extrema (n key &optional (weak-relation #'<=))
"Find the extrema (using weak-relation) of a (funcall key i) and
the positions (indexes i) at which it occurs. Return the extremum and
the list of positions.
Notes:
1) when (and (funcall weak-relation a b) (funcall weak-relation b a)),
a and b are considered equal.
2) the list of positions is in reversed order, you may use nreverse on
it as nothing shares this structure.
Examples:
(index-extrema 5 (lambda (x) (abs (- x 2)))) => 2, (4 0)
"
(let ((maximum nil)
(positions nil))
(dotimes (i n)
(let ((element (funcall key i)))
(cond
((null maximum)
(setf maximum element
positions (list i)))
((funcall weak-relation maximum element)
(if (funcall weak-relation element maximum)
(setf positions (cons i positions)) ; equality
(setf maximum element ; strictly greater
positions (list i)))))))
(values maximum positions)))
(defun array-extrema (array &key (key #'identity) (weak-relation #'<=))
"Find the extrema (using weak-relation) of an array and the
positions at which it occurs. The positions are flat, one-dimensional
vector indexes in the array (like the index used with
row-major-aref). Return the extremum and the list of positions.
Notes:
1) when (and (funcall weak-relation a b) (funcall weak-relation b a)),
a and b are considered equal.
2) the list of positions is in reversed order, you may use nreverse on
it as nothing shares this structure.
Examples:
(array-extrema #(1 2 2 3 3 2)) => 3, (4 3)
(array-extrema #2A((2 1) (3 1) (2 3))) => 3, (5 2)
"
(bind (((:values flat-array index-offset total-size)
(find-or-displace-to-flat-array array)))
(index-extrema total-size
(lambda (i)
(funcall key
(aref flat-array (+ index-offset i))))
weak-relation)))
(defmacro vectorize ((&rest vectors) expression
&key (result-element-type ''double-float)
(into nil))
"Expand into a loop so that `expression' is evaluated in all
corresponding elements of the `vectors', where the name of the vector
will be replaced by the actual values the appropriate expression.
Results from the elementwise evaluation of expression will be returned
as a vector.
If `into' is non-nil, it has to contain the name of one of the
vectors, where the element will be placed. All other parts of the
expression will have their original meaning (see example below).
Return the resulting vector (which is one of the original, if `into'
was specified).
Example:
(let ((a #(1d0 2d0 3d0))
(b #(4d0 5d0 6d0))
(c 0.25d0))
(vectorize (a b)
(+ a b c 0.25))) ; => #(5.5d0 7.5d0 9.5d0)
Notes:
1. Obviously, `vectors' can only contain symbols (otherwise the macro
would not know which vector you are referring to). If you want to use
a vector you just constructed, use let as above.
2. If you want to put the result in an array that has a different
element type, you need to use coerce explicitly in `expression'."
(assert (every #'symbolp vectors))
(assert (plusp (length vectors)))
(with-unique-names (result n i)
;; we setup another name for each vector, otherwise the symbol
;; macros would be recursive
(let ((shadowed-vector-names (mapcar (lambda (x) (gensym (symbol-name x)))
vectors)))
`(progn
,result-element-type
;; check first vector
(assert (vectorp ,(car vectors)))
;; assign vectors to shadow names
(let (,@(mapcar #'list shadowed-vector-names vectors))
(let* ((,n (length ,(car vectors))))
;; check that all vectors are the same length
(assert (every (lambda (v)
(and (= (length v) ,n) (vectorp v)))
(list ,@(cdr vectors))))
;; setup result if necessary, if not, results end up in
;; the vector specified by `into'
(let ((,result ,(if into
(progn
(assert (and (symbolp into)
(member into vectors)))
into)
`(make-array ,n :element-type
,result-element-type))))
;; symbol macros index in shadow names
(symbol-macrolet (,@(mapcar
(lambda (name shadowed-name)
(list name (list 'aref shadowed-name i)))
vectors
shadowed-vector-names))
;; the loop calculates the expression
(dotimes (,i ,n)
(setf (aref ,result ,i) ,expression)))
,result)))))))
;;; !!!! should do something for simpons-rule-on-index
(defun map-vector-to-matrix (function vector)
"Call `function' that maps an atom to a vector on each element of
`vector', and collect the results as columns of a matrix. The
array-element-type and dimensions of the matrix are established
automatically by calling `function' on (aref vector 0), so `function'
needs to return the same kind of vector all the time."
(assert (and (vectorp vector) (plusp (length vector))))
(let* ((first-result (funcall function (aref vector 0)))
(m (length first-result))
(n (length vector))
(matrix (make-array (list m n)
:element-type (array-element-type first-result))))
(dotimes (j n)
(let ((result (if (zerop j)
first-result
(funcall function (aref vector j)))))
(assert (vectorp result))
(dotimes (i m)
(setf (aref matrix i j) (aref result i)))))
matrix))
(defun array-find-min (array &optional (key #'identity))
"Find the element of array which returns the smallest (funcall key
element), where key should return a real number. Return (values
min-element min-key)."
(bind (((:values flat-array start length) (find-or-displace-to-flat-array array))
(end (+ start length))
(min-element (aref flat-array start))
(min-key (funcall key min-element)))
(iter
(for i :from (1+ start) :below end)
(for element := (aref flat-array i))
(for element-key := (funcall key element))
(when (< element-key min-key)
(setf min-key element-key
min-element element)))
(values min-element min-key)))
(defun transpose (matrix)
"Transpose a matrix."
(check-type matrix (array * (* *)))
(bind (((rows cols) (array-dimensions matrix))
(transpose (make-array (list cols rows)
:element-type (array-element-type matrix))))
(dotimes (i rows)
(dotimes (j cols)
(setf (aref transpose j i) (aref matrix i j))))
transpose))