1 /++
2 $(H1 Lubeck - Linear Algebra)
3 
4 Authors: Ilya Yaroshenko, Lars Tandle Kyllingstad (SciD author)
5 +/
6 module lubeck;
7 
8 import cblas : Diag;
9 import mir.blas;
10 import mir.lapack;
11 import mir.math.common;
12 import mir.ndslice.allocation;
13 import mir.ndslice.dynamic: transposed;
14 import mir.ndslice.slice;
15 import mir.ndslice.topology;
16 import mir.utility;
17 import std.meta;
18 import std.traits;
19 import std.typecons: Flag, Yes, No;
20 
21 version(LDC)
22     import ldc.attributes: fastmath;
23 else
24     enum { fastmath };
25 
26 private template IterationType(Iterator)
27 {
28     alias T = Unqual!(typeof(Iterator.init[0]));
29     static if (isIntegral!T || is(T == real))
30         alias IterationType = double;
31     else
32     static if (is(T == creal))
33         alias IterationType = cdouble;
34     else
35     {
36         static assert(
37             is(T == double) ||
38             is(T == float) ||
39             is(T == cdouble) ||
40             is(T == cfloat));
41         alias IterationType = T;
42     }
43 }
44 
45 private alias BlasType(Iterators...) =
46     CommonType!(staticMap!(IterationType, Iterators));
47 
48 /++
49 General matrix-matrix multiplication. Allocates result to an uninitialized slice using GC.
50 Params:
51     a = m(rows) x k(cols) matrix
52     b = k(rows) x n(cols) matrix
53 Result: 
54     m(rows) x n(cols)
55 +/
56 Slice!(Contiguous, [2], BlasType!(IteratorA, IteratorB)*)
57     mtimes(SliceKind kindA, IteratorA, SliceKind kindB, IteratorB)(
58         Slice!(kindA, [2], IteratorA) a,
59         Slice!(kindB, [2], IteratorB) b)
60 {
61     assert(a.length!1 == b.length!0);
62 
63     // reallocate data if required
64     alias A = BlasType!IteratorA;
65     alias B = BlasType!IteratorB;
66     alias C = CommonType!(A, B);
67     static if (!is(Unqual!IteratorA == C*))
68         return .mtimes(a.as!C.slice, b);
69     else
70     static if (!is(Unqual!IteratorB == C*))
71         return .mtimes(a, b.as!C.slice);
72     else
73     {
74         static if (kindA != Contiguous)
75             if (a._stride!0 != 1 && a._stride!1 != 1
76                 || a._stride!0 <= 0
77                 || a._stride!1 <= 0)
78                 return .mtimes(a.slice, b);
79 
80         static if (kindB != Contiguous)
81             if (b._stride!0 != 1 && b._stride!1 != 1
82                 || b._stride!0 <= 0
83                 || b._stride!1 <= 0)
84                 return .mtimes(a, b.slice);
85 
86         auto c = uninitSlice!C(a.length!0, b.length!1);
87 
88         if (a.length!1 == 1 && b.length!0 == 1)
89         {
90             c[] = cast(C) 0;
91             ger(cast(C)1, a.front!1, b.front, c);
92         }
93         else
94         {
95             gemm(cast(C)1, a, b, cast(C)0, c);
96         }
97         return c;
98     }
99 }
100 
101 ///
102 unittest
103 {
104     auto a =
105         [-5,  1,  7, 7, -4,
106          -1, -5,  6, 3, -3,
107          -5, -2, -3, 6,  0].sliced(3, 5);
108 
109     auto b = slice!double(5, 4);
110     b[] =
111         [[-5, -3,  3,  1],
112          [ 4,  3,  6,  4],
113          [-4, -2, -2,  2],
114          [-1,  9,  4,  8],
115          [ 9, 8,  3, -2]];
116 
117     assert(mtimes(a, b) ==
118         [[-42,  35,  -7, 77],
119          [-69, -21, -42, 21],
120          [ 23,  69,   3, 29]]
121         );
122 }
123 
124 /// ger specialized case in mtimes
125 unittest
126 {
127     // from https://github.com/kaleidicassociates/lubeck/issues/8
128     {
129         auto a = [1.0f, 2.0f].sliced(2, 1);
130         auto b = [1.0f, 2.0f].sliced(2, 1);
131         assert(mtimes(a, b.transposed) == [[1, 2], [2, 4]]);
132     }
133     {
134         auto a = [1.0, 2.0].sliced(1, 2);
135         auto b = [1.0, 2.0].sliced(1, 2);
136         assert(mtimes(a.transposed, b) == [[1, 2], [2, 4]]);
137     }
138 }
139 
140 ///
141 unittest
142 {
143     // from https://github.com/kaleidicassociates/lubeck/issues/3
144     Slice!(cast(SliceKind)2, [2LU], float*) a = slice!float(1, 1);
145     Slice!(cast(SliceKind)0, [2LU], float*) b1 = slice!float(16, 1).transposed;
146     Slice!(cast(SliceKind)2, [2LU], float*) b2 = slice!float(1, 16);
147 
148     a[] = 3;
149     b1[] = 4;
150     b2[] = 4;
151 
152     // Confirm that this message does not appear
153     // Outputs: ** On entry to SGEMM  parameter number  8 had an illegal value
154     assert(a.mtimes(b1) == a.mtimes(b2));
155 }
156 
157 /++
158 General matrix-matrix multiplication. Allocates result to an uninitialized slice using GC.
159 Params:
160     a = m(rows) x k(cols) matrix
161     b = k(rows) x 1(cols) vector
162 Result:
163     m(rows) x 1(cols)
164 +/
165 Slice!(Contiguous, [1], BlasType!(IteratorA, IteratorB)*)
166     mtimes(SliceKind kindA, IteratorA, SliceKind kindB, IteratorB)(
167         Slice!(kindA, [2], IteratorA) a,
168         Slice!(kindB, [1], IteratorB) b)
169 {
170     assert(a.length!1 == b.length!0);
171 
172     // reallocate data if required
173     alias A = BlasType!IteratorA;
174     alias B = BlasType!IteratorB;
175     alias C = CommonType!(A, B);
176     static if (!is(Unqual!IteratorA == C*))
177         return .mtimes(a.as!C.slice, b);
178     else
179     static if (!is(Unqual!IteratorB == C*))
180         return .mtimes(a, b.as!C.slice);
181     else
182     {
183         static if (kindA != Contiguous)
184             if (a._stride!0 != 1 && a._stride!1 != 1
185                 || a._stride!0 <= 0
186                 || a._stride!1 <= 0)
187                 return .mtimes(a.slice, b);
188 
189         static if (kindB != Contiguous)
190             if (b._stride!1 <= 0)
191                 return .mtimes(a, b.slice);
192 
193         auto c = uninitSlice!C(a.length!0);
194         gemv(cast(C)1, a, b, cast(C)0, c);
195         return c;
196     }
197 }
198 
199 /++
200 General matrix-matrix multiplication.
201 Params:
202     a = 1(rows) x k(cols) vector
203     b = k(rows) x n(cols) matrix
204 Result:
205     1(rows) x n(cols)
206 +/
207 Slice!(Contiguous, [1], BlasType!(IteratorA, IteratorB)*)
208     mtimes(SliceKind kindA, IteratorA, SliceKind kindB, IteratorB)(
209         Slice!(kindB, [1], IteratorB) a,
210         Slice!(kindA, [2], IteratorA) b,
211         )
212 {
213     return .mtimes(b.universal.transposed, a);
214 }
215 
216 ///
217 unittest
218 {
219     auto a =
220         [-5,  1,  7, 7, -4,
221          -1, -5,  6, 3, -3,
222          -5, -2, -3, 6,  0]
223             .sliced(3, 5)
224             .universal
225             .transposed;
226 
227     auto b = slice!double(5);
228     b[] = [-5, 4,-4,-1, 9];
229 
230     assert(mtimes(b, a) == [-42, -69, 23]);
231 }
232 
233 /++
234 Vector-vector multiplication (dot product).
235 Params:
236     a = 1(rows) x k(cols) vector
237     b = k(rows) x 1(cols) matrix
238 Result:
239     scalar
240 +/
241 CommonType!(BlasType!IteratorA, BlasType!IteratorB)
242     mtimes(SliceKind kindA, IteratorA, SliceKind kindB, IteratorB)(
243         Slice!(kindB, [1], IteratorB) a,
244         Slice!(kindA, [1], IteratorA) b,
245         )
246 {
247     alias A = BlasType!IteratorA;
248     alias B = BlasType!IteratorB;
249     alias C = CommonType!(A, B);
250     static if (is(IteratorB == C*) && is(IteratorA == C*))
251     {
252         return dot(a, b);
253     }
254     else
255     {
256         auto c = cast(typeof(return)) 0;
257         import mir.ndslice.algorithm: reduce;
258         return c.reduce!"a + b * c"(a.as!(typeof(return)), b.as!(typeof(return)));
259     }
260 }
261 
262 ///
263 unittest
264 {
265     auto a = [1, 2, 4].sliced;
266     auto b = [3, 4, 2].sliced;
267     assert(a.mtimes(b) == 19);
268 }
269 
270 /++
271 Calculates the inverse of a matrix.
272 +/
273 auto inv(SliceKind kind, Iterator)(Slice!(kind, [2], Iterator) a)
274 in
275 {
276     assert (a.length!0 == a.length!1, "matrix must be square");
277 }
278 body
279 {
280     alias T = BlasType!Iterator;
281 
282     auto m = a.as!T.slice.canonical;
283     auto ipiv = m.length.uninitSlice!lapackint;
284 
285     auto info = getrf!T(m, ipiv);
286     if (info == 0)
287     {
288         info = getri(m, ipiv, m.getri_wq.uninitSlice!T);
289     }
290 
291     import std.exception: enforce;
292     enforce(info == 0, "inv: matrix is singular");
293     return m;
294 }
295 
296 ///
297 unittest
298 {
299     auto a =  [
300         1, 0, 2,
301         2, 2, 0,
302         0, 1, 1]
303         .sliced(3, 3);
304     
305     enum : double { _13 = 1.0/3.0, _16 = 1.0/6.0, _23 = 2.0/3.0 }
306     auto ans = [
307         _13, _13, -_23,
308         -_13,_16, _23,
309         _13, -_16, _13]
310         .sliced(a.shape);
311 
312     import mir.ndslice.algorithm: equal;
313     import std.math: approxEqual;
314     assert(equal!((a, b) => a.approxEqual(b, 1e-10L, 1e-10L))(a.inv, ans));
315     assert(equal!((a, b) => a.approxEqual(b, 1e-10L, 1e-10L))(a.as!cdouble.inv.as!double, ans));
316 }
317 
318 ///
319 unittest
320 {
321     import mir.ndslice.topology: iota;
322 
323     try
324     {
325         auto m = [3, 3].iota.inv;
326         assert (false, "Matrix should be detected as singular");
327     }
328     catch (Exception e)
329     {
330         assert (true);
331     }
332 }
333 
334 ///
335 struct SvdResult(T)
336 {
337     ///
338     Slice!(Contiguous, [2], T*) u;
339     ///
340     Slice!(Contiguous, [1], T*) sigma;
341     ///
342     Slice!(Contiguous, [2], T*) vt;
343 }
344 
345 /++
346 Computes the singular value decomposition.
347 
348 Params:
349     matrix = input `M x N` matrix
350     slim = If true the first `min(M,N)` columns of `u` and the first
351         `min(M,N)` rows of `vt` are returned in the ndslices `u` and `vt`.
352 Returns: $(LREF SvdResult). Results are allocated by the GC.
353 +/
354 auto svd(
355         Flag!"allowDestroy" allowDestroy = No.allowDestroy,
356         string algorithm = "gesvd",
357         SliceKind kind,
358         Iterator
359     )(
360         Slice!(kind, [2], Iterator) matrix,
361         Flag!"slim" slim = No.slim,
362     )
363     if (algorithm == "gesvd" || algorithm == "gesdd")
364 {
365     import lapack;
366     alias T = BlasType!Iterator;
367     static if (allowDestroy && kind != Universal && is(Iterstor == T*))
368         alias a = matrix.canonical;
369     else
370         auto a = matrix.as!T.slice.canonical;
371 
372     auto m = cast(lapackint)a.length!1;
373     auto n = cast(lapackint)a.length!0;
374 
375     auto s = uninitSlice!T(min(m, n));
376     auto u = uninitSlice!T(slim ? s.length : m, m);
377     auto vt = uninitSlice!T(n, slim ? s.length : n);
378 
379     static if (algorithm == "gesvd")
380     {
381         auto jobu = slim ? 'S' : 'A';
382         auto jobvt = slim ? 'S' : 'A';
383         auto work = gesvd_wq(jobu, jobvt, a, u.canonical, vt.canonical).uninitSlice!T;
384         auto info = gesvd(jobu, jobvt, a, s, u.canonical, vt.canonical, work);
385         enum msg = "svd: DBDSQR did not converge";
386     }
387     else // gesdd
388     {
389         auto iwork = uninitSlice!lapackint(s.length * 8);
390         auto jobz = slim ? 'S' : 'A';
391         auto work = gesdd_wq(jobz, a, u.canonical, vt.canonical).uninitSlice!T;
392         auto info = gesdd(jobz, a, s, u.canonical, vt.canonical, work, iwork);
393         enum msg = "svd: DBDSDC did not converge, updating process failed";
394     }
395 
396     import std.exception: enforce;
397     enforce(info == 0, msg);
398     return SvdResult!T(vt, s, u); //transposed
399 }
400 
401 ///
402 unittest
403 {
404     auto a =  [
405          7.52,  -1.10,  -7.95,   1.08,
406         -0.76,   0.62,   9.34,  -7.10,
407          5.13,   6.62,  -5.66,   0.87,
408         -4.75,   8.52,   5.75,   5.30,
409          1.33,   4.91,  -5.49,  -3.52,
410         -2.40,  -6.77,   2.34,   3.95]
411         .sliced(6, 4);
412 
413     auto r = a.svd;
414 
415     auto sigma = slice!double(a.shape, 0);
416     sigma.diagonal[] = r.sigma;
417     auto m = r.u.mtimes(sigma).mtimes(r.vt);
418 
419     import mir.ndslice.algorithm: equal;
420     import std.math: approxEqual;
421     assert(equal!((a, b) => a.approxEqual(b, 1e-8, 1e-8))(a, m));
422 }
423 
424 ///
425 unittest
426 {
427     auto a =  [
428          7.52,  -1.10,  -7.95,   1.08,
429         -0.76,   0.62,   9.34,  -7.10,
430          5.13,   6.62,  -5.66,   0.87,
431         -4.75,   8.52,   5.75,   5.30,
432          1.33,   4.91,  -5.49,  -3.52,
433         -2.40,  -6.77,   2.34,   3.95]
434         .sliced(6, 4);
435 
436     auto r = a.svd(Yes.slim);
437     assert(r.u.shape == [6, 4]);
438     assert(r.vt.shape == [4, 4]);
439 }
440 
441 /++
442 Solve systems of linear equations AX = B for X.
443 Computes minimum-norm solution to a linear least squares problem
444 if A is not a square matrix.
445 +/
446 Slice!(Contiguous, [2], BlasType!(IteratorA, IteratorB)*)
447     mldivide
448     (SliceKind kindA, IteratorA, SliceKind kindB, IteratorB)(
449         Slice!(kindA, [2], IteratorA) a,
450         Slice!(kindB, [2], IteratorB) b)
451 {
452     import std.exception: enforce;
453     import std.conv: to;
454 
455     assert(a.length!0 == b.length!0);
456 
457     alias A = BlasType!IteratorA;
458     alias B = BlasType!IteratorB;
459     alias C = CommonType!(A, B);
460 
461     auto a_ = a.universal.transposed.as!C.slice.canonical;
462     auto b_ = b.universal.transposed.as!C.slice.canonical;
463 
464     if (a.length!0 == a.length!1)
465     {
466         auto ipiv = a_.length.uninitSlice!lapackint;
467         auto info = gesv(a_, ipiv, b_);
468     }
469     else
470     {
471         size_t liwork = void;
472         auto lwork = gelsd_wq(a_, b_, liwork);
473         auto s = min(a_.length!0, a_.length!1).uninitSlice!C;
474         auto work = lwork.uninitSlice!C;
475         auto iwork = liwork.uninitSlice!lapackint;
476         size_t rank;
477         C rcond = -1;
478         
479         auto info = gelsd(a_, b_, s, rcond, rank, work, iwork);
480 
481         enforce(info == 0, to!string(info) ~ " off-diagonal elements of an intermediate bidiagonal form did not converge to zero.");
482         b_ = b_[0 .. $, 0 .. a_.length!0];
483     }
484     return b_.universal.transposed.slice;
485 }
486 
487 /// ditto
488 Slice!(Contiguous, [1], BlasType!(IteratorA, IteratorB)*)
489     mldivide
490     (SliceKind kindA, IteratorA, SliceKind kindB, IteratorB)(
491         Slice!(kindA, [2], IteratorA) a,
492         Slice!(kindB, [1], IteratorB) b)
493 {
494     return a.mldivide(b.repeat(1).unpack.universal.transposed).front!1.assumeContiguous;
495 }
496 
497 /// AX=B
498 unittest
499 {
500     auto a = [
501          1, -1,  1,
502          2,  2, -4,
503         -1,  5,  0].sliced(3, 3);
504     auto b = [
505          2.0,  0,
506         -6  , -6,
507          9  ,  1].sliced(3, 2);
508     auto t = [
509          1.0, -1,
510          2  ,  0,
511          3  ,  1].sliced(3, 2);
512 
513     auto x = mldivide(a, b);
514     assert(x == t);
515 }
516 
517 /// Ax=B
518 unittest
519 {
520     auto a = [
521          1, -1,  1,
522          2,  2, -4,
523         -1,  5,  0].sliced(3, 3);
524     auto b = [
525          2.0,
526         -6  ,
527          9  ].sliced(3);
528     auto t = [
529          1.0,
530          2  ,
531          3  ].sliced(3);
532 
533     auto x = mldivide(a, b);
534     assert(x == t);
535 }
536 
537 /// Least-Squares Solution of Underdetermined System
538 unittest
539 {
540     auto a = [
541         -0.57,  -1.28,  -0.39,   0.25,
542         -1.93,   1.08,  -0.31,  -2.14,
543          2.30,   0.24,   0.40,  -0.35,
544         -1.93,   0.64,  -0.66,   0.08,
545          0.15,   0.30,   0.15,  -2.13,
546         -0.02,   1.03,  -1.43,   0.50,
547         ].sliced(6, 4);
548 
549     auto b = [
550      -2.67,
551      -0.55,
552       3.34,
553      -0.77,
554       0.48,
555       4.10,
556     ].sliced;
557 
558     auto x = [
559         1.5339,
560         1.8707,
561        -1.5241,
562         0.0392].sliced;
563 
564     import std.math: approxEqual;
565     assert(a.mldivide(b).approxEqual(x));
566 }
567 
568 /// Principal component analises result.
569 struct PcaResult(T)
570 {
571     /// Principal component coefficients, also known as loadings.
572     Slice!(Contiguous, [2], T*) coeff;
573     /// Principal component scores.
574     Slice!(Contiguous, [2], T*) score;
575     /// Principal component variances.
576     Slice!(Contiguous, [1], T*) latent;
577 }
578 
579 /++
580 Principal component analysis of raw data.
581 
582 Params:
583     matrix = input `M x N` matrix, where 'M (rows)>= N(cols)'
584     cc = Flag to centern columns. True by default.
585 Returns: $(LREF PcaResult)
586 +/
587 auto pca(Flag!"allowDestroy" allowDestroy = No.allowDestroy, SliceKind kind, Iterator)(Slice!(kind, [2], Iterator) matrix, in Flag!"centerColumns" cc = Yes.centerColumns)
588 in
589 {
590     assert(matrix.length!0 >= matrix.length!1);
591 }
592 body
593 {
594     import mir.math.sum: sum;
595     import mir.ndslice.algorithm: maxIndex, eachUploPair;
596     import mir.utility: swap;
597 
598     alias T = BlasType!Iterator;
599     SvdResult!T svdResult;
600     if (cc)
601     {
602         static if (allowDestroy && kind != Universal && is(Iterstor == T*))
603             alias m = matrix;
604         else
605             auto m = matrix.as!T.slice;
606         foreach (col; m.universal.transposed)
607             col[] -= col.sum!"kb2" / col.length;
608         svdResult = m.svd!(Yes.allowDestroy)(Yes.slim);
609     }
610     else
611     {
612         svdResult = matrix.svd!(allowDestroy)(Yes.slim);
613     }
614     with (svdResult)
615     {
616         foreach (row; u)
617             row[] *= sigma;
618         T c = max(0, ptrdiff_t(matrix.length) - cast(bool) cc);
619         foreach (ref s; sigma)
620             s = s * s / c;
621         foreach(size_t i; 0 .. sigma.length)
622         {
623             auto col = vt[i];
624             if (col[col.map!fabs.maxIndex] < 0)
625             {
626                 col[] *= -1;
627                 u[0 .. $, i] *= -1;
628             }
629         }
630         vt.eachUploPair!swap;
631         return PcaResult!T(vt, u, sigma);
632     }
633 }
634 
635 ///
636 unittest
637 {
638     import std.math: approxEqual;
639     import mir.ndslice.algorithm: equal;
640 
641     auto ingedients = [
642          7,  26,   6,  60,
643          1,  29,  15,  52,
644         11,  56,   8,  20,
645         11,  31,   8,  47,
646          7,  52,   6,  33,
647         11,  55,   9,  22,
648          3,  71,  17,   6,
649          1,  31,  22,  44,
650          2,  54,  18,  22,
651         21,  47,   4,  26,
652          1,  40,  23,  34,
653         11,  66,   9,  12,
654         10,  68,   8,  12].sliced(13, 4);
655 
656     auto res = ingedients.pca;
657 
658     auto coeff = [
659         -0.067799985695474,  -0.646018286568728,   0.567314540990512,   0.506179559977705,
660         -0.678516235418647,  -0.019993340484099,  -0.543969276583817,   0.493268092159297,
661          0.029020832106229,   0.755309622491133,   0.403553469172668,   0.515567418476836,
662          0.730873909451461,  -0.108480477171676,  -0.468397518388289,   0.484416225289198,
663     ].sliced(4, 4);
664 
665     auto score = [
666          36.821825999449700,  -6.870878154227367,  -4.590944457629745,   0.396652582713912,
667          29.607273420710964,   4.610881963526308,  -2.247578163663940,  -0.395843536696492,
668         -12.981775719737618,  -4.204913183175938,   0.902243082694698,  -1.126100587210615,
669          23.714725720918022,  -6.634052554708721,   1.854742000806314,  -0.378564808384691,
670          -0.553191676624597,  -4.461732123178686,  -6.087412652325177,   0.142384896047281,
671         -10.812490833309816,  -3.646571174544059,   0.912970791674604,  -0.134968810314680,
672         -32.588166608817929,   8.979846284936063,  -1.606265913996588,   0.081763927599947,
673          22.606395499005586,  10.725906457369449,   3.236537714483416,   0.324334774646368,
674          -9.262587237675838,   8.985373347478788,  -0.016909578102172,  -0.543746175981799,
675          -3.283969329640680, -14.157277337500918,   7.046512994833761,   0.340509860960606,
676           9.220031117829379,  12.386080787220454,   3.428342878284624,   0.435152769664895,
677         -25.584908517429557,  -2.781693148152386,  -0.386716066864491,   0.446817950545605,
678         -26.903161834677597,  -2.930971165042989,  -2.445522630195304,   0.411607156409658,
679     ].sliced(13, 4);
680 
681     auto latent = [5.177968780739053, 0.674964360487231, 0.124054300480810, 0.002371532651878].sliced;
682     latent[] *= 100;
683 
684     assert(equal!approxEqual(res.coeff, coeff));
685     assert(equal!approxEqual(res.score, score));
686     assert(equal!approxEqual(res.latent, latent));
687 }
688 
689 /++
690 Computes Moore-Penrose pseudoinverse of matrix.
691 
692 Params:
693     matrix = Input `M x N` matrix.
694     tolerance = The computation is based on SVD and any singular values less than tolerance are treated as zero.
695 Returns: Moore-Penrose pseudoinverse matrix
696 +/
697 Slice!(Contiguous, [2], BlasType!Iterator*)
698     pinv(Flag!"allowDestroy" allowDestroy = No.allowDestroy, SliceKind kind, Iterator)(Slice!(kind, [2], Iterator) matrix, BlasType!Iterator tolerance = BlasType!Iterator.nan)
699 {
700     import mir.ndslice.algorithm: find, each;
701     import std.math: nextUp;
702 
703     auto svd = matrix.svd!allowDestroy(Yes.slim);
704     if (tolerance != tolerance)
705     {
706         auto n = svd.sigma.front;
707         auto eps = n.nextUp - n;
708         tolerance = max(matrix.length!0, matrix.length!1) * eps;
709     }
710     auto s = svd.sigma[0 .. $ - svd.sigma.find!(a => !(a >= tolerance))[0]];
711     s.each!"a = 1 / a";
712     svd.vt[0 .. s.length].pack!1.map!"a".zip(s).each!"a.a[] *= a.b";
713     auto v = svd.vt[0 .. s.length].universal.transposed;
714     auto ut = svd.u.universal.transposed[0 .. s.length];
715     return v.mtimes(ut);
716 }
717 
718 ///
719 unittest
720 {
721     auto a = [
722         64,  2,  3, 61, 60,  6,
723          9, 55, 54, 12, 13, 51,
724         17, 47, 46, 20, 21, 43,
725         40, 26, 27, 37, 36, 30,
726         32, 34, 35, 29, 28, 38,
727         41, 23, 22, 44, 45, 19,
728         49, 15, 14, 52, 53, 11,
729          8, 58, 59,  5,  4, 62].sliced(8, 6);
730 
731     auto b = a.pinv;
732 
733     auto result = [
734         0.0177, -0.0165, -0.0164,  0.0174,  0.0173, -0.0161, -0.0160,  0.0170,
735        -0.0121,  0.0132,  0.0130, -0.0114, -0.0112,  0.0124,  0.0122, -0.0106,
736        -0.0055,  0.0064,  0.0060, -0.0043, -0.0040,  0.0049,  0.0045, -0.0028,
737        -0.0020,  0.0039,  0.0046, -0.0038, -0.0044,  0.0064,  0.0070, -0.0063,
738        -0.0086,  0.0108,  0.0115, -0.0109, -0.0117,  0.0139,  0.0147, -0.0141,
739         0.0142, -0.0140, -0.0149,  0.0169,  0.0178, -0.0176, -0.0185,  0.0205];
740 
741     import std.math: approxEqual;
742 
743     assert(b.field.approxEqual(result, 1e-2, 1e-2));
744 }
745 
746 /++
747 Covariance matrix.
748 
749 Params:
750     matrix = matrix whose rows represent observations and whose columns represent random variables.
751 Reuturns:
752     Normalized by `N-1` covariance matrix.
753 +/
754 Slice!(Contiguous, [2], BlasType!Iterator*)
755     cov(SliceKind kind, Iterator)(Slice!(kind, [2], Iterator) matrix)
756 {
757     import mir.math.sum: sum;
758     import mir.ndslice.algorithm: each, eachUploPair;
759     alias A = BlasType!Iterator;
760     static if (kind == Contiguous)
761         auto mc = matrix.canonical;
762     else
763         alias mc = matrix;
764     auto m = mc.shape.uninitSlice!A.canonical;
765     auto s = m;
766 
767     auto factor = 1 / A(m.length!0 - 1).sqrt;
768     while(m.length!1)
769     {
770         auto shift = - mc.front!1.sum!A / m.length!0;
771         m.front!1.each!((ref a, b) { a = (b + shift) * factor; })(mc.front!1);
772         mc.popFront!1;
773         m.popFront!1;
774     }
775 
776     auto alpha = cast(A) 1;
777     auto beta = cast(A) 0;
778     auto c = [s.length!1, s.length!1].uninitSlice!A;
779     syrk(Uplo.Upper, alpha, s.universal.transposed, beta, c);
780 
781     c.eachUploPair!"b = a";
782     return c;
783 }
784 
785 ///
786 unittest
787 {
788     import std.stdio;
789     import mir.ndslice;
790 
791     auto c = 8.magic[0..$-1].cov;
792 
793     import std.math: approxEqual;
794     assert(c.field.approxEqual([
795          350.0000, -340.6667, -331.3333,  322.0000,  312.6667, -303.3333, -294.0000,  284.6667,
796         -340.6667,  332.4762,  324.2857, -316.0952, -307.9048,  299.7143,  291.5238, -283.3333,
797         -331.3333,  324.2857,  317.2381, -310.1905, -303.1429,  296.0952,  289.0476, -282.0000,
798          322.0000, -316.0952, -310.1905,  304.2857,  298.3810, -292.4762, -286.5714,  280.6667,
799          312.6667, -307.9048, -303.1429,  298.3810,  293.6190, -288.8571, -284.0952,  279.3333,
800         -303.3333,  299.7143,  296.0952, -292.4762, -288.8571,  285.2381,  281.6190, -278.0000,
801         -294.0000,  291.5238,  289.0476, -286.5714, -284.0952,  281.6190,  279.1429, -276.6667,
802          284.6667, -283.3333, -282.0000,  280.6667,  279.3333, -278.0000, -276.6667,  275.3333]));
803 }
804 
805 /++
806 Matrix determinant.
807 +/
808 auto detSymmetric(SliceKind kind, Iterator)(Slice!(kind, [2], Iterator) a, char store = 'L')
809 in
810 {
811     assert(store == 'U' || store == 'L');
812     assert (a.length!0 == a.length!1, "matrix must be square");
813     assert (a.length!0, "matrix must not be empty");
814 }
815 body
816 {
817     import mir.ndslice.algorithm: each;
818     import mir.ndslice.topology: diagonal;
819     import mir.math.numeric: Prod;
820 
821     alias T = BlasType!Iterator;
822 
823     auto packed = uninitSlice!T(a.length * (a.length + 1) / 2);
824     auto ipiv = a.length.uninitSlice!lapackint;
825     int sign;
826     Prod!T prod;
827     if (store == 'L')
828     {
829         auto pck = packed.stairs!"+"(a.length);
830         auto gen = a.stairs!"+";
831         pck.each!"a[] = b"(gen);
832         auto info = sptrf(pck, ipiv);
833         if (info > 0)
834             return cast(T) 0;
835         for (size_t j; j < ipiv.length; j++)
836         {
837             auto i = ipiv[j];
838             // 1x1 block at m[k,k]
839             if (i > 0)
840             {
841                 prod.put(pck[j].back);
842                 sign ^= i != j + 1; // i.e. row interchanged with another
843             }
844             else
845             {
846                 i = -i;
847                 auto offDiag = pck[j + 1][$ - 2];
848                 auto blockDet = pck[j].back * pck[j + 1].back - offDiag * offDiag;
849                 prod.put(blockDet);
850                 sign ^= i != j + 1 && i != j + 2; // row interchanged with other
851                 j++;
852             }
853         }
854     }
855     else
856     {
857         auto pck = packed.stairs!"-"(a.length);
858         auto gen = a.stairs!"-";
859         pck.each!"a[] = b"(gen);
860         auto info = sptrf(pck, ipiv);
861         if (info > 0)
862             return cast(T) 0;
863         for (size_t j; j < ipiv.length; j++)
864         {
865             auto i = ipiv[j];
866             // 1x1 block at m[k,k]
867             if (i > 0)
868             {
869                 prod.put(pck[j].front);
870                 sign ^= i != j + 1; // i.e. row interchanged with another
871             }
872             else
873             {
874                 i = -i;
875                 auto offDiag = pck[j][1];
876                 auto blockDet = pck[j].front * pck[j + 1].front - offDiag * offDiag;
877                 prod.put(blockDet);
878                 sign ^= i != j + 1 && i != j + 2; // row interchanged with other
879                 j++;
880             }
881         }
882     }
883     if(sign & 1)
884         prod.x = -prod.x;
885     return prod.value;
886 }
887 
888 /// ditto
889 auto det(SliceKind kind, Iterator)(Slice!(kind, [2], Iterator) a)
890 in
891 {
892     assert (a.length!0 == a.length!1, "matrix must be square");
893 }
894 body
895 {
896     import mir.ndslice.topology: diagonal, zip, iota;
897     import mir.math.numeric: Prod;
898 
899     alias T = BlasType!Iterator;
900 
901     auto m = a.as!T.slice.canonical;
902     auto ipiv = a.length.uninitSlice!lapackint;
903 
904     // LU factorization
905     auto info = m.getrf(ipiv);
906 
907     // If matrix is singular, determinant is zero.
908     if (info > 0)
909     {
910         return cast(T) 0;
911     }
912 
913     // The determinant is the product of the diagonal entries
914     // of the upper triangular matrix. The array ipiv contains
915     // the pivots.
916     int sign;
917     Prod!T prod;
918     foreach (tup; m.diagonal.zip(ipiv, [ipiv.length].iota(1)))
919     {
920         prod.put(tup.a);
921         sign ^= tup.b != tup.c; // i.e. row interchanged with another
922     }
923     if(sign & 1)
924         prod.x = -prod.x;
925     return prod.value;
926 }
927 
928 ///
929 unittest
930 {
931     // Check for zero-determinant shortcut.
932     auto ssing = [4, 2, 2, 1].sliced(2, 2);
933     auto ssingd = det(ssing);
934     assert (det(ssing) == 0);
935     assert (detSymmetric(ssing) == 0);
936     assert (detSymmetric(ssing, 'L') == 0);
937 
938     //import std.stdio;
939 
940 
941     // General dense matrix.
942     int dn = 101;
943     auto d = uninitSlice!double(dn, dn);
944     foreach (k; 0 .. dn)
945     foreach (l; 0 .. dn)
946         d[k,l] = 0.5 * (k == l ? (k + 1) * (k + 1) + 1 : 2 * (k + 1) * (l + 1));
947 
948     auto dd = det(d);
949     import std.math: approxEqual;
950     assert (approxEqual(dd, 3.539152633479803e289, double.epsilon.sqrt));
951 
952     // Symmetric packed matrix
953     auto spa = [ 1.0, -2, 3, 4, 5, -6, -7, -8, -9, 10].sliced.stairs!"+"(4);
954     auto sp = [spa.length, spa.length].uninitSlice!double;
955     import mir.ndslice.algorithm: each;
956     sp.stairs!"+".each!"a[] = b"(spa);
957     assert (sp.detSymmetric('L').approxEqual(5874.0, double.epsilon.sqrt));
958     assert (sp.universal.transposed.detSymmetric('U').approxEqual(5874.0, double.epsilon.sqrt));
959 }
960 
961 /++
962 Eigenvalues and eigenvectors POD.
963 
964 See_also: $(LREF eigSymmetric).
965 +/
966 struct EigSymmetricResult(T)
967 {
968     /// Eigenvalues
969     Slice!(Contiguous, [1], T*) values;
970     /// Eigenvectors stored in rows
971     Slice!(Contiguous, [2], T*) vectors;
972 }
973 
974 /++
975 Eigenvalues and eigenvectors of symmetric matrix.
976 
977 Returns:
978     $(LREF EigSymmetricResult)
979 +/
980 auto eigSymmetric(Flag!"computeVectors" cv = Yes.computeVectors, SliceKind kind, Iterator)(Slice!(kind, [2], Iterator) a, char store = 'L')
981 in
982 {
983     assert(store == 'U' || store == 'L');
984     assert (a.length!0 == a.length!1, "matrix must be square");
985     assert (a.length!0, "matrix must not be empty");
986 }
987 body
988 {
989     import mir.ndslice.algorithm: each;
990     import mir.ndslice.topology: diagonal;
991     import mir.math.numeric: Prod;
992 
993     alias T = BlasType!Iterator;
994 
995     auto packed = [a.length * (a.length + 1) / 2].uninitSlice!T;
996     auto w = [a.length].uninitSlice!T;
997     static if (cv)
998     {
999         auto z = [a.length, a.length].uninitSlice!T;
1000     }
1001     else
1002     {
1003         T[1] _vData = void;
1004         auto z = _vData.sliced(1, 1);
1005     }
1006     auto work = [a.length * 3].uninitSlice!T;
1007     size_t info = void;
1008     auto jobz = cv ? 'V' : 'N';
1009     if (store == 'L')
1010     {
1011         auto pck = packed.stairs!"+"(a.length);
1012         auto gen = a.stairs!"+";
1013         pck.each!"a[] = b"(gen);
1014         info = spev!T(jobz, pck, w, z.canonical, work);
1015     }
1016     else
1017     {
1018         auto pck = packed.stairs!"-"(a.length);
1019         auto gen = a.stairs!"-";
1020         pck.each!"a[] = b"(gen);
1021         info = spev!T(jobz, pck, w, z.canonical, work);
1022     }
1023     import std.exception: enforce;
1024     import std.format: format;
1025     enforce (info == 0, format("The algorithm failed to converge." ~
1026         "%s off-diagonal elements of an intermediate tridiagonal form did not converge to zero.", info));
1027     static if (cv)
1028     {
1029         return EigSymmetricResult!T(w, z);
1030     }
1031     else
1032     {
1033         return EigSymmetricResult!T(w);
1034     }
1035 }
1036 
1037 ///
1038 unittest
1039 {
1040     import mir.ndslice.slice: sliced;
1041     import mir.ndslice.topology: universal, map;
1042     import mir.ndslice.dynamic: transposed;
1043     import std.math: approxEqual;
1044 
1045     auto a = [
1046         1.0000, 0.5000, 0.3333, 0.2500,
1047         0.5000, 1.0000, 0.6667, 0.5000,
1048         0.3333, 0.6667, 1.0000, 0.7500,
1049         0.2500, 0.5000, 0.7500, 1.0000].sliced(4, 4);
1050 
1051     auto eigr = a.eigSymmetric;
1052 
1053     assert(eigr.values.approxEqual([0.2078,0.4078,0.8482,2.5362]));
1054 
1055     auto test = [
1056          0.0693, -0.4422, -0.8105, 0.3778,
1057         -0.3618,  0.7420, -0.1877, 0.5322,
1058          0.7694,  0.0486,  0.3010, 0.5614,
1059         -0.5219, -0.5014,  0.4662, 0.5088]
1060             .sliced(4, 4)
1061             .universal
1062             .transposed;
1063 
1064     foreach (i; 0 .. 4)
1065         assert(eigr.vectors[i].approxEqual(test[i]) ||
1066             eigr.vectors[i].map!"-a".approxEqual(test[i]));
1067 }
1068 
1069 version (unittest)
1070 {
1071 /++
1072 Swaps rows of input matrix
1073 Params
1074     ipiv = pivot points
1075 Returns:
1076     a = shifted matrix
1077 +/
1078     private void moveRows(SliceKind kind, Iterator)
1079                         (Slice!(kind, [2], Iterator) a,
1080                         Slice!(Contiguous, [1], lapackint*) ipiv)
1081     {
1082         import mir.ndslice.algorithm: each;
1083         foreach_reverse(i;0..ipiv.length)
1084         {
1085             if(ipiv[i] == i + 1)
1086                 continue;
1087             each!swap(a[i], a[ipiv[i] - 1]);
1088         }
1089     }
1090 }
1091 
1092 ///
1093 unittest
1094 {
1095     auto A = 
1096            [ 9,  9,  9,
1097              8,  8,  8,
1098              7,  7,  7,
1099              6,  6,  6,
1100              5,  5,  5,
1101              4,  4,  4,
1102              3,  3,  3,
1103              2,  2,  2,
1104              1,  1,  1,
1105              0,  0,  0 ]
1106              .sliced(10, 3)
1107              .as!double.slice;
1108     auto ipiv = [ lapackint(10), 9, 8, 7, 6, 6, 7, 8, 9, 10 ].sliced(10);
1109     moveRows(A, ipiv);
1110 
1111     auto B = 
1112            [ 0,  0,  0,
1113              1,  1,  1,
1114              2,  2,  2,
1115              3,  3,  3,
1116              4,  4,  4,
1117              5,  5,  5,
1118              6,  6,  6,
1119              7,  7,  7,
1120              8,  8,  8,
1121              9,  9,  9 ]
1122              .sliced(10, 3)
1123              .as!double.slice;
1124 
1125     import mir.ndslice.algorithm: equal;
1126     assert(equal!((a, b) => fabs(a - b) < 1e-12)(B, A));
1127 }
1128 
1129 unittest
1130 {
1131     auto A = 
1132            [ 1,  1,  1,
1133              2,  2,  2,
1134              3,  3,  3,
1135              4,  4,  4,
1136              5,  5,  5,
1137              6,  6,  6,
1138              7,  7,  7,
1139              8,  8,  8,
1140              9,  9,  9,
1141              0,  0,  0 ]
1142              .sliced(10, 3)
1143              .as!double.slice;
1144     auto ipiv = [ lapackint(2), 3, 4, 5, 6, 7, 8, 9, 10, 10 ].sliced(10);
1145     moveRows(A, ipiv);
1146     
1147     auto B = 
1148            [ 0,  0,  0,
1149              1,  1,  1,
1150              2,  2,  2,
1151              3,  3,  3,
1152              4,  4,  4,
1153              5,  5,  5,
1154              6,  6,  6,
1155              7,  7,  7,
1156              8,  8,  8,
1157              9,  9,  9 ]
1158              .sliced(10, 3)
1159              .as!double.slice;
1160 
1161     import mir.ndslice.algorithm: equal;
1162     assert(equal!((a, b) => fabs(a - b) < 1e-12)(B, A));
1163 }
1164 
1165 ///LUResult consist lu factorization.
1166 struct LUResult(T)
1167 {
1168     /++
1169     Matrix in witch lower triangular is L part of factorization
1170     (diagonal elements of L are not stored), upper triangular
1171     is U part of factorization.
1172     +/
1173     Slice!(Canonical, [2], T*) lut;
1174     /++
1175     The pivot indices, for 1 <= i <= min(M,N), row i of the matrix
1176     was interchanged with row ipiv(i).
1177     +/
1178     Slice!(Contiguous, [1], lapackint*) ipiv;
1179     ///L part of the factorization.
1180     auto l() @property
1181     {
1182         import mir.ndslice.algorithm: eachUpper;
1183         auto l = lut.transposed[0..lut.length!1, 0..min(lut.length!0, lut.length!1)].slice.canonical;
1184         l.eachUpper!"a = 0";
1185         l.diagonal[] = 1;
1186         return l;
1187     }
1188     ///U part of the factorization.
1189     auto u() @property
1190     {
1191         import mir.ndslice.algorithm: eachLower;
1192         auto u = lut.transposed[0..min(lut.length!0, lut.length!1), 0..lut.length!0].slice.canonical;
1193         u.eachLower!"a = 0";
1194         return u;
1195     }
1196     auto solve(Flag!"allowDestroy" allowDestroy = No.allowDestroy,
1197                SliceKind kind, size_t[] n, Iterator)
1198               (Slice!(kind, n, Iterator) b,
1199                char trans = 'N')
1200     {
1201         return luSolve!(allowDestroy)(lut, ipiv, b, trans);
1202     }
1203 }
1204 
1205 /++
1206 Computes LU factorization of a general 'M x N' matrix 'A' using partial
1207 pivoting with row interchanges.
1208 The factorization has the form:
1209     \A = P * L * U
1210 Where P is a permutation matrix, L is lower triangular with unit
1211 diagonal elements (lower trapezoidal if m > n), and U is upper
1212 triangular (upper trapezoidal if m < n).
1213 Params:
1214     allowDestroy = flag to delete the source matrix.
1215     a = input 'M x N' matrix for factorization.
1216 Returns: $(LREF LUResalt)
1217 +/
1218 auto luDecomp(Flag!"allowDestroy" allowDestroy = No.allowDestroy,
1219               SliceKind kind, Iterator)
1220              (Slice!(kind, [2], Iterator) a)
1221 {
1222     alias T = BlasType!Iterator;
1223     auto ipiv = uninitSlice!lapackint(min(a.length!0, a.length!1));
1224     auto b = a.transposed;
1225     auto m = (allowDestroy && b._stride!1 == 1) ? b.assumeCanonical : a.transposed.as!T.slice.canonical;
1226     
1227     getrf(m, ipiv);
1228     return LUResult!T(m, ipiv);
1229 }
1230 
1231 /++
1232 Solves a system of linear equations
1233     \A * X = B, or
1234     \A**T * X = B
1235 with a general 'N x N' matrix 'A' using the LU factorization computed by luDecomp.
1236 Params:
1237     allowDestroy = flag to delete the source matrix.
1238     lut = factorization of matrix 'A', A = P * L * U.
1239     ipiv = the pivot indices from luDecomp.
1240     b = the right hand side matrix B.
1241     trans = specifies the form of the system of equations:
1242           = 'N': A * X = B (No transpose)
1243           = 'T': A**T * X = B (Transpose)
1244           = 'C': A**T * X = B (Conjugate transpose = Transpose)
1245 Returns:
1246     Return solve of the system linear equations.
1247 +/
1248 auto luSolve(Flag!"allowDestroy" allowDestroy = No.allowDestroy,
1249            SliceKind kindB, size_t[] n, IteratorB, IteratorLU)
1250           (Slice!(Canonical, [2], IteratorLU) lut,
1251            Slice!(Contiguous, [1], lapackint*) ipiv,
1252            Slice!(kindB, n, IteratorB) b,
1253            char trans = 'N'
1254           )
1255 in
1256 {
1257     assert(lut.length!0 == lut.length!1, "matrix must be squared");
1258     assert(ipiv.length == lut.length, "size of ipiv must be equal to the number of rows a");
1259     assert(lut.length!1 == b.length!0, "number of columns a should be equal to the number of rows b");
1260 }
1261 body
1262 {
1263     alias LU = BlasType!IteratorLU;
1264     alias B = BlasType!IteratorB;
1265     alias T = CommonType!(LU, B);
1266     static if(is(T* == IteratorLU))
1267         auto lut_ = lut;
1268     else
1269         auto lut_ = lut.as!T.slice.canonical;
1270     
1271     //convect vector to matrix.
1272     static if(n[0] == 1)
1273         auto k = b.sliced(1, b.length);
1274     else
1275         auto k = b.transposed;
1276 
1277     static if(is(IteratorB == T*))
1278         auto m = (allowDestroy && k._stride!1 == 1) ? k.assumeCanonical : k.as!T.slice.canonical;
1279     else
1280         auto m = k.as!T.slice.canonical;
1281     getrs!T(lut_, m, ipiv, trans);
1282     return m.transposed;
1283 }
1284 
1285 unittest
1286 {
1287     auto A =
1288         [ 1,  4, -3,  5,  6,
1289          -2,  8,  5,  7,  8,
1290           3,  4,  7,  9,  1,
1291           2,  4,  6,  3,  2,
1292           6,  8,  3,  5,  2 ]
1293             .sliced(5, 5)
1294             .as!double.slice
1295             .canonical;
1296     auto B =
1297         [ 1,  3,  4,  8,  0,  0,  0,
1298           2,  1,  7,  1,  0,  0,  0,
1299           3,  5,  7,  7,  0,  0,  0,
1300           4,  4,  9,  8,  0,  0,  0,
1301           5,  5,  8,  1,  0,  0,  0 ]
1302             .sliced(5, 7)
1303             .as!double.slice
1304             .universal;
1305 
1306     auto B_ = B[0..$, 0..4];
1307     auto LU = A.luDecomp();
1308     auto m = luSolve(LU.lut, LU.ipiv, B_);
1309 
1310     import std.math: approxEqual;
1311     import mir.ndslice.algorithm: equal;
1312     assert(equal!approxEqual(mtimes(A, m), B_));
1313 }
1314 
1315 ///
1316 unittest
1317 {
1318     auto A =
1319         [ 1,  4, -3,  5,  6,
1320          -2,  8,  5,  7,  8,
1321           3,  4,  7,  9,  1,
1322           2,  4,  6,  3,  2,
1323           6,  8,  3,  5,  2 ]
1324             .sliced(5, 5)
1325             .as!double.slice
1326             .canonical;
1327     
1328     import mir.random.variable;
1329     import mir.random.algorithm;
1330     auto B = randomSlice!double(uniformVar(-100, 100), 5, 100);
1331     
1332     auto LU = A.luDecomp();
1333     auto X = LU.solve(B);
1334 
1335     import mir.ndslice.algorithm: equal;
1336     assert(equal!((a, b) => fabs(a - b) < 1e-12)(mtimes(A, X), B));
1337 }
1338 
1339 unittest
1340 {
1341     auto A =
1342         [ 1,  4, -3,  5,  6,
1343          -2,  8,  5,  7,  8,
1344           3,  4,  7,  9,  1,
1345           2,  4,  6,  3,  2,
1346           6,  8,  3,  5,  2 ]
1347             .sliced(5, 5)
1348             .as!double.slice
1349             .canonical;
1350     auto B =
1351         [ 2,  8,  3,  5,  8,
1352           8,  1,  4,  9,  86,
1353           1,  6,  7,  1,  67,
1354           6,  1,  5,  4,  45,
1355           1,  2,  3,  1,  11 ]
1356             .sliced(5, 5)
1357             .as!double.slice
1358             .universal;
1359     auto C = B.slice;
1360 
1361     auto LU = A.luDecomp();
1362     auto m = luSolve!(Yes.allowDestroy)(LU.lut, LU.ipiv, B.transposed);
1363     auto m2 = LU.solve(C);
1364 
1365     import std.math: approxEqual;
1366     import mir.ndslice.algorithm: equal;
1367     assert(equal!approxEqual(mtimes(A, m), C.transposed));
1368     assert(equal!approxEqual(mtimes(A, m2), C));
1369 }
1370 
1371 unittest
1372 {
1373     auto A =
1374         [ 1,  4, -3,  5,  6,
1375          -2,  8,  5,  7,  8,
1376           3,  4,  7,  9,  1,
1377           2,  4,  6,  3,  2,
1378           6,  8,  3,  5,  2 ]
1379             .sliced(5, 5)
1380             .as!float.slice
1381             .canonical;
1382     auto B = [ 1,  2,  3,  4,  5 ].sliced(5).as!double.slice;
1383     auto C = B.slice.sliced(5, 1);
1384 
1385     auto LU = A.luDecomp();
1386     auto m = luSolve!(Yes.allowDestroy)(LU.lut, LU.ipiv, B);
1387 
1388     import std.math: approxEqual;
1389     import mir.ndslice.algorithm: equal;
1390     assert(equal!approxEqual(mtimes(A, m), C));
1391 }
1392 
1393 unittest
1394 {
1395     auto A =
1396         [ 1,  4, -3,  5,  6,
1397          -2,  8,  5,  7,  8,
1398           3,  4,  7,  9,  1,
1399           2,  4,  6,  3,  2,
1400           6,  8,  3,  5,  2 ]
1401             .sliced(5, 5)
1402             .as!double.slice
1403             .canonical;
1404     auto B = [ 1,  15,  4,  5,  8,
1405                3,  20,  1,  9,  11 ].sliced(5, 2).as!float.slice;
1406 
1407     auto LU = A.luDecomp();
1408     auto m = luSolve(LU.lut, LU.ipiv, B);
1409 
1410     import std.math: approxEqual;
1411     import mir.ndslice.algorithm: equal;
1412     assert(equal!approxEqual(mtimes(A, m), B));
1413 }
1414 
1415 unittest
1416 {
1417     auto A =
1418         [ 11,  14, -31,  53,  62,
1419          -92,  83,  52,  74,  83,
1420           31,  45,  73,  96,  17,
1421           23,  14,  65,  35,  26,
1422           62,  28,  34,  51,  25 ]
1423             .sliced(5, 5)
1424             .as!float.slice
1425             .universal;
1426     auto B =
1427         [ 6,  1,  3,  1,  11,
1428          12,  5,  7,  6,  78,
1429           8,  4,  1,  5,  54,
1430           3,  1,  8,  1,  45,
1431           1,  6,  8,  6,  312 ]
1432             .sliced(5, 5)
1433             .as!double.slice;
1434     auto B2 = B.slice;
1435     auto C = B.slice;
1436 
1437     auto LU = luDecomp(A.transposed);
1438     auto m = luSolve!(Yes.allowDestroy)(LU.lut, LU.ipiv, B, 'T');
1439     auto m2 = luSolve!(Yes.allowDestroy)(LU.lut, LU.ipiv, B2);
1440 
1441     import std.math: approxEqual;
1442     import mir.ndslice.algorithm: equal;
1443     assert(equal!approxEqual(mtimes(A, m), C));
1444     assert(equal!approxEqual(mtimes(A.transposed, m2), C));
1445 }
1446 
1447 unittest
1448 {
1449     auto A =
1450         [ 54,  93,  14,  44,  33,
1451           51,  85,  28,  81,  75,
1452           89,  17,  15,  44,  58,
1453           75,  80,  18,  35,  14,
1454           21,  48,  72,  21,  88 ]
1455             .sliced(5, 5)
1456             .as!double.slice
1457             .universal;
1458     auto B =
1459         [ 5,  7,  8,  3,  78,
1460           1,  2,  5,  4,  5,
1461           2,  4,  1,  5,  15,
1462           1,  1,  4,  1,  154,
1463           1,  3,  1,  8,  17 ]
1464             .sliced(5, 5)
1465             .as!float.slice
1466             .canonical;
1467 
1468     auto LU = A.luDecomp();
1469     auto m = luSolve(LU.lut, LU.ipiv, B);
1470 
1471     import std.math: approxEqual;
1472     import mir.ndslice.algorithm: equal;
1473     assert(equal!approxEqual(mtimes(A, m), B));
1474 }
1475 
1476 unittest
1477 {
1478     
1479     auto B =
1480         [ 1,  4, -3,
1481          -2,  8,  5,
1482           3,  4,  7,
1483           2,  4,  6 ]
1484             .sliced(4, 3)
1485             .as!double.slice;
1486 
1487     auto LU = B.luDecomp!(Yes.allowDestroy)();
1488     LU.l; LU.u;
1489     auto res = mtimes(LU.l, LU.u);
1490     moveRows(res, LU.ipiv);
1491 
1492     import mir.ndslice.algorithm: equal;
1493     import std.math: approxEqual;
1494     assert(res.equal!approxEqual(B));
1495 }
1496 
1497 ///
1498 unittest
1499 {
1500     auto B =
1501         [ 3, -7, -2,  2,
1502          -3,  5,  1,  0,
1503           6, -4,  0, -5,
1504          -9,  5, -5, 12 ]
1505             .sliced(4, 4)
1506             .as!double.slice
1507             .canonical;
1508     auto C = B.transposed.slice;
1509 
1510     auto LU = B.transposed.luDecomp!(Yes.allowDestroy)();
1511     auto res = mtimes(LU.l, LU.u);
1512     moveRows(res, LU.ipiv);
1513 
1514     import mir.ndslice.algorithm: equal;
1515     import std.math: approxEqual;
1516     assert(res.equal!approxEqual(C));
1517 }
1518 
1519 ///Consist LDL factorization;
1520 struct LDLResult(T)
1521 {
1522     /++
1523     Matrix in witch lower triangular matrix is 'L' part of
1524     factorization, diagonal is 'D' part.
1525     +/
1526     Slice!(Canonical, [2], T*) matrix;
1527     /++
1528     The pivot indices.
1529     If ipiv(k) > 0, then rows and columns k and ipiv(k) were
1530     interchanged and D(k, k) is a '1 x 1' diagonal block.
1531     If ipiv(k) = ipiv(k + 1) < 0, then rows and columns k+1 and
1532     -ipiv(k) were interchanged and D(k:k+1, k:k+1) is a '2 x 2'
1533     diagonal block.
1534     +/
1535     Slice!(Contiguous, [1], lapackint*) ipiv;
1536     /++
1537     uplo = 'U': Upper triangle is stored;
1538          = 'L': lower triangle is stored.
1539     +/
1540     char uplo;
1541     /++
1542     Return solves a system of linear equations
1543         \A * X = B,
1544     using LDL factorization.
1545     +/
1546     auto solve(Flag!"allowDestroy" allowDestroy = No.allowDestroy,
1547                SliceKind kindB, size_t[] n, IteratorB)
1548               (Slice!(kindB, n, IteratorB) b)
1549     {
1550         return ldlSolve!(allowDestroy)(matrix, ipiv, b, uplo);
1551     }
1552 }
1553 
1554 /++
1555 Computes the factorization of a real symmetric matrix A using the
1556 Bunch-Kaufman diagonal pivoting method.
1557 The for of the factorization is:
1558     \A = L*D*L**T
1559 Where L is product if permutation and unit lower triangular matrices,
1560 and D is symmetric and block diagonal with '1 x 1' and '2 x 2'
1561 diagonal blocks.
1562 Params:
1563     allowDestroy = flag to delete the source matrix.
1564     a = input symmetric 'n x n' matrix for factorization.
1565     uplo = 'U': Upper triangle is stored;
1566          = 'L': lower triangle is stored.
1567 Returns:$(LREF LDLResult)
1568 +/
1569 auto ldlDecomp(Flag!"allowDestroy" allowDestroy = No.allowDestroy,
1570                SliceKind kind, Iterator)
1571               (Slice!(kind, [2], Iterator) a,
1572                char uplo = 'L')
1573 in
1574 {
1575     assert(a.length!0 == a.length!1, "matrix must be squared");
1576 }
1577 body
1578 {
1579     alias T = BlasType!Iterator;
1580     auto work = [T.sizeof * a.length].uninitSlice!T;
1581     auto ipiv = a.length.uninitSlice!lapackint;
1582     auto m = (allowDestroy && a._stride!1 == 1) ? a.assumeCanonical : a.transposed.as!T.slice.canonical;
1583 
1584     sytrf!T(m, ipiv, work, uplo);
1585     return LDLResult!T(m, ipiv, uplo);
1586 }
1587 
1588 /++
1589 Solves a system of linear equations \A * X = B with symmetric matrix 'A' using the
1590 factorization
1591 \A = U * D * U**T, or
1592 \A = L * D * L**T
1593 computed by ldlDecomp.
1594 Params:
1595     allowDestroy = flag to delete the source matrix.
1596     a = 'LD' or 'UD' matrix computed by ldlDecomp.
1597     ipiv = details of the interchanges and the block structure of D as determined by ldlDecomp.
1598     b = the right hand side matrix.
1599     uplo = specifies whether the details of the factorization are stored as an upper or
1600            lower triangular matrix:
1601          = 'U': Upper triangular, form is \A = U * D * U**T;
1602          = 'L': Lower triangular, form is \A = L * D * L**T.
1603 Returns:
1604     The solution matrix.
1605 +/
1606 auto ldlSolve(Flag!"allowDestroy" allowDestroy = No.allowDestroy,
1607               SliceKind kindB, size_t[] n, IteratorB, IteratorA)
1608              (Slice!(Canonical, [2], IteratorA) a,
1609               Slice!(Contiguous, [1], lapackint*) ipiv,
1610               Slice!(kindB, n, IteratorB) b,
1611               char uplo = 'L'
1612              )
1613 in
1614 {
1615     assert(a.length!0 == a.length!1, "matrix must be squared");
1616     assert(ipiv.length == a.length, "size of ipiv must be equal to the number of rows a");
1617     assert(a.length!1 == b.length!0, "number of columns a should be equal to the number of rows b");
1618 }
1619 body
1620 {
1621     alias A = BlasType!IteratorA;
1622     alias B = BlasType!IteratorB;
1623     alias T = CommonType!(A, B);
1624     static if(is(T* == IteratorA))
1625         auto a_ = a;
1626     else
1627         auto a_ = a.as!T.slice.canonical;
1628     
1629     //convect vector to matrix.
1630     static if(n[0] == 1)
1631         auto k = b.sliced(1, b.length);
1632     else
1633         auto k = b.transposed;
1634 
1635     auto work = [T.sizeof * a.length].uninitSlice!T;
1636     static if(is(IteratorB == T*))
1637         auto m = (allowDestroy && k._stride!1 == 1) ? k.assumeCanonical : k.as!T.slice.canonical;
1638     else
1639         auto m = k.as!T.slice.canonical;
1640     sytrs2!T(a_, m, ipiv, work, uplo);
1641     return m.transposed;
1642 }
1643 
1644 ///
1645 unittest
1646 {
1647     auto A =
1648         [ 2.07,  3.87,  4.20, -1.15,
1649           3.87, -0.21,  1.87,  0.63,
1650           4.20,  1.87,  1.15,  2.06,
1651          -1.15,  0.63,  2.06, -1.81 ]
1652             .sliced(4, 4)
1653             .as!double.slice
1654             .canonical;
1655 
1656     import mir.random.variable;
1657     import mir.random.algorithm;
1658     auto B = randomSlice!double(uniformVar(-100, 100), 4, 100);
1659 
1660     auto LDL = A.ldlDecomp('L');
1661     auto X = LDL.solve(B);
1662 
1663     import std.math: approxEqual;
1664     import mir.ndslice.algorithm: equal;
1665     assert(equal!approxEqual(mtimes(A, X), B));
1666 }
1667 
1668 unittest
1669 {
1670     auto A =
1671         [ 9, -1,  2,
1672          -1,  8, -5,
1673           2, -5,  7 ]
1674             .sliced(3, 3)
1675             .as!float.slice
1676             .canonical;
1677     auto A_ = A.slice;
1678 
1679     auto B =
1680         [ 5,  7,  1,
1681           1,  8,  5,
1682           9,  3,  2 ]
1683           .sliced(3, 3)
1684           .as!double.slice
1685           .canonical;
1686     auto B_ = B.slice;
1687 
1688     auto LDL = A.ldlDecomp!(Yes.allowDestroy)('L');
1689     auto X = ldlSolve!(Yes.allowDestroy)(A, LDL.ipiv, B.transposed, LDL.uplo);
1690 
1691     import std.math: approxEqual;
1692     import mir.ndslice.algorithm: equal;
1693     assert(equal!approxEqual(mtimes(A_, X), B_.transposed));
1694 }
1695 
1696 unittest
1697 {
1698     auto A =
1699         [10, 20, 30,
1700          20, 45, 80,
1701          30, 80, 171 ]
1702             .sliced(3, 3)
1703             .as!double.slice
1704             .canonical;
1705     auto B = [ 1, 4, 7 ].sliced(3).as!float.slice.canonical;
1706     auto B_ = B.sliced(3, 1);
1707 
1708     auto LDL = A.ldlDecomp('L');
1709     auto X = LDL.solve(B);
1710     
1711     import std.math: approxEqual;
1712     import mir.ndslice.algorithm: equal;
1713     assert(equal!approxEqual(mtimes(A, X), B_));
1714 }
1715 
1716 struct choleskyResult(T)
1717 {
1718     /++
1719     If uplo = 'L': lower triangle of 'matrix' is stored.
1720     If uplo = 'U': upper triangle of 'matrix' is stored.
1721     +/
1722     char uplo;
1723     /++
1724     if uplo = Lower, the leading 'N x N' lower triangular part of A
1725     contains the lower triangular part of the matrix A, and the
1726     strictly upper triangular part if A is not referenced.
1727     if uplo = Upper, the leading 'N x N' upper triangular part of A
1728     contains the upper triangular part of the matrix A, and the
1729     strictly lower triangular part if A is not referenced.
1730     +/
1731     Slice!(Canonical, [2], T*) matrix;
1732     /++
1733     Return solves a system of linear equations
1734         \A * X = B,
1735     using Cholesky factorization.
1736     +/
1737     auto solve(Flag!"allowDestroy" allowDestroy = No.allowDestroy,
1738                SliceKind kind, size_t[] n, Iterator)
1739               (Slice!(kind, n, Iterator) b)
1740     {
1741         return choleskySolve!(allowDestroy)(matrix, b, uplo);
1742     }
1743 }
1744 /++
1745 Computs Cholesky decomposition of symmetric positive definite matrix 'A'.
1746 The factorization has the form:
1747     \A = U**T * U, if UPLO = Upper, or
1748     \A = L * L**T, if UPLO = Lower
1749 Where U is an upper triangular matrix and L is lower triangular.
1750 Params:
1751     allowDestroy = flag to delete the source matrix.
1752     a = symmetric 'N x N' matrix.
1753     uplo = if uplo is Upper, then upper triangle of A is stored, else
1754     lower.
1755 Returns: $(LREF choleskyResult)
1756 +/
1757 auto choleskyDecomp(Flag!"allowDestroy" allowDestroy = No.allowDestroy,
1758               SliceKind kind, Iterator)
1759              (Slice!(kind, [2], Iterator) a,
1760               char uplo)
1761 in
1762 {
1763     assert(a.length!0 == a.length!1, "matrix must be squared");
1764 }
1765 body
1766 {
1767     alias T = BlasType!Iterator;
1768     auto m = (allowDestroy && a._stride!1 == 1) ? a.assumeCanonical : a.as!T.slice.canonical;
1769     potrf!T(m, uplo);
1770     return choleskyResult!T(uplo, m);
1771 }
1772 
1773 /++
1774     Solves a system of linear equations A * X = B with a symmetric matrix A using the
1775     Cholesky factorization:
1776     \A = U**T * U or
1777     \A = L * L**T
1778     computed by choleskyDecomp.
1779 Params:
1780     allowDestroy = flag to delete the source matrix.
1781     c = the triangular factor 'U' or 'L' from the Cholesky factorization
1782         \A = U**T * U or
1783         \A = L * L**T,
1784     as computed by choleskyDecomp.
1785     b = the right hand side matrix.
1786     uplo = 'U': Upper triangle of A is stored;
1787          = 'L': Lower triangle of A is stored.
1788 Returns:
1789     The solution matrix X.
1790 +/
1791 auto choleskySolve(Flag!"allowDestroy" allowDestroy = No.allowDestroy,
1792                    SliceKind kindB, size_t[] n, IteratorB, IteratorC)
1793                   (Slice!(Canonical, [2], IteratorC) c,
1794                    Slice!(kindB, n, IteratorB) b,
1795                    char uplo)
1796 in
1797 {
1798     assert(c.length!0 == c.length!1, "matrix must be squared");
1799     assert(c.length!1 == b.length!0, "number of columns a should be equal to the number of rows b");
1800 }
1801 body
1802 {
1803     alias B = BlasType!IteratorB;
1804     alias C = BlasType!IteratorC;
1805     alias T = CommonType!(B, C);
1806     static if(is(T* == IteratorC))
1807         auto c_ = c;
1808     else
1809         auto c_ = c.as!T.slice.canonical;
1810 
1811     //convect vector to matrix.
1812     static if(n[0] == 1)
1813         auto k = b.sliced(1, b.length);
1814     else
1815         auto k = b.transposed;
1816 
1817     static if(is(IteratorB == T*))
1818         auto m = (allowDestroy && k._stride!1 == 1) ? k.assumeCanonical : k.as!T.slice.canonical;
1819     else
1820         auto m = k.as!T.slice.canonical;
1821     potrs!T(c_, m, uplo);
1822     return m.transposed;
1823 }
1824 
1825 ///
1826 unittest
1827 {
1828     auto A =
1829            [ 25, 15, -5,
1830              15, 18,  0,
1831              -5,  0, 11 ]
1832              .sliced(3, 3)
1833              .as!double.slice;
1834     
1835     import mir.random.variable;
1836     import mir.random.algorithm;
1837     auto B = randomSlice!double(uniformVar(-100, 100), 3, 100);
1838 
1839     auto C = A.choleskyDecomp('L');
1840     auto X = C.solve(B);
1841 
1842     import std.math: approxEqual;
1843     import mir.ndslice.algorithm: equal;
1844     assert(equal!approxEqual(mtimes(A, X), B));
1845 }
1846 
1847 unittest
1848 {
1849     auto A =
1850             [ 1,  1,  3,
1851               1,  5,  5,
1852               3,  5, 19 ]
1853              .sliced(3, 3)
1854              .as!double.slice
1855              .universal;
1856     auto B = [ 10,  157,  80 ].sliced(3).as!float.slice;
1857     auto C_ = B.slice.sliced(3, 1);
1858 
1859     auto C = A.choleskyDecomp('U');
1860     auto X = choleskySolve!(Yes.allowDestroy)(C.matrix, B, C.uplo);
1861 
1862     import std.math: approxEqual;
1863     import mir.ndslice.algorithm: equal;
1864     assert(equal!approxEqual(mtimes(A, X), C_));
1865 }
1866 
1867 unittest
1868 {
1869     auto A =
1870             [ 6,  15,  55,
1871              15,  55, 225,
1872              55, 225, 979 ]
1873              .sliced(3, 3)
1874              .as!float.slice
1875              .canonical;
1876     auto B =
1877             [ 7,  3,
1878               2,  1,
1879               1,  8 ]
1880               .sliced(3, 2)
1881               .as!double.slice
1882               .universal;
1883 
1884     auto C = A.choleskyDecomp('L');
1885     auto X = choleskySolve(C.matrix, B, C.uplo);
1886 
1887     import std.math: approxEqual;
1888     import mir.ndslice.algorithm: equal;
1889     assert(equal!approxEqual(mtimes(A, X), B));
1890 }
1891 
1892 
1893 ///
1894 struct QRResult(T)
1895 {
1896     /++
1897     Matrix in witch the elements on and above the diagonal of the array contain the min(M, N) x N
1898     upper trapezoidal matrix 'R' (R is upper triangular if m >= n). The elements below the
1899     diagonal, with the array tau, represent the orthogonal matrix 'Q' as product of min(m, n).
1900     +/
1901     Slice!(Canonical, [2], T*) matrix;
1902     ///The scalar factors of the elementary reflectors
1903     Slice!(Contiguous, [1], T*) tau;
1904     /++
1905     Solve the least squares problem:
1906         \min ||A * X - B||
1907     Using the QR factorization:
1908         \A = Q * R
1909     computed by qrDecomp.
1910     +/
1911     auto solve(Flag!"allowDestroy" allowDestroy = No.allowDestroy,
1912                SliceKind kind, size_t[] n, Iterator)
1913               (Slice!(kind, n, Iterator) b)
1914     {
1915         return qrSolve!(allowDestroy)(matrix, tau, b);
1916     }
1917 }
1918 
1919 /++
1920 Computes a QR factorization of matrix 'a'.
1921 Params:
1922     allowDestroy = flag to delete the source matrix.
1923     a = initial matrix
1924 Returns: $(LREF QRResult)
1925 +/
1926 auto qrDecomp(Flag!"allowDestroy" allowDestroy = No.allowDestroy,
1927               SliceKind kind, Iterator)
1928              (Slice!(kind, [2], Iterator) a)
1929 {
1930     alias T = BlasType!Iterator;
1931     auto work = [T.sizeof * a.length].uninitSlice!T;
1932     auto tau = (cast(int) min(a.length!0, a.length!1)).uninitSlice!T;
1933     auto m = (allowDestroy && a._stride!1 == 1) ? a.assumeCanonical : a.transposed.as!T.slice.canonical;
1934 
1935     geqrf!T(m, tau, work);
1936     return QRResult!T(m, tau);
1937 }
1938 
1939 /++
1940 Solve the least squares problem:
1941     \min ||A * X - B||
1942 Using the QR factorization:
1943     \A = Q * R
1944 computed by qrDecomp.
1945 Params:
1946     allowDestroy = flag to delete the source matrix.
1947     a = detalis of the QR factorization of the original matrix as returned by qrDecomp.
1948     tau = details of the orhtogonal matrix Q.
1949     b = right hand side matrix.
1950 Returns: solution matrix.
1951 +/
1952 auto qrSolve(Flag!"allowDestroy" allowDestroy = No.allowDestroy,
1953              SliceKind kindB, size_t[] n, IteratorB, IteratorA, IteratorT)
1954             (Slice!(Canonical, [2], IteratorA) a,
1955              Slice!(Contiguous, [1], IteratorT) tau,
1956              Slice!(kindB, n, IteratorB) b
1957             )
1958 in
1959 {
1960     assert(a.length!0 == a.length!1, "matrix must be squared");
1961     assert(a.length!1 == b.length!0, "number of columns a should be equal to the number of rows b");
1962 }
1963 body
1964 {
1965     alias A = BlasType!IteratorA;
1966     alias B = BlasType!IteratorB;
1967     alias T = CommonType!(A, B);
1968     static if(is(T* == IteratorA))
1969         auto a_ = a;
1970     else
1971         auto a_ = a.as!T.slice.canonical;
1972     static if(is(T* == IteratorT))
1973         auto tau_ = tau;
1974     else
1975         auto tau_ = tau.as!T.slice.canonical;
1976 
1977     //convect vector to matrix.
1978     static if(n[0] == 1)
1979         auto k = b.sliced(1, b.length);
1980     else
1981         auto k = b.transposed;
1982 
1983     static if(is(IteratorB == T*))
1984         auto m = (allowDestroy && k._stride!1 == 1) ? k.assumeCanonical : k.as!T.slice.canonical;
1985     else
1986         auto m = k.as!T.slice.canonical;
1987     auto work = [m.length!0].uninitSlice!T;
1988     static if(is(T == double) || is(T == float))
1989         ormqr!T(a_, tau_, m, work, 'L', 'T');
1990     else
1991         unmqr!T(a_, tau_, m, work, 'L', 'C');
1992     trsm!T(Side.Right, Uplo.Lower, Diag.NonUnit, cast(T) 1.0, a_, m);
1993 
1994     return m.transposed;
1995 }
1996 
1997 ///
1998 unittest
1999 {
2000     auto A =
2001             [ 3,  1, -1,  2,
2002              -5,  1,  3, -4,
2003               2,  0,  1, -1,
2004               1, -5,  3, -3 ]
2005               .sliced(4, 4)
2006               .as!double.slice;
2007 
2008     import mir.random.variable;
2009     import mir.random.algorithm;
2010     auto B = randomSlice!double(uniformVar(-100, 100), 4, 100);
2011 
2012     auto C = qrDecomp(A);
2013     auto X = C.solve(B);
2014 
2015     import std.math: approxEqual;
2016     import mir.ndslice.algorithm: equal;
2017     assert(equal!approxEqual(mtimes(A, X), B));
2018 }
2019 
2020 
2021 unittest
2022 {
2023     auto A =
2024             [ 3,  1, -1,  2,
2025              -5,  1,  3, -4,
2026               2,  0,  1, -1,
2027               1, -5,  3, -3 ]
2028               .sliced(4, 4)
2029               .as!float.slice;
2030     auto B = [ 6, -12, 1, 3 ].sliced(4).as!double.slice.canonical;
2031     auto B_ = B.slice.sliced(B.length, 1);
2032     auto C = qrDecomp(A);
2033     auto X = qrSolve(C.matrix, C.tau, B);
2034 
2035     import std.math: approxEqual;
2036     import mir.ndslice.algorithm: equal;
2037     assert(equal!approxEqual(mtimes(A, X), B_));
2038 }
2039 
2040 unittest
2041 {
2042     auto A =
2043             [ 1,  1,  0,
2044               1,  0,  1,
2045               0,  1,  1 ]
2046               .sliced(3, 3)
2047               .as!double.slice;
2048 
2049     auto B =
2050             [ 7,  6,  98,
2051               4,  8,  17,
2052               5,  3,  24 ]
2053               .sliced(3, 3)
2054               .as!float.slice;
2055     auto C = qrDecomp(A);
2056     auto X = qrSolve(C.matrix, C.tau, B);
2057 
2058     import std.math: approxEqual;
2059     import mir.ndslice.algorithm: equal;
2060     
2061     assert(equal!approxEqual(mtimes(A, X), B));
2062 }
2063 
2064 unittest
2065 {
2066     auto A =
2067             [ 1,  1,  0,
2068               1,  0,  1,
2069               0,  1,  1 ]
2070               .sliced(3, 3)
2071               .as!cdouble.slice;
2072 
2073     auto B =
2074             [ 15,  78,  11,
2075               21,  47,  71,
2076               81,  11,  81 ]
2077               .sliced(3, 3)
2078               .as!cfloat.slice;
2079     auto C = qrDecomp(A);
2080     auto X = qrSolve(C.matrix, C.tau, B);
2081     auto res = mtimes(A, X);
2082 
2083     import mir.ndslice.algorithm: equal;
2084     assert(equal!((a, b) => fabs(a - b) < 1e-12)(res, B));
2085 }