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