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 	/* assert(argmin!(x => x*x)([1, 2, 3]) == 1); */
131 	/* assert(argmin!(x => x*x)([3, 2, 1]) == 1); */
132 }
133 
134 /** Returns: Element in $(D r ) that maximizes $(D fun).
135 	LaTeX: \underset{x}{\arg\max} */
136 auto argmax(alias fun, Range)(in Range r) @safe pure
137 	if (isInputRange!Range &&
138 		is(typeof(fun(r.front) >
139 				  fun(r.front)) == bool))
140 {
141 	import std.front: front;
142 	return typeof(r.front).min.reduce!((a,b) => fun(a) > fun(b) ? a : b)(r);
143 }
144 
145 ///
146 unittest {
147 	/* assert(argmax!(x => x*x)([1, 2, 3]) == 3); */
148 	/* assert(argmax!(x => x*x)([3, 2, 1]) == 3); */
149 }
150 
151 // ==============================================================================================
152 
153 /// Returns: 0.0 if x < edge, otherwise it returns 1.0.
154 CommonType!(T1, T2) step(T1, T2)(T1 edge, T2 x) => x < edge ? 0 : 1;
155 
156 ///
157 unittest {
158 	assert(step(0, 1) == 1.0f);
159 	assert(step(0, 10) == 1.0f);
160 	assert(step(1, 0) == 0.0f);
161 	assert(step(10, 0) == 0.0f);
162 	assert(step(1, 1) == 1.0f);
163 }
164 
165 // ==============================================================================================
166 
167 /** Smoothstep from $(D edge0) to $(D edge1) at $(D x).
168 	Returns: 0.0 if x <= edge0 and 1.0 if x >= edge1 and performs smooth
169 	hermite interpolation between 0 and 1 when edge0 < x < edge1.
170 	This is useful in cases where you would want a threshold function with a smooth transition.
171 */
172 CommonType!(T1,T2,T3) smoothstep(T1, T2, T3) (T1 edge0, T2 edge1, T3 x) @safe pure nothrow
173 if (isFloatingPoint!(CommonType!(T1,T2,T3)))
174 {
175 	import std.algorithm: clamp;
176 	x = clamp((x - edge0) / (edge1 - edge0), 0, 1);
177 	return x * x * (3 - 2 * x);
178 }
179 
180 ///
181 unittest {
182 	//  assert(smoothstep(1, 0, 2) == 0);
183 	assert(smoothstep(1.0, 0.0, 2.0) == 0);
184 	assert(smoothstep(1.0, 0.0, 0.5) == 0.5);
185 	// assert(almost_equal(smoothstep(0.0, 2.0, 0.5), 0.15625, 0.00001));
186 }
187 
188 /** Smootherstep from $(D edge0) to $(D edge1) at $(D x). */
189 @safe pure nothrow E smootherstep(E)(E edge0, E edge1, E x)
190 if (isFloatingPoint!(E))
191 {
192 	// Scale, and clamp x to 0..1 range
193 	import std.algorithm: clamp;
194 	x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
195 	// Evaluate polynomial
196 	return x*x*x*(x*(x*6 - 15) + 10); // evaluate polynomial
197 }
198 
199 ///
200 unittest {
201 	assert(smootherstep(1.0, 0.0, 2.0) == 0);
202 }