1 /**
2 This module contains the MatrixView class as well as some
3 functions related to it.
4 
5 Authors:    Lars Tandle Kyllingstad
6 Copyright:  Copyright (c) 2009, Lars T. Kyllingstad. All rights reserved.
7 License:    Boost License 1.0
8 */
9 module scid.matrix;
10 
11 import std.string: format;
12 import std.traits;
13 
14 import scid.core.meta;
15 import scid.core.traits;
16 
17 
18 /// Various matrix representations.
19 enum Storage
20 {
21     General,        /// General (dense) matrices
22     Triangular,     /// Packed storage of triangular matrices
23     Symmetric,      /// Packed storage of symmetric matrices
24 }
25 
26 
27 /**
28 In packed storage (triangular, symmetric, and Hermitian matrices),
29 one can choose to store either the upper or lower triangle.
30 */
31 enum Triangle : char
32 {
33     Upper = 'U',    /// Store upper triangle
34     Lower = 'L'     /// Store lower triangle
35 }
36 
37 
38 /**
39 A convenience function that allocates memory for a matrix (using the
40 GC), optionally sets the values of the matrix elements, and returns
41 a MatrixView of the allocated memory.
42 
43 Examples:
44 ---
45 // Allocate general dense 3x4 matrix:
46 auto denseMatrix = matrix!real(3, 4);
47 
48 // Allocate dense 3x2 zero-filled matrix:
49 auto denseZeroMatrix = matrix!real(3, 2, 0.0L);
50 
51 // Allocate lower triangular 3x3 matrix:
52 auto loMatrix = matrix!(real, Storage.Triangular, Triangle.Lower)(3);
53 
54 // Allocate upper triangular 2x2 matrix where the upper
55 // triangular elements are set to 3.14.
56 auto upMatrix = matrix!(real, Storage.Triangular)(2, 3.14L);
57 ---
58 */
59 MatrixView!(T) matrix (T) (size_t rows, size_t cols) pure
60 {
61     return typeof(return)(new T[rows*cols], rows, cols);
62 }
63 
64 /// ditto
65 MatrixView!(T) matrix(T) (size_t rows, size_t cols, T init) pure
66 {
67     auto array = new T[rows*cols];
68     array[] = init;
69     return typeof(return)(array, rows, cols);
70 }
71 
72 /// ditto
73 MatrixView!(T, stor, tri) matrix
74     (T, Storage stor, Triangle tri = Triangle.Upper)
75     (size_t n, T init=T.init)
76     pure
77     if (stor == Storage.Triangular)
78 {
79     auto array = new T[(n*n+n)/2];
80     if (init != T.init) array[] = init; // Because of DMD bug #3576 this can't
81                                         // be done with a function overload.
82     return typeof(return)(array, n);
83 }
84 
85 unittest
86 {
87     import std.math;
88     auto dense1 = matrix!real(3, 4);
89     assert (dense1.rows == 3  &&  dense1.cols == 4);
90     assert (isNaN(dense1[1,2]));
91 
92     auto dense2 = matrix(4, 3, 1.0);
93     assert (dense2.rows == 4  &&  dense2.cols == 3);
94     assert (dense2[1,2] == 1.0);
95 
96     auto upTri = matrix!(real, Storage.Triangular)(3);
97     assert (upTri.rows == 3  &&  upTri.cols == 3);
98     assert (isNaN(upTri[0,2])  &&  upTri[2,0] == 0);
99 
100     auto loTri = matrix!(double, Storage.Triangular, Triangle.Lower)(3, 1.0);
101     assert (loTri.rows == 3  &&  loTri.cols == 3);
102     assert (loTri[0,2] == 0  &&  loTri[2,0] == 1.0);
103 }
104 
105 
106 /**
107 A convenience function that creates a copy of the input matrix. Memory for
108 the copy is allocated using the GC.
109 */
110 MatrixView!(T, stor, tri) copy(T, Storage stor, Triangle tri)
111     (const MatrixView!(T, stor, tri) m)
112     pure
113 {
114     MatrixView!(T, stor, tri) mcopy;
115     mcopy.rows = m.rows;
116     mcopy.cols = m.cols;
117     mcopy.array = m.array.dup;
118     return mcopy;
119 }
120 
121 unittest
122 {
123     auto a = matrix!double(2, 2);
124     a[1,0] = 1.0;
125     auto b = copy(a);
126     b[1,0] = 2.0;
127     assert (b[1,0] == 2.0  &&  a[1,0] == 1.0);
128 }
129 
130 
131 /**
132 A matrix-like view of the contents of an array.
133 
134 In order to be compatible with LAPACK routines, the following matrix
135 representations (i.e., memory layouts) are supported.
136 
137 General_matrices:
138 The elements of dense matrices are stored in column-major order.
139 This means that if the wrapped array contains the elements
140 ---
141 a b c d e f g h i j k l
142 ---
143 then a 3x4 dense matrix view of this array looks like this:
144 ---
145 a d g j
146 b e h k
147 c f i l
148 ---
149 
150 Triangular_matrices:
151 Triangular matrices are required to be square. If the wrapped
152 array contains the six elements
153 ---
154 a b c d e f
155 ---
156 then the resulting 3x3 upper and lower triangular matrix views
157 will look like this, respectively:
158 ---
159 a b d         a 0 0
160 0 c e   and   b d 0
161 0 0 f         c e f
162 ---
163 
164 Symmetric_matrices:
165 Symmetric matrices are stored in the same way as triangular
166 matrices. This means that for the array above, the corresponding
167 symmetric matrix view will be
168 ---
169 a b d       a b c
170 b c e   or  b d e
171 d e f       c e f
172 ---
173 depending on whether the upper or lower triangle is stored.
174 
175 Hermitian_matrices:
176 Hermitian matrices are not implemented yet.
177 
178 See_also:
179 LAPACK User's Guide: Matrix storage schemes,
180 $(LINK http://www.netlib.org/lapack/lug/node121.html)
181 */
182 struct MatrixView (T, Storage stor = Storage.General,
183     Triangle tri = Triangle.Upper)
184 {
185     enum Storage storage = stor;
186     enum Triangle triangle = tri;
187 
188 private:
189     // Writing fully-qualified names in static ifs gets tiresome, so
190     // we introduce a few flags.
191     enum : bool
192     {
193         isGen   = (storage == Storage.General),
194         isTri   = (storage == Storage.Triangular),
195         isUpTri = (isTri && triangle == Triangle.Upper),
196         isLoTri = (isTri && triangle == Triangle.Lower),
197         isSym   = (storage == Storage.Symmetric),
198         isUpSym = (isSym && triangle == Triangle.Upper),
199         isLoSym = (isSym && triangle == Triangle.Lower),
200     }
201 
202 
203     static if (!isGen)
204     {
205         static assert (isNumeric!T || isComplex!T,
206             "MatrixView: Non-general matrices can only contain numeric values. "
207            ~"Non-appropriate type given: "~T.stringof);
208     }
209 
210     // The zero element in triangular matrices.
211     static if (isTri)
212     {
213         T zero = Zero!T;
214     }
215 
216 
217 public:
218     /** The array that is wrapped by this MatrixView. */
219     T[] array;
220 
221 
222     /** The number of rows in the matrix. */
223     size_t rows;
224 
225     /** The number of columns in the matrix. */
226     size_t cols;
227 
228     /** The leading (row) dimension.  Included to support matrix slicing,
229         currently just an alias to rows.
230     */
231     alias rows leading;
232 
233 
234     /**
235     Wraps a MatrixView with m rows around the given array.
236 
237     For general matrices, the number of columns in the matrix
238     is set to a.length/m, whereas for triangular and symmetric
239     matrices the number of columns is set equal to the number
240     of rows.
241     */
242     this (T[] a, size_t m)  pure nothrow
243     in
244     {
245         static if (isGen)  assert (a.length % m == 0);
246     }
247     body
248     {
249         static if (isGen)  this (a, m, a.length/m);
250         else static if (isTri || isSym)  this(a, m, m);
251     }
252 
253 
254 
255     /**
256     Wraps a MatrixView with m rows and n columns around the given array.
257     For a given set of a, m, and n, the following must be true for
258     a general matrix:
259     ---
260     a.length >= m*n
261     ---
262     For triangular and symmetric matrices, there are two constraints:
263     ---
264     m == n
265     a.length >= (n*n + n)/2
266     ---
267     These conditions are only checked in non-release builds.
268     */
269     this (T[] a, size_t m, size_t n) pure nothrow
270     in
271     {
272         static if (isGen)
273             assert (a.length >= m*n);
274         else static if (isTri || isSym)
275         {
276             assert (m == n);
277             assert (a.length >= (n*n + n)/2);
278         }
279     }
280     body
281     {
282         array = a;
283         rows = m;
284         cols = n;
285     }
286 
287 
288     /**
289     Returns (a reference to) the element at row i, column j.
290 
291     Warning:
292     For convenience, this method returns values by reference. This
293     means that one can do stuff like this:
294     ---
295     m[1,2] += 3.14;
296     ---
297     Unfortunately, it also means that in a triangular matrix one
298     can change the zero element (which is common for all zero elements).
299     ---
300     assert ((m[1,0] == 0.0)  &&  m[2,0] == 0.0);
301     m[1,0] += 3.14;
302     assert (m[2,0] == 3.14);    // passes
303     ---
304     You are hereby warned.
305     */
306     ref T opIndex(size_t i, size_t j) pure nothrow
307     in
308     {
309         assert (i < rows  &&  j < cols);
310     }
311     body
312     {
313         static if (isGen)
314             return array.ptr[i + rows*j];
315         else static if (isUpTri)
316         {
317             if (i <= j)  return array.ptr[i + (j*j+j)/2];
318             else return zero;
319         }
320         else static if (isLoTri)
321         {
322             if (i >= j)  return array.ptr[i + ((rows+rows-j-1)*j)/2];
323             else return zero;
324         }
325         else static if (isUpSym)
326         {
327             if (i <= j)  return array.ptr[i + (j*j+j)/2];
328             else return array.ptr[j + (i*i+i)/2];
329         }
330         else static if (isLoSym)
331         {
332             if (i >= j)  return array.ptr[i + ((rows+rows-j-1)*j)/2];
333             else return array.ptr[j + ((rows+rows-i-1)*i)/2];
334         }
335         else static assert (false);
336     }
337 
338 
339     /**
340     Assigns a value to the element at row i, column j.
341 
342     Unlike opIndex(), this method checks that zero elements in
343     a triangular matrix aren't assigned to, but only in non-release
344     builds.
345     */
346     T opIndexAssign(T value, size_t i, size_t j) nothrow
347     in
348     {
349         assert (i < rows  &&  j < cols);
350         static if (isUpTri)  assert (i <= j);
351         static if (isLoTri)  assert (i >= j);
352     }
353     body
354     {
355         static if (isGen)
356             return array.ptr[i + rows*j] = value;
357         else static if (isUpTri)
358             return array.ptr[i + (j*j+j)/2] = value;
359         else static if (isLoTri)
360             return array.ptr[i + ((rows+rows-j-1)*j)/2] = value;
361         else static if (isUpSym)
362         {
363             if (i <= j)  return array.ptr[i + (j*j+j)/2] = value;
364             else  return array.ptr[j + (i*i+i)/2] = value;
365         }
366         else static if (isLoSym)
367         {
368             if (i >= j)  return array.ptr[i + ((rows+rows-j-1)*j)/2] = value;
369             else return array.ptr[j + ((rows+rows-i-1)*i)/2] = value;
370         }
371         else static assert (false);
372     }
373 }
374 
375 unittest
376 {
377     alias MatrixView!real GeneralMatrix;
378     real[] g = [1.0L, 2, 3, 4, 5, 6];
379 
380     auto gm1 = GeneralMatrix(g, 2);
381     auto gm2 = GeneralMatrix(g, 3);
382 
383     assert (gm1.cols == 3);
384     assert (gm2.cols == 2);
385 
386     assert (gm1[1,0] == 2);
387     assert (gm2[1,0] == 2);
388 
389     assert (gm1[1,1] == 4);
390     assert (gm2[1,1] == 5);
391 
392     gm2[1,1] += 1; assert (gm2[1,1] == 6);
393     gm2[1,1] = 10; assert (gm2[1,1] == 10);
394 
395 
396     alias MatrixView!(real, Storage.Triangular) UTMatrix;
397     real[] u = [1.0, 2, 3, 4, 5, 6];
398 
399     auto um1 = UTMatrix(u, 3);
400     assert (um1.cols == 3);
401     assert (um1[1,0] == 0.0);
402     assert (um1[1,1] == 3.0);
403     um1[0,2] += 3; assert (u[3] == 7);
404     um1[2,2] = 10; assert (u[5] == 10);
405 
406 
407     alias MatrixView!(real, Storage.Triangular, Triangle.Lower) LTMatrix;
408     real[] l = [1.0, 2, 3, 4, 5, 6];
409 
410     auto lm1 = LTMatrix(l, 3);
411     assert (lm1.cols == 3);
412     assert (lm1[0,1] == 0.0);
413     assert (lm1[1,1] == 4.0);
414     lm1[2,0] += 4; assert (l[2] == 7);
415     lm1[2,2] = 10; assert (l[5] == 10);
416 
417 
418     alias MatrixView!(real, Storage.Symmetric) USMatrix;
419     real[] us = [1.0, 2, 3, 4, 5, 6];
420 
421     auto usm1 = USMatrix(us, 3);
422     assert (usm1.cols == 3);
423     assert (usm1[1,2] == 5.0);
424     foreach (i; 0 .. usm1.rows)
425         foreach (j; 0 .. i)
426             assert (usm1[i,j] == usm1[j,i]);
427     usm1[0,2] += 3; assert (usm1[2,0] == 7);
428     usm1[1,2] = 10; assert (usm1[2,1] == 10);
429 
430 
431     alias MatrixView!(real, Storage.Symmetric, Triangle.Lower) LSMatrix;
432     real[] ls = [1.0, 2, 3, 4, 5, 6];
433 
434     auto lsm1 = LSMatrix(ls, 3);
435     assert (lsm1.cols == 3);
436     assert (lsm1[1,2] == 5.0);
437     foreach (i; 0 .. lsm1.rows)
438         foreach (j; 0 .. i)
439             assert (lsm1[i,j] == lsm1[j,i]);
440     lsm1[0,2] += 3; assert (lsm1[2,0] == 6);
441     lsm1[1,2] = 10; assert (lsm1[2,1] == 10);
442 }
443 
444 
445 /**
446 Evaluates to true if the given type is an instantiation of
447 MatrixView. Optionally test the element type and/or storage
448 scheme.
449 */
450 template isMatrixView(MatrixT)
451 {
452     static if (is(MatrixT E == MatrixView!(E, S, T), int S, char T))
453     {
454         enum bool isMatrixView = true;
455     }
456     else enum bool isMatrixView = false;
457 }
458 
459 version(unittest)
460 {
461     static assert (isMatrixView!(MatrixView!int));
462     static assert (!isMatrixView!int);
463 }
464 
465 
466 /// ditto
467 template isMatrixView(MatrixT, ElemT)
468 {
469     static if (is(MatrixT E == MatrixView!(E, S, T), int S, char T))
470     {
471         enum bool isMatrixView = is(E == ElemT);
472     }
473     else enum bool isMatrixView = false;
474 }
475 
476 version(unittest)
477 {
478     static assert (isMatrixView!(MatrixView!int, int));
479     static assert (!isMatrixView!(MatrixView!int, float));
480 }
481 
482 
483 /// ditto
484 template isMatrixView(MatrixT, Storage stor)
485 {
486     static if (is(MatrixT E == MatrixView!(E, S, T), int S, char T))
487     {
488         enum bool isMatrixView = S == stor;
489     }
490     else enum bool isMatrixView = false;
491 }
492 
493 version(unittest)
494 {
495     static assert (isMatrixView!(
496         MatrixView!(int, Storage.Triangular),
497         Storage.Triangular));
498     static assert (!isMatrixView!(
499         MatrixView!(int, Storage.Triangular),
500         Storage.Symmetric));
501 }
502 
503 
504 /// ditto
505 template isMatrixView(MatrixT, ElemT, Storage stor)
506 {
507     static if (is(MatrixT E == MatrixView!(E, S, T), int S, char T))
508     {
509         enum bool isMatrixView = is(E == ElemT)  &&  S == stor;
510     }
511     else enum bool isMatrixView = false;
512 }
513 
514 version(unittest)
515 {
516     static assert (isMatrixView!(
517         MatrixView!(int, Storage.Triangular),
518         int, Storage.Triangular));
519     static assert (!isMatrixView!(
520         MatrixView!(int, Storage.Triangular),
521         float, Storage.Triangular));
522     static assert (!isMatrixView!(
523         MatrixView!(int, Storage.Triangular),
524         int, Storage.Symmetric));
525 }