1 /++
2 $(H1 Lubeck 2 - `@nogc` Linear Algebra)
3 +/
4 module lubeck2;
5 
6 import mir.algorithm.iteration: equal, each;
7 import mir.blas;
8 import mir.exception;
9 import mir.internal.utility: isComplex, realType;
10 import mir.lapack;
11 import mir.math.common: sqrt, approxEqual, pow, fabs;
12 import mir.ndslice;
13 import mir.rc.array;
14 import mir.utility: min, max;
15 import std.traits: isFloatingPoint, Unqual;
16 import std.typecons: Flag, Yes, No;
17 
18 /++
19 Identity matrix.
20 
21 Params:
22 n = number of columns
23 m = optional number of rows, default n
24 Results:
25 Matrix which is 1 on the diagonal and 0 elsewhere
26 +/
27 @safe pure nothrow @nogc
28 Slice!(RCI!T, 2) eye(T = double)(
29     size_t n,
30     size_t m = 0
31 )
32     if (isFloatingPoint!T || isComplex!T)
33 in
34 {
35     assert(n>0);
36     assert(m>=0);
37 }
38 out (i)
39 {
40     assert(i.length!0==n);
41     assert(i.length!1== (m==0 ? n : m));
42 }
43 do
44 {
45     auto c = rcslice!T([n, (m==0?n:m)], cast(T)0);
46     c.diagonal[] = cast(T)1;
47     return c;
48 }
49 
50 /// Real numbers
51 @safe pure nothrow
52 unittest
53 {
54     import mir.ndslice;
55     import mir.math;
56 
57     assert(eye(1)== [
58         [1]]);
59     assert(eye(2)== [
60         [1, 0],
61         [0, 1]]);
62     assert(eye(3)== [
63         [1, 0, 0],
64         [0, 1, 0],
65         [0, 0, 1]]);
66     assert(eye(1,2) == [
67         [1,0]]);
68     assert(eye(2,1) == [
69         [1],
70         [0]]);
71 }
72 
73 /++
74 General matrix-matrix multiplication. Allocates result to using Mir refcounted arrays.
75 Params:
76 a = m(rows) x k(cols) matrix
77 b = k(rows) x n(cols) matrix
78 Result: 
79 m(rows) x n(cols)
80 +/
81 @safe pure nothrow @nogc
82 Slice!(RCI!T, 2) mtimes(T, SliceKind kindA, SliceKind kindB)(
83     Slice!(const(T)*, 2, kindA) a,
84     Slice!(const(T)*, 2, kindB) b
85 )
86     if (isFloatingPoint!T || isComplex!T)
87 in
88 {
89     assert(a.length!1 == b.length!0);
90 }
91 out (c)
92 {
93     assert(c.length!0 == a.length!0);
94     assert(c.length!1 == b.length!1);
95 }
96 do
97 {
98     // optimisations for spcecial cases can be added in the future
99     auto c = mininitRcslice!T(a.length!0, b.length!1);
100     gemm(cast(T)1, a, b, cast(T)0, c.lightScope);
101     return c;
102 }
103 
104 /// ditto
105 @safe pure nothrow @nogc
106 Slice!(RCI!(Unqual!A), 2) mtimes(A, B, SliceKind kindA, SliceKind kindB)(
107     auto ref const Slice!(RCI!A, 2, kindA) a,
108     auto ref const Slice!(RCI!B, 2, kindB) b
109 )
110     if (is(Unqual!A == Unqual!B))
111 in
112 {
113     assert(a.length!1 == b.length!0);
114 }
115 do
116 {
117     auto scopeA = a.lightScope.lightConst;
118     auto scopeB = b.lightScope.lightConst;
119     return .mtimes(scopeA, scopeB);
120 }
121 
122 @safe pure nothrow @nogc
123 Slice!(RCI!(Unqual!A), 2) mtimes(A, B, SliceKind kindA, SliceKind kindB)(
124     auto ref const Slice!(RCI!A, 2, kindA) a,
125     Slice!(const(B)*, 2, kindB) b
126 )
127 if (is(Unqual!A == Unqual!B))
128 in
129 {
130     assert(a.length!1 == b.length!0);
131 }
132 do
133 {
134     auto scopeA = a.lightScope.lightConst;
135     return .mtimes(scopeA, b);
136 }
137 
138 @safe pure nothrow @nogc
139 Slice!(RCI!(Unqual!A), 2) mtimes(A, B, SliceKind kindA, SliceKind kindB)(
140     Slice!(const(A)*, 2, kindA) a,
141     auto ref const Slice!(RCI!B, 2, kindB) b
142 )
143 if (is(Unqual!A == Unqual!B))
144 in
145 {
146     assert(a.length!1 == b.length!0);
147 }
148 do
149 {
150     auto scopeB = b.lightScope.lightConst;
151     return .mtimes(a, scopeB);
152 }
153 /// Real numbers
154 @safe pure nothrow
155 unittest
156 {
157     import mir.ndslice;
158     import mir.math;
159 
160     auto a = mininitRcslice!double(3, 5);
161     auto b = mininitRcslice!double(5, 4);
162 
163     a[] =
164     [[-5,  1,  7, 7, -4],
165      [-1, -5,  6, 3, -3],
166      [-5, -2, -3, 6,  0]];
167 
168     b[] =
169     [[-5, -3,  3,  1],
170      [ 4,  3,  6,  4],
171      [-4, -2, -2,  2],
172      [-1,  9,  4,  8],
173      [ 9,  8,  3, -2]];
174 
175     assert(mtimes!(double, double)(a, b) ==
176         [[-42,  35,  -7, 77],
177          [-69, -21, -42, 21],
178          [ 23,  69,   3, 29]]);
179 }
180 
181 /// Complex numbers
182 @safe pure nothrow
183 unittest
184 {
185     import mir.ndslice;
186     import mir.math;
187 
188     auto a = mininitRcslice!cdouble(3, 5);
189     auto b = mininitRcslice!cdouble(5, 4);
190 
191     a[] =
192     [[-5 + 0i,  1,  7, 7, -4],
193      [-1 + 0i, -5,  6, 3, -3],
194      [-5 + 0i, -2, -3, 6,  0]];
195 
196     b[] =
197     [[-5 + 0i, -3,  3,  1],
198      [ 4 + 0i,  3,  6,  4],
199      [-4 + 0i, -2, -2,  2],
200      [-1 + 0i,  9,  4,  8],
201      [ 9 + 0i, 8,  3, -2]];
202 
203     assert(mtimes!(cdouble, cdouble)(a, b) ==
204         [[-42,  35,  -7, 77],
205          [-69, -21, -42, 21],
206          [ 23,  69,   3, 29]]);
207 }
208 
209 /++
210 Solve systems of linear equations AX = B for X.
211 Computes minimum-norm solution to a linear least squares problem
212 if A is not a square matrix.
213 +/
214 @safe pure @nogc
215 Slice!(RCI!T, 2) mldivide (T, SliceKind kindA, SliceKind kindB)(
216     Slice!(const(T)*, 2, kindA) a,
217     Slice!(const(T)*, 2, kindB) b,
218 )
219     if (isFloatingPoint!T || isComplex!T)
220 {
221     enforce!"mldivide: parameter shapes mismatch"(a.length!0 == b.length!0);
222 
223     auto rcat = a.transposed.as!T.rcslice;
224     auto at = rcat.lightScope.canonical;
225     auto rcbt = b.transposed.as!T.rcslice;
226     auto bt = rcbt.lightScope.canonical;
227     size_t info;
228     if (a.length!0 == a.length!1)
229     {
230         auto rcipiv = at.length.mininitRcslice!lapackint;
231         auto ipiv = rcipiv.lightScope;
232         foreach(i; 0 .. ipiv.length)
233             ipiv[i] = 0;
234         info = gesv!T(at, ipiv, bt);
235         //info > 0 means some diagonla elem of U is 0 so no solution
236     }
237     else
238     {
239         static if(!isComplex!T)
240         {
241             size_t liwork = void;
242             auto lwork = gelsd_wq(at, bt, liwork);
243             auto rcs = min(at.length!0, at.length!1).mininitRcslice!T;
244             auto s = rcs.lightScope;
245             auto rcwork = lwork.rcslice!T;
246             auto work = rcwork.lightScope;
247             auto rciwork = liwork.rcslice!lapackint;
248             auto iwork = rciwork.lightScope;
249             size_t rank = void;
250             T rcond = -1;
251 
252             info = gelsd!T(at, bt, s, rcond, rank, work, iwork);
253             //info > 0 means that many components failed to converge
254         }
255         else
256         {
257             size_t liwork = void;
258             size_t lrwork = void;
259             auto lwork = gelsd_wq(at, bt, lrwork, liwork);
260             auto rcs = min(at.length!0, at.length!1).mininitRcslice!(realType!T);
261             auto s = rcs.lightScope;
262             auto rcwork = lwork.rcslice!T;
263             auto work = rcwork.lightScope;
264             auto rciwork = liwork.rcslice!lapackint;
265             auto iwork = rciwork.lightScope;
266             auto rcrwork = lrwork.rcslice!(realType!T);
267             auto rwork = rcrwork.lightScope;
268             size_t rank = void;
269             realType!T rcond = -1;
270 
271             info = gelsd!T(at, bt, s, rcond, rank, work, rwork, iwork);
272             //info > 0 means that many components failed to converge
273         }
274         bt = bt[0 .. $, 0 .. at.length!0];
275     }
276     enforce!"mldivide: some off-diagonal elements of an intermediate bidiagonal form did not converge to zero."(!info);
277     return bt.transposed.as!T.rcslice;
278 }
279 
280 /// ditto
281 @safe pure @nogc
282 Slice!(RCI!(Unqual!A), 2)
283 mldivide
284 (A, B, SliceKind kindA, SliceKind kindB)(
285     auto ref const Slice!(RCI!A, 2, kindA) a,
286     auto ref const Slice!(RCI!B, 2, kindB) b
287 )
288 do
289 {
290     auto al = a.lightScope.lightConst;
291     auto bl = b.lightScope.lightConst;
292     return mldivide(al, bl);
293 }
294 
295 /// ditto
296 @safe pure @nogc
297 Slice!(RCI!(Unqual!A), 1)
298 mldivide
299 (A, B, SliceKind kindA, SliceKind kindB)(
300     auto ref const Slice!(RCI!A, 2, kindA) a,
301     auto ref const Slice!(RCI!B, 1, kindB) b
302 )
303 do
304 {
305     auto al = a.lightScope.lightConst;
306     auto bl = b.lightScope.lightConst;
307     return mldivide(al, bl);
308 }
309 
310 /// ditto
311 @safe pure @nogc
312 Slice!(RCI!T, 1) mldivide (T, SliceKind kindA, SliceKind kindB)(
313     Slice!(const(T)*, 2, kindA) a,
314     Slice!(const(T)*, 1, kindB) b,
315 )
316     if (isFloatingPoint!T || isComplex!T)
317 {
318     import mir.ndslice.topology: flattened;
319     return mldivide(a, b.sliced(b.length, 1)).flattened;
320 }
321 
322 pure unittest
323 {
324     auto a = mininitRcslice!double(2, 2);
325     a[] = [[2,3],
326            [1, 4]];
327     auto res = mldivide(eye(2), a);
328     assert(equal!approxEqual(res, a));
329     auto b = mininitRcslice!cdouble(2, 2);
330     b[] = [[2+1i,3+2i],
331            [1+3i, 4+4i]];
332     auto cres = mldivide(eye!cdouble(2), b);
333     assert(cres == b);
334     auto c = mininitRcslice!double(2, 2);
335     c[] = [[5,3],
336            [2,6]];
337     auto d = mininitRcslice!double(2,1);
338     d[] = [[4],
339            [1]];
340     auto e = mininitRcslice!double(2,1);
341     e[] = [[23],
342            [14]];
343     res = mldivide(c, e);
344     assert(equal!approxEqual(res, d));
345 }
346 
347 pure unittest
348 {
349     import mir.ndslice;
350     import mir.math;
351 
352     auto a =  mininitRcslice!double(6, 4);
353     a[] = [
354         -0.57,  -1.28,  -0.39,   0.25,
355         -1.93,   1.08,  -0.31,  -2.14,
356         2.30,   0.24,   0.40,  -0.35,
357         -1.93,   0.64,  -0.66,   0.08,
358         0.15,   0.30,   0.15,  -2.13,
359         -0.02,   1.03,  -1.43,   0.50,
360     ].sliced(6, 4);
361 
362     auto b = mininitRcslice!double(6,1);
363     b[] = [
364         -2.67,
365         -0.55,
366         3.34,
367         -0.77,
368         0.48,
369         4.10,
370     ].sliced(6,1);
371 
372     auto x = mininitRcslice!double(4,1);
373     x[] = [
374         1.5339,
375         1.8707,
376         -1.5241,
377         0.0392
378     ].sliced(4,1);
379 
380     auto res = mldivide(a, b);
381     assert(equal!((a, b) => fabs(a - b) < 5e-5)(res, x));
382 
383     auto ca =  mininitRcslice!cdouble(6, 4);
384     ca[] = [
385         -0.57 + 0.0i,  -1.28 + 0.0i,  -0.39 + 0.0i,   0.25 + 0.0i,
386         -1.93 + 0.0i,   1.08 + 0.0i,  -0.31 + 0.0i,  -2.14 + 0.0i,
387         2.30 + 0.0i,   0.24 + 0.0i,   0.40 + 0.0i,  -0.35 + 0.0i,
388         -1.93 + 0.0i,   0.64 + 0.0i,  -0.66 + 0.0i,   0.08 + 0.0i,
389         0.15 + 0.0i,   0.30 + 0.0i,   0.15 + 0.0i,  -2.13 + 0.0i,
390         -0.02 + 0.0i,   1.03 + 0.0i,  -1.43 + 0.0i,   0.50 + 0.0i,
391     ].sliced(6, 4);
392 
393     auto cb = mininitRcslice!cdouble(6,1);
394     cb[] = [
395         -2.67 + 0.0i,
396         -0.55 + 0.0i,
397         3.34 + 0.0i,
398         -0.77 + 0.0i,
399         0.48 + 0.0i,
400         4.10 + 0.0i,
401     ].sliced(6,1);
402 
403     auto cx = mininitRcslice!cdouble(4,1);
404     cx[] = [
405         1.5339 + 0.0i,
406         1.8707 + 0.0i,
407         -1.5241 + 0.0i,
408         0.0392 + 0.0i
409     ].sliced(4,1);
410 
411     auto cres = mldivide(ca, cb);
412     assert(equal!((a, b) => fabs(a - b) < 5e-5)(cres, x));
413 }
414 
415 /++
416 Solve systems of linear equations AX = I for X, where I is the identity.
417 X is the right inverse of A if it exists, it's also a (Moore-Penrose) Pseudoinverse if A is invertible then X is the inverse.
418 Computes minimum-norm solution to a linear least squares problem
419 if A is not a square matrix.
420 +/
421 @safe pure @nogc Slice!(RCI!A, 2) mlinverse(A, SliceKind kindA)(
422     auto ref Slice!(RCI!A, 2, kindA) a
423 )
424 {
425     auto aScope = a.lightScope.lightConst;
426     return mlinverse!A(aScope);
427 }
428 
429 @safe pure @nogc Slice!(RCI!A, 2) mlinverse(A, SliceKind kindA)(
430     Slice!(const(A)*, 2, kindA) a
431 )
432 {
433     auto a_i = a.as!A.rcslice;
434     auto a_i_light = a_i.lightScope.canonical;
435     auto rcipiv = min(a_i.length!0, a_i.length!1).mininitRcslice!lapackint;
436     auto ipiv = rcipiv.lightScope;
437     auto info = getrf!A(a_i_light, ipiv);
438     if (info == 0)
439     {
440         auto rcwork = getri_wq!A(a_i_light).mininitRcslice!A;
441         auto work = rcwork.lightScope;
442         info = getri!A(a_i_light, ipiv, work);
443     }
444     enforce!"Matrix is not invertible as has zero determinant"(!info);
445     return a_i;
446 }
447 
448 pure unittest
449 {
450     import mir.ndslice;
451     import mir.math;
452 
453     auto a = mininitRcslice!double(2, 2);
454     a[] = [[1,0],
455            [0,-1]];
456     auto ans = mlinverse!double(a);
457     assert(equal!approxEqual(ans, a));
458 }
459 
460 pure 
461 unittest
462 {
463     import mir.ndslice;
464     import mir.math;
465 
466     auto a = mininitRcslice!double(2, 2);
467     a[] = [[ 0, 1],
468            [-1, 0]];
469     auto aInv = mininitRcslice!double(2, 2);
470     aInv[] = [[0, -1],
471               [1,  0]];
472     auto ans = a.mlinverse;
473     assert(equal!approxEqual(ans, aInv));
474 }
475 
476 ///Singuar value decomposition result
477 struct SvdResult(T)
478 {
479     ///
480     Slice!(RCI!T, 2) u;
481     ///Singular Values
482     Slice!(RCI!T) sigma;
483     ///
484     Slice!(RCI!T, 2) vt;
485 }
486 
487 /++
488 Computes the singular value decomposition.
489 Params:
490     a = input `M x N` matrix
491     slim = If true the first `min(M,N)` columns of `u` and the first
492         `min(M,N)` rows of `vt` are returned in the ndslices `u` and `vt`.
493 out result = $(LREF SvdResult). ]
494 Returns: error code from CBlas
495 +/
496 @safe pure @nogc SvdResult!T svd
497 (
498     T,
499     string algorithm = "gesvd",
500     SliceKind kind,
501 )(
502     Slice!(const(T)*, 2, kind) a,
503     Flag!"slim" slim = No.slim,
504 )
505     if (algorithm == "gesvd" || algorithm == "gesdd")
506 {
507     auto m = cast(lapackint)a.length!1;
508     auto n = cast(lapackint)a.length!0;
509 
510     auto s = mininitRcslice!T(min(m, n));
511     auto u = mininitRcslice!T(slim ? s.length : m, m);
512     auto vt = mininitRcslice!T(n, slim ? s.length : n);
513 
514     static if (algorithm == "gesvd")
515     {
516         auto jobu = slim ? 'S' : 'A';
517         auto jobvt = slim ? 'S' : 'A';
518         auto rca_sliced = a.as!T.rcslice;
519         auto rca = rca_sliced.lightScope.canonical;
520         auto rcwork = gesvd_wq(jobu, jobvt, rca, u.lightScope.canonical, vt.lightScope.canonical).mininitRcslice!T;
521         auto work = rcwork.lightScope;
522         auto info = gesvd(jobu, jobvt, rca, s.lightScope, u.lightScope.canonical, vt.lightScope.canonical, work);
523     }
524     else // gesdd
525     {
526         auto rciwork = mininitRcslice!lapackint(s.length * 8);
527         auto iwork = rciwork.lightScope;
528         auto jobz = slim ? 'S' : 'A';
529         auto rca_sliced = a.as!T.rcslice;
530         auto rca = rca_sliced.lightScope.canonical;
531         auto rcwork = gesdd_wq(jobz, rca, u.lightScope, vt.lightScope).minitRcslice!T;
532         auto work = rcwork.lightScope;
533         auto info = gesdd(jobz, rca, s.lightScope, u.lightScope, vt.lightScope, work, iwork);
534     }
535     enum msg = (algorithm == "gesvd" ? "svd: DBDSDC did not converge, updating process failed" : "svd: DBDSQR did not converge");
536     enforce!("svd: " ~ msg)(!info);
537     return SvdResult!T(vt, s, u); //transposed
538 }
539 
540 @safe pure SvdResult!T svd
541 (
542     T,
543     string algorithm = "gesvd",
544     SliceKind kind,
545 )(
546     auto ref scope const Slice!(RCI!T,2,kind) matrix,
547     Flag!"slim" slim = No.slim
548 )
549 {
550     auto matrixScope = matrix.lightScope.lightConst;
551     return svd!(T, algorithm)(matrixScope, slim); 
552 }
553 
554 pure unittest
555 {
556     import mir.ndslice;
557     import mir.math;
558 
559     auto a = mininitRcslice!double(6, 4);
560     a[] = [[7.52,  -1.10,  -7.95,   1.08],
561            [-0.76,   0.62,   9.34,  -7.10],
562            [5.13,   6.62,  -5.66,   0.87],
563            [-4.75,   8.52,   5.75,   5.30],
564            [1.33,   4.91,  -5.49,  -3.52],
565            [-2.40,  -6.77,   2.34,   3.95]];
566 
567     auto r1 = a.svd;
568 
569     auto sigma1 = rcslice!double(a.shape, 0);
570     sigma1.diagonal[] = r1.sigma;
571     auto m1 = (r1.u).mtimes(sigma1).mtimes(r1.vt);
572     assert(equal!((a, b) => fabs(a-b)< 1e-8)(a, m1));
573 
574     auto r2 = a.svd;
575 
576     auto sigma2 = rcslice!double(a.shape, 0.0);
577     sigma2.diagonal[] = r2.sigma;
578     auto m2 = r2.u.mtimes(sigma2).mtimes(r2.vt);
579 
580     assert(equal!((a, b) => fabs(a-b)< 1e-8)(a, m2));
581 
582     auto r = a.svd(Yes.slim);
583     assert(r.u.shape == [6, 4]);
584     assert(r.vt.shape == [4, 4]);
585 }
586 
587 pure unittest
588 {
589     import mir.ndslice;
590     import mir.math;
591 
592     auto a = mininitRcslice!double(6, 4);
593     a[] =   [[7.52,  -1.10,  -7.95,   1.08],
594             [-0.76,   0.62,   9.34,  -7.10],
595             [5.13,   6.62,  -5.66,   0.87],
596             [-4.75,   8.52,   5.75,   5.30],
597             [1.33,   4.91,  -5.49,  -3.52],
598             [-2.40,  -6.77,   2.34,   3.95]];
599 
600     auto r1 = a.svd(No.slim);
601 
602     auto sigma1 = rcslice!double(a.shape, 0.0);
603     sigma1.diagonal[] = r1.sigma;
604     auto m1 = r1.u.mtimes(sigma1).mtimes(r1.vt);
605 
606     assert(equal!((a, b) => fabs(a-b)< 1e-8)(a, m1));
607 }
608 
609 
610 struct EigenResult(T)
611 {
612     Slice!(RCI!(complexType!T), 2) eigenvectors;
613     Slice!(RCI!(complexType!T)) eigenvalues;
614 }
615 
616 struct QRResult(T)
617 {
618     Slice!(RCI!T,2) Q;
619     Slice!(RCI!T,2) R;
620 
621     @safe this
622     (
623         SliceKind kindA,
624         SliceKind kindTau
625     )(
626         Slice!(RCI!T, 2, kindA) a,
627         Slice!(RCI!T, 1, kindTau) tau
628     )
629     {
630         auto aScope = a.lightScope.lightConst;
631         auto tauScope = tau.lightScope.lightConst;
632         this(aScope, tauScope);
633     }
634 
635     @safe this
636     (
637         SliceKind kindA,
638         SliceKind kindTau
639     )(
640         Slice!(const(T)*, 2, kindA) a,
641         Slice!(const(T)*, 1, kindTau) tau
642     )
643     {
644         R = mininitRcslice!T(a.shape);
645         foreach (i; 0 .. R.length!0)
646         {
647             foreach (j; 0 .. R.length!1)
648             {
649                 if (i >= j)
650                 {
651                     R[j, i] = a[i, j];
652                 }
653                 else
654                 {
655                     R[j ,i] = cast(T)0;
656                 }
657             }
658         }
659         auto rcwork = mininitRcslice!T(a.length!0);
660         auto work = rcwork.lightScope;
661         auto aSliced = a.as!T.rcslice;
662         auto aScope = aSliced.lightScope.canonical;
663         auto tauSliced = tau.as!T.rcslice;
664         auto tauScope = tauSliced.lightScope;
665         orgqr!T(aScope, tauScope, work);
666         Q = aScope.transposed.as!T.rcslice;
667     }
668 }
669 
670 @safe pure QRResult!T qr(T, SliceKind kind)(
671     auto ref const Slice!(RCI!T, 2, kind) matrix
672 )
673 {
674     auto matrixScope = matrix.lightScope.lightConst;
675     return qr!(T, kind)(matrixScope);
676 }
677 
678 @safe pure QRResult!T qr(T, SliceKind kind)(
679     auto ref const Slice!(const(T)*, 2, kind) matrix
680 )
681 {
682     auto a = matrix.transposed.as!T.rcslice;
683     auto tau = mininitRcslice!T(a.length!0);
684     auto rcwork = mininitRcslice!T(a.length!0);
685     auto work = rcwork.lightScope;
686     auto aScope = a.lightScope.canonical;
687     auto tauScope = tau.lightScope;
688     geqrf!T(aScope, tauScope, work);
689     return QRResult!T(aScope, tauScope);
690 }
691 
692 pure nothrow
693 unittest
694 {
695     import mir.ndslice;
696     import mir.math;
697 
698     auto data = mininitRcslice!double(3, 3);
699     data[] = [[12, -51,   4],
700               [ 6, 167, -68],
701               [-4,  24, -41]];
702 
703     auto res = qr(data);
704     auto q = mininitRcslice!double(3, 3);
705     q[] = [[-6.0/7.0,   69.0/175.0, 58.0/175.0],
706            [-3.0/7.0, -158.0/175.0, -6.0/175.0],
707            [ 2.0/7.0,   -6.0/35.0 , 33.0/35.0 ]];
708     auto aE = function (double x, double y) => approxEqual(x, y, 0.00005, 0.00005);
709     auto r = mininitRcslice!double(3, 3);
710     r[] = [[-14,  -21,  14],
711            [  0, -175,  70],
712            [  0,    0, -35]];
713     assert(equal!approxEqual(mtimes(res.Q, res.R), data));
714 }
715 
716 @safe pure @nogc
717 EigenResult!(realType!T) eigen(T, SliceKind kind)(
718     auto ref Slice!(const(T)*,2, kind) a,
719 )
720 {
721     enforce!"eigen: input matrix must be square"(a.length!0 == a.length!1);
722     const n = a.length;
723     auto rcw = n.mininitRcslice!(complexType!T);
724     auto w = rcw.lightScope;
725     auto rcwork = mininitRcslice!T(16 * n);
726     auto work = rcwork.lightScope;
727     auto z = [n, n].mininitRcslice!(complexType!T);//vl (left eigenvectors)
728     auto rca = a.transposed.as!T.rcslice;
729     auto as = rca.lightScope;
730     static if (isComplex!T)
731     {
732         auto rcrwork = [2 * n].mininitRcslice!(realType!T);
733         auto rwork = rcrwork.lightScope;
734         auto info = geev!(T, realType!T)('N', 'V', as.canonical, w, Slice!(T*, 2, Canonical).init, z.lightScope.canonical, work, rwork);
735         enforce!"eigen failed"(!info);
736     }
737     else
738     {
739         alias C = complexType!T;
740         auto wr = sliced((cast(T[]) w.field)[0 .. n]);
741         auto wi = sliced((cast(T[]) w.field)[n .. n * 2]);
742         auto rczr = [n, n].mininitRcslice!T;
743         auto zr = rczr.lightScope;
744         auto info = geev!T('N', 'V', as.canonical, wr, wi, Slice!(T*, 2, Canonical).init, zr.canonical, work);
745         enforce!"eigen failed"(!info);
746         work[0 .. n] = wr;
747         work[n .. n * 2] = wi;
748         foreach (i, ref e; w.field)
749         {
750             e = work[i] + work[n + i] * 1fi;
751             auto zi = z.lightScope[i];
752             if (e.im > 0)
753                 zi[] = zip(zr[i], zr[i + 1]).map!"a + b * 1fi";
754             else
755             if (e.im < 0)
756                 zi[] = zip(zr[i - 1], zr[i]).map!"a - b * 1fi";
757             else
758                 zi[] = zr[i].as!C;
759         }
760     }
761     return typeof(return)(z, rcw);
762 }
763 
764 //@safe pure @nogc
765 EigenResult!(realType!T) eigen(T, SliceKind kind)(
766     auto ref Slice!(RCI!T,2, kind) a
767 )
768 {
769     auto as = a.lightScope.lightConst;
770     return eigen(as);
771 }
772 
773 ///
774 // pure
775 unittest
776 {
777     import mir.ndslice;
778     import mir.math;
779 
780     auto data = 
781         [[ 0, 1],
782          [-1, 0]].fuse.as!double.rcslice;
783     auto eigenvalues = [0 + 1i, 0 - 1i].sliced;
784     auto eigenvectors =
785         [[0 - 1i, 1 + 0i],
786          [0 + 1i, 1 + 0i]].fuse;
787 
788     auto res = data.eigen;
789 
790     assert(res.eigenvalues.equal!approxEqual(eigenvalues));
791     foreach (i; 0 .. eigenvectors.length)
792         assert((res.eigenvectors.lightScope[i] / eigenvectors[i]).diff.slice.nrm2.approxEqual(0));
793 }
794 
795 
796 ///
797 @safe pure
798 unittest
799 {
800     import mir.ndslice;
801     import mir.math;
802     import mir.blas;
803 
804     auto data =
805         [[0, 1, 0],
806          [0, 0, 1],
807          [1, 0, 0]].fuse.as!double.rcslice;
808     auto c = 3.0.sqrt;
809     auto eigenvalues = [(-1 + c * 1i) / 2, (-1 - c * 1i) / 2, 1 + 0i];
810 
811     auto eigenvectors =
812         [[-1 + c * 1i , -1 - c * 1i , 2 + 0i],
813          [-1 - c * 1i , -1 + c * 1i , 2 + 0i],
814          [1 + 0i, 1 + 0i, 1 + 0i]].fuse;
815 
816     auto res = data.eigen;
817 
818     assert(res.eigenvalues.equal!approxEqual(eigenvalues));
819     foreach (i; 0 .. eigenvectors.length)
820         assert((res.eigenvectors.lightScope[i] / eigenvectors[i]).diff.slice.nrm2.approxEqual(0));
821 
822     auto cdata = data.lightScope.as!cdouble.rcslice;
823     res = cdata.eigen;
824 
825     assert(res.eigenvalues.equal!approxEqual(eigenvalues));
826     foreach (i; 0 .. eigenvectors.length)
827         assert((res.eigenvectors.lightScope[i] / eigenvectors[i]).diff.slice.nrm2.approxEqual(0));
828 }
829 
830 
831 /// Principal component analysis result.
832 struct PcaResult(T)
833 {
834     /// Eigenvectors (Eigenvectors[i] is the ith eigenvector)
835     Slice!(RCI!T,2) eigenvectors;
836     /// Principal component scores. (Input matrix rotated to basis of eigenvectors)
837     Slice!(RCI!T, 2) scores;
838     /// Principal component variances. (Eigenvalues)
839     Slice!(RCI!T) eigenvalues;
840     /// The means of each data column (0 if not centred)
841     Slice!(RCI!T) mean;
842     /// The standard deviations of each column (1 if not normalized)
843     Slice!(RCI!T) stdDev;
844     /// Principal component Loadings vectors (Eigenvectors times sqrt(Eigenvalue))
845     Slice!(RCI!T, 2) loadings()
846     {
847         auto result = mininitRcslice!T(eigenvectors.shape);
848         for (size_t i = 0; i < eigenvectors.length!0 && i < eigenvalues.length; i++){
849             if(eigenvalues[i] != 0)
850                 foreach (j; 0 .. eigenvectors.length!1)
851                     result[i, j] = eigenvectors[i, j] * sqrt(eigenvalues[i]);
852             else
853                 result[i][] = eigenvectors[i][];
854         }
855         return result;
856     }
857 
858     Slice!(RCI!T, 2) loadingsScores() {// normalized data in basis of {sqrt(eval)*evect}
859         //if row i is a vector p, the original data point is mean[] + p[] * stdDev[]
860         auto result = mininitRcslice!T(scores.shape);
861         foreach (i; 0 .. scores.length!0){
862             for (size_t j=0; j < scores.length!1 && j < eigenvalues.length; j++)
863             {
864                 if(eigenvalues[j] != 0)
865                     result[i, j] = scores[i, j] / sqrt(eigenvalues[j]);
866                 else
867                     result[i, j] = scores[i, j];
868             }
869         }
870         return result;
871     }
872 
873     Slice!(RCI!T) explainedVariance() {
874         import mir.math.sum: sum;
875         //auto totalVariance = eigenvalues.sum!"kb2";
876         auto totalVariance = 0.0;
877         foreach (val; eigenvalues)
878             totalVariance += val;
879         auto result = mininitRcslice!double(eigenvalues.shape);
880         foreach (i; 0 .. result.length!0)
881             result[i] = eigenvalues[i] / totalVariance;
882         return result;
883     }
884 
885     Slice!(RCI!T,2) q()
886     {
887         return eigenvectors;
888     }
889 
890     //returns matrix to transform into basis of 'n' most significatn eigenvectors
891     Slice!(RCI!(T),2) q(size_t n)
892     in {
893         assert(n <= eigenvectors.length!0);
894     }
895     do {
896         auto res = mininitRcslice!T(eigenvectors.shape);
897         res[] = eigenvectors;
898         for ( ; n < eigenvectors.length!0; n++)
899             res[n][] = 0;
900         return res;
901     }
902     //returns matrix to transform into basis of eigenvectors with value larger than delta
903     Slice!(RCI!(T),2) q(T delta)
904     do
905     {
906         auto res = mininitRcslice!T(eigenvectors.shape);
907         res[] = eigenvectors;
908         foreach (i; 0 .. eigenvectors.length!0)
909             if (fabs(eigenvalues[i]) <= delta)
910                 res[i][] = 0;
911         return res;
912     }
913 
914     //transforms data into eigenbasis
915     Slice!(RCI!(T),2) transform(Slice!(RCI!(T),2) data)
916     {
917         return q.mlinverse.mtimes(data);
918     }
919 
920     //transforms data into eigenbasis
921     Slice!(RCI!(T),2) transform(Slice!(RCI!(T),2) data, size_t n)
922     {
923         return q(n).mlinverse.mtimes(data);
924     }
925     //transforms data into eigenbasis
926     Slice!(RCI!(T),2) transform(Slice!(RCI!(T),2) data, T delta)
927     {
928         return q(delta).mlinverse.mtimes(data);
929     }
930 }
931 
932 
933 /++
934 Principal component analysis of raw data.
935 Template:
936 correlation = Flag to use correlation matrix instead of covariance
937 Params:
938     data = input `M x N` matrix, where 'M (rows)>= N(cols)'
939     devEst =
940     meanEst =
941     fixEigenvectorDirections =
942 Returns: $(LREF PcaResult)
943 +/
944 @safe pure @nogc
945 PcaResult!T pca(
946     SliceKind kind,
947     T
948  )(
949     Slice!(const(T)*, 2, kind) data,
950     DeviationEstimator devEst = DeviationEstimator.sample,
951     MeanEstimator meanEst = MeanEstimator.average,
952     Flag!"fixEigenvectorDirections" fixEigenvectorDirections = Yes.fixEigenvectorDirections,
953 )
954 in
955 {
956     assert(data.length!0 >= data.length!1);
957 }
958 do
959 {
960     real n = (data.length!0 <= 1 ? 1 : data.length!0 -1 );//num observations
961     auto mean = rcslice!(T,1)([data.length!1], cast(T)0);
962     auto stdDev = rcslice!(T,1)([data.length!1], cast(T)1);
963     auto centeredData = centerColumns!T(data, mean, meanEst);
964     //this part gets the eigenvectors of the sample covariance without explicitly calculating the covariance matrix
965     //to implement a minimum covariance deteriminant this block would need to be redone
966     //firstly one would calculate the MCD then call eigen to get it's eigenvectors
967     auto processedData = normalizeColumns!T(centeredData, stdDev, devEst);
968     auto svdResult = processedData.svd(Yes.slim);
969     with (svdResult)
970     {
971         //u[i][] is the ith eigenvector
972         foreach (i; 0 .. u.length!0){
973             foreach (j; 0 .. u.length!1){
974                 u[i, j] *= sigma[j];
975             }
976         }
977         auto eigenvalues = mininitRcslice!double(sigma.shape);
978         for (size_t i = 0; i < sigma.length && i < eigenvalues.length; i++){
979             eigenvalues[i] = sigma[i] * sigma[i] / n; //square singular values to get eigenvalues
980         }
981         if (fixEigenvectorDirections)
982         {
983             foreach(size_t i; 0 .. sigma.length)
984             {
985                 //these are directed so the 0th component is +ve
986                 if (vt[i, 0] < 0)
987                 {
988                     vt[i][] *= -1;
989                     u[0 .. $, i] *= -1;
990                 }
991             }
992         }
993         PcaResult!T result;
994         result.scores = u;
995         result.eigenvectors = vt;
996         result.eigenvalues = eigenvalues;
997         result.mean = mean;
998         result.stdDev = stdDev;
999         return result;
1000     }
1001 }
1002 
1003 @safe pure @nogc
1004 PcaResult!T pca(T, SliceKind kind)
1005 (
1006     auto ref const Slice!(RCI!T, 2, kind) data,
1007     DeviationEstimator devEst = DeviationEstimator.sample,
1008     MeanEstimator meanEst = MeanEstimator.average,
1009     Flag!"fixEigenvectorDirections" fixEigenvectorDirections = Yes.fixEigenvectorDirections,
1010 )
1011 do
1012 {
1013     auto d = data.lightScope.lightConst;
1014     return d.pca(devEst, meanEst, fixEigenvectorDirections);
1015 }
1016 
1017 pure
1018 unittest
1019 {
1020     import mir.ndslice;
1021     import mir.math;
1022 
1023     auto data = mininitRcslice!double(3, 2);
1024     data[] = [[ 1, -1],
1025               [ 0,  1],
1026     [-1,  0]];
1027     //cov =0.5 * [[ 2, -1],
1028     //            [-1,  2]]
1029     const auto const_data = data;
1030     auto mean = mininitRcslice!double(2);
1031     assert(data == centerColumns(const_data, mean));
1032     assert(mean == [0,0]);
1033     PcaResult!double res = const_data.pca;
1034 
1035     auto evs = mininitRcslice!double(2, 2);
1036     evs[] = [[1, -1],
1037              [1,  1]];
1038     evs[] /= sqrt(2.0);
1039     assert(equal!approxEqual(res.eigenvectors, evs));
1040 
1041     auto score = mininitRcslice!double(3, 2);
1042     score[] = [[ 1.0,  0.0],
1043                [-0.5,  0.5],
1044     [-0.5, -0.5]];
1045     score[] *= sqrt(2.0);
1046     assert(equal!approxEqual(res.scores, score));
1047 
1048     auto evals = mininitRcslice!double(2);
1049     evals[] = [1.5, 0.5];
1050     assert(equal!approxEqual(res.eigenvalues, evals));
1051 }
1052 
1053 pure
1054 unittest
1055 {
1056     import mir.ndslice;
1057     import mir.math;
1058 
1059     auto data = mininitRcslice!double(10, 3);
1060     data[] = [[7, 4, 3],
1061               [4, 1, 8],
1062               [6, 3, 5],
1063               [8, 6, 1],
1064               [8, 5, 7],
1065               [7, 2, 9],
1066               [5, 3, 3],
1067               [9, 5, 8],
1068               [7, 4, 5],
1069               [8, 2, 2]];
1070 
1071     auto m1 = 69.0/10.0;
1072     auto m2 = 35.0/10.0;
1073     auto m3 = 51.0/10.0;
1074 
1075     auto centeredData = mininitRcslice!double(10, 3);
1076     centeredData[] =   [[7-m1, 4-m2, 3-m3],
1077                         [4-m1, 1-m2, 8-m3],
1078                         [6-m1, 3-m2, 5-m3],
1079                         [8-m1, 6-m2, 1-m3],
1080                         [8-m1, 5-m2, 7-m3],
1081                         [7-m1, 2-m2, 9-m3],
1082                         [5-m1, 3-m2, 3-m3],
1083                         [9-m1, 5-m2, 8-m3],
1084                         [7-m1, 4-m2, 5-m3],
1085                         [8-m1, 2-m2, 2-m3]];
1086     auto mean = mininitRcslice!double(3);
1087     auto cenRes = centerColumns(data, mean);
1088 
1089     assert(equal!approxEqual(centeredData, cenRes));
1090     assert(equal!approxEqual(mean, [m1,m2,m3]));
1091 
1092     auto res = data.pca;
1093     auto coeff = mininitRcslice!double(3, 3);
1094     coeff[] = [[0.6420046 ,  0.6863616 , -0.3416692 ],
1095                [0.38467229,  0.09713033,  0.91792861], 
1096                [0.6632174 , -0.7207450 , -0.2016662 ]];
1097 
1098     auto score = mininitRcslice!double(10, 3);
1099     score[] = [[ 0.5148128, -0.63083556, -0.03351152],
1100                [-2.6600105,  0.06280922, -0.33089322],
1101                [-0.5840389, -0.29060575, -0.15658900],
1102                [ 2.0477577, -0.90963475, -0.36627323],
1103                [ 0.8832739,  0.99120250, -0.34153847],
1104                [-1.0837642,  1.20857108,  0.44706241],
1105                [-0.7618703, -1.19712391, -0.44810271],
1106                [ 1.1828371,  1.57067601,  0.02182598],
1107                [ 0.2713493,  0.02325373, -0.17721300],
1108                [ 0.1896531, -0.82831257,  1.38523276]];
1109 
1110     auto stdDev = mininitRcslice!double(3);
1111     stdDev[] = [1.3299527, 0.9628478, 0.5514979];
1112 
1113     auto eigenvalues = mininitRcslice!double(3);
1114     eigenvalues[] = [1.768774, 0.9270759, 0.3041499];
1115 
1116     assert(equal!approxEqual(res.eigenvectors, coeff));
1117     assert(equal!approxEqual(res.scores, score));
1118     assert(equal!approxEqual(res.eigenvalues, eigenvalues));
1119 }
1120 
1121 pure
1122 unittest
1123 {
1124     import mir.ndslice;
1125     import mir.math;
1126 
1127     auto data = mininitRcslice!double(13, 4);
1128     data[] =[[ 7,  26,   6,  60],
1129              [ 1,  29,  15,  52],
1130              [11,  56,   8,  20],
1131              [11,  31,   8,  47],
1132              [ 7,  52,   6,  33],
1133              [11,  55,   9,  22],
1134              [ 3,  71,  17,   6],
1135              [ 1,  31,  22,  44],
1136              [ 2,  54,  18,  22],
1137              [21,  47,   4,  26],
1138              [ 1,  40,  23,  34],
1139              [11,  66,   9,  12],
1140              [10,  68,   8,  12]];
1141 
1142     auto m1 = 97.0/13.0;
1143     auto m2 = 626.0/13.0;
1144     auto m3 = 153.0/13.0;
1145     auto m4 = 390.0/13.0;
1146 
1147     auto centeredData = mininitRcslice!double(13, 4);
1148     centeredData[] = [[ 7-m1, 26-m2,  6-m3, 60-m4],
1149                       [ 1-m1, 29-m2, 15-m3, 52-m4],
1150                       [11-m1, 56-m2,  8-m3, 20-m4],
1151                       [11-m1, 31-m2,  8-m3, 47-m4],
1152                       [ 7-m1, 52-m2,  6-m3, 33-m4],
1153                       [11-m1, 55-m2,  9-m3, 22-m4],
1154                       [ 3-m1, 71-m2, 17-m3,  6-m4],
1155                       [ 1-m1, 31-m2, 22-m3, 44-m4],
1156                       [ 2-m1, 54-m2, 18-m3, 22-m4],
1157                       [21-m1, 47-m2,  4-m3, 26-m4],
1158                       [ 1-m1, 40-m2, 23-m3, 34-m4],
1159                       [11-m1, 66-m2,  9-m3, 12-m4],
1160                       [10-m1, 68-m2  ,8-m3, 12-m4]];
1161     auto mean = mininitRcslice!double(4);
1162     auto cenRes = centerColumns(data, mean);
1163 
1164     assert(equal!approxEqual(centeredData, cenRes));
1165     assert(mean == [m1,m2,m3,m4]);
1166 
1167     auto res = data.pca(DeviationEstimator.none);
1168     auto coeff = mininitRcslice!double(4, 4);
1169     coeff[] = [[0.067799985695474,  0.678516235418647, -0.029020832106229, -0.730873909451461],
1170                [0.646018286568728,  0.019993340484099, -0.755309622491133,  0.108480477171676],
1171                [0.567314540990512, -0.543969276583817,  0.403553469172668, -0.468397518388289],
1172                [0.506179559977705,  0.493268092159297,  0.515567418476836,  0.484416225289198]];
1173 
1174     auto score = mininitRcslice!double(13, 4);
1175     score[] = [[-36.821825999449700,   6.870878154227367,  -4.590944457629745,   0.396652582713912],
1176                [-29.607273420710964,  -4.610881963526308,  -2.247578163663940,  -0.395843536696492],
1177                [ 12.981775719737618,   4.204913183175938,   0.902243082694698,  -1.126100587210615],
1178                [-23.714725720918022,   6.634052554708721,   1.854742000806314,  -0.378564808384691],
1179                [  0.553191676624597,   4.461732123178686,  -6.087412652325177,   0.142384896047281],
1180                [ 10.812490833309816,   3.646571174544059,   0.912970791674604,  -0.134968810314680],
1181                [ 32.588166608817929,  -8.979846284936063,  -1.606265913996588,   0.081763927599947],
1182                [-22.606395499005586, -10.725906457369449,   3.236537714483416,   0.324334774646368],
1183                [  9.262587237675838,  -8.985373347478788,  -0.016909578102172,  -0.543746175981799],
1184                [  3.283969329640680,  14.157277337500918,   7.046512994833761,   0.340509860960606],
1185                [ -9.220031117829379, -12.386080787220454,   3.428342878284624,   0.435152769664895],
1186                [ 25.584908517429557,   2.781693148152386,  -0.386716066864491,   0.446817950545605],
1187                [ 26.903161834677597,   2.930971165042989,  -2.445522630195304,   0.411607156409658]];
1188 
1189     auto eigenvalues = mininitRcslice!double(4);
1190 
1191 
1192     eigenvalues[] = [517.7968780739053, 67.4964360487231, 12.4054300480810, 0.2371532651878];
1193 
1194     assert(equal!approxEqual(res.eigenvectors, coeff));
1195     assert(equal!approxEqual(res.scores, score));
1196     assert(equal!approxEqual(res.eigenvalues, eigenvalues));
1197 }
1198 
1199 ///complex extensions
1200 
1201 private T conj(T)(
1202     const T z
1203 )
1204     if (isComplex!T)
1205 {
1206     return z.re - (1fi* z.im);
1207 }
1208 
1209 private template complexType(C)
1210 {
1211     static if (isComplex!C)
1212         alias complexType = Unqual!C;
1213     else static if (is(Unqual!C == double))
1214         alias complexType = cdouble;
1215     else static if (is (Unqual!C == float))
1216         alias complexType = cfloat;
1217     else static if (is (Unqual!C == real))
1218         alias complexType = creal;
1219 }
1220 
1221 ///
1222 enum MeanEstimator
1223 {
1224     ///
1225     none,
1226     ///
1227     average,
1228     ///
1229     median
1230 }
1231 
1232 ///
1233 @safe pure nothrow @nogc
1234 T median(T)(auto ref Slice!(RCI!T) data)
1235 {
1236     auto dataScope = data.lightScope.lightConst;
1237     return median!T(dataScope);
1238 }
1239 
1240 /// ditto
1241 @safe pure nothrow @nogc
1242 T median(T)(Slice!(const(T)*) data)
1243 {
1244     import mir.ndslice.sorting: sort;
1245     size_t len = cast(int) data.length;
1246     size_t n = len / 2;
1247     auto temp = data.as!T.rcslice;
1248     temp.lightScope.sort();
1249     return len % 2 ? temp[n] : 0.5f * (temp[n - 1] + temp[n]);
1250 }
1251 
1252 ///
1253 @safe pure
1254 unittest
1255 {
1256     import mir.ndslice;
1257     import mir.math;
1258 
1259     auto a = mininitRcslice!double(3);
1260     a[] = [3, 1, 7];
1261     auto med = median!double(a.flattened);
1262     assert(med == 3.0);
1263     assert(a == [3, 1, 7]);//add in stddev out param
1264     double aDev;
1265     auto aCenter = centerColumns(a, aDev, MeanEstimator.median);
1266     assert(aCenter == [0.0, -2.0, 4.0]);
1267     assert(aDev == 3.0);
1268     auto b = mininitRcslice!double(4);
1269     b[] = [4,2,5,1];
1270     auto medB = median!double(b.flattened);
1271     assert(medB == 3.0);
1272     assert(b == [4,2,5,1]);
1273     double bDev;
1274     auto bCenter = centerColumns(b, bDev, MeanEstimator.median);
1275     assert(bCenter == [1.0, -1.0, 2.0, -2.0]);
1276     assert(bDev == 3.0);
1277 }
1278 
1279 /++
1280 Mean Centring of raw data.
1281 Params:
1282     matrix = input `M x N` matrix
1283     mean = column means
1284     est = mean estimation method
1285 Returns:
1286 `M x N` matrix with each column translated by the column mean
1287 +/
1288 @safe pure nothrow @nogc
1289 Slice!(RCI!T,2) centerColumns(T, SliceKind kind)
1290 (
1291     Slice!(const(T)*, 2, kind) matrix,
1292     out Slice!(RCI!T) mean,
1293     MeanEstimator est = MeanEstimator.average,
1294 )
1295 {
1296     mean = rcslice!T([matrix.length!1], cast(T)0);
1297     if (est == MeanEstimator.none)
1298     {
1299         return matrix.as!T.rcslice;
1300     }
1301     auto at = matrix.transposed.as!T.rcslice;
1302     auto len = at.length!1;
1303     foreach (i; 0 .. at.length!0)
1304     {
1305         if (est == MeanEstimator.average)
1306         {
1307             foreach(j; 0 .. at.length!1)
1308                 mean[i] += (at[i][j]/len);
1309         }
1310         else // (est == MeanEstimator.median)
1311         {
1312             mean[i] = median(at[i].flattened);
1313         }
1314         at[i][] -= mean[i];
1315     }
1316     auto atSliced = at.transposed.as!T.rcslice;
1317     return atSliced;
1318 }
1319 
1320 /// ditto
1321 @safe pure nothrow @nogc
1322 Slice!(RCI!T) centerColumns(T)
1323 (
1324     Slice!(const(T)*) col,
1325     out T mean,
1326     MeanEstimator est = MeanEstimator.average,
1327 )
1328 {
1329     mean = cast(T)0;
1330     if (est == MeanEstimator.none)
1331     {
1332         return col.as!T.rcslice;
1333     }
1334     auto len = col.length;
1335     if (est == MeanEstimator.average)
1336     {
1337         foreach(j; 0 .. len)
1338             mean += (col[j]/len);
1339     }
1340     else // (est == MeanEstimator.median)
1341     {
1342         mean = median(col);
1343     }
1344     auto result = mininitRcslice!T(len);
1345     foreach (j; 0 .. len)
1346         result[j] = col[j] - mean;
1347     return result;
1348 }
1349 
1350 /// ditto
1351 @safe pure nothrow @nogc
1352 Slice!(RCI!T) centerColumns(T)
1353 (
1354    auto ref const Slice!(RCI!T) col,
1355    out T mean,
1356     MeanEstimator est = MeanEstimator.average,
1357 )
1358 {
1359     auto colScope = col.lightScope;
1360     return centerColumns!(T)(colScope, mean, est);
1361 }
1362 
1363 /// ditto
1364 @safe pure nothrow @nogc
1365 Slice!(RCI!T,2) centerColumns(T, SliceKind kind)
1366 (
1367     auto ref const Slice!(RCI!T,2,kind) matrix,
1368     out Slice!(RCI!T) mean,
1369     MeanEstimator est = MeanEstimator.average,
1370 )
1371 {
1372     auto matrixScope = matrix.lightScope;
1373     return centerColumns(matrixScope, mean, est);
1374 }
1375 
1376 ///
1377 @safe pure nothrow
1378 unittest
1379 {
1380     import mir.ndslice;
1381     import mir.math;
1382 
1383     auto data = mininitRcslice!double(2,1);
1384     data[] = [[1],
1385               [3]];
1386     auto mean = mininitRcslice!double(1);
1387     auto res = centerColumns(data, mean, MeanEstimator.average);
1388     assert(mean[0] == 2);
1389     assert(res == [[-1],[1]]);
1390 }
1391 
1392 ///
1393 enum DeviationEstimator
1394 {
1395     ///
1396     none,
1397     ///
1398     sample,
1399     /// median absolute deviation
1400     mad
1401 }
1402 
1403 /++
1404 Normalization of raw data.
1405 Params:
1406     matrix = input `M x N` matrix, each row an observation and each column mean centred
1407     stdDev = column standard deviation
1408     devEst = estimation method
1409 Returns:
1410     `M x N` matrix with each column divided by it's standard deviation
1411 +/
1412 @safe pure nothrow @nogc Slice!(RCI!T,2) normalizeColumns(T, SliceKind kind)(
1413     auto ref const Slice!(const(T)*,2,kind) matrix,
1414     out Slice!(RCI!T) stdDev,
1415     DeviationEstimator devEst = DeviationEstimator.sample
1416 )
1417 {
1418     stdDev = rcslice!T([matrix.length!1], cast(T)0);
1419     if (devEst == DeviationEstimator.none)
1420     {
1421         auto matrixSliced = matrix.as!T.rcslice;
1422         return matrixSliced;
1423     }
1424     else
1425     {   
1426         import mir.math.sum: sum;
1427         auto mTSliced = matrix.transposed.as!T.rcslice;
1428         auto mT = mTSliced.lightScope.canonical;
1429         foreach (i; 0 .. mT.length!0)
1430         {
1431             auto processedRow = mininitRcslice!T(mT.length!1);
1432             if (devEst == DeviationEstimator.sample)
1433             {
1434                 foreach (j; 0 .. mT.length!1)
1435                     processedRow[j] = mT[i, j] * mT[i, j];
1436                 stdDev[i] = sqrt(processedRow.sum!"kb2" / (mT[i].length - 1));
1437             }
1438             else if (devEst == DeviationEstimator.mad)
1439             {
1440                 foreach (j; 0 .. mT.length!1)
1441                     processedRow[j] = fabs(mT[i,j]);
1442                 stdDev[i] = median!T(processedRow);
1443             }
1444             mT[i][] /= stdDev[i];
1445         }
1446         auto mSliced = mT.transposed.rcslice;
1447         return mSliced;
1448     }
1449 }
1450 
1451 /// ditto
1452 @safe pure nothrow @nogc Slice!(RCI!T,2) normalizeColumns(T, SliceKind kind)(
1453     auto ref const Slice!(RCI!T,2,kind) matrix,
1454     out Slice!(RCI!T) stdDev,
1455     DeviationEstimator devEst = DeviationEstimator.sample,
1456 )
1457 {
1458     auto matrixScope = matrix.lightScope.lightConst;
1459     return normalizeColumns!(T, kind)(matrixScope, stdDev, devEst);
1460 }
1461 
1462 ///
1463 @safe pure nothrow unittest
1464 {
1465     import mir.ndslice;
1466     import mir.math;
1467 
1468     auto data = mininitRcslice!double(2,2);
1469     data[] = [[ 2, -1],
1470               [-2,  1]];
1471     //sd1 = 2 * sqrt(2.0);
1472     //sd2 = sqrt(2.0);
1473     auto x = 1.0 / sqrt(2.0);
1474     auto scaled = mininitRcslice!double(2,2);
1475     scaled[] = [[ x, -x],
1476                 [-x,  x]];
1477     auto stdDev = mininitRcslice!double(2);
1478     assert(normalizeColumns(data, stdDev) == scaled);
1479     assert(stdDev == [2*sqrt(2.0), sqrt(2.0)]);
1480 }