1 /** This module contains functions related to linear algebra. 2 3 For the time being these functions are just a user-friendly interface 4 to the corresponding LAPACK functions. Hence, one can only use matrices 5 and vectors where the elements are of FORTRAN-compatible types, namely: 6 --- 7 float 8 double 9 Complex!float 10 Complex!double 11 --- 12 Specifically, real- and creal-valued matrices/vectors cannot be used. 13 14 Note: 15 Some of the functions in this module come in two forms with the same 16 function signature, but where the name of one ends with an underscore. 17 An example is 18 --- 19 solve(a, b) 20 solve_(a, b) 21 --- 22 The difference between these is that, for performance reasons, the 23 underscore-suffixed functions use some or all of the input 24 matrices/vectors as a workspace, and one can't expect them to 25 contain the same values after the function returns. The functions 26 without an underscore suffix simply copy the input data and calls the 27 high-performance function using the copied data as input. 28 29 Authors: Lars Tandle Kyllingstad 30 Copyright: Copyright (c) 2009, Lars T. Kyllingstad. All rights reserved. 31 License: Boost License 1.0 32 */ 33 module scid.linalg; 34 35 36 import std.algorithm; 37 import std.complex; 38 import std.conv; 39 import std.exception; 40 import std.math; 41 import std.traits; 42 43 import scid.bindings.lapack.dlapack; 44 import scid.core.fortran; 45 import scid.core.memory; 46 import scid.core.meta; 47 import scid.core.traits; 48 import scid.matrix; 49 import scid.util; 50 51 52 /** Solve one or more systems of n linear equations in n variables. 53 54 The set of equations is given on the form AX=B, where A 55 is an n-by-n matrix and X and B are either vectors of 56 length n (one system of n equations in n variables) or n-by-m 57 matrices (m systems of n equations in n variables). Given A 58 and B as input, this function returns X. 59 60 Examples: 61 Solving a system of equations: 62 --- 63 MatrixView!double a = ... 64 double[] b = ... 65 double[] x = solve(a, b); 66 --- 67 Solving several systems of equations: 68 --- 69 MatrixView!double a = ... 70 MatrixView!double b = ... 71 MatrixView!double x = solve(a, b); 72 --- 73 */ 74 Real[] solve (MatrixViewA, Real) 75 (const MatrixViewA a, const Real[] b, Real[] buffer=null) 76 if (isMatrixView!MatrixViewA && isFortranType!Real) 77 { 78 mixin (newFrame); 79 80 static assert (is (BaseElementType!MatrixViewA == Real), 81 "solve: a and b have different element types"); 82 83 // Make a copy of a in TempAlloc space. 84 auto adup = tempdup(tailConst(a.array)); 85 86 // b is copied into the buffer. 87 buffer.length = b.length; 88 buffer[] = b[]; 89 90 solveImpl!(Real, a.storage, a.triangle) 91 (adup, a.rows, buffer, buffer.length, 1); 92 return buffer; 93 } 94 95 96 /// ditto 97 Real[] solve_ (MatrixViewA, Real) 98 (MatrixViewA a, Real[] b) 99 if (isMatrixView!MatrixViewA && isFortranType!Real) 100 { 101 static assert (is (BaseElementType!MatrixViewA == Real), 102 "solve: a and b have different element types"); 103 104 solveImpl!(Real, a.storage, a.triangle) 105 (a.array, a.rows, b, b.length, 1); 106 return b; 107 } 108 109 110 /// ditto 111 MatrixViewB solve (MatrixViewA, MatrixViewB) 112 (const MatrixViewA a, const MatrixViewB b) 113 if (isMatrixView!(MatrixViewA) 114 && isMatrixView!(MatrixViewB, Storage.General)) 115 { 116 mixin (newFrame); 117 118 alias BaseElementType!MatrixViewB Real; 119 static assert (is (BaseElementType!MatrixViewA == Real), 120 "solve: a and b have different element types"); 121 static assert (isFortranType!Real, 122 "solve: Not a FORTRAN-compatible type: "~Real.stringof); 123 124 // Make a copy of a in TempAlloc space. 125 auto adup = tempdup(tailConst(a.array)); 126 127 // Make an ordinary copy of b. 128 auto bdup = copy(b); 129 130 solveImpl!(Real, a.storage, a.triangle) 131 (adup, a.rows, bdup.array, bdup.rows, bdup.cols); 132 return bdup; 133 } 134 135 136 /// ditto 137 MatrixViewB solve_ (MatrixViewA, MatrixViewB) 138 (MatrixViewA a, MatrixViewB b) 139 if (isMatrixView!(MatrixViewA) 140 && isMatrixView!(MatrixViewB, Storage.General)) 141 in 142 { 143 assert (a.rows == a.cols); 144 assert (a.cols == b.rows); 145 } 146 body 147 { 148 alias BaseElementType!MatrixViewB Real; 149 150 static assert (is (BaseElementType!MatrixViewA == Real), 151 "solve: a and b have different element types"); 152 static assert (isFortranType!Real, 153 "solve: Not a FORTRAN-compatible type: "~Real.stringof); 154 155 solveImpl!(Real, a.storage, a.triangle) 156 (a.array, a.rows, b.array, b.rows, b.cols); 157 return b; 158 } 159 160 161 // This is where the solve() magic happens. 162 private void solveImpl(Real, Storage aStorage, Triangle aTriangle) 163 (Real[] aMatrix, size_t aRows, Real[] bxMatrix, size_t bRows, size_t bCols) 164 { 165 mixin (newFrame); 166 167 int info; 168 static if (aStorage == Storage.General) 169 { 170 int* ipiv = cast(int*) TempAlloc.malloc(aRows*int.sizeof); 171 gesv( 172 toInt(aRows), // N 173 toInt(bCols), // NRHS 174 aMatrix.ptr, // A 175 toInt(aRows), // LDA 176 ipiv, // IPIV 177 bxMatrix.ptr, // B 178 toInt(bRows), // LDB 179 info); // INFO 180 } 181 else static if (aStorage == Storage.Symmetric) 182 { 183 int* ipiv = cast(int*) TempAlloc.malloc(aRows*int.sizeof); 184 spsv( 185 aTriangle, // UPLO 186 toInt(aRows), // N 187 toInt(bCols), // NRHS 188 aMatrix.ptr, // AP 189 ipiv, // IPIV 190 bxMatrix.ptr, // B 191 toInt(bRows), // LDB 192 info); // INFO 193 } 194 else static if (aStorage == Storage.Triangular) 195 { 196 enum char trans = 'N'; // Matrix isn't transposed. 197 enum char diag = 'N'; // We don't know that the matrix 198 // is unit triangular. 199 200 tptrs( 201 aTriangle, // UPLO 202 trans, // TRANS 203 diag, // DIAG 204 toInt(aRows), // N 205 toInt(bCols), // NRHS 206 aMatrix.ptr, // AP 207 bxMatrix.ptr, // B 208 toInt(bRows), // LDB 209 info); // INFO 210 211 } 212 else static assert (false, "solve: Unsupported matrix storage."); 213 214 assert (info >= 0); 215 enforce(info == 0, "solve: matrix is singular"); 216 return; 217 } 218 219 unittest 220 { 221 alias solve!(MatrixView!float, MatrixView!float) solve_f; 222 alias solve!(MatrixView!double, MatrixView!double) solve_d; 223 alias solve!(MatrixView!(Complex!float), MatrixView!(Complex!float)) solve_cf; 224 alias solve!(MatrixView!(Complex!double), MatrixView!(Complex!double)) solve_cd; 225 226 alias solve!(MatrixView!(float, Storage.Symmetric), MatrixView!float) solve_f_s; 227 alias solve!(MatrixView!(double, Storage.Symmetric), MatrixView!double) solve_d_s; 228 alias solve!(MatrixView!(Complex!float, Storage.Symmetric), MatrixView!(Complex!float)) solve_cf_s; 229 alias solve!(MatrixView!(Complex!double, Storage.Symmetric), MatrixView!(Complex!double)) solve_cd_s; 230 231 alias solve!(MatrixView!(float, Storage.Triangular), MatrixView!float) solve_f_t; 232 alias solve!(MatrixView!(double, Storage.Triangular), MatrixView!double) solve_d_t; 233 alias solve!(MatrixView!(Complex!float, Storage.Triangular), MatrixView!(Complex!float)) solve_cf_t; 234 alias solve!(MatrixView!(Complex!double, Storage.Triangular), MatrixView!(Complex!double)) solve_cd_t; 235 236 alias solve!(MatrixView!double, double) solve_vec; 237 } 238 239 unittest 240 { 241 alias solve_!(MatrixView!float, MatrixView!float) solve_f; 242 alias solve_!(MatrixView!double, MatrixView!double) solve_d; 243 alias solve_!(MatrixView!(Complex!float), MatrixView!(Complex!float)) solve_cf; 244 alias solve_!(MatrixView!(Complex!double), MatrixView!(Complex!double)) solve_cd; 245 246 alias solve_!(MatrixView!(float, Storage.Symmetric), MatrixView!float) solve_f_s; 247 alias solve_!(MatrixView!(double, Storage.Symmetric), MatrixView!double) solve_d_s; 248 alias solve_!(MatrixView!(Complex!float, Storage.Symmetric), MatrixView!(Complex!float)) solve_cf_s; 249 alias solve_!(MatrixView!(Complex!double, Storage.Symmetric), MatrixView!(Complex!double)) solve_cd_s; 250 251 alias solve_!(MatrixView!(float, Storage.Triangular), MatrixView!float) solve_f_t; 252 alias solve_!(MatrixView!(double, Storage.Triangular), MatrixView!double) solve_d_t; 253 alias solve_!(MatrixView!(Complex!float, Storage.Triangular), MatrixView!(Complex!float)) solve_cf_t; 254 alias solve_!(MatrixView!(Complex!double, Storage.Triangular), MatrixView!(Complex!double)) solve_cd_t; 255 256 alias solve_!(MatrixView!double, double) solve_vec; 257 } 258 259 unittest 260 { 261 // A is dense. 262 double[] daa = [ 1.0, 2, -1, -1, 2, 5, 1, -4, 0 ]; 263 double[] dba = [ 2.0, -6, 9, 0, -6, 1 ]; 264 double[] dxa = [ 1.0, 2, 3, -1, 0, 1 ]; 265 auto da = MatrixView!double(daa, 3); 266 auto db = MatrixView!double(dba, 3); 267 auto dx = solve(da, db); 268 assert (approxEqual(dx.array, dxa, double.epsilon)); 269 270 // Vector version. 271 double[] dbva = [ 2.0, -6, 9 ]; 272 double[] dxva = [ 1.0, 2, 3 ]; 273 auto dxv = solve(da, dbva); 274 assert (approxEqual(dxv, dxva, double.epsilon)); 275 276 // A is symmetric, packed storage. 277 float[] saa = [1.0, 2, 4, -3, 5, -6]; 278 float[] sba = [-4.0, 25, -11, -4, 58, -23]; 279 float[] sxa = [1.0, 2, 3, 4, 5, 6]; 280 auto sa = MatrixView!(float, Storage.Symmetric)(saa, 3); 281 auto sb = MatrixView!(float)(sba, 3); 282 auto sx = solve(sa, sb); 283 assert (approxEqual(sx.array, sxa, float.epsilon)); 284 285 // A is triangular, packed storage. 286 double[] taa = [1.0, 2, 4, -3, 5, -6]; 287 double[] tba = [-4.0, 23, -18, -4, 50, -36]; 288 double[] txa = [1.0, 2, 3, 4, 5, 6]; 289 auto ta = MatrixView!(double, Storage.Triangular)(taa, 3); 290 auto tb = MatrixView!(double)(tba, 3); 291 auto tx = solve(ta, tb); 292 assert (approxEqual(tx.array, txa, double.epsilon)); 293 } 294 295 296 297 298 /** Calculate the determinant of a square matrix. 299 300 Examples: 301 --- 302 import scid.matrix; 303 ... 304 auto m = matrix!double(2, 2); 305 m[0,0] = 1.0; m[0,1] = 2.0; 306 m[1,0] = 3.0; m[1,1] = 4.0; 307 308 auto d = det(m); 309 writeln(d); // Prints "-2" 310 --- 311 */ 312 Unqual!T det 313 (T, Storage stor) 314 (const MatrixView!(T, stor) m) 315 { 316 return det_(copy(m)); 317 } 318 319 320 /// ditto 321 Unqual!T det_ 322 (T, Storage stor) 323 (MatrixView!(T, stor) m) 324 in 325 { 326 assert (m.rows == m.cols); 327 } 328 body 329 { 330 mixin (newFrame); 331 332 static assert (isFortranType!(T), 333 "det: Not a FORTRAN-compatible type: "~T.stringof); 334 335 alias typeof(return) DT; 336 DT d = One!DT; 337 size_t sign; 338 static if(isFloatingPoint!DT) 339 long exp; 340 341 //accumulates exponent and mantissa for floating points numbers and 342 //annihilates exponent overflow in some cases. 343 //See Also: std.numeric.sumOfLog2s 344 void mul(DT x) 345 { 346 d *= x; 347 static if(isFloatingPoint!DT) 348 { 349 int lexp = void; 350 d = frexp(d, lexp); 351 exp += lexp; 352 } 353 } 354 355 // LU factorise matrix. 356 int info; 357 static if (m.storage == Storage.General) 358 { 359 auto ipiv = newStack!int(m.rows); 360 getrf(toInt(m.rows), toInt(m.cols), m.array.ptr, toInt(m.rows), ipiv.ptr, info); 361 assert (info >= 0, "invalid input to getrf"); 362 363 // If matrix is singular, determinant is zero. 364 if (info > 0) return Zero!DT; 365 366 // The determinant is the product of the diagonal entries 367 // of the upper triangular matrix. The array ipiv contains 368 // the pivots. 369 for (int i=0; i<m.rows; i++) 370 { 371 auto p = ipiv[i]; 372 mul(m[i,i]); 373 sign += p != i+1; // i.e. row interchanged with another 374 } 375 } 376 377 else static if (m.storage == Storage.Symmetric) 378 { 379 auto ipiv = newStack!int(m.rows); 380 sptrf(m.triangle, toInt(m.rows), m.array.ptr, ipiv.ptr, info); 381 assert (info >= 0, "invalid input to sptrf"); 382 383 // If matrix is singular, determinant is zero. 384 if (info > 0) return Zero!DT; 385 386 // Calculate determinant. 387 for (int k=0; k<m.rows; k++) 388 { 389 auto p = ipiv[k]; 390 if (p > 0) // 1x1 block at m[k,k] 391 { 392 mul(m[k,k]); 393 sign += p != k+1; // row interchanged with other 394 } 395 else // p < 0, 2x2 block at m[k..k+1, k..k+1] 396 { 397 auto kp1 = k+1; 398 auto offDiag = m[k, kp1]; 399 auto blockDet = m[k,k]*m[kp1,kp1] - offDiag*offDiag; 400 mul(blockDet); 401 sign += -p != kp1 && -p != k+2; // row interchanged with other 402 k++; 403 } 404 } 405 } 406 else static if (m.storage == Storage.Triangular) 407 { 408 foreach (i; 0 .. m.rows) d *= m[i,i]; 409 } 410 else static assert (false, "det: Unsupported matrix storage."); 411 if(sign & 1) 412 d = -d; 413 static if(isFloatingPoint!DT) 414 return cast(int)exp == exp ? ldexp(d, cast(int)exp) : d * DT.infinity; 415 else 416 return d; 417 } 418 419 420 unittest 421 { 422 // Check for all FORTRAN compatible types. 423 alias det!(float, Storage.General) det_fd; 424 alias det!(double, Storage.General) det_dd; 425 alias det!(Complex!float, Storage.General) cfdet_cfd; 426 alias det!(Complex!double, Storage.General) cddet_cdd; 427 428 alias det!(float, Storage.Symmetric) det_fs; 429 alias det!(double, Storage.Symmetric) det_ds; 430 alias det!(Complex!float, Storage.Symmetric) cfdet_cfs; 431 alias det!(Complex!double, Storage.Symmetric) cddet_cds; 432 433 // Check for zero-determinant shortcut. 434 double[] dsinga = [4.0, 2, 2, 1]; // dense singular 435 auto dsing = MatrixView!double(dsinga, 2); 436 auto dsingd = det(dsing); 437 assert (dsingd == 0.0); 438 439 double[] ssinga = [4.0, 2, 1]; // symmetric packed singular 440 auto ssing = MatrixView!(double, Storage.Symmetric)(ssinga, 2); 441 auto ssingd = det(ssing); 442 assert (ssingd == 0.0); 443 444 445 // General dense matrix. 446 int dn = 101; 447 auto d = matrix!double(dn, dn); 448 for (int k=0; k<dn; k++) 449 { 450 for (int l=0; l<dn; l++) 451 { 452 if (k == l) 453 d[k,l] = (k+1)*(k+1) + 1.0; 454 else 455 d[k,l] = 2.0*(k+1)*(l+1); 456 d[k,l] /= 2; 457 } 458 } 459 auto dd = det(d); 460 assert (approxEqual(dd, ldexp(8.972817920259982e319L, -dn), sqrt(real.epsilon))); 461 462 463 // Symmetric packed matrix 464 double[] spa = [ 1.0, -2, 3, 4, 5, -6, -7, -8, -9, 10]; 465 auto sp = MatrixView!(double, Storage.Symmetric)(spa, 4); 466 auto spd = det(sp); 467 assert (approxEqual(spd, 5874.0, sqrt(double.epsilon))); 468 469 470 // Triangular packed matrix 471 double[] tpa = [ 1.0, -2, 3, 4, 5, -6, -7, -8, -9, 10]; 472 auto tp = MatrixView!(double, Storage.Triangular)(tpa, 4); 473 auto tpd = det(tp); 474 assert (approxEqual(tpd, -180.0, sqrt(double.epsilon))); 475 } 476 477 478 479 480 /** Calculate the eigenvalues of a general dense square matrix. 481 482 If some eigenvalues cannot be calculated, the algorithm 483 throws an EigenvalueException containing an array of the ones 484 that have been calculated. 485 486 Params: 487 m = An n-by-n symmetric matrix. 488 buffer = (optional) A buffer for the returned values, must 489 have length >= n and type Complex!T[]. 490 491 Examples: 492 --- 493 auto m = matrix!double(3, 3); 494 ... 495 auto e = eigenvalues(m); 496 --- 497 */ 498 EigenvalueType!ElementT[] eigenvalues(ElementT, Storage stor, Triangle tri) 499 (MatrixView!(ElementT, stor, tri) m) 500 if (stor == Storage.General) 501 { 502 return eigenvalues_(copy(m)); 503 } 504 505 /// ditto 506 ComplexT[] eigenvalues(ElementT, ComplexT, Storage stor, Triangle tri) 507 (MatrixView!(ElementT, stor, tri) m, ComplexT[] buffer) 508 if (stor == Storage.General) 509 { 510 return eigenvalues_(copy(m), buffer); 511 } 512 513 /// ditto 514 EigenvalueType!ElementT[] eigenvalues_(ElementT, Storage stor, Triangle tri) 515 (MatrixView!(ElementT, stor, tri) m) 516 if (stor == Storage.General) 517 { 518 static assert (isFortranType!ElementT, 519 "eigenvalues: Not a FORTRAN-compatible type: "~T.stringof); 520 521 static if (scid.core.traits.isComplex!ElementT) 522 return eigenvaluesComplex_!ElementT(m, null); 523 else 524 return eigenvaluesReal_!ElementT(m, null); 525 } 526 527 /// ditto 528 ComplexT[] eigenvalues_(ElementT, ComplexT, Storage stor, Triangle tri) 529 (MatrixView!(ElementT, stor, tri) m, ComplexT[] buffer) 530 if (stor == Storage.General) 531 { 532 static assert (scid.core.traits.isComplex!ComplexT, 533 "eigenvalues: Not a complex type: "~ComplexT.stringof); 534 static assert (is(ElementT == ComplexT) 535 || is(ElementT == typeof(ComplexT.re)), 536 "eigenvalues: The elements of the matrix must be of type " 537 ~ComplexT.stringof~" or "~typeof(ComplexT.re).stringof 538 ~", and not "~ElementT.stringof); 539 static assert (isFortranType!ComplexT, 540 "eigenvalues: Not a FORTRAN-compatible type: "~T.stringof); 541 542 static if (scid.core.traits.isComplex!ElementT) 543 { 544 return eigenvaluesComplex_(m, buffer); 545 } 546 else 547 { 548 return eigenvaluesReal_(m, buffer); 549 } 550 } 551 552 553 template EigenvalueType(T) 554 { 555 static if (isFloatingPoint!T) alias Complex!T EigenvalueType; 556 else alias T EigenvalueType; 557 } 558 559 560 private Complex!T[] eigenvaluesReal_ (T) 561 (MatrixView!(T) m, Complex!T[] buffer) 562 if (isFloatingPoint!T) 563 in 564 { 565 assert (m.rows == m.cols, "eigenvalues: Matrix must be square"); 566 } 567 body 568 { 569 mixin (newFrame); 570 571 immutable int n = toInt(m.rows); 572 if (n == 0) return null; // Empty matrix. 573 buffer.length = n; 574 575 // Calculate optimal workspace size. 576 int info; 577 T optimal; 578 geev('N', 'N', // Not going to compute eigenvectors. 579 n, null, n, // Need matrix info. 580 null, null, // Eigenvalues, not calculated. 581 null, 1, // Left eigenvectors, not calculated. 582 null, 1, // Right eigenvectors, not calculated. 583 &optimal, -1, // Query for optimal workspace size. 584 info); 585 586 // Allocate workspace and result arrays. 587 immutable npn = n+n; 588 auto workspace = newStack!T(npn + to!int(optimal)); 589 auto wr = workspace[0 .. n]; 590 auto wi = workspace[n .. npn]; 591 auto work = workspace[npn .. $]; 592 593 // Call LAPACK routine GEEV to calculate eigenvalues. 594 geev('N', 'N', // Don't compute eigenvectors. 595 n, m.array.ptr, n, // Input matrix. 596 wr.ptr, wi.ptr, // Eigenvalues. 597 null, 1, // Left eigenvectors, not calculated. 598 null, 1, // Right eigenvectors, not calculated. 599 work.ptr, toInt(work.length), // Workspace. 600 info); 601 602 if (info == 0) // Success! 603 { 604 foreach (i, ref e; buffer) 605 { 606 e.re = wr[i]; 607 e.im = wi[i]; 608 } 609 return buffer; 610 } 611 else if (info > 0) // Only some eigenvalues were computed. 612 { 613 buffer = buffer[0 .. n-info]; 614 int i = info; 615 foreach (ref e; buffer) 616 { 617 e.re = wr[i]; 618 e.im = wi[i]; 619 i++; 620 } 621 throw new EigenvalueException!(typeof(buffer))(buffer.dup); 622 } 623 624 assert (0); 625 } 626 627 628 private T[] eigenvaluesComplex_ (T) 629 (MatrixView!(T) m, T[] buffer) 630 if (scid.core.traits.isComplex!T) 631 in 632 { 633 assert (m.rows == m.cols, "eigenvalues: Matrix must be square"); 634 } 635 body 636 { 637 mixin (newFrame); 638 639 immutable int n = toInt(m.rows); 640 if (n == 0) return null; // Empty matrix. 641 buffer.length = n; 642 643 // Calculate optimal workspace size. 644 int info; 645 T optimal; 646 geev('N', 'N', 647 n, null, n, // Need matrix info. 648 null, // Eigenvalues, not calculated. 649 null, 1, // Left eigenvectors, not calculated. 650 null, 1, // Right eigenvectors, not calculated. 651 &optimal, -1, // Query for optimal workspace size. 652 null, // Second workspace, not needed. 653 info); 654 655 // Allocate workspace arrays. 656 auto rwork = newStack!(typeof(T.re))(2*n); 657 auto work = newStack!T(to!int(optimal.re)); 658 659 // Call LAPACK routine GEEV to calculate eigenvalues. 660 geev('N', 'N', // Don't compute eigenvectors. 661 n, cast(T*) m.array.ptr, n, // Input matrix. 662 cast(T*) buffer.ptr, // Eigenvalues. 663 null, 1, // Left eigenvectors, not calculated. 664 null, 1, // Right eigenvectors, not calculated. 665 cast(T*) work.ptr, toInt(work.length), // Workspace 1. 666 rwork.ptr, // Workspace 2. 667 info); 668 669 if (info == 0) // Success! 670 { 671 return buffer; 672 } 673 else if (info > 0) // Only some eigenvalues were computed. 674 { 675 throw new EigenvalueException!(typeof(buffer))(buffer[info .. n].dup); 676 } 677 678 assert (0); 679 } 680 681 unittest 682 { 683 // General double matrix. 684 auto m = matrix!double(3, 3); 685 foreach (i; 0 .. m.rows) 686 foreach (j; 0 .. m.cols) m[i,j] = i + 2.0*j; 687 auto v = eigenvalues(m); 688 assert (approxEqual(v[0].re, (9+sqrt(129.0))/2, 1e-10) && v[0].im == 0); 689 assert (approxEqual(v[1].re, (9-sqrt(129.0))/2, 1e-10) && v[1].im == 0); 690 assert (approxEqual(v[2].re, 0.0, 1e-10) && v[1].im == 0); 691 } 692 693 unittest 694 { 695 // General complex matrix. 696 alias Complex!double C; 697 698 auto m = matrix!C(3, 3); 699 foreach (i; 0 .. m.rows) 700 foreach (j; 0 .. m.cols) m[i,j] = C(i, j); 701 702 auto ev = eigenvalues(m); 703 704 double a = 1.5; 705 double b = cos(PI_4)*sqrt(42.0)*0.5; 706 assert (approxEqual(ev[0].re, a+b, 1e-10)); 707 assert (approxEqual(ev[1].re, a-b, 1e-10)); 708 assert (approxEqual(ev[2].re, 0.0, 1e-10)); 709 foreach (e; ev) assert (approxEqual(e.re, e.im, 1e-10)); 710 } 711 712 713 714 715 /** Calculate the eigenvalues of a triangular matrix. Note that this 716 is a trivial operation - the function just returns the diagonal 717 entries of the matrix. 718 719 Params: 720 m = An n-by-n triangular matrix. 721 buffer = (optional) A buffer for the returned values, must 722 have length >= n. 723 */ 724 T[] eigenvalues (T, Storage stor, Triangle tri) 725 (MatrixView!(T, stor, tri) m, T[] buffer=null) 726 if (stor == Storage.Triangular) 727 { 728 immutable int n = toInt(m.rows); 729 if (n == 0) return null; // Empty matrix. 730 buffer.length = n; 731 732 foreach (i, ref ev; buffer) ev = m[i,i]; 733 return buffer; 734 } 735 736 unittest 737 { 738 double[] a = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; 739 auto m = MatrixView!(double, Storage.Triangular)(a, 3); 740 auto v = eigenvalues(m); 741 assert (v.length == 3); 742 assert (v[0] == 1.0 && v[1] == 3.0 && v[2] == 6.0); 743 foreach (e; v) assert (e.im == 0.0); 744 } 745 746 747 748 749 /** Calculate the eigenvalues of a symmetric matrix. 750 751 Params: 752 m = An n-by-n symmetric matrix. 753 buffer = (optional) A buffer for the returned values, must 754 have length >= n. 755 */ 756 T[] eigenvalues (T, Storage stor, Triangle tri) 757 (MatrixView!(T, stor, tri) m, T[] buffer=null) 758 if (isFloatingPoint!T && stor == Storage.Symmetric) 759 { 760 return eigenvalues_(copy(m), buffer); 761 } 762 763 /// ditto 764 T[] eigenvalues_ (T, Storage stor, Triangle tri) 765 (MatrixView!(T, stor, tri) m, T[] buffer=null) 766 if (isFloatingPoint!T && stor == Storage.Symmetric) 767 { 768 mixin (newFrame); 769 770 static assert (isFortranType!T, 771 "eigenvalues: Not a FORTRAN-compatible type: "~T.stringof); 772 773 immutable int n = toInt(m.rows); 774 if (n == 0) return null; // Empty matrix. 775 buffer.length = n; 776 777 // Allocate workspace. 778 auto workspace = cast(T*) TempAlloc.malloc(3*n*T.sizeof); 779 780 // Call LAPACK routine SPEV. 781 int info; 782 spev('N', // Don't compute eigenvectors 783 m.triangle, toInt(m.rows), m.array.ptr, // Input matrix. 784 buffer.ptr, // Eigenvalue array. 785 null, 1, // Eigenvectors, not calculated. 786 workspace, // Workspace. 787 info); 788 789 if (info == 0) 790 { 791 return buffer; 792 } 793 else if (info > 0) 794 { 795 throw new Exception(text("The algorithm failed to converge. ", 796 info, " off-diagonal elements of an intermediate tridiagonal form " 797 ~ "did not converge to zero.")); 798 } 799 800 assert(0); 801 } 802 803 unittest 804 { 805 // Symmetric double matrix. 806 double[] a = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; 807 auto m = MatrixView!(double, Storage.Symmetric, Triangle.Lower)(a, 3); 808 auto v = eigenvalues(m); 809 assert (v.length == 3); 810 assert (approxEqual(v[0], - 0.5157294716, 1e-10)); 811 assert (approxEqual(v[1], 0.1709151888, 1e-10)); 812 assert (approxEqual(v[2], 11.34481428, 1e-10)); 813 } 814 815 816 817 818 /** This exception is thrown when the eigenvalue() function fails to 819 compute all eigenvalues. The ones that have been computed are stored 820 in the member variable eigenvalues. 821 */ 822 class EigenvalueException(T) : Exception 823 { 824 /// The computed eigenvalues. 825 T eigenvalues; 826 827 this (T v) 828 { 829 super ("Failed to compute all eigenvalues. " 830 ~to!string(v.length)~" eigenvalue(s) have been computed"); 831 eigenvalues = v; 832 } 833 } 834 835 836 837 838 /** Calculate the inverse of a matrix. 839 840 Currently only defined for general real matrices. 841 */ 842 void invert(T, Storage stor)(MatrixView!(T, stor) m) 843 if (isFortranType!T && !scid.core.traits.isComplex!T 844 && stor == Storage.General) 845 in 846 { 847 assert (m.rows == m.cols, "invert: can only invert square matrices"); 848 } 849 body 850 { 851 mixin (newFrame); 852 853 // Calculate optimal workspace size. 854 int info; 855 T optimal; 856 getri( 857 toInt(m.rows), null, toInt(m.leading), // Info about M 858 null, &optimal, -1, // Do workspace query 859 info); 860 861 // Allocate workspace memory. 862 int* ipiv = cast(int*) TempAlloc.malloc(m.rows*int.sizeof); 863 T[] work = newStack!T(to!int(optimal)); 864 865 // Calculate LU factorisation. 866 getrf( 867 toInt(m.rows), toInt(m.cols), m.array.ptr, toInt(m.leading), 868 ipiv, info); 869 870 // Invert matrix. 871 getri( 872 toInt(m.rows), m.array.ptr, toInt(m.leading), // Matrix 873 ipiv, work.ptr, toInt(work.length), // Workspace 874 info); 875 876 assert (info >= 0); 877 enforce(info == 0, "invert: matrix is singular"); 878 return; 879 } 880 881 unittest 882 { 883 double[] aa = [1.0, 0, 2, 2, 2, 0, 0, 1, 1]; 884 auto a = MatrixView!double(aa, 3, 3); 885 invert(a); 886 enum : real { _13 = 1.0L/3.0, _16 = 1.0L/6.0, _23 = 2.0L/3.0 } 887 real[] ans = [_13, _13, -_23, -_13, _16, _23, _13, -_16, _13]; 888 foreach (i, e; aa) assert (approxEqual(cast(real) e, ans[i], 1e-10L)); 889 } 890 891 unittest 892 { 893 double[] aa = new double[9]; 894 foreach (i, ref e; aa) e = i; 895 auto a = MatrixView!double(aa, 3, 3); 896 897 try { invert(a); assert (false, "Matrix should be detected as singular"); } 898 catch (Exception e) { assert (true); } 899 }