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