1 module tagion.logger.Statistic;
2 
3 import std.exception : assumeWontThrow;
4 import std.format;
5 import std.meta : AliasSeq;
6 import std.typecons : Flag, No, Tuple, Yes;
7 import tagion.hibon.HiBONRecord;
8 
9 @safe @recordType("S")
10 struct Statistic(T, Flag!"histogram" flag = No.histogram) {
11     protected {
12         double sum2 = 0.0;
13         double sum = 0.0;
14         @label("min") T _min = T.max;
15         @label("max") T _max = T.min;
16         uint N;
17         static if (flag) {
18             @label("H") uint[T] _histogram;
19         }
20     }
21 
22     void opCall(const T value) pure nothrow {
23         import std.algorithm.comparison : max, min;
24 
25         _min = min(_min, value);
26         _max = max(_max, value);
27         immutable double x = value;
28         sum += x;
29         sum2 += x * x;
30         N++;
31         static if (flag) {
32             _histogram.update(
33                     value,
34                     () => 1,
35                     (ref uint a) => a += 1);
36         }
37     }
38 
39     alias Result = Tuple!(double, "sigma", double, "mean", uint, "N", T, "min", T, "max");
40     mixin HiBONRecord;
41 
42     const pure nothrow @nogc {
43         const(Result) result() {
44             immutable mx = sum / N;
45             immutable mx2 = mx * mx;
46             immutable M = sum2 + N * mx2 - 2 * mx * sum;
47             import std.math : sqrt;
48 
49             return Result(sqrt(M / (N - 1)), mx, N, _min, _max);
50         }
51 
52         static if (flag) {
53             bool contains(const T size) {
54                 return (size in _histogram) !is null;
55             }
56 
57             const(uint[T]) histogram() {
58                 return _histogram;
59             }
60         }
61     }
62     string toString() pure const nothrow {
63         return assumeWontThrow(format("N=%d sum2=%s sum=%s min=%s max=%s", N, sum2, sum, _min, _max));
64     }
65 
66     static if (flag == Yes.histogram) {
67 
68         string histogramString(const bool logscale = false,const uint axis_scale = 100) pure const nothrow {
69             import std.algorithm;
70             import std.format;
71             import std.range : repeat;
72             import std.math : round, log10;
73 
74             double max_value;
75             if (logscale) {
76                  max_value = log10(double(_histogram.byValue.maxPos.front));
77 
78             }
79             else {
80                  max_value = double(_histogram.byValue.maxPos.front);
81             }
82             uint height(const T size) {
83                  if (logscale) {
84                     return cast(uint)(round(axis_scale * log10(double(size)) / max_value));
85                 }
86                     return cast(uint)(round(axis_scale * double(size) / max_value));
87             }
88             string[] result;
89             result~=assumeWontThrow(format("%4s|%-(%s%)| %-(%9d|%)|", 
90             (logscale)?"log":"lin", ' '.repeat(8),
91             iota(1,axis_scale/10+1).map!(a => 10*a)));
92             foreach (keypair; _histogram.byKeyValue.array.sort!((a, b) => a.key < b.key)) {
93                 const number = keypair.key;
94                 const size = keypair.value;
95                 result ~= assumeWontThrow(format("%4d|%8d| %s", number, size, "#".repeat(height(size)).join));
96             }
97 
98             return result.join("\n");
99 
100         }
101 
102     }
103 
104 }
105 
106 ///
107 @safe
108 unittest {
109     Statistic!uint s;
110     const samples = [10, 15, 17, 6, 8, 12, 18];
111     samples.each!(a => s(a));
112 
113     auto r = s.result;
114     // Mean
115     assert(approx(r.mean, 12.2857));
116     // Number of samples
117     assert(r.N == samples.length);
118     // Sigma
119     assert(approx(r.sigma, 4.5721));
120 
121     assert(r.max == samples.maxElement);
122     assert(r.min == samples.minElement);
123 
124 }
125 
126 ///
127 @safe
128 unittest {
129     /// Use of the Statistic including histogram
130     Statistic!(long, Yes.histogram) s;
131     const samples = [-10, 15, -10, 6, 8, -12, 18, 8, -12, 9, 4, 5, 6];
132     samples.each!(n => s(n));
133 
134     auto r = s.result;
135     // Mean
136     assert(approx(r.mean, 2.6923));
137     // Number of samples
138     assert(r.N == samples.length);
139     // Sigma
140     assert(approx(r.sigma, 10.266));
141 
142     assert(r.max == samples.maxElement);
143     assert(r.min == samples.minElement);
144     // samples/histogram does not contain -4
145     assert(!s.contains(-4));
146     // but conatians -10
147     assert(s.contains(-10));
148 
149     // Get the statiscal histogram
150     const histogram = s.histogram;
151 
152     assert(histogram.get(-4, 0) == 0);
153     assert(histogram.get(-10, 0) > 0);
154 
155     // verifies the number of samples in the histogram
156     assert(histogram.get(-10, 0) == samples.filter!(a => a == -10).count);
157 }
158 
159 version (unittest) {
160     import std.algorithm.iteration : each, filter;
161     import std.algorithm.searching : count, maxElement, minElement;
162     import std.math : isClose;
163 
164     alias approx = (a, b) => isClose(a, b, 0.001);
165 }