1 module nxt.numeric_ex;
2 
3 import std.range: isInputRange, ElementType;
4 import std.traits: CommonType, isFloatingPoint;
5 import std.algorithm: reduce;
6 
7 /* import std.numeric: sum; */
8 
9 /** TODO: Issue 4725: Remove when sum is standard in Phobos.
10     See_Also: http://d.puremagic.com/issues/show_bug.cgi?id=4725
11     See_Also: https://github.com/D-Programming-Language/phobos/pull/1205
12     See_Also: http://forum.dlang.org/thread/bug-4725-3@http.d.puremagic.com%2Fissues%2F
13  */
14 auto sum(Range, SumType = ElementType!Range)(Range range) @safe pure nothrow
15 if (isInputRange!Range)
16 	=> reduce!"a+b"(0, range);
17 
18 ///
19 unittest { assert([1, 2, 3, 4].sum == 10); }
20 
21 /** TODO: Remove when product is standard in Phobos. */
22 auto product(Range)(Range range) @safe pure nothrow
23 if (isInputRange!Range)
24 	=> reduce!"a*b"(1, range);
25 
26 ///
27 unittest { assert([1, 2, 3, 4].product == 24); }
28 
29 // ==============================================================================================
30 
31 version(none)
32 {
33 
34     /** Computes $(LUCKY Discrete Signal Entropy) of input range $(D range).
35      */
36     auto signalEntropy(Range, RequestedBinType = double)(in Range range) @safe pure
37         if (isInputRange!Range &&
38             !is(CommonType!(ElementType!Range, F, G) == void))
39     {
40         enum normalized = true; // we need normalized histogram
41         import nxt.ngram: histogram, Kind, Storage, Symmetry;
42         auto hist = range.histogram!(Kind.saturated,
43                                      Storage.denseDynamic,
44                                      Symmetry.ordered,
45                                      RequestedBinType);
46         import std.numeric: entropy;
47         hist.normalize;
48         return hist[].entropy;
49     }
50 
51     ///
52     unittest
53     {
54         const ubyte[] p1 = [ 0, 1, 0, 1 ];
55         assert(p1.signalEntropy == 1);
56 
57         const ubyte[] p2 = [ 0, 1, 2, 3 ];
58         assert(p2.signalEntropy == 2);
59 
60         const ubyte[] p3 = [ 0, 255, 0, 255];
61         assert(p3.signalEntropy == 1);
62     }
63 
64     /** Returns: Element in $(D r ) that minimizes $(D fun).
65         LaTeX: \underset{x}{\arg\min} */
66     auto argmin_(alias fun, Range)(in Range r) @safe pure
67         if (isInputRange!Range &&
68             is(typeof(fun(r.front) <
69                       fun(r.front)) == bool))
70     {
71         auto bestX = r.front;
72         auto bestY = fun(bestX);
73         r.popFront();
74         foreach (e; r) {
75             auto candY = fun(e);     // candidate
76             if (candY > bestY) continue;
77             bestX = e;
78             bestY = candY;
79         }
80         return bestX;
81     }
82 
83     ///
84     unittest
85     {
86         assert(argmin!(x => x*x)([1, 2, 3]) == 1);
87         assert(argmin!(x => x*x)([3, 2, 1]) == 1);
88     }
89 
90   /** Returns: Element in $(D r ) that maximizes $(D fun).
91       LaTeX: \underset{x}{\arg\max} */
92     auto argmax_(alias fun, Range)(in Range r) @safe pure
93         if (isInputRange!Range &&
94             is(typeof(fun(r.front) >
95                       fun(r.front)) == bool))
96     {
97         auto bestX = r.front;
98         auto bestY = fun(bestX);
99         r.popFront();
100         foreach (e; r) {
101             auto candY = fun(e);     // candidate
102             if (candY < bestY) continue;
103             bestX = e;
104             bestY = candY;
105         }
106         return bestX;
107     }
108 
109     ///
110     unittest
111     {
112         assert(argmax!(x => x*x)([1, 2, 3]) == 3);
113         assert(argmax!(x => x*x)([3, 2, 1]) == 3);
114     }
115 
116 }
117 
118 /** Returns: Element in $(D r ) that minimizes $(D fun).
119     LaTeX: \underset{x}{\arg\min} */
120 auto argmin(alias fun, Range)(in Range r)
121     @safe pure if (isInputRange!Range &&
122                    is(typeof(fun(r.front) < fun(r.front)) == bool))
123 {
124     import std.front: front;
125     return typeof(r.front).max.reduce!((a,b) => fun(a) < fun(b) ? a : b)(r);
126 }
127 
128 ///
129 unittest
130 {
131     /* assert(argmin!(x => x*x)([1, 2, 3]) == 1); */
132     /* assert(argmin!(x => x*x)([3, 2, 1]) == 1); */
133 }
134 
135 /** Returns: Element in $(D r ) that maximizes $(D fun).
136     LaTeX: \underset{x}{\arg\max} */
137 auto argmax(alias fun, Range)(in Range r) @safe pure
138     if (isInputRange!Range &&
139         is(typeof(fun(r.front) >
140                   fun(r.front)) == bool))
141 {
142     import std.front: front;
143     return typeof(r.front).min.reduce!((a,b) => fun(a) > fun(b) ? a : b)(r);
144 }
145 
146 ///
147 unittest
148 {
149     /* assert(argmax!(x => x*x)([1, 2, 3]) == 3); */
150     /* assert(argmax!(x => x*x)([3, 2, 1]) == 3); */
151 }
152 
153 // ==============================================================================================
154 
155 /// Returns: 0.0 if x < edge, otherwise it returns 1.0.
156 CommonType!(T1, T2) step(T1, T2)(T1 edge, T2 x) => x < edge ? 0 : 1;
157 
158 ///
159 unittest
160 {
161     assert(step(0, 1) == 1.0f);
162     assert(step(0, 10) == 1.0f);
163     assert(step(1, 0) == 0.0f);
164     assert(step(10, 0) == 0.0f);
165     assert(step(1, 1) == 1.0f);
166 }
167 
168 // ==============================================================================================
169 
170 /** Smoothstep from $(D edge0) to $(D edge1) at $(D x).
171     Returns: 0.0 if x <= edge0 and 1.0 if x >= edge1 and performs smooth
172     hermite interpolation between 0 and 1 when edge0 < x < edge1.
173     This is useful in cases where you would want a threshold function with a smooth transition.
174 */
175 CommonType!(T1,T2,T3) smoothstep(T1, T2, T3) (T1 edge0, T2 edge1, T3 x) @safe pure nothrow
176 if (isFloatingPoint!(CommonType!(T1,T2,T3)))
177 {
178     import std.algorithm: clamp;
179     x = clamp((x - edge0) / (edge1 - edge0), 0, 1);
180     return x * x * (3 - 2 * x);
181 }
182 
183 ///
184 unittest
185 {
186     //  assert(smoothstep(1, 0, 2) == 0);
187     assert(smoothstep(1.0, 0.0, 2.0) == 0);
188     assert(smoothstep(1.0, 0.0, 0.5) == 0.5);
189     // assert(almost_equal(smoothstep(0.0, 2.0, 0.5), 0.15625, 0.00001));
190 }
191 
192 /** Smootherstep from $(D edge0) to $(D edge1) at $(D x). */
193 @safe pure nothrow E smootherstep(E)(E edge0, E edge1, E x)
194 if (isFloatingPoint!(E))
195 {
196     // Scale, and clamp x to 0..1 range
197     import std.algorithm: clamp;
198     x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
199     // Evaluate polynomial
200     return x*x*x*(x*(x*6 - 15) + 10); // evaluate polynomial
201 }
202 
203 ///
204 unittest
205 {
206     assert(smootherstep(1.0, 0.0, 2.0) == 0);
207 }