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 }