Chris@5
|
1
|
Chris@94
|
2 module yetilab.matrix.matrix;
|
Chris@94
|
3
|
Chris@94
|
4 // A matrix is an array of fvectors (i.e. primitive double[]s).
|
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@93
|
12 vec = load yetilab.block.fvector;
|
Chris@96
|
13 block = load yetilab.block.block;
|
Chris@99
|
14 bf = load yetilab.block.blockfuncs;
|
Chris@9
|
15
|
Chris@195
|
16 load yetilab.block.blocktype;
|
Chris@195
|
17 load yetilab.matrix.matrixtype;
|
Chris@195
|
18
|
Chris@96
|
19 make d = {
|
Chris@96
|
20 get data () = d,
|
Chris@96
|
21 get size () =
|
Chris@96
|
22 case d of
|
Chris@96
|
23 RowM r:
|
Chris@96
|
24 major = length r;
|
Chris@96
|
25 {
|
Chris@96
|
26 rows = major,
|
Chris@96
|
27 columns = if major > 0 then vec.length r[0] else 0 fi,
|
Chris@96
|
28 };
|
Chris@96
|
29 ColM c:
|
Chris@96
|
30 major = length c;
|
Chris@96
|
31 {
|
Chris@96
|
32 rows = if major > 0 then vec.length c[0] else 0 fi,
|
Chris@96
|
33 columns = major,
|
Chris@96
|
34 };
|
Chris@94
|
35 esac,
|
Chris@96
|
36 getColumn j =
|
Chris@96
|
37 case d of
|
Chris@96
|
38 RowM rows: block.fromList (map do i: getAt i j done [0..length rows-1]);
|
Chris@96
|
39 ColM cols: block.block cols[j];
|
Chris@94
|
40 esac,
|
Chris@96
|
41 getRow i =
|
Chris@96
|
42 case d of
|
Chris@96
|
43 RowM rows: block.block rows[i];
|
Chris@96
|
44 ColM cols: block.fromList (map do j: getAt i j done [0..length cols-1]);
|
Chris@94
|
45 esac,
|
Chris@96
|
46 getAt row col =
|
Chris@96
|
47 case d of
|
Chris@96
|
48 RowM rows: r = rows[row]; (r is ~double[])[col];
|
Chris@96
|
49 ColM cols: c = cols[col]; (c is ~double[])[row];
|
Chris@94
|
50 esac,
|
Chris@158
|
51 setAt row col n = //!!! dangerous, could modify copies -- should it be allowed?
|
Chris@96
|
52 case d of
|
Chris@96
|
53 RowM rows: r = rows[row]; (r is ~double[])[col] := n;
|
Chris@96
|
54 ColM cols: c = cols[col]; (c is ~double[])[row] := n;
|
Chris@95
|
55 esac,
|
Chris@95
|
56 get isRowMajor? () =
|
Chris@96
|
57 case d of
|
Chris@96
|
58 RowM _: true;
|
Chris@96
|
59 ColM _: false;
|
Chris@95
|
60 esac,
|
Chris@94
|
61 };
|
Chris@94
|
62
|
Chris@98
|
63 newStorage { rows, columns } =
|
Chris@97
|
64 if rows < 1 then array []
|
Chris@98
|
65 else array (map \(vec.zeros rows) [1..columns])
|
Chris@97
|
66 fi;
|
Chris@94
|
67
|
Chris@98
|
68 zeroMatrix { rows, columns } =
|
Chris@98
|
69 make (ColM (newStorage { rows, columns }));
|
Chris@5
|
70
|
Chris@98
|
71 generate f { rows, columns } =
|
Chris@98
|
72 (m = newStorage { rows, columns };
|
Chris@98
|
73 for [0..columns-1] do col:
|
Chris@94
|
74 for [0..rows-1] do row:
|
Chris@94
|
75 m[col][row] := f row col;
|
Chris@5
|
76 done;
|
Chris@5
|
77 done;
|
Chris@96
|
78 make (ColM m));
|
Chris@5
|
79
|
Chris@20
|
80 constMatrix n = generate do row col: n done;
|
Chris@20
|
81 randomMatrix = generate do row col: Math#random() done;
|
Chris@5
|
82 identityMatrix = constMatrix 1;
|
Chris@158
|
83 zeroSizeMatrix () = zeroMatrix { rows = 0, columns = 0 };
|
Chris@5
|
84
|
Chris@96
|
85 width m = m.size.columns;
|
Chris@96
|
86 height m = m.size.rows;
|
Chris@6
|
87
|
Chris@100
|
88 transposed m =
|
Chris@100
|
89 make
|
Chris@100
|
90 (case m.data of
|
Chris@100
|
91 RowM d: ColM d;
|
Chris@100
|
92 ColM d: RowM d;
|
Chris@100
|
93 esac);
|
Chris@100
|
94
|
Chris@100
|
95 flipped m =
|
Chris@100
|
96 if m.isRowMajor? then
|
Chris@100
|
97 generate do row col: m.getAt row col done m.size;
|
Chris@100
|
98 else
|
Chris@100
|
99 transposed
|
Chris@100
|
100 (generate do row col: m.getAt col row done
|
Chris@100
|
101 { rows = m.size.columns, columns = m.size.rows });
|
Chris@100
|
102 fi;
|
Chris@100
|
103
|
Chris@161
|
104 toRowMajor m =
|
Chris@161
|
105 if m.isRowMajor? then m else flipped m fi;
|
Chris@161
|
106
|
Chris@161
|
107 toColumnMajor m =
|
Chris@161
|
108 if not m.isRowMajor? then m else flipped m fi;
|
Chris@161
|
109
|
Chris@100
|
110 // Matrices with different storage order but the same contents are
|
Chris@100
|
111 // equal (but comparing them is slow)
|
Chris@97
|
112 equal m1 m2 =
|
Chris@159
|
113 if m1.size != m2.size then false
|
Chris@159
|
114 elif m1.isRowMajor? != m2.isRowMajor? then equal (flipped m1) m2;
|
Chris@100
|
115 else
|
Chris@100
|
116 compare d1 d2 = all id (map2 vec.equal d1 d2);
|
Chris@100
|
117 case m1.data of
|
Chris@100
|
118 RowM d1: case m2.data of RowM d2: compare d1 d2; _: false; esac;
|
Chris@100
|
119 ColM d1: case m2.data of ColM d2: compare d1 d2; _: false; esac;
|
Chris@100
|
120 esac
|
Chris@100
|
121 fi;
|
Chris@97
|
122
|
Chris@95
|
123 copyOf m =
|
Chris@95
|
124 (copyOfData d = (array (map vec.copyOf d));
|
Chris@96
|
125 make
|
Chris@95
|
126 (case m.data of
|
Chris@96
|
127 RowM d: RowM (copyOfData d);
|
Chris@96
|
128 ColM d: ColM (copyOfData d);
|
Chris@95
|
129 esac));
|
Chris@6
|
130
|
Chris@163
|
131 newMatrix type data = //!!! NB does not copy data
|
Chris@96
|
132 (tagger = case type of RowMajor (): RowM; ColumnMajor (): ColM esac;
|
Chris@163
|
133 if empty? data or block.empty? (head data)
|
Chris@98
|
134 then zeroMatrix { rows = 0, columns = 0 }
|
Chris@163
|
135 else make (tagger (array (map block.data data)))
|
Chris@96
|
136 fi);
|
Chris@96
|
137
|
Chris@163
|
138 newRowVector data = //!!! NB does not copy data
|
Chris@163
|
139 make (RowM (array [block.data data]));
|
Chris@96
|
140
|
Chris@163
|
141 newColumnVector data = //!!! NB does not copy data
|
Chris@163
|
142 make (ColM (array [block.data data]));
|
Chris@8
|
143
|
Chris@195
|
144 scaled factor m = //!!! v inefficient
|
Chris@98
|
145 generate do row col: factor * m.getAt row col done m.size;
|
Chris@98
|
146
|
Chris@195
|
147 resizedTo newsize m = //!!! also v inefficient
|
Chris@158
|
148 (oldsize = m.size;
|
Chris@158
|
149 if newsize == oldsize then m
|
Chris@158
|
150 else
|
Chris@158
|
151 generate do row col:
|
Chris@158
|
152 if row < oldsize.rows and col < oldsize.columns
|
Chris@158
|
153 then m.getAt row col else 0 fi
|
Chris@158
|
154 done newsize;
|
Chris@158
|
155 fi);
|
Chris@158
|
156
|
Chris@98
|
157 sum' m1 m2 =
|
Chris@98
|
158 if m1.size != m2.size
|
Chris@98
|
159 then failWith "Matrices are not the same size: \(m1.size), \(m2.size)";
|
Chris@98
|
160 else
|
Chris@98
|
161 generate do row col: m1.getAt row col + m2.getAt row col done m1.size;
|
Chris@98
|
162 fi;
|
Chris@98
|
163
|
Chris@98
|
164 product m1 m2 =
|
Chris@98
|
165 if m1.size.columns != m2.size.rows
|
Chris@98
|
166 then failWith "Matrix dimensions incompatible: \(m1.size), \(m2.size) (\(m1.size.columns != m2.size.rows)";
|
Chris@98
|
167 else
|
Chris@98
|
168 generate do row col:
|
Chris@99
|
169 bf.sum (bf.multiply (m1.getRow row) (m2.getColumn col))
|
Chris@98
|
170 done { rows = m1.size.rows, columns = m2.size.columns }
|
Chris@98
|
171 fi;
|
Chris@98
|
172
|
Chris@161
|
173 asRows m =
|
Chris@171
|
174 map m.getRow [0 .. m.size.rows - 1];
|
Chris@161
|
175
|
Chris@161
|
176 asColumns m =
|
Chris@171
|
177 map m.getColumn [0 .. m.size.columns - 1];
|
Chris@161
|
178
|
Chris@178
|
179 concatAgainstGrain tagger getter counter mm =
|
Chris@178
|
180 (n = counter (head mm).size;
|
Chris@177
|
181 make (tagger (array
|
Chris@177
|
182 (map do i:
|
Chris@177
|
183 block.data (block.concat (map do m: getter m i done mm))
|
Chris@178
|
184 done [0..n-1]))));
|
Chris@177
|
185
|
Chris@178
|
186 concatWithGrain tagger getter counter mm =
|
Chris@177
|
187 make (tagger (array
|
Chris@177
|
188 (concat
|
Chris@177
|
189 (map do m:
|
Chris@178
|
190 n = counter m.size;
|
Chris@178
|
191 map do i: block.data (getter m i) done [0..n-1]
|
Chris@177
|
192 done mm))));
|
Chris@177
|
193
|
Chris@178
|
194 checkDimensionsFor direction first mm =
|
Chris@178
|
195 (counter = if direction == Horizontal () then (.rows) else (.columns) fi;
|
Chris@178
|
196 n = counter first.size;
|
Chris@178
|
197 if not (all id (map do m: counter m.size == n done mm)) then
|
Chris@178
|
198 failWith "Matrix dimensions incompatible for concat (found \(map do m: counter m.size done mm) not all of which are \(n))";
|
Chris@178
|
199 fi);
|
Chris@178
|
200
|
Chris@187
|
201 concat direction mm = //!!! doc: storage order is taken from first matrix in sequence
|
Chris@187
|
202 //!!! would this be better as separate concatHorizontal/concatVertical functions?
|
Chris@190
|
203 case mm of
|
Chris@190
|
204 first::rest:
|
Chris@178
|
205 checkDimensionsFor direction first mm;
|
Chris@178
|
206 row = first.isRowMajor?;
|
Chris@178
|
207 // horizontal, row-major: against grain with rows
|
Chris@178
|
208 // horizontal, col-major: with grain with cols
|
Chris@178
|
209 // vertical, row-major: with grain with rows
|
Chris@178
|
210 // vertical, col-major: against grain with cols
|
Chris@178
|
211 case direction of
|
Chris@178
|
212 Horizontal ():
|
Chris@178
|
213 if row then concatAgainstGrain RowM (.getRow) (.rows) mm;
|
Chris@178
|
214 else concatWithGrain ColM (.getColumn) (.columns) mm;
|
Chris@178
|
215 fi;
|
Chris@178
|
216 Vertical ():
|
Chris@178
|
217 if row then concatWithGrain RowM (.getRow) (.rows) mm;
|
Chris@178
|
218 else concatAgainstGrain ColM (.getColumn) (.columns) mm;
|
Chris@178
|
219 fi;
|
Chris@178
|
220 esac;
|
Chris@190
|
221 [single]: single;
|
Chris@190
|
222 _: zeroSizeMatrix ();
|
Chris@190
|
223 esac;
|
Chris@177
|
224
|
Chris@187
|
225 rowSlice start count m = //!!! doc: storage order same as input
|
Chris@187
|
226 if m.isRowMajor? then
|
Chris@187
|
227 make (RowM (array (map (block.data . m.getRow) [start .. start + count - 1])))
|
Chris@187
|
228 else
|
Chris@187
|
229 make (ColM (array (map (block.data . (block.rangeOf start count)) (asColumns m))))
|
Chris@187
|
230 fi;
|
Chris@187
|
231
|
Chris@187
|
232 columnSlice start count m = //!!! doc: storage order same as input
|
Chris@187
|
233 if not m.isRowMajor? then
|
Chris@187
|
234 make (ColM (array (map (block.data . m.getColumn) [start .. start + count - 1])))
|
Chris@187
|
235 else
|
Chris@187
|
236 make (RowM (array (map (block.data . (block.rangeOf start count)) (asRows m))))
|
Chris@187
|
237 fi;
|
Chris@187
|
238
|
Chris@5
|
239 {
|
Chris@195
|
240 generate is (number -> number -> number) -> { .rows is number, .columns is number } -> matrix,
|
Chris@195
|
241 constMatrix is number -> { .rows is number, .columns is number } -> matrix,
|
Chris@195
|
242 randomMatrix is { .rows is number, .columns is number } -> matrix,
|
Chris@195
|
243 zeroMatrix is { .rows is number, .columns is number } -> matrix,
|
Chris@195
|
244 identityMatrix is { .rows is number, .columns is number } -> matrix,
|
Chris@195
|
245 zeroSizeMatrix is () -> matrix,
|
Chris@195
|
246 width is matrix -> number,
|
Chris@195
|
247 height is matrix -> number,
|
Chris@195
|
248 equal is matrix -> matrix -> boolean,
|
Chris@195
|
249 copyOf is matrix -> matrix,
|
Chris@195
|
250 transposed is matrix -> matrix,
|
Chris@195
|
251 flipped is matrix -> matrix,
|
Chris@195
|
252 toRowMajor is matrix -> matrix,
|
Chris@195
|
253 toColumnMajor is matrix -> matrix,
|
Chris@195
|
254 scaled is number -> matrix -> matrix,
|
Chris@195
|
255 resizedTo is { .rows is number, .columns is number } -> matrix -> matrix,
|
Chris@195
|
256 asRows is matrix -> list<block>,
|
Chris@195
|
257 asColumns is matrix -> list<block>,
|
Chris@198
|
258 sum is matrix -> matrix -> matrix = sum',
|
Chris@195
|
259 product is matrix -> matrix -> matrix,
|
Chris@195
|
260 concat is (Horizontal () | Vertical ()) -> list<matrix> -> matrix,
|
Chris@195
|
261 rowSlice is number -> number -> matrix -> matrix,
|
Chris@195
|
262 columnSlice is number -> number -> matrix -> matrix,
|
Chris@195
|
263 newMatrix is (ColumnMajor () | RowMajor ()) -> list<block> -> matrix,
|
Chris@195
|
264 newRowVector is block -> matrix,
|
Chris@195
|
265 newColumnVector is block -> matrix,
|
Chris@5
|
266 }
|
Chris@5
|
267
|