The OpenD Programming Language

1 /++
2 This module contains base statistical algorithms.
3 
4 Note that used specialized summing algorithms execute more primitive operations
5 than vanilla summation. Therefore, if in certain cases maximum speed is required
6 at expense of precision, one can use $(REF_ALTTEXT $(TT Summation.fast), Summation.fast, mir, math, sum)$(NBSP).
7 
8 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
9 
10 Authors: Shigeki Karita (original numir code), Ilia Ki, John Michael Hall
11 
12 Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments
13 
14 Macros:
15 SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, math, $1)$(NBSP)
16 T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
17 T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4))
18 +/
19 module mir.math.stat;
20 
21 import core.lifetime: move;
22 import mir.internal.utility: isFloatingPoint;
23 import mir.math.common: fmamath;
24 import mir.math.sum;
25 import mir.ndslice.slice: Slice, SliceKind, hasAsSlice;
26 import mir.primitives;
27 import std.traits: Unqual, isArray, isMutable, isIterable, isIntegral, CommonType;
28 
29 ///
30 public import mir.math.sum: Summation;
31 
32 ///
33 package(mir)
34 template statType(T, bool checkComplex = true)
35 {
36     import mir.internal.utility: isFloatingPoint;
37 
38     static if (isFloatingPoint!T) {
39         import std.traits: Unqual;
40         alias statType = Unqual!T;
41     } else static if (is(T : double)) {
42         alias statType = double;
43     } else static if (checkComplex) {
44         import mir.internal.utility: isComplex;
45         static if (isComplex!T) {
46             static if (__traits(getAliasThis, T).length == 1)
47             {
48                 alias statType = .statType!(typeof(__traits(getMember, T, __traits(getAliasThis, T)[0]))); 
49             }
50             else
51             {
52                 alias statType = Unqual!T;
53             }
54         } else {
55             static assert(0, "statType: type " ~ T.stringof ~ " must be convertible to a complex floating point type");
56         }
57     } else {
58         static assert(0, "statType: type " ~ T.stringof ~ " must be convertible to a floating point type");
59     }
60 }
61 
62 version(mir_test)
63 @safe pure nothrow @nogc
64 unittest
65 {
66     static assert(is(statType!int == double));
67     static assert(is(statType!uint == double));
68     static assert(is(statType!double == double));
69     static assert(is(statType!float == float));
70     static assert(is(statType!real == real));
71     
72     static assert(is(statType!(const(int)) == double));
73     static assert(is(statType!(immutable(int)) == double));
74     static assert(is(statType!(const(double)) == double));
75     static assert(is(statType!(immutable(double)) == double));
76 }
77 
78 version(mir_test)
79 @safe pure nothrow @nogc
80 unittest
81 {
82     import mir.complex: Complex;
83 
84     static assert(is(statType!(Complex!float) == Complex!float));
85     static assert(is(statType!(Complex!double) == Complex!double));
86     static assert(is(statType!(Complex!real) == Complex!real));
87 }
88 
89 version(mir_test)
90 @safe pure nothrow @nogc
91 unittest
92 {
93     static struct Foo {
94         float x;
95         alias x this;
96     }
97 
98     static assert(is(statType!Foo == double)); // note: this is not float
99 }
100 
101 version(mir_test)
102 @safe pure nothrow @nogc
103 unittest
104 {
105     import mir.complex;
106     static struct Foo {
107         Complex!float x;
108         alias x this;
109     }
110 
111     static assert(is(statType!Foo == Complex!float));
112 }
113 
114 version(mir_test)
115 @safe pure nothrow @nogc
116 unittest
117 {
118     static struct Foo {
119         double x;
120         alias x this;
121     }
122 
123     static assert(is(statType!Foo == double));
124 }
125 
126 version(mir_test)
127 @safe pure nothrow @nogc
128 unittest
129 {
130     import mir.complex;
131     static struct Foo {
132         Complex!double x;
133         alias x this;
134     }
135 
136     static assert(is(statType!Foo == Complex!double));
137 }
138 
139 version(mir_test)
140 @safe pure nothrow @nogc
141 unittest
142 {
143     static struct Foo {
144         real x;
145         alias x this;
146     }
147 
148     static assert(is(statType!Foo == double)); // note: this is not real
149 }
150 
151 version(mir_test)
152 @safe pure nothrow @nogc
153 unittest
154 {
155     import mir.complex;
156     static struct Foo {
157         Complex!real x;
158         alias x this;
159     }
160 
161     static assert(is(statType!Foo == Complex!real));
162 }
163 
164 version(mir_test)
165 @safe pure nothrow @nogc
166 unittest
167 {
168     static struct Foo {
169         int x;
170         alias x this;
171     }
172 
173     static assert(is(statType!Foo == double)); // note: this is not ints
174 }
175 
176 ///
177 package(mir)
178 template meanType(T)
179 {
180     import mir.math.sum: sumType;
181 
182     alias U = sumType!T;
183 
184     static if (__traits(compiles, {
185         auto temp = U.init + U.init;
186         auto a = temp / 2;
187         temp += U.init;
188     })) {
189         alias V = typeof((U.init + U.init) / 2);
190         alias meanType = statType!V;
191     } else {
192         static assert(0, "meanType: Can't calculate mean of elements of type " ~ U.stringof);
193     }
194 }
195 
196 version(mir_test)
197 @safe pure nothrow @nogc
198 unittest
199 {
200     static assert(is(meanType!(int[]) == double));
201     static assert(is(meanType!(double[]) == double));
202     static assert(is(meanType!(float[]) == float));
203 }
204 
205 version(mir_test)
206 @safe pure nothrow @nogc
207 unittest
208 {
209     import mir.complex;
210     static assert(is(meanType!(Complex!float[]) == Complex!float));
211 }
212 
213 version(mir_test)
214 @safe pure nothrow @nogc
215 unittest
216 {
217     static struct Foo {
218         float x;
219         alias x this;
220     }
221 
222     static assert(is(meanType!(Foo[]) == float));
223 }
224 
225 version(mir_test)
226 @safe pure nothrow @nogc
227 unittest
228 {
229     import mir.complex;
230     static struct Foo {
231         Complex!float x;
232         alias x this;
233     }
234 
235     static assert(is(meanType!(Foo[]) == Complex!float));
236 }
237 
238 /++
239 Output range for mean.
240 +/
241 struct MeanAccumulator(T, Summation summation)
242 {
243     ///
244     size_t count;
245     ///
246     Summator!(T, summation) summator;
247 
248     ///
249     F mean(F = T)() const @safe @property pure nothrow @nogc
250     {
251         return cast(F) summator.sum / cast(F) count;
252     }
253     
254     ///
255     F sum(F = T)() const @safe @property pure nothrow @nogc
256     {
257         return cast(F) summator.sum;
258     }
259 
260     ///
261     void put(Range)(Range r)
262         if (isIterable!Range)
263     {
264         static if (hasShape!Range)
265         {
266             count += r.elementCount;
267             summator.put(r);
268         }
269         else
270         {
271             foreach(x; r)
272             {
273                 count++;
274                 summator.put(x);
275             }
276         }
277     }
278 
279     ///
280     void put()(T x)
281     {
282         count++;
283         summator.put(x);
284     }
285     
286     ///
287     void put(F = T)(MeanAccumulator!(F, summation) m)
288     {
289         count += m.count;
290         summator.put(cast(T) m.summator);
291     }
292 }
293 
294 ///
295 version(mir_test)
296 @safe pure nothrow
297 unittest
298 {
299     import mir.ndslice.slice: sliced;
300 
301     MeanAccumulator!(double, Summation.pairwise) x;
302     x.put([0.0, 1, 2, 3, 4].sliced);
303     assert(x.mean == 2);
304     x.put(5);
305     assert(x.mean == 2.5);
306 }
307 
308 version(mir_test)
309 @safe pure nothrow
310 unittest
311 {
312     import mir.ndslice.slice: sliced;
313 
314     MeanAccumulator!(float, Summation.pairwise) x;
315     x.put([0, 1, 2, 3, 4].sliced);
316     assert(x.mean == 2);
317     assert(x.sum == 10);
318     x.put(5);
319     assert(x.mean == 2.5);
320 }
321 
322 version(mir_test)
323 @safe pure nothrow
324 unittest
325 {
326     double[] x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25];
327     double[] y = [2.0, 7.5, 5.0, 1.0, 1.5, 0.0];
328     
329     MeanAccumulator!(float, Summation.pairwise) m0;
330     m0.put(x);
331     MeanAccumulator!(float, Summation.pairwise) m1;
332     m1.put(y);
333     m0.put(m1);
334     assert(m0.mean == 29.25 / 12);
335 }
336 
337 /++
338 Computes the mean of the input.
339 
340 By default, if `F` is not floating point type or complex type, then the result
341 will have a `double` type if `F` is implicitly convertible to a floating point 
342 type or a type for which `isComplex!F` is true.
343 
344 Params:
345     F = controls type of output
346     summation = algorithm for calculating sums (default: Summation.appropriate)
347 Returns:
348     The mean of all the elements in the input, must be floating point or complex type
349 
350 See_also: 
351     $(SUBREF sum, Summation)
352 +/
353 template mean(F, Summation summation = Summation.appropriate)
354 {
355     /++
356     Params:
357         r = range, must be finite iterable
358     +/
359     @fmamath meanType!F mean(Range)(Range r)
360         if (isIterable!Range)
361     {
362         alias G = typeof(return);
363         MeanAccumulator!(G, ResolveSummationType!(summation, Range, G)) mean;
364         mean.put(r.move);
365         return mean.mean;
366     }
367     
368     /++
369     Params:
370         ar = values
371     +/
372     @fmamath meanType!F mean(scope const F[] ar...)
373     {
374         alias G = typeof(return);
375         MeanAccumulator!(G, ResolveSummationType!(summation, const(G)[], G)) mean;
376         mean.put(ar);
377         return mean.mean;
378     }
379 }
380 
381 /// ditto
382 template mean(Summation summation = Summation.appropriate)
383 {
384     /++
385     Params:
386         r = range, must be finite iterable
387     +/
388     @fmamath meanType!Range mean(Range)(Range r)
389         if (isIterable!Range)
390     {
391         alias F = typeof(return);
392         return .mean!(F, summation)(r.move);
393     }
394     
395     /++
396     Params:
397         ar = values
398     +/
399     @fmamath meanType!T mean(T)(scope const T[] ar...)
400     {
401         alias F = typeof(return);
402         return .mean!(F, summation)(ar);
403     }
404 }
405 
406 /// ditto
407 template mean(F, string summation)
408 {
409     mixin("alias mean = .mean!(F, Summation." ~ summation ~ ");");
410 }
411 
412 /// ditto
413 template mean(string summation)
414 {
415     mixin("alias mean = .mean!(Summation." ~ summation ~ ");");
416 }
417 
418 ///
419 version(mir_test)
420 @safe pure nothrow
421 unittest
422 {
423     import mir.ndslice.slice: sliced;
424     import mir.complex;
425     alias C = Complex!double;
426 
427     assert(mean([1.0, 2, 3]) == 2);
428     assert(mean([C(1, 3), C(2), C(3)]) == C(2, 1));
429     
430     assert(mean!float([0, 1, 2, 3, 4, 5].sliced(3, 2)) == 2.5);
431     
432     static assert(is(typeof(mean!float([1, 2, 3])) == float));
433 }
434 
435 /// Mean of vector
436 version(mir_test)
437 @safe pure nothrow
438 unittest
439 {
440     import mir.ndslice.slice: sliced;
441 
442     auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
443               2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;
444     assert(x.mean == 29.25 / 12);
445 }
446 
447 /// Mean of matrix
448 version(mir_test)
449 @safe pure
450 unittest
451 {
452     import mir.ndslice.fuse: fuse;
453 
454     auto x = [
455         [0.0, 1.0, 1.5, 2.0, 3.5, 4.25],
456         [2.0, 7.5, 5.0, 1.0, 1.5, 0.0]
457     ].fuse;
458 
459     assert(x.mean == 29.25 / 12);
460 }
461 
462 /// Column mean of matrix
463 version(mir_test)
464 @safe pure
465 unittest
466 {
467     import mir.ndslice.fuse: fuse;
468     import mir.ndslice.topology: alongDim, byDim, map;
469     import mir.algorithm.iteration: all;
470     import mir.math.common: approxEqual;
471 
472     auto x = [
473         [0.0, 1.0, 1.5, 2.0, 3.5, 4.25],
474         [2.0, 7.5, 5.0, 1.0, 1.5, 0.0]
475     ].fuse;
476     auto result = [1, 4.25, 3.25, 1.5, 2.5, 2.125];
477 
478     // Use byDim or alongDim with map to compute mean of row/column.
479     assert(x.byDim!1.map!mean.all!approxEqual(result));
480     assert(x.alongDim!0.map!mean.all!approxEqual(result));
481 
482     // FIXME
483     // Without using map, computes the mean of the whole slice
484     // assert(x.byDim!1.mean == x.sliced.mean);
485     // assert(x.alongDim!0.mean == x.sliced.mean);
486 }
487 
488 /// Can also set algorithm or output type
489 version(mir_test)
490 @safe pure nothrow
491 unittest
492 {
493     import mir.ndslice.slice: sliced;
494     import mir.ndslice.topology: repeat;
495 
496     //Set sum algorithm or output type
497 
498     auto a = [1, 1e100, 1, -1e100].sliced;
499 
500     auto x = a * 10_000;
501 
502     assert(x.mean!"kbn" == 20_000 / 4);
503     assert(x.mean!"kb2" == 20_000 / 4);
504     assert(x.mean!"precise" == 20_000 / 4);
505     assert(x.mean!(double, "precise") == 20_000.0 / 4);
506 
507     auto y = uint.max.repeat(3);
508     assert(y.mean!ulong == 12884901885 / 3);
509 }
510 
511 /++
512 For integral slices, pass output type as template parameter to ensure output
513 type is correct.
514 +/
515 version(mir_test)
516 @safe pure nothrow
517 unittest
518 {
519     import mir.math.common: approxEqual;
520     import mir.ndslice.slice: sliced;
521 
522     auto x = [0, 1, 1, 2, 4, 4,
523               2, 7, 5, 1, 2, 0].sliced;
524 
525     auto y = x.mean;
526     assert(y.approxEqual(29.0 / 12, 1.0e-10));
527     static assert(is(typeof(y) == double));
528 
529     assert(x.mean!float.approxEqual(29f / 12, 1.0e-10));
530 }
531 
532 /++
533 Mean works for complex numbers and other user-defined types (provided they
534 can be converted to a floating point or complex type)
535 +/
536 version(mir_test)
537 @safe pure nothrow
538 unittest
539 {
540     import mir.complex.math: approxEqual;
541     import mir.ndslice.slice: sliced;
542     import mir.complex;
543     alias C = Complex!double;
544 
545     auto x = [C(1.0, 2), C(2, 3), C(3, 4), C(4, 5)].sliced;
546     assert(x.mean.approxEqual(C(2.5, 3.5)));
547 }
548 
549 /// Compute mean tensors along specified dimention of tensors
550 version(mir_test)
551 @safe pure nothrow
552 unittest
553 {
554     import mir.ndslice: alongDim, iota, as, map;
555     /++
556       [[0,1,2],
557        [3,4,5]]
558      +/
559     auto x = iota(2, 3).as!double;
560     assert(x.mean == (5.0 / 2.0));
561 
562     auto m0 = [(0.0+3.0)/2.0, (1.0+4.0)/2.0, (2.0+5.0)/2.0];
563     assert(x.alongDim!0.map!mean == m0);
564     assert(x.alongDim!(-2).map!mean == m0);
565 
566     auto m1 = [(0.0+1.0+2.0)/3.0, (3.0+4.0+5.0)/3.0];
567     assert(x.alongDim!1.map!mean == m1);
568     assert(x.alongDim!(-1).map!mean == m1);
569 
570     assert(iota(2, 3, 4, 5).as!double.alongDim!0.map!mean == iota([3, 4, 5], 3 * 4 * 5 / 2));
571 }
572 
573 /// Arbitrary mean
574 version(mir_test)
575 @safe pure nothrow @nogc
576 unittest
577 {
578     assert(mean(1.0, 2, 3) == 2);
579     assert(mean!float(1, 2, 3) == 2);
580 }
581 
582 version(mir_test)
583 @safe pure nothrow
584 unittest
585 {
586     assert([1.0, 2, 3, 4].mean == 2.5);
587 }
588 
589 version(mir_test)
590 @safe pure nothrow
591 unittest
592 {
593     import mir.algorithm.iteration: all;
594     import mir.math.common: approxEqual;
595     import mir.ndslice.topology: iota, alongDim, map;
596 
597     auto x = iota([2, 2], 1);
598     auto y = x.alongDim!1.map!mean;
599     assert(y.all!approxEqual([1.5, 3.5]));
600     static assert(is(meanType!(typeof(y)) == double));
601 }
602 
603 version(mir_test)
604 @safe pure nothrow @nogc
605 unittest
606 {
607     import mir.ndslice.slice: sliced;
608 
609     static immutable x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
610                           2.0, 7.5, 5.0, 1.0, 1.5, 0.0];
611 
612     assert(x.sliced.mean == 29.25 / 12);
613     assert(x.sliced.mean!float == 29.25 / 12);
614 }
615 
616 ///
617 package(mir)
618 template hmeanType(T)
619 {
620     import mir.math.sum: sumType;
621     
622     alias U = sumType!T;
623 
624     static if (__traits(compiles, {
625         U t = U.init + cast(U) 1; //added for when U.init = 0
626         auto temp = cast(U) 1 / t + cast(U) 1 / t;
627     })) {
628         alias V = typeof(cast(U) 1 / ((cast(U) 1 / U.init + cast(U) 1 / U.init) / cast(U) 2));
629         alias hmeanType = statType!V;
630     } else {
631         static assert(0, "hmeanType: Can't calculate hmean of elements of type " ~ U.stringof);
632     }
633 }
634 
635 version(mir_test)
636 @safe pure nothrow @nogc
637 unittest
638 {
639     import mir.complex;
640     static assert(is(hmeanType!(int[]) == double));
641     static assert(is(hmeanType!(double[]) == double));
642     static assert(is(hmeanType!(float[]) == float)); 
643     static assert(is(hmeanType!(Complex!float[]) == Complex!float));    
644 }
645 
646 version(mir_test)
647 @safe pure nothrow @nogc
648 unittest
649 {
650     import mir.complex;
651     static struct Foo {
652         float x;
653         alias x this;
654     }
655     
656     static struct Bar {
657         Complex!float x;
658         alias x this;
659     }
660 
661     static assert(is(hmeanType!(Foo[]) == float));
662     static assert(is(hmeanType!(Bar[]) == Complex!float));
663 }
664 
665 /++
666 Computes the harmonic mean of the input.
667 
668 By default, if `F` is not floating point type or complex type, then the result
669 will have a `double` type if `F` is implicitly convertible to a floating point 
670 type or a type for which `isComplex!F` is true.
671 
672 Params:
673     F = controls type of output
674     summation = algorithm for calculating sums (default: Summation.appropriate)
675 Returns:
676     harmonic mean of all the elements of the input, must be floating point or complex type
677 
678 See_also: 
679     $(SUBREF sum, Summation)
680 +/
681 template hmean(F, Summation summation = Summation.appropriate)
682 {
683     /++
684     Params:
685         r = range
686     +/
687     @fmamath hmeanType!F hmean(Range)(Range r)
688         if (isIterable!Range)
689     {
690         import mir.ndslice.topology: map;
691 
692         alias G = typeof(return);
693         auto numerator = cast(G) 1;
694 
695         static if (summation == Summation.fast && __traits(compiles, r.move.map!"numerator / a"))
696         {
697             return numerator / r.move.map!"numerator / a".mean!(G, summation);
698         }
699         else
700         {
701             MeanAccumulator!(G, ResolveSummationType!(summation, Range, G)) imean;
702             foreach (e; r)
703                 imean.put(numerator / e);
704             return numerator / imean.mean;
705         }
706     }
707    
708     /++
709     Params:
710         ar = values
711     +/
712     @fmamath hmeanType!F hmean(scope const F[] ar...)
713     {
714         alias G = typeof(return);
715 
716         auto numerator = cast(G) 1;
717 
718         static if (summation == Summation.fast && __traits(compiles, ar.map!"numerator / a"))
719         {
720             return numerator / ar.map!"numerator / a".mean!(G, summation);
721         }
722         else
723         {
724             MeanAccumulator!(G, ResolveSummationType!(summation, const(G)[], G)) imean;
725             foreach (e; ar)
726                 imean.put(numerator / e);
727             return numerator / imean.mean;
728         }
729     }
730 }
731 
732 /// ditto
733 template hmean(Summation summation = Summation.appropriate)
734 {
735     /++
736     Params:
737         r = range
738     +/
739     @fmamath hmeanType!Range hmean(Range)(Range r)
740         if (isIterable!Range)
741     {
742         alias F = typeof(return);
743         return .hmean!(F, summation)(r.move);
744     }
745     
746     /++
747     Params:
748         ar = values
749     +/
750     @fmamath hmeanType!T hmean(T)(scope const T[] ar...)
751     {
752         alias F = typeof(return);
753         return .hmean!(F, summation)(ar);
754     }
755 }
756 
757 /// ditto
758 template hmean(F, string summation)
759 {
760     mixin("alias hmean = .hmean!(F, Summation." ~ summation ~ ");");
761 }
762 
763 /// ditto
764 template hmean(string summation)
765 {
766     mixin("alias hmean = .hmean!(Summation." ~ summation ~ ");");
767 }
768 
769 /// Harmonic mean of vector
770 version(mir_test)
771 @safe pure nothrow
772 unittest
773 {
774     import mir.math.common: approxEqual;
775     import mir.ndslice.slice: sliced;
776 
777     auto x = [20.0, 100.0, 2000.0, 10.0, 5.0, 2.0].sliced;
778 
779     assert(x.hmean.approxEqual(6.97269));
780 }
781 
782 /// Harmonic mean of matrix
783 version(mir_test)
784 pure @safe
785 unittest
786 {
787     import mir.math.common: approxEqual;
788     import mir.ndslice.fuse: fuse;
789 
790     auto x = [
791         [20.0, 100.0, 2000.0], 
792         [10.0, 5.0, 2.0]
793     ].fuse;
794 
795     assert(x.hmean.approxEqual(6.97269));
796 }
797 
798 /// Column harmonic mean of matrix
799 version(mir_test)
800 pure @safe
801 unittest
802 {
803     import mir.algorithm.iteration: all;
804     import mir.math.common: approxEqual;
805     import mir.ndslice: fuse;
806     import mir.ndslice.topology: alongDim, byDim, map;
807 
808     auto x = [
809         [20.0, 100.0, 2000.0],
810         [ 10.0, 5.0, 2.0]
811     ].fuse;
812 
813     auto y = [13.33333, 9.52381, 3.996004];
814 
815     // Use byDim or alongDim with map to compute mean of row/column.
816     assert(x.byDim!1.map!hmean.all!approxEqual(y));
817     assert(x.alongDim!0.map!hmean.all!approxEqual(y));
818 }
819 
820 /// Can also pass arguments to hmean
821 version(mir_test)
822 pure @safe nothrow
823 unittest
824 {
825     import mir.math.common: approxEqual;
826     import mir.ndslice.topology: repeat;
827     import mir.ndslice.slice: sliced;
828 
829     //Set sum algorithm or output type
830     auto x = [1, 1e-100, 1, -1e-100].sliced;
831 
832     assert(x.hmean!"kb2".approxEqual(2));
833     assert(x.hmean!"precise".approxEqual(2));
834     assert(x.hmean!(double, "precise").approxEqual(2));
835 
836     //Provide the summation type
837     assert(float.max.repeat(3).hmean!double.approxEqual(float.max));
838 }
839 
840 /++
841 For integral slices, pass output type as template parameter to ensure output
842 type is correct. 
843 +/
844 version(mir_test)
845 @safe pure nothrow
846 unittest
847 {
848     import mir.math.common: approxEqual;
849     import mir.ndslice.slice: sliced;
850 
851     auto x = [20, 100, 2000, 10, 5, 2].sliced;
852 
853     auto y = x.hmean;
854 
855     assert(y.approxEqual(6.97269));
856     static assert(is(typeof(y) == double));
857 
858     assert(x.hmean!float.approxEqual(6.97269));
859 }
860 
861 /++
862 hmean works for complex numbers and other user-defined types (provided they
863 can be converted to a floating point or complex type)
864 +/
865 version(mir_test)
866 @safe pure nothrow
867 unittest
868 {
869     import mir.complex.math: approxEqual;
870     import mir.ndslice.slice: sliced;
871     import mir.complex;
872     alias C = Complex!double;
873 
874     auto x = [C(1, 2), C(2, 3), C(3, 4), C(4, 5)].sliced;
875     assert(x.hmean.approxEqual(C(1.97110904, 3.14849332)));
876 }
877 
878 /// Arbitrary harmonic mean
879 version(mir_test)
880 @safe pure nothrow @nogc
881 unittest
882 {
883     import mir.math.common: approxEqual;
884     import mir.ndslice.slice: sliced;
885 
886     auto x = hmean(20.0, 100, 2000, 10, 5, 2);
887     assert(x.approxEqual(6.97269));
888     
889     auto y = hmean!float(20, 100, 2000, 10, 5, 2);
890     assert(y.approxEqual(6.97269));
891 }
892 
893 version(mir_test)
894 @safe pure nothrow @nogc
895 unittest
896 {
897     import mir.math.common: approxEqual;
898     import mir.ndslice.slice: sliced;
899 
900     static immutable x = [20.0, 100.0, 2000.0, 10.0, 5.0, 2.0];
901 
902     assert(x.sliced.hmean.approxEqual(6.97269));
903     assert(x.sliced.hmean!float.approxEqual(6.97269));
904 }
905 
906 private
907 F nthroot(F)(in F x, in size_t n)
908     if (isFloatingPoint!F)
909 {
910     import mir.math.common: sqrt, pow;
911 
912     if (n > 2) {
913         return pow(x, cast(F) 1 / cast(F) n);
914     } else if (n == 2) {
915         return sqrt(x);
916     } else if (n == 1) {
917         return x;
918     } else {
919         return cast(F) 1;
920     }
921 }
922 
923 version(mir_test)
924 @safe pure nothrow @nogc
925 unittest
926 {
927     import mir.math.common: approxEqual;
928 
929     assert(nthroot(9.0, 0).approxEqual(1));
930     assert(nthroot(9.0, 1).approxEqual(9));
931     assert(nthroot(9.0, 2).approxEqual(3));
932     assert(nthroot(9.5, 2).approxEqual(3.08220700));
933     assert(nthroot(9.0, 3).approxEqual(2.08008382));
934 }
935 
936 /++
937 Output range for gmean.
938 +/
939 struct GMeanAccumulator(T) 
940     if (isMutable!T && isFloatingPoint!T)
941 {
942     import mir.math.numeric: ProdAccumulator;
943 
944     ///
945     size_t count;
946     ///
947     ProdAccumulator!T prodAccumulator;
948 
949     ///
950     F gmean(F = T)() const @property
951         if (isFloatingPoint!F)
952     {
953         import mir.math.common: exp2;
954 
955         return nthroot(cast(F) prodAccumulator.mantissa, count) * exp2(cast(F) prodAccumulator.exp / count);
956     }
957 
958     ///
959     void put(Range)(Range r)
960         if (isIterable!Range)
961     {
962         static if (hasShape!Range)
963         {
964             count += r.elementCount;
965             prodAccumulator.put(r);
966         }
967         else
968         {
969             foreach(x; r)
970             {
971                 count++;
972                 prodAccumulator.put(x);
973             }
974         }
975     }
976 
977     ///
978     void put()(T x)
979     {
980         count++;
981         prodAccumulator.put(x);
982     }
983 }
984 
985 ///
986 version(mir_test)
987 @safe pure nothrow
988 unittest
989 {
990     import mir.math.common: approxEqual;
991     import mir.ndslice.slice: sliced;
992 
993     GMeanAccumulator!double x;
994     x.put([1.0, 2, 3, 4].sliced);
995     assert(x.gmean.approxEqual(2.21336384));
996     x.put(5);
997     assert(x.gmean.approxEqual(2.60517108));
998 }
999 
1000 version(mir_test)
1001 @safe pure nothrow
1002 unittest
1003 {
1004     import mir.math.common: approxEqual;
1005     import mir.ndslice.slice: sliced;
1006 
1007     GMeanAccumulator!float x;
1008     x.put([1, 2, 3, 4].sliced);
1009     assert(x.gmean.approxEqual(2.21336384));
1010     x.put(5);
1011     assert(x.gmean.approxEqual(2.60517108));
1012 }
1013 
1014 ///
1015 package(mir)
1016 template gmeanType(T)
1017 {
1018     import mir.math.numeric: prodType;
1019     
1020     alias U = prodType!T;
1021 
1022     static if (__traits(compiles, {
1023         auto temp = U.init * U.init;
1024         auto a = nthroot(temp, 2);
1025         temp *= U.init;
1026     })) {
1027         alias V = typeof(nthroot(U.init * U.init, 2));
1028         alias gmeanType = statType!(V, false);
1029     } else {
1030         static assert(0, "gmeanType: Can't calculate gmean of elements of type " ~ U.stringof);
1031     }
1032 }
1033 
1034 version(mir_test)
1035 @safe pure nothrow @nogc
1036 unittest
1037 {
1038     static assert(is(gmeanType!int == double));
1039     static assert(is(gmeanType!double == double));
1040     static assert(is(gmeanType!float == float));
1041     static assert(is(gmeanType!(int[]) == double));
1042     static assert(is(gmeanType!(double[]) == double));
1043     static assert(is(gmeanType!(float[]) == float));    
1044 }
1045 
1046 /++
1047 Computes the geometric average of the input.
1048 
1049 By default, if `F` is not floating point type, then the result will have a 
1050 `double` type if `F` is implicitly convertible to a floating point type.
1051 
1052 Params:
1053     r = range, must be finite iterable
1054 Returns:
1055     The geometric average of all the elements in the input, must be floating point type
1056 
1057 See_also: 
1058     $(SUBREF numeric, prod)
1059 +/
1060 @fmamath gmeanType!F gmean(F, Range)(Range r)
1061     if (isFloatingPoint!F && isIterable!Range)
1062 {
1063     alias G = typeof(return);
1064     GMeanAccumulator!G gmean;
1065     gmean.put(r.move);
1066     return gmean.gmean;
1067 }
1068     
1069 /// ditto
1070 @fmamath gmeanType!Range gmean(Range)(Range r)
1071     if (isIterable!Range)
1072 {
1073     alias G = typeof(return);
1074     return .gmean!(G, Range)(r.move);
1075 }
1076 
1077 /++
1078 Params:
1079     ar = values
1080 +/
1081 @fmamath gmeanType!F gmean(F)(scope const F[] ar...)
1082     if (isFloatingPoint!F)
1083 {
1084     alias G = typeof(return);
1085     GMeanAccumulator!G gmean;
1086     gmean.put(ar);
1087     return gmean.gmean;
1088 }
1089 
1090 ///
1091 version(mir_test)
1092 @safe pure nothrow
1093 unittest
1094 {
1095     import mir.math.common: approxEqual;
1096     import mir.ndslice.slice: sliced;
1097 
1098     assert(gmean([1.0, 2, 3]).approxEqual(1.81712059));
1099     
1100     assert(gmean!float([1, 2, 3, 4, 5, 6].sliced(3, 2)).approxEqual(2.99379516));
1101     
1102     static assert(is(typeof(gmean!float([1, 2, 3])) == float));
1103 }
1104 
1105 /// Geometric mean of vector
1106 version(mir_test)
1107 @safe pure nothrow
1108 unittest
1109 {
1110     import mir.math.common: approxEqual;
1111     import mir.ndslice.slice: sliced;
1112 
1113     auto x = [3.0, 1.0, 1.5, 2.0, 3.5, 4.25,
1114               2.0, 7.5, 5.0, 1.0, 1.5, 2.0].sliced;
1115 
1116     assert(x.gmean.approxEqual(2.36178395));
1117 }
1118 
1119 /// Geometric mean of matrix
1120 version(mir_test)
1121 @safe pure
1122 unittest
1123 {
1124     import mir.math.common: approxEqual;
1125     import mir.ndslice.fuse: fuse;
1126 
1127     auto x = [
1128         [3.0, 1.0, 1.5, 2.0, 3.5, 4.25],
1129         [2.0, 7.5, 5.0, 1.0, 1.5, 2.0]
1130     ].fuse;
1131 
1132     assert(x.gmean.approxEqual(2.36178395));
1133 }
1134 
1135 /// Column gmean of matrix
1136 version(mir_test)
1137 @safe pure
1138 unittest
1139 {
1140     import mir.algorithm.iteration: all;
1141     import mir.math.common: approxEqual;
1142     import mir.ndslice.fuse: fuse;
1143     import mir.ndslice.topology: alongDim, byDim, map;
1144 
1145     auto x = [
1146         [3.0, 1.0, 1.5, 2.0, 3.5, 4.25],
1147         [2.0, 7.5, 5.0, 1.0, 1.5, 2.0]
1148     ].fuse;
1149     auto result = [2.44948974, 2.73861278, 2.73861278, 1.41421356, 2.29128784, 2.91547594];
1150 
1151     // Use byDim or alongDim with map to compute mean of row/column.
1152     assert(x.byDim!1.map!gmean.all!approxEqual(result));
1153     assert(x.alongDim!0.map!gmean.all!approxEqual(result));
1154 
1155     // FIXME
1156     // Without using map, computes the mean of the whole slice
1157     // assert(x.byDim!1.gmean.all!approxEqual(result));
1158     // assert(x.alongDim!0.gmean.all!approxEqual(result));
1159 }
1160 
1161 /// Can also set output type
1162 version(mir_test)
1163 @safe pure nothrow
1164 unittest
1165 {
1166     import mir.math.common: approxEqual;
1167     import mir.ndslice.slice: sliced;
1168     import mir.ndslice.topology: repeat;
1169 
1170     auto x = [5120.0, 7340032, 32, 3758096384].sliced;
1171 
1172     assert(x.gmean!float.approxEqual(259281.45295212));
1173 
1174     auto y = uint.max.repeat(2);
1175     assert(y.gmean!float.approxEqual(cast(float) uint.max));
1176 }
1177 
1178 /++
1179 For integral slices, pass output type as template parameter to ensure output
1180 type is correct.
1181 +/
1182 version(mir_test)
1183 @safe pure nothrow
1184 unittest
1185 {
1186     import mir.math.common: approxEqual;
1187     import mir.ndslice.slice: sliced;
1188 
1189     auto x = [5, 1, 1, 2, 4, 4,
1190               2, 7, 5, 1, 2, 10].sliced;
1191 
1192     auto y = x.gmean;
1193     static assert(is(typeof(y) == double));
1194     
1195     assert(x.gmean!float.approxEqual(2.79160522));
1196 }
1197 
1198 /// gean works for user-defined types, provided the nth root can be taken for them
1199 version(mir_test)
1200 @safe pure nothrow
1201 unittest
1202 {
1203     static struct Foo {
1204         float x;
1205         alias x this;
1206     }
1207 
1208     import mir.math.common: approxEqual;
1209     import mir.ndslice.slice: sliced;
1210 
1211     auto x = [Foo(1.0), Foo(2.0), Foo(3.0)].sliced;
1212     assert(x.gmean.approxEqual(1.81712059));
1213 }
1214 
1215 /// Compute gmean tensors along specified dimention of tensors
1216 version(mir_test)
1217 @safe pure
1218 unittest
1219 {
1220     import mir.algorithm.iteration: all;
1221     import mir.math.common: approxEqual;
1222     import mir.ndslice.fuse: fuse;
1223     import mir.ndslice.topology: alongDim, iota, map;
1224     
1225     auto x = [
1226         [1.0, 2, 3],
1227         [4.0, 5, 6]
1228     ].fuse;
1229 
1230     assert(x.gmean.approxEqual(2.99379516));
1231 
1232     auto result0 = [2.0, 3.16227766, 4.24264069];
1233     assert(x.alongDim!0.map!gmean.all!approxEqual(result0));
1234     assert(x.alongDim!(-2).map!gmean.all!approxEqual(result0));
1235 
1236     auto result1 = [1.81712059, 4.93242414];
1237     assert(x.alongDim!1.map!gmean.all!approxEqual(result1));
1238     assert(x.alongDim!(-1).map!gmean.all!approxEqual(result1));
1239 
1240     auto y = [
1241         [
1242             [1.0, 2, 3],
1243             [4.0, 5, 6]
1244         ], [
1245             [7.0, 8, 9],
1246             [10.0, 9, 10]
1247         ]
1248     ].fuse;
1249     
1250     auto result3 = [
1251         [2.64575131, 4.0,        5.19615242],
1252         [6.32455532, 6.70820393, 7.74596669]
1253     ];
1254     assert(y.alongDim!0.map!gmean.all!approxEqual(result3));
1255 }
1256 
1257 /// Arbitrary gmean
1258 version(mir_test)
1259 @safe pure nothrow @nogc
1260 unittest
1261 {
1262     import mir.math.common: approxEqual;
1263 
1264     assert(gmean(1.0, 2, 3).approxEqual(1.81712059));
1265     assert(gmean!float(1, 2, 3).approxEqual(1.81712059));
1266 }
1267 
1268 version(mir_test)
1269 @safe pure nothrow
1270 unittest
1271 {
1272     import mir.math.common: approxEqual;
1273 
1274     assert([1.0, 2, 3, 4].gmean.approxEqual(2.21336384));
1275 }
1276 
1277 version(mir_test)
1278 @safe pure nothrow
1279 unittest
1280 {
1281     import mir.math.common: approxEqual;
1282 
1283     assert(gmean([1, 2, 3]).approxEqual(1.81712059));
1284 }
1285 
1286 version(mir_test)
1287 @safe pure nothrow @nogc
1288 unittest
1289 {
1290     import mir.math.common: approxEqual;
1291     import mir.ndslice.slice: sliced;
1292 
1293     static immutable x = [3.0, 1.0, 1.5, 2.0, 3.5, 4.25,
1294                           2.0, 7.5, 5.0, 1.0, 1.5, 2.0];
1295 
1296     assert(x.sliced.gmean.approxEqual(2.36178395));
1297     assert(x.sliced.gmean!float.approxEqual(2.36178395));
1298 }
1299 
1300 /++
1301 Computes the median of `slice`.
1302 
1303 By default, if `F` is not floating point type or complex type, then the result
1304 will have a `double` type if `F` is implicitly convertible to a floating point 
1305 type or a type for which `isComplex!F` is true.
1306 
1307 Can also pass a boolean variable, `allowModify`, that allows the input slice to
1308 be modified. By default, a reference-counted copy is made. 
1309 
1310 Params:
1311     F = output type
1312     allowModify = Allows the input slice to be modified, default is false
1313 Returns:
1314     the median of the slice
1315 
1316 See_also: 
1317     $(SUBREF stat, mean)
1318 +/
1319 template median(F, bool allowModify = false)
1320 {
1321     /++
1322     Params:
1323         slice = slice
1324     +/
1325     @nogc
1326     meanType!F median(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice)
1327     {
1328         static assert (!allowModify ||
1329                        isMutable!(slice.DeepElement),
1330                            "allowModify must be false or the input must be mutable");
1331         alias G = typeof(return);
1332         size_t len = slice.elementCount;
1333         assert(len > 0, "median: slice must have length greater than zero");
1334 
1335         import mir.ndslice.topology: as, flattened;
1336 
1337         static if (!allowModify) {
1338             import mir.ndslice.allocation: rcslice;
1339             
1340             if (len > 2) {
1341                 auto view = slice.lightScope;
1342                 auto val = view.as!(Unqual!(slice.DeepElement)).rcslice;
1343                 auto temp = val.lightScope.flattened;
1344                 return .median!(G, true)(temp);
1345             } else {
1346                 return mean!G(slice);
1347             }
1348         } else {
1349             import mir.ndslice.sorting: partitionAt;
1350             
1351             auto temp = slice.flattened;
1352 
1353             if (len > 5) {
1354                 size_t half_n = len / 2;
1355                 partitionAt(temp, half_n);
1356                 if (len % 2 == 1) {
1357                     return cast(G) temp[half_n];
1358                 } else {
1359                     //move largest value in first half of slice to half_n - 1
1360                     partitionAt(temp[0 .. half_n], half_n - 1);
1361                     return (temp[half_n - 1] + temp[half_n]) / cast(G) 2;
1362                 }
1363             } else {
1364                 return smallMedianImpl!(G)(temp);
1365             }
1366         }
1367     }
1368 }
1369 
1370 /// ditto
1371 template median(bool allowModify = false)
1372 {
1373     /// ditto
1374     meanType!(Slice!(Iterator, N, kind))
1375         median(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice)
1376     {
1377         static assert (!allowModify ||
1378                        isMutable!(DeepElementType!(Slice!(Iterator, N, kind))),
1379                            "allowModify must be false or the input must be mutable");
1380         alias F = typeof(return);
1381         return .median!(F, allowModify)(slice.move);
1382     }
1383 }
1384 
1385 /++
1386 Params:
1387     ar = array
1388 +/
1389 meanType!(T[]) median(T)(scope const T[] ar...)
1390 {
1391     import mir.ndslice.slice: sliced;
1392 
1393     alias F = typeof(return);
1394     return median!(F, false)(ar.sliced);
1395 }
1396 
1397 /++
1398 Params:
1399     withAsSlice = input that satisfies hasAsSlice
1400 +/
1401 auto median(T)(T withAsSlice)
1402     if (hasAsSlice!T)
1403 {
1404     return median(withAsSlice.asSlice);
1405 }
1406 
1407 /// Median of vector
1408 version(mir_test)
1409 @safe pure nothrow
1410 unittest
1411 {
1412     import mir.ndslice.slice: sliced;
1413 
1414     auto x0 = [9.0, 1, 0, 2, 3, 4, 6, 8, 7, 10, 5].sliced;
1415     assert(x0.median == 5);
1416 
1417     auto x1 = [9.0, 1, 0, 2, 3, 4, 6, 8, 7, 10].sliced;
1418     assert(x1.median == 5);
1419 }
1420 
1421 /// Median of dynamic array
1422 version(mir_test)
1423 @safe pure nothrow
1424 unittest
1425 {
1426     auto x0 = [9.0, 1, 0, 2, 3, 4, 6, 8, 7, 10, 5];
1427     assert(x0.median == 5);
1428 
1429     auto x1 = [9.0, 1, 0, 2, 3, 4, 6, 8, 7, 10];
1430     assert(x1.median == 5);
1431 }
1432 
1433 /// Median of matrix
1434 version(mir_test)
1435 @safe pure
1436 unittest
1437 {
1438     import mir.ndslice.fuse: fuse;
1439 
1440     auto x0 = [
1441         [9.0, 1, 0, 2,  3], 
1442         [4.0, 6, 8, 7, 10]
1443     ].fuse;
1444 
1445     assert(x0.median == 5);
1446 }
1447 
1448 /// Row median of matrix
1449 version(mir_test)
1450 @safe pure
1451 unittest
1452 {
1453     import mir.algorithm.iteration: all;
1454     import mir.math.common: approxEqual;
1455     import mir.ndslice.fuse: fuse;
1456     import mir.ndslice.slice: sliced;
1457     import mir.ndslice.topology: alongDim, byDim, map;
1458 
1459     auto x = [
1460         [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], 
1461         [2.0, 7.5, 5.0, 1.0, 1.5, 0.0]
1462     ].fuse;
1463 
1464     auto result = [1.75, 1.75].sliced;
1465 
1466     // Use byDim or alongDim with map to compute median of row/column.
1467     assert(x.byDim!0.map!median.all!approxEqual(result));
1468     assert(x.alongDim!1.map!median.all!approxEqual(result));
1469 }
1470 
1471 /// Can allow original slice to be modified or set output type
1472 version(mir_test)
1473 @safe pure nothrow
1474 unittest
1475 {
1476     import mir.ndslice.slice: sliced;
1477 
1478     auto x0 = [9.0, 1, 0, 2, 3, 4, 6, 8, 7, 10, 5].sliced;
1479     assert(x0.median!true == 5);
1480     
1481     auto x1 = [9, 1, 0, 2, 3, 4, 6, 8, 7, 10].sliced;
1482     assert(x1.median!(float, true) == 5);
1483 }
1484 
1485 /// Arbitrary median
1486 version(mir_test)
1487 @safe pure nothrow
1488 unittest
1489 {
1490     assert(median(0, 1, 2, 3, 4) == 2);
1491 }
1492 
1493 // @nogc test
1494 version(mir_test)
1495 @safe pure nothrow @nogc
1496 unittest
1497 {
1498     import mir.ndslice.slice: sliced;
1499 
1500     static immutable x = [9.0, 1, 0, 2, 3];
1501     assert(x.sliced.median == 2);
1502 }
1503 
1504 // withAsSlice test
1505 version(mir_test)
1506 @safe pure nothrow @nogc
1507 unittest
1508 {
1509     import mir.math.common: approxEqual;
1510     import mir.rc.array: RCArray;
1511 
1512     static immutable a = [9.0, 1, 0, 2, 3, 4, 6, 8, 7, 10, 5];
1513 
1514     auto x = RCArray!double(11);
1515     foreach(i, ref e; x)
1516         e = a[i];
1517 
1518     assert(x.median.approxEqual(5));
1519 }
1520 
1521 /++
1522 For integral slices, can pass output type as template parameter to ensure output
1523 type is correct
1524 +/
1525 version(mir_test)
1526 @safe pure nothrow
1527 unittest
1528 {
1529     import mir.ndslice.slice: sliced;
1530 
1531     auto x = [9, 1, 0, 2, 3, 4, 6, 8, 7, 10].sliced;
1532     assert(x.median!float == 5f);
1533 
1534     auto y = x.median;
1535     assert(y == 5.0);
1536     static assert(is(typeof(y) == double));
1537 }
1538 
1539 // additional logic tests
1540 version(mir_test)
1541 @safe pure nothrow
1542 unittest
1543 {
1544     import mir.math.common: approxEqual;
1545     import mir.ndslice.slice: sliced;
1546 
1547     auto x = [3, 3, 2, 0, 2, 0].sliced;
1548     assert(x.median!float.approxEqual(2));
1549 
1550     x[] = [2, 2, 4, 0, 4, 3];
1551     assert(x.median!float.approxEqual(2.5));
1552     x[] = [1, 4, 5, 4, 4, 3];
1553     assert(x.median!float.approxEqual(4));
1554     x[] = [1, 5, 3, 5, 2, 2];
1555     assert(x.median!float.approxEqual(2.5));
1556     x[] = [4, 3, 2, 1, 4, 5];
1557     assert(x.median!float.approxEqual(3.5));
1558     x[] = [4, 5, 3, 5, 5, 4];
1559     assert(x.median!float.approxEqual(4.5));
1560     x[] = [3, 3, 3, 0, 0, 1];
1561     assert(x.median!float.approxEqual(2));
1562     x[] = [4, 2, 2, 1, 2, 5];
1563     assert(x.median!float.approxEqual(2));
1564     x[] = [2, 3, 1, 4, 5, 5];
1565     assert(x.median!float.approxEqual(3.5));
1566     x[] = [1, 1, 4, 5, 5, 5];
1567     assert(x.median!float.approxEqual(4.5));
1568     x[] = [2, 4, 0, 5, 1, 0];
1569     assert(x.median!float.approxEqual(1.5));
1570     x[] = [3, 5, 2, 5, 4, 2];
1571     assert(x.median!float.approxEqual(3.5));
1572     x[] = [3, 5, 4, 1, 4, 3];
1573     assert(x.median!float.approxEqual(3.5));
1574     x[] = [4, 2, 0, 3, 1, 3];
1575     assert(x.median!float.approxEqual(2.5));
1576     x[] = [100, 4, 5, 0, 5, 1];
1577     assert(x.median!float.approxEqual(4.5));
1578     x[] = [100, 5, 4, 0, 5, 1];
1579     assert(x.median!float.approxEqual(4.5));
1580     x[] = [100, 5, 4, 0, 1, 5];
1581     assert(x.median!float.approxEqual(4.5));
1582     x[] = [4, 5, 100, 1, 5, 0];
1583     assert(x.median!float.approxEqual(4.5));
1584     x[] = [0, 1, 2, 2, 3, 4];
1585     assert(x.median!float.approxEqual(2));
1586     x[] = [0, 2, 2, 3, 4, 5];
1587     assert(x.median!float.approxEqual(2.5));
1588 }
1589 
1590 // smallMedianImpl tests
1591 version(mir_test)
1592 @safe pure nothrow
1593 unittest
1594 {
1595     import mir.math.common: approxEqual;
1596     import mir.ndslice.slice: sliced;
1597 
1598     auto x0 = [9.0, 1, 0, 2, 3].sliced;
1599     assert(x0.median.approxEqual(2));
1600 
1601     auto x1 = [9.0, 1, 0, 2].sliced;
1602     assert(x1.median.approxEqual(1.5));
1603     
1604     auto x2 = [9.0, 0, 1].sliced;
1605     assert(x2.median.approxEqual(1));
1606     
1607     auto x3 = [1.0, 0].sliced;
1608     assert(x3.median.approxEqual(0.5));
1609     
1610     auto x4 = [1.0].sliced;
1611     assert(x4.median.approxEqual(1));
1612 }
1613 
1614 // Check issue #328 fixed
1615 version(mir_test)
1616 @safe pure nothrow
1617 unittest {
1618     import mir.ndslice.topology: iota;
1619 
1620     auto x = iota(18);
1621     auto y = median(x);
1622     assert(y == 8.5);
1623 }
1624 
1625 private pure @trusted nothrow @nogc
1626 F smallMedianImpl(F, Iterator)(Slice!Iterator slice) 
1627 {
1628     size_t n = slice.elementCount;
1629 
1630     assert(n > 0, "smallMedianImpl: slice must have elementCount greater than 0");
1631     assert(n <= 5, "smallMedianImpl: slice must have elementCount of 5 or less");
1632 
1633     import mir.functional: naryFun;
1634     import mir.ndslice.sorting: medianOf;
1635     import mir.utility: swapStars;
1636 
1637     auto sliceI0 = slice._iterator;
1638     
1639     if (n == 1) {
1640         return cast(F) *sliceI0;
1641     }
1642 
1643     auto sliceI1 = sliceI0;
1644     ++sliceI1;
1645 
1646     if (n > 2) {
1647         auto sliceI2 = sliceI1;
1648         ++sliceI2;
1649         alias less = naryFun!("a < b");
1650 
1651         if (n == 3) {
1652             medianOf!less(sliceI0, sliceI1, sliceI2);
1653             return cast(F) *sliceI1;
1654         } else {
1655             auto sliceI3 = sliceI2;
1656             ++sliceI3;
1657             if (n == 4) {
1658                 // Put min in slice[0], lower median in slice[1]
1659                 medianOf!less(sliceI0, sliceI1, sliceI2, sliceI3);
1660                 // Ensure slice[2] < slice[3]
1661                 medianOf!less(sliceI2, sliceI3);
1662                 return cast(F) (*sliceI1 + *sliceI2) / cast(F) 2;
1663             } else {
1664                 auto sliceI4 = sliceI3;
1665                 ++sliceI4;
1666                 medianOf!less(sliceI0, sliceI1, sliceI2, sliceI3, sliceI4);
1667                 return cast(F) *sliceI2;
1668             }
1669         }
1670     } else {
1671         return cast(F) (*sliceI0 + *sliceI1) / cast(F) 2;
1672     }
1673 }
1674 
1675 // smallMedianImpl tests
1676 version(mir_test)
1677 @safe pure nothrow
1678 unittest
1679 {
1680     import mir.math.common: approxEqual;
1681     import mir.ndslice.slice: sliced;
1682 
1683     auto x0 = [9.0, 1, 0, 2, 3].sliced;
1684     assert(x0.smallMedianImpl!double.approxEqual(2));
1685 
1686     auto x1 = [9.0, 1, 0, 2].sliced;
1687     assert(x1.smallMedianImpl!double.approxEqual(1.5));
1688 
1689     auto x2 = [9.0, 0, 1].sliced;
1690     assert(x2.smallMedianImpl!double.approxEqual(1));
1691 
1692     auto x3 = [1.0, 0].sliced;
1693     assert(x3.smallMedianImpl!double.approxEqual(0.5));
1694 
1695     auto x4 = [1.0].sliced;
1696     assert(x4.smallMedianImpl!double.approxEqual(1));
1697 
1698     auto x5 = [2.0, 1, 0, 9].sliced;
1699     assert(x5.smallMedianImpl!double.approxEqual(1.5));
1700 
1701     auto x6 = [1.0, 2, 0, 9].sliced;
1702     assert(x6.smallMedianImpl!double.approxEqual(1.5));
1703 
1704     auto x7 = [1.0, 0, 9, 2].sliced;
1705     assert(x7.smallMedianImpl!double.approxEqual(1.5));
1706 }
1707 
1708 /++
1709 Centers `slice`, which must be a finite iterable.
1710 
1711 By default, `slice` is centered by the mean. A custom function may also be
1712 provided using `centralTendency`.
1713 
1714 Returns:
1715     The elements in the slice with the average subtracted from them.
1716 +/
1717 template center(alias centralTendency = mean!(Summation.appropriate))
1718 {
1719     import mir.ndslice.slice: isConvertibleToSlice, isSlice, Slice, SliceKind;
1720     /++
1721     Params:
1722         slice = slice
1723     +/
1724     auto center(Iterator, size_t N, SliceKind kind)(
1725         Slice!(Iterator, N, kind) slice)
1726     {
1727         import core.lifetime: move;
1728         import mir.ndslice.internal: LeftOp, ImplicitlyUnqual;
1729         import mir.ndslice.topology: vmap;
1730 
1731         auto m = centralTendency(slice.lightScope);
1732         alias T = typeof(m);
1733         return slice.move.vmap(LeftOp!("-", ImplicitlyUnqual!T)(m));
1734     }
1735     
1736     /// ditto
1737     auto center(T)(T x)
1738         if (isConvertibleToSlice!T && !isSlice!T)
1739     {
1740         import mir.ndslice.slice: toSlice;
1741         return center(x.toSlice);
1742     }
1743 }
1744 
1745 /// Center vector
1746 version(mir_test)
1747 @safe pure nothrow
1748 unittest
1749 {
1750     import mir.algorithm.iteration: all;
1751     import mir.math.common: approxEqual;
1752     import mir.ndslice.slice: sliced;
1753 
1754     auto x = [1.0, 2, 3, 4, 5, 6].sliced;
1755     assert(x.center.all!approxEqual([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5]));
1756     
1757     // Can center using different functions
1758     assert(x.center!hmean.all!approxEqual([-1.44898, -0.44898, 0.55102, 1.55102, 2.55102, 3.55102]));
1759     assert(x.center!gmean.all!approxEqual([-1.99379516, -0.99379516, 0.00620483, 1.00620483, 2.00620483, 3.00620483]));
1760     assert(x.center!median.all!approxEqual([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5]));
1761 
1762     // center operates lazily, if original slice is changed, then 
1763     auto y = x.center;
1764     assert(y.all!approxEqual([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5]));
1765     x[0]++;
1766     assert(y.all!approxEqual([-1.5, -1.5, -0.5, 0.5, 1.5, 2.5]));
1767 }
1768 
1769 /// Example of lazy behavior of center
1770 version(mir_test)
1771 @safe pure nothrow
1772 unittest
1773 {
1774     import mir.algorithm.iteration: all;
1775     import mir.math.common: approxEqual;
1776     import mir.ndslice.allocation: slice;
1777     import mir.ndslice.slice: sliced;
1778 
1779     auto x = [1.0, 2, 3, 4, 5, 6].sliced;
1780     auto y = x.center;
1781     auto z = x.center.slice;
1782     assert(y.all!approxEqual([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5]));
1783     x[0]++;
1784     // y changes, while z does not
1785     assert(y.all!approxEqual([-1.5, -1.5, -0.5, 0.5, 1.5, 2.5]));
1786     assert(z.all!approxEqual([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5]));   
1787 }
1788 
1789 /// Center dynamic array
1790 version(mir_test)
1791 @safe pure nothrow
1792 unittest
1793 {
1794     import mir.algorithm.iteration: all;
1795     import mir.math.common: approxEqual;
1796 
1797     auto x = [1.0, 2, 3, 4, 5, 6];
1798     assert(x.center.all!approxEqual([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5]));
1799 }
1800 
1801 /// Center matrix
1802 version(mir_test)
1803 @safe pure
1804 unittest
1805 {
1806     import mir.algorithm.iteration: all;
1807     import mir.math.common: approxEqual;
1808     import mir.ndslice: fuse;
1809     
1810     auto x = [
1811         [0.0, 1, 2], 
1812         [3.0, 4, 5]
1813     ].fuse;
1814     
1815     auto y = [
1816         [-2.5, -1.5, -0.5], 
1817         [ 0.5,  1.5,  2.5]
1818     ].fuse;
1819     
1820     assert(x.center.all!approxEqual(y));
1821 }
1822 
1823 /// Column center matrix
1824 version(mir_test)
1825 @safe pure
1826 unittest
1827 {
1828     import mir.algorithm.iteration: all, equal;
1829     import mir.math.common: approxEqual;
1830     import mir.ndslice: fuse;
1831     import mir.ndslice.topology: alongDim, byDim, map;
1832 
1833     auto x = [
1834         [20.0, 100.0, 2000.0],
1835         [10.0,   5.0,    2.0]
1836     ].fuse;
1837 
1838     auto result = [
1839         [ 5.0,  47.5,  999],
1840         [-5.0, -47.5, -999]
1841     ].fuse;
1842 
1843     // Use byDim with map to compute average of row/column.
1844     auto xCenterByDim = x.byDim!1.map!center;
1845     auto resultByDim = result.byDim!1;
1846     assert(xCenterByDim.equal!(equal!approxEqual)(resultByDim));
1847 
1848     auto xCenterAlongDim = x.alongDim!0.map!center;
1849     auto resultAlongDim = result.alongDim!0;
1850     assert(xCenterByDim.equal!(equal!approxEqual)(resultAlongDim));
1851 }
1852 
1853 /// Can also pass arguments to average function used by center
1854 version(mir_test)
1855 pure @safe nothrow
1856 unittest
1857 {
1858     import mir.ndslice.slice: sliced;
1859 
1860     //Set sum algorithm or output type
1861     auto a = [1, 1e100, 1, -1e100];
1862 
1863     auto x = a.sliced * 10_000;
1864 
1865     //Due to Floating Point precision, subtracting the mean from the second
1866     //and fourth numbers in `x` does not change the value of the result
1867     auto result = [5000, 1e104, 5000, -1e104].sliced;
1868 
1869     assert(x.center!(mean!"kbn") == result);
1870     assert(x.center!(mean!"kb2") == result);
1871     assert(x.center!(mean!"precise") == result);
1872 }
1873 
1874 /++
1875 Passing a centered input to `variance` or `standardDeviation` with the
1876 `assumeZeroMean` algorithm is equivalent to calculating `variance` or
1877 `standardDeviation` on the original input.
1878 +/
1879 version(mir_test)
1880 @safe pure nothrow
1881 unittest
1882 {
1883     import mir.algorithm.iteration: all;
1884     import mir.math.common: approxEqual;
1885     import mir.ndslice.slice: sliced;
1886 
1887     auto x = [1.0, 2, 3, 4, 5, 6].sliced;
1888     assert(x.center.variance!"assumeZeroMean".approxEqual(x.variance));
1889     assert(x.center.standardDeviation!"assumeZeroMean".approxEqual(x.standardDeviation));
1890 }
1891 
1892 // dynamic array test
1893 version(mir_test)
1894 @safe pure nothrow
1895 unittest
1896 {
1897     import mir.algorithm.iteration: all;
1898     import mir.math.common: approxEqual;
1899 
1900     double[] x = [1.0, 2, 3, 4, 5, 6];
1901 
1902     assert(x.center.all!approxEqual([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5]));
1903 }
1904 
1905 // withAsSlice test
1906 version(mir_test)
1907 @safe pure nothrow @nogc
1908 unittest
1909 {
1910     import mir.algorithm.iteration: all;
1911     import mir.math.common: approxEqual;
1912     import mir.rc.array: RCArray;
1913 
1914     static immutable a = [1.0, 2, 3, 4, 5, 6];
1915     static immutable result = [-2.5, -1.5, -0.5, 0.5, 1.5, 2.5];
1916 
1917     auto x = RCArray!double(6);
1918     foreach(i, ref e; x)
1919         e = a[i];
1920 
1921     assert(x.center.all!approxEqual(result));
1922 }
1923 
1924 /++
1925 Output range that applies function `fun` to each input before summing
1926 +/
1927 struct MapSummator(alias fun, T, Summation summation) 
1928     if(isMutable!T)
1929 {
1930     ///
1931     Summator!(T, summation) summator;
1932 
1933     ///
1934     F sum(F = T)() const @property
1935     {
1936         return cast(F) summator.sum;
1937     }
1938     
1939     ///
1940     void put(Range)(Range r)
1941         if (isIterable!Range)
1942     {
1943         import mir.ndslice.topology: map;
1944         summator.put(r.map!fun);
1945     }
1946 
1947     ///
1948     void put()(T x)
1949     {
1950         summator.put(fun(x));
1951     }
1952 }
1953 
1954 ///
1955 version(mir_test)
1956 @safe pure nothrow
1957 unittest
1958 {
1959     import mir.math.common: powi;
1960     import mir.ndslice.slice: sliced;
1961 
1962     alias f = (double x) => (powi(x, 2));
1963     MapSummator!(f, double, Summation.pairwise) x;
1964     x.put([0.0, 1, 2, 3, 4].sliced);
1965     assert(x.sum == 30.0);
1966     x.put(5);
1967     assert(x.sum == 55.0);
1968 }
1969 
1970 version(mir_test)
1971 @safe pure nothrow
1972 unittest
1973 {
1974     import mir.ndslice.slice: sliced;
1975 
1976     alias f = (double x) => (x + 1);
1977     MapSummator!(f, double, Summation.pairwise) x;
1978     x.put([0.0, 1, 2, 3, 4].sliced);
1979     assert(x.sum == 15.0);
1980     x.put(5);
1981     assert(x.sum == 21.0);
1982 }
1983 
1984 version(mir_test)
1985 @safe pure nothrow @nogc
1986 unittest
1987 {
1988     import mir.ndslice.slice: sliced;
1989 
1990     alias f = (double x) => (x + 1);
1991     MapSummator!(f, double, Summation.pairwise) x;
1992     static immutable a = [0.0, 1, 2, 3, 4];
1993     x.put(a.sliced);
1994     assert(x.sum == 15.0);
1995     x.put(5);
1996     assert(x.sum == 21.0);
1997 }
1998 
1999 version(mir_test)
2000 @safe pure
2001 unittest
2002 {
2003     import mir.ndslice.fuse: fuse;
2004     import mir.ndslice.slice: sliced;
2005 
2006     alias f = (double x) => (x + 1);
2007     MapSummator!(f, double, Summation.pairwise) x;
2008     auto a = [
2009         [0.0, 1, 2],
2010         [3.0, 4, 5]
2011     ].fuse;
2012     auto b = [6.0, 7, 8].sliced;
2013     x.put(a);
2014     assert(x.sum == 21.0);
2015     x.put(b);
2016     assert(x.sum == 45.0);
2017 }
2018 
2019 /++
2020 Variance algorithms.
2021 
2022 See Also:
2023     $(WEB en.wikipedia.org/wiki/Algorithms_for_calculating_variance, Algorithms for calculating variance).
2024 +/
2025 enum VarianceAlgo
2026 {
2027     /++
2028     Performs Welford's online algorithm for updating variance. Can also `put`
2029     another VarianceAccumulator of the same type, which uses the parallel
2030     algorithm from Chan et al., described above.
2031     +/
2032     online,
2033     
2034     /++
2035     Calculates variance using E(x^^2) - E(x)^2 (alowing for adjustments for 
2036     population/sample variance). This algorithm can be numerically unstable.
2037     +/
2038     naive,
2039 
2040     /++
2041     Calculates variance using a two-pass algorithm whereby the input is first 
2042     centered and then the sum of squares is calculated from that.
2043     +/
2044     twoPass,
2045 
2046     /++
2047     Calculates variance assuming the mean of the dataseries is zero. 
2048     +/
2049     assumeZeroMean
2050 }
2051 
2052 ///
2053 struct VarianceAccumulator(T, VarianceAlgo varianceAlgo, Summation summation)
2054     if (isMutable!T && varianceAlgo == VarianceAlgo.naive)
2055 {
2056     import mir.functional: naryFun;
2057 
2058     ///
2059     this(Range)(Range r)
2060         if (isIterable!Range)
2061     {
2062         import core.lifetime: move;
2063         this.put(r.move);
2064     }
2065 
2066     ///
2067     this()(T x)
2068     {
2069         this.put(x);
2070     }
2071 
2072     ///
2073     MeanAccumulator!(T, summation) meanAccumulator;
2074 
2075     ///
2076     Summator!(T, summation) sumOfSquares;
2077 
2078     ///
2079     void put(Range)(Range r)
2080         if (isIterable!Range)
2081     {
2082         foreach(x; r)
2083         {
2084             this.put(x);
2085         }
2086     }
2087 
2088     ///
2089     void put()(T x)
2090     {
2091         meanAccumulator.put(x);
2092         sumOfSquares.put(x * x);
2093     }
2094 
2095 const:
2096 
2097     ///
2098     size_t count() @property
2099     {
2100         return meanAccumulator.count;
2101     }
2102 
2103     ///
2104     F mean(F = T)() const @property
2105     {
2106         return meanAccumulator.mean;
2107     }
2108 
2109     ///
2110     F variance(F = T)(bool isPopulation) @property
2111     {
2112         return cast(F) sumOfSquares.sum / (count + isPopulation - 1) - 
2113             (cast(F) meanAccumulator.mean) ^^ 2 * (cast(F) count / (count + isPopulation - 1));
2114     }
2115 }
2116 
2117 ///
2118 version(mir_test)
2119 @safe pure nothrow
2120 unittest
2121 {
2122     import mir.math.common: approxEqual;
2123     import mir.ndslice.slice: sliced;
2124 
2125     auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
2126               2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;
2127 
2128     enum PopulationTrueCT = true;
2129     enum PopulationFalseCT = false;
2130     bool PopulationTrueRT = true;
2131     bool PopulationFalseRT = false;
2132 
2133     VarianceAccumulator!(double, VarianceAlgo.naive, Summation.naive) v;
2134     v.put(x);
2135     assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12));
2136     assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12));
2137     assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11));
2138     assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11));
2139 
2140     v.put(4.0);
2141     assert(v.variance(PopulationTrueRT).approxEqual(57.01923 / 13));
2142     assert(v.variance(PopulationTrueCT).approxEqual(57.01923 / 13));
2143     assert(v.variance(PopulationFalseRT).approxEqual(57.01923 / 12));
2144     assert(v.variance(PopulationFalseCT).approxEqual(57.01923 / 12));
2145 }
2146 
2147 ///
2148 struct VarianceAccumulator(T, VarianceAlgo varianceAlgo, Summation summation)
2149     if (isMutable!T && 
2150         varianceAlgo == VarianceAlgo.online)
2151 {
2152     ///
2153     this(Range)(Range r)
2154         if (isIterable!Range)
2155     {
2156         import core.lifetime: move;
2157         this.put(r.move);
2158     }
2159 
2160     ///
2161     this()(T x)
2162     {
2163         this.put(x);
2164     }
2165 
2166     ///
2167     MeanAccumulator!(T, summation) meanAccumulator;
2168 
2169     ///
2170     Summator!(T, summation) centeredSumOfSquares;
2171 
2172     ///
2173     void put(Range)(Range r)
2174         if (isIterable!Range)
2175     {
2176         foreach(x; r)
2177         {
2178             this.put(x);
2179         }
2180     }
2181 
2182     ///
2183     void put()(T x)
2184     {
2185         T delta = x;
2186         if (count > 0) {
2187             delta -= meanAccumulator.mean;
2188         }
2189         meanAccumulator.put(x);
2190         centeredSumOfSquares.put(delta * (x - meanAccumulator.mean));
2191     }
2192 
2193     ///
2194     void put()(VarianceAccumulator!(T, varianceAlgo, summation) v)
2195     {
2196         size_t oldCount = count;
2197         T delta = v.mean;
2198         if (oldCount > 0) {
2199             delta -= meanAccumulator.mean;
2200         }
2201         meanAccumulator.put!T(v.meanAccumulator);
2202         centeredSumOfSquares.put(v.centeredSumOfSquares.sum + delta * delta * v.count * oldCount / count);
2203     }
2204 
2205 const:
2206 
2207     ///
2208     size_t count() @property
2209     {
2210         return meanAccumulator.count;
2211     }
2212 
2213     ///
2214     F mean(F = T)() const @property
2215     {
2216         return meanAccumulator.mean;
2217     }
2218 
2219     ///
2220     F variance(F = T)(bool isPopulation) @property
2221     {
2222         return cast(F) centeredSumOfSquares.sum / (count + isPopulation - 1);
2223     }
2224 }
2225 
2226 ///
2227 version(mir_test)
2228 @safe pure nothrow
2229 unittest
2230 {
2231     import mir.math.common: approxEqual;
2232     import mir.ndslice.slice: sliced;
2233 
2234     auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
2235               2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;
2236 
2237     enum PopulationTrueCT = true;
2238     enum PopulationFalseCT = false;
2239     bool PopulationTrueRT = true;
2240     bool PopulationFalseRT = false;
2241 
2242     VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) v;
2243     v.put(x);
2244 
2245     assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12));
2246     assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12));
2247     assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11));
2248     assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11));
2249 
2250     v.put(4.0);
2251     assert(v.variance(PopulationTrueRT).approxEqual(57.01923 / 13));
2252     assert(v.variance(PopulationTrueCT).approxEqual(57.01923 / 13));
2253     assert(v.variance(PopulationFalseRT).approxEqual(57.01923 / 12));
2254     assert(v.variance(PopulationFalseCT).approxEqual(57.01923 / 12));
2255 }
2256 
2257 ///
2258 version(mir_test)
2259 @safe pure nothrow
2260 unittest
2261 {
2262     import mir.math.common: approxEqual;
2263     import mir.ndslice.slice: sliced;
2264 
2265     auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25].sliced;
2266     auto y = [2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;
2267 
2268     enum PopulationTrueCT = true;
2269     enum PopulationFalseCT = false;
2270     bool PopulationTrueRT = true;
2271     bool PopulationFalseRT = false;
2272 
2273     VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) v;
2274     v.put(x);
2275     assert(v.variance(PopulationTrueRT).approxEqual(12.55208 / 6));
2276     assert(v.variance(PopulationTrueCT).approxEqual(12.55208 / 6));
2277     assert(v.variance(PopulationFalseRT).approxEqual(12.55208 / 5));
2278     assert(v.variance(PopulationFalseCT).approxEqual(12.55208 / 5));
2279 
2280     v.put(y);
2281     assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12));
2282     assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12));
2283     assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11));
2284     assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11));
2285 }
2286 
2287 version(mir_test)
2288 @safe pure nothrow
2289 unittest
2290 {
2291     import mir.math.common: approxEqual;
2292     import mir.ndslice.slice: sliced;
2293 
2294     auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25].sliced;
2295     auto y = [2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;
2296 
2297     enum PopulationTrueCT = true;
2298     enum PopulationFalseCT = false;
2299     bool PopulationTrueRT = true;
2300     bool PopulationFalseRT = false;
2301 
2302     VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) v;
2303     v.put(x);
2304     assert(v.variance(PopulationTrueRT).approxEqual(12.55208 / 6));
2305     assert(v.variance(PopulationTrueCT).approxEqual(12.55208 / 6));
2306     assert(v.variance(PopulationFalseRT).approxEqual(12.55208 / 5));
2307     assert(v.variance(PopulationFalseCT).approxEqual(12.55208 / 5));
2308 
2309     VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) w;
2310     w.put(y);
2311     v.put(w);
2312     assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12));
2313     assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12));
2314     assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11));
2315     assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11));
2316 }
2317 
2318 version(mir_test)
2319 @safe pure nothrow
2320 unittest
2321 {
2322     import mir.complex.math: approxEqual;
2323     import mir.ndslice.slice: sliced;
2324     import mir.complex: Complex;
2325 
2326     auto x = [Complex!double(1.0, 3), Complex!double(2), Complex!double(3)].sliced;
2327 
2328     VarianceAccumulator!(Complex!double, VarianceAlgo.online, Summation.naive) v;
2329     v.put(x);
2330     assert(v.variance(true).approxEqual(Complex!double(-4.0, -6) / 3));
2331     assert(v.variance(false).approxEqual(Complex!double(-4.0, -6) / 2));
2332 }
2333 
2334 version(mir_test)
2335 @safe pure nothrow
2336 unittest
2337 {
2338     import mir.math.common: approxEqual;
2339     import mir.ndslice.slice: sliced;
2340 
2341     auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
2342               2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;
2343 
2344     VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) v;
2345     v.put(x);
2346     assert(v.variance(false).approxEqual(54.76562 / 11));
2347 
2348     v.put(4.0);
2349     assert(v.variance(false).approxEqual(57.01923 / 12));
2350 }
2351 
2352 version(mir_test)
2353 @safe pure nothrow
2354 unittest
2355 {
2356     import mir.math.common: approxEqual;
2357     import mir.ndslice.slice: sliced;
2358 
2359     auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25].sliced;
2360     auto y = [2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;
2361 
2362     VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) v;
2363     v.put(x);
2364     assert(v.variance(false).approxEqual(12.55208 / 5));
2365 
2366     VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) w;
2367     w.put(y);
2368     v.put(w);
2369     assert(v.variance(false).approxEqual(54.76562 / 11));
2370 }
2371 
2372 ///
2373 struct VarianceAccumulator(T, VarianceAlgo varianceAlgo, Summation summation)
2374     if (isMutable!T && varianceAlgo == VarianceAlgo.twoPass)
2375 {
2376     import mir.functional: naryFun;
2377     import mir.ndslice.slice: Slice, SliceKind, hasAsSlice;
2378 
2379     ///
2380     MeanAccumulator!(T, summation) meanAccumulator;
2381 
2382     ///
2383     Summator!(T, summation) centeredSumOfSquares;
2384 
2385     ///
2386     this(Iterator, size_t N, SliceKind kind)(
2387          Slice!(Iterator, N, kind) slice)
2388     {
2389         import core.lifetime: move;
2390         import mir.ndslice.internal: LeftOp;
2391         import mir.ndslice.topology: vmap, map;
2392 
2393         meanAccumulator.put(slice.lightScope);
2394         centeredSumOfSquares.put(slice.move.vmap(LeftOp!("-", T)(meanAccumulator.mean)).map!(naryFun!"a * a"));
2395     }
2396 
2397     ///
2398     this(U)(U[] array)
2399     {
2400         import mir.ndslice.slice: sliced;
2401         this(array.sliced);
2402     }
2403 
2404     ///
2405     this(T)(T withAsSlice)
2406         if (hasAsSlice!T)
2407     {
2408         this(withAsSlice.asSlice);
2409     }
2410 
2411     ///
2412     this()(T x)
2413     {
2414         meanAccumulator.put(x);
2415         centeredSumOfSquares.put(cast(T) 0);
2416     }
2417 
2418 const:
2419 
2420     ///
2421     size_t count() @property
2422     {
2423         return meanAccumulator.count;
2424     }
2425 
2426     ///
2427     F mean(F = T)() const @property
2428     {
2429         return meanAccumulator.mean;
2430     }
2431 
2432     ///
2433     F variance(F = T)(bool isPopulation) @property
2434     {
2435         return cast(F) centeredSumOfSquares.sum / (count + isPopulation - 1);
2436     }
2437 }
2438 
2439 ///
2440 version(mir_test)
2441 @safe pure nothrow
2442 unittest
2443 {
2444     import mir.math.common: approxEqual;
2445     import mir.ndslice.slice: sliced;
2446 
2447     auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
2448               2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;
2449 
2450     enum PopulationTrueCT = true;
2451     enum PopulationFalseCT = false;
2452     bool PopulationTrueRT = true;
2453     bool PopulationFalseRT = false;
2454 
2455     auto v = VarianceAccumulator!(double, VarianceAlgo.twoPass, Summation.naive)(x);
2456     assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12));
2457     assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12));
2458     assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11));
2459     assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11));
2460 }
2461 
2462 // dynamic array test
2463 version(mir_test)
2464 @safe pure nothrow
2465 unittest
2466 {
2467     import mir.math.common: approxEqual;
2468     import mir.rc.array: RCArray;
2469 
2470     double[] x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
2471                   2.0, 7.5, 5.0, 1.0, 1.5, 0.0];
2472 
2473     auto v = VarianceAccumulator!(double, VarianceAlgo.twoPass, Summation.naive)(x);
2474     assert(v.centeredSumOfSquares.sum.approxEqual(54.76562));
2475 }
2476 
2477 // withAsSlice test
2478 version(mir_test)
2479 @safe pure nothrow @nogc
2480 unittest
2481 {
2482     import mir.math.common: approxEqual;
2483     import mir.rc.array: RCArray;
2484 
2485     static immutable a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
2486                           2.0, 7.5, 5.0, 1.0, 1.5, 0.0];
2487 
2488     auto x = RCArray!double(12);
2489     foreach(i, ref e; x)
2490         e = a[i];
2491 
2492     auto v = VarianceAccumulator!(double, VarianceAlgo.twoPass, Summation.naive)(x);
2493     assert(v.centeredSumOfSquares.sum.approxEqual(54.76562));
2494 }
2495 
2496 ///
2497 struct VarianceAccumulator(T, VarianceAlgo varianceAlgo, Summation summation)
2498     if (isMutable!T && varianceAlgo == VarianceAlgo.assumeZeroMean)
2499 {
2500     import mir.ndslice.slice: Slice, SliceKind, hasAsSlice;
2501 
2502     private size_t _count;
2503 
2504     ///
2505     Summator!(T, summation) centeredSumOfSquares;
2506 
2507     ///
2508     this(Range)(Range r)
2509         if (isIterable!Range)
2510     {
2511         this.put(r);
2512     }
2513 
2514     ///
2515     this()(T x)
2516     {
2517         this.put(x);
2518     }
2519 
2520     ///
2521     void put(Range)(Range r)
2522         if (isIterable!Range)
2523     {
2524         foreach(x; r)
2525         {
2526             this.put(x);
2527         }
2528     }
2529 
2530     ///
2531     void put()(T x)
2532     {
2533         _count++;
2534         centeredSumOfSquares.put(x * x);
2535     }
2536 
2537     ///
2538     void put()(VarianceAccumulator!(T, varianceAlgo, summation) v)
2539     {
2540         _count += v.count;
2541         centeredSumOfSquares.put(v.centeredSumOfSquares.sum);
2542     }
2543 
2544 const:
2545 
2546     ///
2547     size_t count() @property
2548     {
2549         return _count;
2550     }
2551     
2552     ///
2553     F mean(F = T)() const @property
2554     {
2555         return cast(F) 0;
2556     }
2557 
2558     ///
2559     F variance(F = T)(bool isPopulation) @property
2560     {
2561         return cast(F) centeredSumOfSquares.sum / (count + isPopulation - 1);
2562     }
2563 }
2564 
2565 ///
2566 version(mir_test)
2567 @safe pure nothrow
2568 unittest
2569 {
2570     import mir.math.common: approxEqual;
2571     import mir.ndslice.slice: sliced;
2572 
2573     auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
2574               2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;
2575     auto x = a.center;
2576 
2577     enum PopulationTrueCT = true;
2578     enum PopulationFalseCT = false;
2579     bool PopulationTrueRT = true;
2580     bool PopulationFalseRT = false;
2581 
2582     VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) v;
2583     v.put(x);
2584 
2585     assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12));
2586     assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12));
2587     assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11));
2588     assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11));
2589 
2590     v.put(4.0);
2591     assert(v.variance(PopulationTrueRT).approxEqual(70.76562 / 13));
2592     assert(v.variance(PopulationTrueCT).approxEqual(70.76562 / 13));
2593     assert(v.variance(PopulationFalseRT).approxEqual(70.76562 / 12));
2594     assert(v.variance(PopulationFalseCT).approxEqual(70.76562 / 12));
2595 }
2596 
2597 ///
2598 version(mir_test)
2599 @safe pure nothrow
2600 unittest
2601 {
2602     import mir.math.common: approxEqual;
2603     import mir.ndslice.slice: sliced;
2604 
2605     auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
2606               2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;
2607     auto b = a.center;
2608     auto x = b[0 .. 6];
2609     auto y = b[6 .. $];
2610 
2611     enum PopulationTrueCT = true;
2612     enum PopulationFalseCT = false;
2613     bool PopulationTrueRT = true;
2614     bool PopulationFalseRT = false;
2615 
2616     VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) v;
2617     v.put(x);
2618     assert(v.variance(PopulationTrueRT).approxEqual(13.492188 / 6));
2619     assert(v.variance(PopulationTrueCT).approxEqual(13.492188 / 6));
2620     assert(v.variance(PopulationFalseRT).approxEqual(13.492188 / 5));
2621     assert(v.variance(PopulationFalseCT).approxEqual(13.492188 / 5));
2622 
2623     v.put(y);
2624     assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12));
2625     assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12));
2626     assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11));
2627     assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11));
2628 }
2629 
2630 version(mir_test)
2631 @safe pure nothrow
2632 unittest
2633 {
2634     import mir.math.common: approxEqual;
2635     import mir.ndslice.slice: sliced;
2636 
2637     auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
2638               2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;
2639     auto b = a.center;
2640     auto x = b[0 .. 6];
2641     auto y = b[6 .. $];
2642 
2643     enum PopulationTrueCT = true;
2644     enum PopulationFalseCT = false;
2645     bool PopulationTrueRT = true;
2646     bool PopulationFalseRT = false;
2647 
2648     VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) v;
2649     v.put(x);
2650     assert(v.variance(PopulationTrueRT).approxEqual(13.492188 / 6));
2651     assert(v.variance(PopulationTrueCT).approxEqual(13.492188 / 6));
2652     assert(v.variance(PopulationFalseRT).approxEqual(13.492188 / 5));
2653     assert(v.variance(PopulationFalseCT).approxEqual(13.492188 / 5));
2654 
2655     VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) w;
2656     w.put(y);
2657     v.put(w);
2658     assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12));
2659     assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12));
2660     assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11));
2661     assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11));
2662 }
2663 
2664 version(mir_test)
2665 @safe pure nothrow
2666 unittest
2667 {
2668     import mir.complex.math: approxEqual;
2669     import mir.ndslice.slice: sliced;
2670     import mir.complex: Complex;
2671 
2672     auto a = [Complex!double(1.0, 3), Complex!double(2), Complex!double(3)].sliced;
2673     auto x = a.center;
2674 
2675     VarianceAccumulator!(Complex!double, VarianceAlgo.assumeZeroMean, Summation.naive) v;
2676     v.put(x);
2677     assert(v.variance(true).approxEqual(Complex!double(-4.0, -6) / 3));
2678     assert(v.variance(false).approxEqual(Complex!double(-4.0, -6) / 2));
2679 }
2680 
2681 version(mir_test)
2682 @safe pure nothrow
2683 unittest
2684 {
2685     import mir.math.common: approxEqual;
2686     import mir.ndslice.slice: sliced;
2687 
2688     auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
2689               2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;
2690     auto x = a.center;
2691 
2692     VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) v;
2693     v.put(x);
2694     assert(v.variance(false).approxEqual(54.76562 / 11));
2695 
2696     v.put(4.0);
2697     assert(v.variance(false).approxEqual(70.76562 / 12));
2698 }
2699 
2700 version(mir_test)
2701 @safe pure nothrow
2702 unittest
2703 {
2704     import mir.math.common: approxEqual;
2705     import mir.ndslice.slice: sliced;
2706 
2707     auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
2708               2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;
2709     auto b = a.center;
2710     auto x = b[0 .. 6];
2711     auto y = b[6 .. $];
2712 
2713     VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) v;
2714     v.put(x);
2715     assert(v.variance(false).approxEqual(13.492188 / 5));
2716 
2717     VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) w;
2718     w.put(y);
2719     v.put(w);
2720     assert(v.variance(false).approxEqual(54.76562 / 11));
2721 }
2722 
2723 /++
2724 Calculates the variance of the input
2725 
2726 By default, if `F` is not floating point type or complex type, then the result
2727 will have a `double` type if `F` is implicitly convertible to a floating point 
2728 type or a type for which `isComplex!F` is true.
2729 
2730 Params:
2731     F = controls type of output
2732     varianceAlgo = algorithm for calculating variance (default: VarianceAlgo.online)
2733     summation = algorithm for calculating sums (default: Summation.appropriate)
2734 Returns:
2735     The variance of the input, must be floating point or complex type
2736 +/
2737 template variance(
2738     F, 
2739     VarianceAlgo varianceAlgo = VarianceAlgo.online, 
2740     Summation summation = Summation.appropriate)
2741 {
2742     /++
2743     Params:
2744         r = range, must be finite iterable
2745         isPopulation = true if population variance, false if sample variance (default)
2746     +/
2747     @fmamath meanType!F variance(Range)(Range r, bool isPopulation = false)
2748         if (isIterable!Range)
2749     {
2750         import core.lifetime: move;
2751 
2752         alias G = typeof(return);
2753         auto varianceAccumulator = VarianceAccumulator!(G, varianceAlgo, ResolveSummationType!(summation, Range, G))(r.move);
2754         return varianceAccumulator.variance(isPopulation);
2755     }
2756 
2757     /++
2758     Params:
2759         ar = values
2760     +/
2761     @fmamath meanType!F variance(scope const F[] ar...)
2762     {
2763         alias G = typeof(return);
2764         auto varianceAccumulator = VarianceAccumulator!(G, varianceAlgo, ResolveSummationType!(summation, const(G)[], G))(ar);
2765         return varianceAccumulator.variance(false);
2766     }
2767 }
2768 
2769 /// ditto
2770 template variance(
2771     VarianceAlgo varianceAlgo = VarianceAlgo.online, 
2772     Summation summation = Summation.appropriate)
2773 {
2774     /++
2775     Params:
2776         r = range, must be finite iterable
2777         isPopulation = true if population variance, false if sample variance (default)
2778     +/
2779     @fmamath meanType!Range variance(Range)(Range r, bool isPopulation = false)
2780         if(isIterable!Range)
2781     {
2782         import core.lifetime: move;
2783 
2784         alias F = typeof(return);
2785         return .variance!(F, varianceAlgo, summation)(r.move, isPopulation);
2786     }
2787 
2788     /++
2789     Params:
2790         ar = values
2791     +/
2792     @fmamath meanType!T variance(T)(scope const T[] ar...)
2793     {
2794         alias F = typeof(return);
2795         return .variance!(F, varianceAlgo, summation)(ar);
2796     }
2797 }
2798 
2799 /// ditto
2800 template variance(F, string varianceAlgo, string summation = "appropriate")
2801 {
2802     mixin("alias variance = .variance!(F, VarianceAlgo." ~ varianceAlgo ~ ", Summation." ~ summation ~ ");");
2803 }
2804 
2805 /// ditto
2806 template variance(string varianceAlgo, string summation = "appropriate")
2807 {
2808     mixin("alias variance = .variance!(VarianceAlgo." ~ varianceAlgo ~ ", Summation." ~ summation ~ ");");
2809 }
2810 
2811 ///
2812 version(mir_test)
2813 @safe pure nothrow
2814 unittest
2815 {
2816     import mir.math.common: approxEqual;
2817     import mir.complex.math: capproxEqual = approxEqual;
2818     import mir.ndslice.slice: sliced;
2819     import mir.complex;
2820     alias C = Complex!double;
2821 
2822     assert(variance([1.0, 2, 3]).approxEqual(2.0 / 2));
2823     assert(variance([1.0, 2, 3], true).approxEqual(2.0 / 3));
2824 
2825     assert(variance([C(1, 3), C(2), C(3)]).capproxEqual(C(-4, -6) / 2));
2826     
2827     assert(variance!float([0, 1, 2, 3, 4, 5].sliced(3, 2)).approxEqual(17.5 / 5));
2828     
2829     static assert(is(typeof(variance!float([1, 2, 3])) == float));
2830 }
2831 
2832 /// Variance of vector
2833 version(mir_test)
2834 @safe pure nothrow
2835 unittest
2836 {
2837     import mir.math.common: approxEqual;
2838     import mir.ndslice.slice: sliced;
2839 
2840     auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
2841               2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;
2842 
2843     assert(x.variance.approxEqual(54.76562 / 11));
2844 }
2845 
2846 /// Variance of matrix
2847 version(mir_test)
2848 @safe pure
2849 unittest
2850 {
2851     import mir.math.common: approxEqual;
2852     import mir.ndslice.fuse: fuse;
2853 
2854     auto x = [
2855         [0.0, 1.0, 1.5, 2.0, 3.5, 4.25],
2856         [2.0, 7.5, 5.0, 1.0, 1.5, 0.0]
2857     ].fuse;
2858 
2859     assert(x.variance.approxEqual(54.76562 / 11));
2860 }
2861 
2862 /// Column variance of matrix
2863 version(mir_test)
2864 @safe pure
2865 unittest
2866 {
2867     import mir.algorithm.iteration: all;
2868     import mir.math.common: approxEqual;
2869     import mir.ndslice.fuse: fuse;
2870     import mir.ndslice.topology: alongDim, byDim, map;
2871 
2872     auto x = [
2873         [0.0,  1.0, 1.5, 2.0], 
2874         [3.5, 4.25, 2.0, 7.5],
2875         [5.0,  1.0, 1.5, 0.0]
2876     ].fuse;
2877     auto result = [13.16667 / 2, 7.041667 / 2, 0.1666667 / 2, 30.16667 / 2];
2878 
2879     // Use byDim or alongDim with map to compute variance of row/column.
2880     assert(x.byDim!1.map!variance.all!approxEqual(result));
2881     assert(x.alongDim!0.map!variance.all!approxEqual(result));
2882 
2883     // FIXME
2884     // Without using map, computes the variance of the whole slice
2885     // assert(x.byDim!1.variance == x.sliced.variance);
2886     // assert(x.alongDim!0.variance == x.sliced.variance);
2887 }
2888 
2889 /// Can also set algorithm type
2890 version(mir_test)
2891 @safe pure nothrow
2892 unittest
2893 {
2894     import mir.math.common: approxEqual;
2895     import mir.ndslice.slice: sliced;
2896 
2897     auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
2898               2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;
2899 
2900     auto x = a + 1_000_000_000;
2901 
2902     auto y = x.variance;
2903     assert(y.approxEqual(54.76562 / 11));
2904 
2905     // The naive algorithm is numerically unstable in this case
2906     auto z0 = x.variance!"naive";
2907     assert(!z0.approxEqual(y));
2908 
2909     // But the two-pass algorithm provides a consistent answer
2910     auto z1 = x.variance!"twoPass";
2911     assert(z1.approxEqual(y));
2912 
2913     // And the assumeZeroMean algorithm is way off
2914     auto z2 = x.variance!"assumeZeroMean";
2915     assert(z2.approxEqual(1.2e19 / 11));
2916 }
2917 
2918 /// Can also set algorithm or output type
2919 version(mir_test)
2920 @safe pure nothrow
2921 unittest
2922 {
2923     import mir.math.common: approxEqual;
2924     import mir.ndslice.slice: sliced;
2925     import mir.ndslice.topology: repeat;
2926 
2927     //Set population variance, variance algorithm, sum algorithm or output type
2928 
2929     auto a = [1.0, 1e100, 1, -1e100].sliced;
2930     auto x = a * 10_000;
2931 
2932     bool populationTrueRT = true;
2933     bool populationFalseRT = false;
2934     enum PopulationTrueCT = true;
2935 
2936     /++
2937     Due to Floating Point precision, when centering `x`, subtracting the mean 
2938     from the second and fourth numbers has no effect. Further, after centering 
2939     and squaring `x`, the first and third numbers in the slice have precision 
2940     too low to be included in the centered sum of squares. 
2941     +/
2942     assert(x.variance(populationFalseRT).approxEqual(2.0e208 / 3));
2943     assert(x.variance(populationTrueRT).approxEqual(2.0e208 / 4));
2944     assert(x.variance(PopulationTrueCT).approxEqual(2.0e208 / 4));
2945 
2946     assert(x.variance!("online").approxEqual(2.0e208 / 3));
2947     assert(x.variance!("online", "kbn").approxEqual(2.0e208 / 3));
2948     assert(x.variance!("online", "kb2").approxEqual(2.0e208 / 3));
2949     assert(x.variance!("online", "precise").approxEqual(2.0e208 / 3));
2950     assert(x.variance!(double, "online", "precise").approxEqual(2.0e208 / 3));
2951     assert(x.variance!(double, "online", "precise")(populationTrueRT).approxEqual(2.0e208 / 4));
2952 
2953     auto y = uint.max.repeat(3);
2954     auto z = y.variance!ulong;
2955     assert(z == 0.0);
2956     static assert(is(typeof(z) == double));
2957 }
2958 
2959 /++
2960 For integral slices, pass output type as template parameter to ensure output
2961 type is correct.
2962 +/
2963 version(mir_test)
2964 @safe pure nothrow
2965 unittest
2966 {
2967     import mir.math.common: approxEqual;
2968     import mir.ndslice.slice: sliced;
2969 
2970     auto x = [0, 1, 1, 2, 4, 4,
2971               2, 7, 5, 1, 2, 0].sliced;
2972 
2973     auto y = x.variance;
2974     assert(y.approxEqual(50.91667 / 11));
2975     static assert(is(typeof(y) == double));
2976 
2977     assert(x.variance!float.approxEqual(50.91667 / 11));
2978 }
2979 
2980 /++
2981 Variance works for complex numbers and other user-defined types (provided they
2982 can be converted to a floating point or complex type)
2983 +/
2984 version(mir_test)
2985 @safe pure nothrow
2986 unittest
2987 {
2988     import mir.complex.math: approxEqual;
2989     import mir.ndslice.slice: sliced;
2990     import mir.complex;
2991     alias C = Complex!double;
2992 
2993     auto x = [C(1, 2), C(2, 3), C(3, 4), C(4, 5)].sliced;
2994     assert(x.variance.approxEqual((C(0, 10)) / 3));
2995 }
2996 
2997 /// Compute variance along specified dimention of tensors
2998 version(mir_test)
2999 @safe pure
3000 unittest
3001 {
3002     import mir.algorithm.iteration: all;
3003     import mir.math.common: approxEqual;
3004     import mir.ndslice.fuse: fuse;
3005     import mir.ndslice.topology: as, iota, alongDim, map, repeat;
3006 
3007     auto x = [
3008         [0.0, 1, 2],
3009         [3.0, 4, 5]
3010     ].fuse;
3011 
3012     assert(x.variance.approxEqual(17.5 / 5));
3013 
3014     auto m0 = [4.5, 4.5, 4.5];
3015     assert(x.alongDim!0.map!variance.all!approxEqual(m0));
3016     assert(x.alongDim!(-2).map!variance.all!approxEqual(m0));
3017 
3018     auto m1 = [1.0, 1.0];
3019     assert(x.alongDim!1.map!variance.all!approxEqual(m1));
3020     assert(x.alongDim!(-1).map!variance.all!approxEqual(m1));
3021 
3022     assert(iota(2, 3, 4, 5).as!double.alongDim!0.map!variance.all!approxEqual(repeat(3600.0 / 2, 3, 4, 5)));
3023 }
3024 
3025 /// Arbitrary variance
3026 version(mir_test)
3027 @safe pure nothrow @nogc
3028 unittest
3029 {
3030     assert(variance(1.0, 2, 3) == 1.0);
3031     assert(variance!float(1, 2, 3) == 1f);
3032 }
3033 
3034 version(mir_test)
3035 @safe pure nothrow
3036 unittest
3037 {
3038     import mir.math.common: approxEqual;
3039 
3040     assert([1.0, 2, 3, 4].variance.approxEqual(5.0 / 3));
3041 }
3042 
3043 version(mir_test)
3044 @safe pure nothrow
3045 unittest
3046 {
3047     import mir.algorithm.iteration: all;
3048     import mir.math.common: approxEqual;
3049     import mir.ndslice.topology: iota, alongDim, map;
3050 
3051     auto x = iota([2, 2], 1);
3052     auto y = x.alongDim!1.map!variance;
3053     assert(y.all!approxEqual([0.5, 0.5]));
3054     static assert(is(meanType!(typeof(y)) == double));
3055 }
3056 
3057 version(mir_test)
3058 @safe pure nothrow @nogc
3059 unittest
3060 {
3061     import mir.math.common: approxEqual;
3062     import mir.ndslice.slice: sliced;
3063 
3064     static immutable x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
3065                           2.0, 7.5, 5.0, 1.0, 1.5, 0.0];
3066 
3067     assert(x.sliced.variance.approxEqual(54.76562 / 11));
3068     assert(x.sliced.variance!float.approxEqual(54.76562 / 11));
3069 }
3070 
3071 ///
3072 package(mir)
3073 template stdevType(T)
3074 {
3075     import mir.internal.utility: isFloatingPoint;
3076     
3077     alias U = meanType!T;
3078 
3079     static if (isFloatingPoint!U) {
3080         alias stdevType = U;
3081     } else {
3082         static assert(0, "stdevType: Can't calculate standard deviation of elements of type " ~ U.stringof);
3083     }
3084 }
3085 
3086 version(mir_test)
3087 @safe pure nothrow @nogc
3088 unittest
3089 {
3090     static assert(is(stdevType!(int[]) == double));
3091     static assert(is(stdevType!(double[]) == double));
3092     static assert(is(stdevType!(float[]) == float));
3093 }
3094 
3095 version(mir_test)
3096 @safe pure nothrow @nogc
3097 unittest
3098 {
3099     static struct Foo {
3100         float x;
3101         alias x this;
3102     }
3103 
3104     static assert(is(stdevType!(Foo[]) == float));
3105 }
3106 
3107 /++
3108 Calculates the standard deviation of the input
3109 
3110 By default, if `F` is not floating point type, then the result will have a
3111 `double` type if `F` is implicitly convertible to a floating point type.
3112 
3113 Params:
3114     F = controls type of output
3115     varianceAlgo = algorithm for calculating variance (default: VarianceAlgo.online)
3116     summation = algorithm for calculating sums (default: Summation.appropriate)
3117 Returns:
3118     The standard deviation of the input, must be floating point type type
3119 +/
3120 template standardDeviation(
3121     F, 
3122     VarianceAlgo varianceAlgo = VarianceAlgo.online, 
3123     Summation summation = Summation.appropriate)
3124 {
3125     import mir.math.common: sqrt;
3126 
3127     /++
3128     Params:
3129         r = range, must be finite iterable
3130         isPopulation = true if population standard deviation, false if sample standard deviation (default)
3131     +/
3132     @fmamath stdevType!F standardDeviation(Range)(Range r, bool isPopulation = false)
3133         if (isIterable!Range)
3134     {
3135         import core.lifetime: move;
3136         alias G = typeof(return);
3137         return r.move.variance!(G, varianceAlgo, ResolveSummationType!(summation, Range, G))(isPopulation).sqrt;
3138     }
3139 
3140     /++
3141     Params:
3142         ar = values
3143     +/
3144     @fmamath stdevType!F standardDeviation(scope const F[] ar...)
3145     {
3146         alias G = typeof(return);
3147         return ar.variance!(G, varianceAlgo, ResolveSummationType!(summation, const(G)[], G)).sqrt;
3148     }
3149 }
3150 
3151 /// ditto
3152 template standardDeviation(
3153     VarianceAlgo varianceAlgo = VarianceAlgo.online, 
3154     Summation summation = Summation.appropriate)
3155 {
3156     /++
3157     Params:
3158         r = range, must be finite iterable
3159         isPopulation = true if population standard deviation, false if sample standard deviation (default)
3160     +/
3161     @fmamath stdevType!Range standardDeviation(Range)(Range r, bool isPopulation = false)
3162         if(isIterable!Range)
3163     {
3164         import core.lifetime: move;
3165 
3166         alias F = typeof(return);
3167         return .standardDeviation!(F, varianceAlgo, summation)(r.move, isPopulation);
3168     }
3169 
3170     /++
3171     Params:
3172         ar = values
3173     +/
3174     @fmamath stdevType!T standardDeviation(T)(scope const T[] ar...)
3175     {
3176         alias F = typeof(return);
3177         return .standardDeviation!(F, varianceAlgo, summation)(ar);
3178     }
3179 }
3180 
3181 /// ditto
3182 template standardDeviation(F, string varianceAlgo, string summation = "appropriate")
3183 {
3184     mixin("alias standardDeviation = .standardDeviation!(F, VarianceAlgo." ~ varianceAlgo ~ ", Summation." ~ summation ~ ");");
3185 }
3186 
3187 /// ditto
3188 template standardDeviation(string varianceAlgo, string summation = "appropriate")
3189 {
3190     mixin("alias standardDeviation = .standardDeviation!(VarianceAlgo." ~ varianceAlgo ~ ", Summation." ~ summation ~ ");");
3191 }
3192 
3193 ///
3194 version(mir_test)
3195 @safe pure nothrow
3196 unittest
3197 {
3198     import mir.math.common: approxEqual, sqrt;
3199     import mir.ndslice.slice: sliced;
3200 
3201     assert(standardDeviation([1.0, 2, 3]).approxEqual(sqrt(2.0 / 2)));
3202     assert(standardDeviation([1.0, 2, 3], true).approxEqual(sqrt(2.0 / 3)));
3203     
3204     assert(standardDeviation!float([0, 1, 2, 3, 4, 5].sliced(3, 2)).approxEqual(sqrt(17.5 / 5)));
3205     
3206     static assert(is(typeof(standardDeviation!float([1, 2, 3])) == float));
3207 }
3208 
3209 /// Standard deviation of vector
3210 version(mir_test)
3211 @safe pure nothrow
3212 unittest
3213 {
3214     import mir.math.common: approxEqual, sqrt;
3215     import mir.ndslice.slice: sliced;
3216 
3217     auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
3218               2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;
3219 
3220     assert(x.standardDeviation.approxEqual(sqrt(54.76562 / 11)));
3221 }
3222 
3223 /// Standard deviation of matrix
3224 version(mir_test)
3225 @safe pure
3226 unittest
3227 {
3228     import mir.math.common: approxEqual, sqrt;
3229     import mir.ndslice.fuse: fuse;
3230 
3231     auto x = [
3232         [0.0, 1.0, 1.5, 2.0, 3.5, 4.25],
3233         [2.0, 7.5, 5.0, 1.0, 1.5, 0.0]
3234     ].fuse;
3235 
3236     assert(x.standardDeviation.approxEqual(sqrt(54.76562 / 11)));
3237 }
3238 
3239 /// Column standard deviation of matrix
3240 version(mir_test)
3241 @safe pure
3242 unittest
3243 {
3244     import mir.algorithm.iteration: all;
3245     import mir.math.common: approxEqual, sqrt;
3246     import mir.ndslice.fuse: fuse;
3247     import mir.ndslice.topology: alongDim, byDim, map;
3248 
3249     auto x = [
3250         [0.0,  1.0, 1.5, 2.0], 
3251         [3.5, 4.25, 2.0, 7.5],
3252         [5.0,  1.0, 1.5, 0.0]
3253     ].fuse;
3254     auto result = [13.16667 / 2, 7.041667 / 2, 0.1666667 / 2, 30.16667 / 2].map!sqrt;
3255 
3256     // Use byDim or alongDim with map to compute standardDeviation of row/column.
3257     assert(x.byDim!1.map!standardDeviation.all!approxEqual(result));
3258     assert(x.alongDim!0.map!standardDeviation.all!approxEqual(result));
3259 
3260     // FIXME
3261     // Without using map, computes the standardDeviation of the whole slice
3262     // assert(x.byDim!1.standardDeviation == x.sliced.standardDeviation);
3263     // assert(x.alongDim!0.standardDeviation == x.sliced.standardDeviation);
3264 }
3265 
3266 /// Can also set algorithm type
3267 version(mir_test)
3268 @safe pure nothrow
3269 unittest
3270 {
3271     import mir.math.common: approxEqual, sqrt;
3272     import mir.ndslice.slice: sliced;
3273 
3274     auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
3275               2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;
3276 
3277     auto x = a + 1_000_000_000;
3278 
3279     auto y = x.standardDeviation;
3280     assert(y.approxEqual(sqrt(54.76562 / 11)));
3281 
3282     // The naive algorithm is numerically unstable in this case
3283     auto z0 = x.standardDeviation!"naive";
3284     assert(!z0.approxEqual(y));
3285 
3286     // But the two-pass algorithm provides a consistent answer
3287     auto z1 = x.standardDeviation!"twoPass";
3288     assert(z1.approxEqual(y));
3289 }
3290 
3291 /// Can also set algorithm or output type
3292 version(mir_test)
3293 @safe pure nothrow
3294 unittest
3295 {
3296     import mir.math.common: approxEqual, sqrt;
3297     import mir.ndslice.slice: sliced;
3298     import mir.ndslice.topology: repeat;
3299 
3300     //Set population standard deviation, standardDeviation algorithm, sum algorithm or output type
3301 
3302     auto a = [1.0, 1e100, 1, -1e100].sliced;
3303     auto x = a * 10_000;
3304 
3305     bool populationTrueRT = true;
3306     bool populationFalseRT = false;
3307     enum PopulationTrueCT = true;
3308 
3309     /++
3310     Due to Floating Point precision, when centering `x`, subtracting the mean 
3311     from the second and fourth numbers has no effect. Further, after centering 
3312     and squaring `x`, the first and third numbers in the slice have precision 
3313     too low to be included in the centered sum of squares. 
3314     +/
3315     assert(x.standardDeviation(populationFalseRT).approxEqual(sqrt(2.0e208 / 3)));
3316     assert(x.standardDeviation(populationTrueRT).approxEqual(sqrt(2.0e208 / 4)));
3317     assert(x.standardDeviation(PopulationTrueCT).approxEqual(sqrt(2.0e208 / 4)));
3318 
3319     assert(x.standardDeviation!("online").approxEqual(sqrt(2.0e208 / 3)));
3320     assert(x.standardDeviation!("online", "kbn").approxEqual(sqrt(2.0e208 / 3)));
3321     assert(x.standardDeviation!("online", "kb2").approxEqual(sqrt(2.0e208 / 3)));
3322     assert(x.standardDeviation!("online", "precise").approxEqual(sqrt(2.0e208 / 3)));
3323     assert(x.standardDeviation!(double, "online", "precise").approxEqual(sqrt(2.0e208 / 3)));
3324     assert(x.standardDeviation!(double, "online", "precise")(populationTrueRT).approxEqual(sqrt(2.0e208 / 4)));
3325 
3326     auto y = uint.max.repeat(3);
3327     auto z = y.standardDeviation!ulong;
3328     assert(z == 0.0);
3329     static assert(is(typeof(z) == double));
3330 }
3331 
3332 /++
3333 For integral slices, pass output type as template parameter to ensure output
3334 type is correct.
3335 +/
3336 version(mir_test)
3337 @safe pure nothrow
3338 unittest
3339 {
3340     import mir.math.common: approxEqual, sqrt;
3341     import mir.ndslice.slice: sliced;
3342 
3343     auto x = [0, 1, 1, 2, 4, 4,
3344               2, 7, 5, 1, 2, 0].sliced;
3345 
3346     auto y = x.standardDeviation;
3347     assert(y.approxEqual(sqrt(50.91667 / 11)));
3348     static assert(is(typeof(y) == double));
3349 
3350     assert(x.standardDeviation!float.approxEqual(sqrt(50.91667 / 11)));
3351 }
3352 
3353 /++
3354 Variance works for other user-defined types (provided they
3355 can be converted to a floating point)
3356 +/
3357 version(mir_test)
3358 @safe pure nothrow
3359 unittest
3360 {
3361     import mir.ndslice.slice: sliced;
3362     
3363     static struct Foo {
3364         float x;
3365         alias x this;
3366     }
3367     
3368     Foo[] foo = [Foo(1f), Foo(2f), Foo(3f)];
3369     assert(foo.standardDeviation == 1f);
3370 }
3371 
3372 /// Compute standard deviation along specified dimention of tensors
3373 version(mir_test)
3374 @safe pure
3375 unittest
3376 {
3377     import mir.algorithm.iteration: all;
3378     import mir.math.common: approxEqual, sqrt;
3379     import mir.ndslice.fuse: fuse;
3380     import mir.ndslice.topology: as, iota, alongDim, map, repeat;
3381 
3382     auto x = [
3383         [0.0, 1, 2],
3384         [3.0, 4, 5]
3385     ].fuse;
3386 
3387     assert(x.standardDeviation.approxEqual(sqrt(17.5 / 5)));
3388 
3389     auto m0 = repeat(sqrt(4.5), 3);
3390     assert(x.alongDim!0.map!standardDeviation.all!approxEqual(m0));
3391     assert(x.alongDim!(-2).map!standardDeviation.all!approxEqual(m0));
3392 
3393     auto m1 = [1.0, 1.0];
3394     assert(x.alongDim!1.map!standardDeviation.all!approxEqual(m1));
3395     assert(x.alongDim!(-1).map!standardDeviation.all!approxEqual(m1));
3396 
3397     assert(iota(2, 3, 4, 5).as!double.alongDim!0.map!standardDeviation.all!approxEqual(repeat(sqrt(3600.0 / 2), 3, 4, 5)));
3398 }
3399 
3400 /// Arbitrary standard deviation
3401 version(mir_test)
3402 @safe pure nothrow @nogc
3403 unittest
3404 {
3405     import mir.math.common: sqrt;
3406 
3407     assert(standardDeviation(1.0, 2, 3) == 1.0);
3408     assert(standardDeviation!float(1, 2, 3) == 1f);
3409 }
3410 
3411 version(mir_test)
3412 @safe pure nothrow
3413 unittest
3414 {
3415     import mir.math.common: approxEqual, sqrt;
3416     assert([1.0, 2, 3, 4].standardDeviation.approxEqual(sqrt(5.0 / 3)));
3417 }
3418 
3419 version(mir_test)
3420 @safe pure nothrow
3421 unittest
3422 {
3423     import mir.algorithm.iteration: all;
3424     import mir.math.common: approxEqual, sqrt;
3425     import mir.ndslice.topology: iota, alongDim, map;
3426 
3427     auto x = iota([2, 2], 1);
3428     auto y = x.alongDim!1.map!standardDeviation;
3429     assert(y.all!approxEqual([sqrt(0.5), sqrt(0.5)]));
3430     static assert(is(meanType!(typeof(y)) == double));
3431 }
3432 
3433 version(mir_test)
3434 @safe pure @nogc nothrow
3435 unittest
3436 {
3437     import mir.math.common: approxEqual, sqrt;
3438     import mir.ndslice.slice: sliced;
3439 
3440     static immutable x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
3441                           2.0, 7.5, 5.0, 1.0, 1.5, 0.0];
3442 
3443     assert(x.sliced.standardDeviation.approxEqual(sqrt(54.76562 / 11)));
3444     assert(x.sliced.standardDeviation!float.approxEqual(sqrt(54.76562 / 11)));
3445 }
3446 
3447 /++
3448 A linear regression model with a single explanatory variable.
3449 +/
3450 template simpleLinearRegression(Summation summation = Summation.kbn)
3451 {
3452     import mir.ndslice.slice;
3453     import mir.primitives: isInputRange;
3454 
3455     /++
3456     Params:
3457         x = `x[i]` points
3458         y = `f(x[i])` values
3459     Returns:
3460         The pair of shift and slope of the linear curve.
3461     +/
3462     @fmamath
3463     sumType!YRange[2]
3464     simpleLinearRegression(XRange, YRange)(XRange x, YRange y) @safe
3465         if (isInputRange!XRange && isInputRange!YRange && !(isArray!XRange && isArray!YRange) && isFloatingPoint!(sumType!YRange))
3466     in {
3467         static if (hasLength!XRange && hasLength!YRange)
3468             assert(x.length == y.length);
3469     }
3470     do {
3471         alias X = typeof(sumType!XRange.init * sumType!XRange.init);
3472         alias Y = sumType!YRange;
3473         enum summationX = !__traits(isIntegral, X) ? summation: Summation.naive;
3474         Summator!(X, summationX) xms = 0;
3475         Summator!(Y, summation) yms = 0;
3476         Summator!(X, summationX) xxms = 0;
3477         Summator!(Y, summation) xyms = 0;
3478 
3479         static if (hasLength!XRange)
3480             sizediff_t n = x.length;
3481         else
3482             sizediff_t n = 0;
3483 
3484         while (!x.empty)
3485         {
3486             static if (!(hasLength!XRange && hasLength!YRange))
3487                 assert(!y.empty);
3488 
3489             static if (!hasLength!XRange)
3490                 n++;
3491 
3492             auto xi = x.front;
3493             auto yi = y.front;
3494             xms.put(xi);
3495             yms.put(yi);
3496             xxms.put(xi * xi);
3497             xyms.put(xi * yi);
3498 
3499             y.popFront;
3500             x.popFront;
3501         }
3502 
3503         static if (!(hasLength!XRange && hasLength!YRange))
3504             assert(y.empty);
3505 
3506         auto xm = xms.sum;
3507         auto ym = yms.sum;
3508         auto xxm = xxms.sum;
3509         auto xym = xyms.sum;
3510 
3511         auto slope = (xym * n - xm * ym) / (xxm * n - xm * xm);
3512 
3513         return [(ym - slope * xm) / n, slope];
3514     }
3515 
3516     /// ditto
3517     @fmamath
3518     sumType!(Y[])[2]
3519     simpleLinearRegression(X, Y)(scope const X[] x, scope const Y[] y) @safe
3520     {
3521         return .simpleLinearRegression!summation(x.sliced, y.sliced);
3522     }
3523 }
3524 
3525 /// ditto
3526 template simpleLinearRegression(string summation)
3527 {
3528     mixin("alias simpleLinearRegression = .simpleLinearRegression!(Summation." ~ summation ~ ");");
3529 }
3530 
3531 ///
3532 version(mir_test)
3533 @safe pure nothrow @nogc
3534 unittest
3535 {
3536     import mir.math.common: approxEqual;
3537     static immutable x = [0, 1, 2, 3];
3538     static immutable y = [-1, 0.2, 0.9, 2.1];
3539     auto params = x.simpleLinearRegression(y);
3540     assert(params[0].approxEqual(-0.95)); // shift
3541     assert(params[1].approxEqual(1)); // slope
3542 }