Chris@5
|
1
|
Chris@94
|
2 module yetilab.matrix.matrix;
|
Chris@94
|
3
|
Chris@214
|
4 // A matrix is an array of vectors.
|
Chris@94
|
5
|
Chris@101
|
6 // A matrix can be stored in either column-major (the default) or
|
Chris@101
|
7 // row-major format. Storage order is an efficiency concern only:
|
Chris@101
|
8 // every API function operating on matrix objects will return the same
|
Chris@101
|
9 // result regardless of storage order. (The transpose function just
|
Chris@101
|
10 // switches the row/column order without moving the elements.)
|
Chris@18
|
11
|
Chris@214
|
12 //!!! check that we are not unnecessarily copying in the transform functions
|
Chris@214
|
13
|
Chris@214
|
14 vec = load yetilab.block.vector;
|
Chris@99
|
15 bf = load yetilab.block.blockfuncs;
|
Chris@9
|
16
|
Chris@214
|
17 load yetilab.block.vectortype;
|
Chris@195
|
18 load yetilab.matrix.matrixtype;
|
Chris@195
|
19
|
Chris@208
|
20 size m =
|
Chris@208
|
21 case m of
|
Chris@208
|
22 RowM r:
|
Chris@208
|
23 major = length r;
|
Chris@208
|
24 {
|
Chris@208
|
25 rows = major,
|
Chris@214
|
26 columns = if major > 0 then vec.length r[0] else 0 fi,
|
Chris@208
|
27 };
|
Chris@208
|
28 ColM c:
|
Chris@208
|
29 major = length c;
|
Chris@208
|
30 {
|
Chris@214
|
31 rows = if major > 0 then vec.length c[0] else 0 fi,
|
Chris@208
|
32 columns = major,
|
Chris@208
|
33 };
|
Chris@208
|
34 esac;
|
Chris@208
|
35
|
Chris@208
|
36 width m = (size m).columns;
|
Chris@208
|
37 height m = (size m).rows;
|
Chris@208
|
38
|
Chris@208
|
39 getAt row col m =
|
Chris@208
|
40 case m of
|
Chris@214
|
41 RowM rows: r = rows[row]; vec.at col r;
|
Chris@214
|
42 ColM cols: c = cols[col]; vec.at row c;
|
Chris@208
|
43 esac;
|
Chris@208
|
44
|
Chris@208
|
45 getColumn j m =
|
Chris@208
|
46 case m of
|
Chris@214
|
47 RowM rows: vec.fromList (map do i: getAt i j m done [0..length rows-1]);
|
Chris@208
|
48 ColM cols: cols[j];
|
Chris@208
|
49 esac;
|
Chris@208
|
50
|
Chris@208
|
51 getRow i m =
|
Chris@208
|
52 case m of
|
Chris@208
|
53 RowM rows: rows[i];
|
Chris@214
|
54 ColM cols: vec.fromList (map do j: getAt i j m done [0..length cols-1]);
|
Chris@208
|
55 esac;
|
Chris@208
|
56
|
Chris@214
|
57 /*
|
Chris@208
|
58 setAt row col n m = //!!! dangerous, could modify copies -- should it be allowed?
|
Chris@208
|
59 case m of
|
Chris@214
|
60 RowM rows: r = rows[row]; (vec.data r)[col] := n;
|
Chris@214
|
61 ColM cols: c = cols[col]; (vec.data c)[row] := n;
|
Chris@208
|
62 esac;
|
Chris@214
|
63 */
|
Chris@208
|
64
|
Chris@208
|
65 isRowMajor? m =
|
Chris@208
|
66 case m of
|
Chris@208
|
67 RowM _: true;
|
Chris@208
|
68 ColM _: false;
|
Chris@208
|
69 esac;
|
Chris@94
|
70
|
Chris@201
|
71 newColMajorStorage { rows, columns } =
|
Chris@97
|
72 if rows < 1 then array []
|
Chris@214
|
73 else array (map \(vec.zeros rows) [1..columns])
|
Chris@97
|
74 fi;
|
Chris@94
|
75
|
Chris@98
|
76 zeroMatrix { rows, columns } =
|
Chris@208
|
77 ColM (newColMajorStorage { rows, columns });
|
Chris@201
|
78
|
Chris@201
|
79 zeroMatrixWithTypeOf m { rows, columns } =
|
Chris@208
|
80 if isRowMajor? m then
|
Chris@208
|
81 RowM (newColMajorStorage { rows = columns, columns = rows });
|
Chris@201
|
82 else
|
Chris@208
|
83 ColM (newColMajorStorage { rows, columns });
|
Chris@201
|
84 fi;
|
Chris@5
|
85
|
Chris@214
|
86 zeroSizeMatrix () = zeroMatrix { rows = 0, columns = 0 };
|
Chris@214
|
87
|
Chris@98
|
88 generate f { rows, columns } =
|
Chris@214
|
89 if rows < 1 or columns < 1 then zeroSizeMatrix ()
|
Chris@214
|
90 else
|
Chris@214
|
91 m = array (map \(new double[rows]) [1..columns]);
|
Chris@214
|
92 for [0..columns-1] do col:
|
Chris@214
|
93 for [0..rows-1] do row:
|
Chris@214
|
94 m[col][row] := f row col;
|
Chris@214
|
95 done;
|
Chris@5
|
96 done;
|
Chris@214
|
97 ColM (array (map vec.vector m))
|
Chris@214
|
98 fi;
|
Chris@5
|
99
|
Chris@20
|
100 constMatrix n = generate do row col: n done;
|
Chris@20
|
101 randomMatrix = generate do row col: Math#random() done;
|
Chris@5
|
102 identityMatrix = constMatrix 1;
|
Chris@5
|
103
|
Chris@100
|
104 transposed m =
|
Chris@208
|
105 case m of
|
Chris@208
|
106 RowM d: ColM d;
|
Chris@208
|
107 ColM d: RowM d;
|
Chris@208
|
108 esac;
|
Chris@100
|
109
|
Chris@100
|
110 flipped m =
|
Chris@208
|
111 if isRowMajor? m then
|
Chris@208
|
112 generate do row col: getAt row col m done (size m);
|
Chris@100
|
113 else
|
Chris@100
|
114 transposed
|
Chris@208
|
115 (generate do row col: getAt col row m done
|
Chris@208
|
116 { rows = (width m), columns = (height m) });
|
Chris@100
|
117 fi;
|
Chris@100
|
118
|
Chris@161
|
119 toRowMajor m =
|
Chris@208
|
120 if isRowMajor? m then m else flipped m fi;
|
Chris@161
|
121
|
Chris@161
|
122 toColumnMajor m =
|
Chris@208
|
123 if not isRowMajor? m then m else flipped m fi;
|
Chris@161
|
124
|
Chris@100
|
125 // Matrices with different storage order but the same contents are
|
Chris@100
|
126 // equal (but comparing them is slow)
|
Chris@97
|
127 equal m1 m2 =
|
Chris@208
|
128 if size m1 != size m2 then false
|
Chris@208
|
129 elif isRowMajor? m1 != isRowMajor? m2 then equal (flipped m1) m2;
|
Chris@100
|
130 else
|
Chris@214
|
131 compare d1 d2 = all id (map2 vec.equal d1 d2);
|
Chris@208
|
132 case m1 of
|
Chris@208
|
133 RowM d1: case m2 of RowM d2: compare d1 d2; _: false; esac;
|
Chris@208
|
134 ColM d1: case m2 of ColM d2: compare d1 d2; _: false; esac;
|
Chris@100
|
135 esac
|
Chris@100
|
136 fi;
|
Chris@97
|
137
|
Chris@214
|
138 /*!!! not needed now it's immutable?
|
Chris@95
|
139 copyOf m =
|
Chris@214
|
140 (copyOfData d = (array (map vec.copyOf d));
|
Chris@208
|
141 case m of
|
Chris@208
|
142 RowM d: RowM (copyOfData d);
|
Chris@208
|
143 ColM d: ColM (copyOfData d);
|
Chris@208
|
144 esac);
|
Chris@214
|
145 */
|
Chris@6
|
146
|
Chris@163
|
147 newMatrix type data = //!!! NB does not copy data
|
Chris@96
|
148 (tagger = case type of RowMajor (): RowM; ColumnMajor (): ColM esac;
|
Chris@214
|
149 if empty? data or vec.empty? (head data)
|
Chris@201
|
150 then zeroSizeMatrix ()
|
Chris@208
|
151 else tagger (array data)
|
Chris@96
|
152 fi);
|
Chris@96
|
153
|
Chris@163
|
154 newRowVector data = //!!! NB does not copy data
|
Chris@208
|
155 RowM (array [data]);
|
Chris@96
|
156
|
Chris@163
|
157 newColumnVector data = //!!! NB does not copy data
|
Chris@208
|
158 ColM (array [data]);
|
Chris@8
|
159
|
Chris@195
|
160 scaled factor m = //!!! v inefficient
|
Chris@208
|
161 generate do row col: factor * (getAt row col m) done (size m);
|
Chris@98
|
162
|
Chris@98
|
163 sum' m1 m2 =
|
Chris@208
|
164 if (size m1) != (size m2)
|
Chris@208
|
165 then failWith "Matrices are not the same size: \(size m1), \(size m2)";
|
Chris@98
|
166 else
|
Chris@208
|
167 generate do row col: getAt row col m1 + getAt row col m2 done (size m1);
|
Chris@98
|
168 fi;
|
Chris@98
|
169
|
Chris@98
|
170 product m1 m2 =
|
Chris@208
|
171 if (size m1).columns != (size m2).rows
|
Chris@208
|
172 then failWith "Matrix dimensions incompatible: \(size m1), \(size m2) (\((size m1).columns != (size m2).rows)";
|
Chris@98
|
173 else
|
Chris@98
|
174 generate do row col:
|
Chris@208
|
175 bf.sum (bf.multiply (getRow row m1) (getColumn col m2))
|
Chris@208
|
176 done { rows = (size m1).rows, columns = (size m2).columns }
|
Chris@98
|
177 fi;
|
Chris@98
|
178
|
Chris@161
|
179 asRows m =
|
Chris@208
|
180 map do i: getRow i m done [0 .. (height m) - 1];
|
Chris@161
|
181
|
Chris@161
|
182 asColumns m =
|
Chris@208
|
183 map do i: getColumn i m done [0 .. (width m) - 1];
|
Chris@161
|
184
|
Chris@178
|
185 concatAgainstGrain tagger getter counter mm =
|
Chris@208
|
186 (n = counter (size (head mm));
|
Chris@208
|
187 tagger (array
|
Chris@177
|
188 (map do i:
|
Chris@214
|
189 vec.concat (map (getter i) mm)
|
Chris@208
|
190 done [0..n-1])));
|
Chris@177
|
191
|
Chris@178
|
192 concatWithGrain tagger getter counter mm =
|
Chris@208
|
193 tagger (array
|
Chris@177
|
194 (concat
|
Chris@177
|
195 (map do m:
|
Chris@208
|
196 n = counter (size m);
|
Chris@208
|
197 map do i: getter i m done [0..n-1]
|
Chris@208
|
198 done mm)));
|
Chris@177
|
199
|
Chris@178
|
200 checkDimensionsFor direction first mm =
|
Chris@178
|
201 (counter = if direction == Horizontal () then (.rows) else (.columns) fi;
|
Chris@208
|
202 n = counter (size first);
|
Chris@208
|
203 if not (all id (map do m: counter (size m) == n done mm)) then
|
Chris@208
|
204 failWith "Matrix dimensions incompatible for concat (found \(map do m: counter (size m) done mm) not all of which are \(n))";
|
Chris@178
|
205 fi);
|
Chris@178
|
206
|
Chris@187
|
207 concat direction mm = //!!! doc: storage order is taken from first matrix in sequence
|
Chris@187
|
208 //!!! would this be better as separate concatHorizontal/concatVertical functions?
|
Chris@190
|
209 case mm of
|
Chris@190
|
210 first::rest:
|
Chris@178
|
211 checkDimensionsFor direction first mm;
|
Chris@208
|
212 row = isRowMajor? first;
|
Chris@178
|
213 // horizontal, row-major: against grain with rows
|
Chris@178
|
214 // horizontal, col-major: with grain with cols
|
Chris@178
|
215 // vertical, row-major: with grain with rows
|
Chris@178
|
216 // vertical, col-major: against grain with cols
|
Chris@178
|
217 case direction of
|
Chris@178
|
218 Horizontal ():
|
Chris@208
|
219 if row then concatAgainstGrain RowM getRow (.rows) mm;
|
Chris@208
|
220 else concatWithGrain ColM getColumn (.columns) mm;
|
Chris@178
|
221 fi;
|
Chris@178
|
222 Vertical ():
|
Chris@208
|
223 if row then concatWithGrain RowM getRow (.rows) mm;
|
Chris@208
|
224 else concatAgainstGrain ColM getColumn (.columns) mm;
|
Chris@178
|
225 fi;
|
Chris@178
|
226 esac;
|
Chris@190
|
227 [single]: single;
|
Chris@190
|
228 _: zeroSizeMatrix ();
|
Chris@190
|
229 esac;
|
Chris@177
|
230
|
Chris@187
|
231 rowSlice start count m = //!!! doc: storage order same as input
|
Chris@208
|
232 if isRowMajor? m then
|
Chris@208
|
233 RowM (array (map ((flip getRow) m) [start .. start + count - 1]))
|
Chris@187
|
234 else
|
Chris@214
|
235 ColM (array (map (vec.rangeOf start count) (asColumns m)))
|
Chris@187
|
236 fi;
|
Chris@187
|
237
|
Chris@187
|
238 columnSlice start count m = //!!! doc: storage order same as input
|
Chris@208
|
239 if not isRowMajor? m then
|
Chris@208
|
240 ColM (array (map ((flip getColumn) m) [start .. start + count - 1]))
|
Chris@187
|
241 else
|
Chris@214
|
242 RowM (array (map (vec.rangeOf start count) (asRows m)))
|
Chris@187
|
243 fi;
|
Chris@187
|
244
|
Chris@201
|
245 resizedTo newsize m =
|
Chris@208
|
246 (if newsize == (size m) then
|
Chris@201
|
247 m
|
Chris@208
|
248 elif (height m) == 0 or (width m) == 0 then
|
Chris@202
|
249 zeroMatrixWithTypeOf m newsize;
|
Chris@201
|
250 else
|
Chris@208
|
251 growrows = newsize.rows - (height m);
|
Chris@208
|
252 growcols = newsize.columns - (width m);
|
Chris@208
|
253 rowm = isRowMajor? m;
|
Chris@201
|
254 resizedTo newsize
|
Chris@201
|
255 if rowm and growrows < 0 then
|
Chris@201
|
256 rowSlice 0 newsize.rows m
|
Chris@201
|
257 elif (not rowm) and growcols < 0 then
|
Chris@201
|
258 columnSlice 0 newsize.columns m
|
Chris@201
|
259 elif growrows < 0 then
|
Chris@201
|
260 rowSlice 0 newsize.rows m
|
Chris@201
|
261 elif growcols < 0 then
|
Chris@201
|
262 columnSlice 0 newsize.columns m
|
Chris@201
|
263 else
|
Chris@201
|
264 if growrows > 0 then
|
Chris@201
|
265 concat (Vertical ())
|
Chris@208
|
266 [m, zeroMatrixWithTypeOf m ((size m) with { rows = growrows })]
|
Chris@201
|
267 else
|
Chris@201
|
268 concat (Horizontal ())
|
Chris@208
|
269 [m, zeroMatrixWithTypeOf m ((size m) with { columns = growcols })]
|
Chris@201
|
270 fi
|
Chris@201
|
271 fi
|
Chris@202
|
272 fi);
|
Chris@201
|
273
|
Chris@5
|
274 {
|
Chris@208
|
275 size,
|
Chris@208
|
276 width,
|
Chris@208
|
277 height,
|
Chris@208
|
278 getAt,
|
Chris@208
|
279 getColumn,
|
Chris@208
|
280 getRow,
|
Chris@214
|
281 // setAt,
|
Chris@208
|
282 isRowMajor?,
|
Chris@208
|
283 generate,
|
Chris@208
|
284 constMatrix,
|
Chris@208
|
285 randomMatrix,
|
Chris@208
|
286 zeroMatrix,
|
Chris@208
|
287 identityMatrix,
|
Chris@208
|
288 zeroSizeMatrix,
|
Chris@208
|
289 equal,
|
Chris@214
|
290 // copyOf,
|
Chris@208
|
291 transposed,
|
Chris@208
|
292 flipped,
|
Chris@208
|
293 toRowMajor,
|
Chris@208
|
294 toColumnMajor,
|
Chris@208
|
295 scaled,
|
Chris@208
|
296 resizedTo,
|
Chris@208
|
297 asRows,
|
Chris@208
|
298 asColumns,
|
Chris@208
|
299 sum = sum',
|
Chris@208
|
300 product,
|
Chris@208
|
301 concat,
|
Chris@208
|
302 rowSlice,
|
Chris@208
|
303 columnSlice,
|
Chris@208
|
304 newMatrix,
|
Chris@208
|
305 newRowVector,
|
Chris@208
|
306 newColumnVector,
|
Chris@208
|
307 }
|
Chris@208
|
308 as
|
Chris@208
|
309 {
|
Chris@208
|
310 //!!! check whether these are right to be .selector rather than just selector
|
Chris@208
|
311
|
Chris@208
|
312 size is matrix -> { .rows is number, .columns is number },
|
Chris@208
|
313 width is matrix -> number,
|
Chris@208
|
314 height is matrix -> number,
|
Chris@208
|
315 getAt is number -> number -> matrix -> number,
|
Chris@214
|
316 getColumn is number -> matrix -> vector,
|
Chris@214
|
317 getRow is number -> matrix -> vector,
|
Chris@214
|
318 // setAt is number -> number -> number -> matrix -> (), //!!! lose?
|
Chris@208
|
319 isRowMajor? is matrix -> boolean,
|
Chris@195
|
320 generate is (number -> number -> number) -> { .rows is number, .columns is number } -> matrix,
|
Chris@195
|
321 constMatrix is number -> { .rows is number, .columns is number } -> matrix,
|
Chris@195
|
322 randomMatrix is { .rows is number, .columns is number } -> matrix,
|
Chris@195
|
323 zeroMatrix is { .rows is number, .columns is number } -> matrix,
|
Chris@195
|
324 identityMatrix is { .rows is number, .columns is number } -> matrix,
|
Chris@195
|
325 zeroSizeMatrix is () -> matrix,
|
Chris@195
|
326 equal is matrix -> matrix -> boolean,
|
Chris@214
|
327 // copyOf is matrix -> matrix,
|
Chris@195
|
328 transposed is matrix -> matrix,
|
Chris@195
|
329 flipped is matrix -> matrix,
|
Chris@195
|
330 toRowMajor is matrix -> matrix,
|
Chris@195
|
331 toColumnMajor is matrix -> matrix,
|
Chris@195
|
332 scaled is number -> matrix -> matrix,
|
Chris@195
|
333 resizedTo is { .rows is number, .columns is number } -> matrix -> matrix,
|
Chris@214
|
334 asRows is matrix -> list<vector>,
|
Chris@214
|
335 asColumns is matrix -> list<vector>,
|
Chris@208
|
336 sum is matrix -> matrix -> matrix,
|
Chris@195
|
337 product is matrix -> matrix -> matrix,
|
Chris@195
|
338 concat is (Horizontal () | Vertical ()) -> list<matrix> -> matrix,
|
Chris@195
|
339 rowSlice is number -> number -> matrix -> matrix,
|
Chris@195
|
340 columnSlice is number -> number -> matrix -> matrix,
|
Chris@214
|
341 newMatrix is (ColumnMajor () | RowMajor ()) -> list<vector> -> matrix,
|
Chris@214
|
342 newRowVector is vector -> matrix,
|
Chris@214
|
343 newColumnVector is vector -> matrix,
|
Chris@5
|
344 }
|
Chris@5
|
345
|