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 }