The OpenD Programming Language

1 /++
2 Polynomial ref-counted structure.
3 
4 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
5 Authors: Ilia Ki
6 +/
7 module mir.polynomial;
8 
9 import mir.math.common: fmamath;
10 import mir.rc.array;
11 
12 @fmamath:
13 
14 /++
15 Polynomial callable ref-counted structure.
16 +/
17 struct Polynomial(F)
18 {
19     ///
20     RCArray!(const F) coefficients;
21 
22     /++
23     Params:
24         coefficients = coefficients `c[i]` for polynomial function `f(x)=c[0]+c[1]*x^^1+...+c[n]*x^^n`
25     +/
26     this(RCArray!(const F) coefficients)
27     {
28         import core.lifetime: move;
29         this.coefficients = coefficients.move;
30     }
31 
32     /++
33     Params:
34         derivative = derivative order
35     +/
36     template opCall(uint derivative = 0)
37     {
38         /++
39         Params:
40             x = `x` point
41         +/
42         @fmamath typeof(F.init * X.init * 1f + F.init) opCall(X)(in X x) const
43         {
44             return x.poly!derivative(this.coefficients[]);
45         }
46     }
47 }
48 
49 /// ditto
50 Polynomial!F polynomial(F)(RCArray!(const F) coefficients)
51 {
52     import core.lifetime: move;
53     return typeof(return)(coefficients.move);
54 }
55 
56 ///
57 version (mir_test) @safe pure nothrow @nogc unittest
58 {
59     import mir.test;
60     import mir.rc.array;
61     auto a = rcarray!(const double)(3.0, 4.5, 1.9, 2);
62     auto p = a.polynomial;
63 
64     alias f = (x) => 3.0 + 4.5 * x^^1 + 1.9 * x^^2 + 2 * x^^3;
65     alias df = (x) => 4.5 + 2 * 1.9 * x^^1 + 3 * 2 * x^^2;
66     alias d2f = (x) => 2 * 1.9  + 6 * 2 * x^^1;
67 
68     p(3.3).shouldApprox == f(3.3);
69     p(7.2).shouldApprox == f(7.2);
70 
71     p.opCall!1(3.3).shouldApprox == df(3.3);
72     p.opCall!1(7.2).shouldApprox == df(7.2);
73 
74     p.opCall!2(3.3).shouldApprox == d2f(3.3);
75     p.opCall!2(7.2).shouldApprox == d2f(7.2);
76 }
77 
78 /++
79 Evaluate polynomial.
80 
81 Coefficients assumed to be in the order a0 + a1 * x ^^ 1 + ... + aN * x ^^ N
82 
83 Params:
84     F = controls type of output
85     derivative = order of derivatives (default = 0)
86 
87 Returns:
88     Value of the polynomial, evaluated at `x`
89 
90 See_also:
91     $(WEB en.wikipedia.org/wiki/Polynomial, Polynomial).
92 +/
93 template poly(uint derivative = 0)
94 {
95     import std.traits: ForeachType;
96     /++
97     Params:
98         x = value to evaluate
99         coefficients = coefficients of polynomial
100     +/
101     @fmamath typeof(F.init * X.init * 1f + F.init) poly(X, F)(in X x, scope const F[] coefficients...)
102     {
103         import mir.internal.utility: Iota;
104         auto ret = cast(typeof(return))0;
105         if (coefficients.length > 0)
106         {
107             ptrdiff_t i = coefficients.length - 1;
108             assert(i >= 0);
109             auto c = cast()coefficients[i];
110             static foreach (d; Iota!derivative)
111                 c *= i - d;
112             ret = cast(typeof(return)) c;
113             while (--i >= cast(ptrdiff_t)derivative)
114             {
115                 assert(i < coefficients.length);
116                 c = cast()coefficients[i];
117                 static foreach (d; Iota!derivative)
118                     c *= i - d;
119                 ret *= x;
120                 ret += c;
121             }
122         }
123         return ret;
124     }
125 }
126 
127 ///
128 version (mir_test) @safe pure nothrow unittest
129 {
130     import mir.math.common: approxEqual;
131 
132     double[] x = [3.0, 4.5, 1.9, 2];
133 
134     alias   f = (x) =>     3.0 +     4.5 * x^^1 +   1.9 * x^^2 + 2 * x^^3;
135     alias  df = (x) =>     4.5 + 2 * 1.9 * x^^1 + 3 * 2 * x^^2;
136     alias d2f = (x) => 2 * 1.9 + 6 *   2 * x^^1;
137 
138     assert(poly(3.3, x).approxEqual(f(3.3)));
139     assert(poly(7.2, x).approxEqual(f(7.2)));
140 
141     assert(poly!1(3.3, x).approxEqual(df(3.3)));
142     assert(poly!1(7.2, x).approxEqual(df(7.2)));
143 
144     assert(poly!2(3.3, x).approxEqual(d2f(3.3)));
145     assert(poly!2(7.2, x).approxEqual(d2f(7.2)));
146 }
147 
148 // static array test
149 version (mir_test) @safe pure @nogc nothrow unittest
150 {
151     import mir.math.common: approxEqual;
152 
153     double[4] x = [3.0, 4.5, 1.9, 2];
154 
155     alias   f = (x) =>     3.0 +     4.5 * x^^1 +   1.9 * x^^2 + 2 * x^^3;
156     alias  df = (x) =>     4.5 + 2 * 1.9 * x^^1 + 3 * 2 * x^^2;
157     alias d2f = (x) => 2 * 1.9 + 6 *   2 * x^^1;
158 
159     assert(poly(3.3, x).approxEqual(f(3.3)));
160     assert(poly(7.2, x).approxEqual(f(7.2)));
161 
162     assert(poly!1(3.3, x).approxEqual(df(3.3)));
163     assert(poly!1(7.2, x).approxEqual(df(7.2)));
164 
165     assert(poly!2(3.3, x).approxEqual(d2f(3.3)));
166     assert(poly!2(7.2, x).approxEqual(d2f(7.2)));
167 }
168 
169 // Check coefficient.length = 3
170 version (mir_test) @safe pure nothrow unittest
171 {
172     import mir.math.common: approxEqual;
173 
174     double[] x = [3.0, 4.5, 1.9];
175 
176     alias   f = (x) =>     3.0 +     4.5 * x^^1 + 1.9 * x^^2;
177     alias  df = (x) =>     4.5 + 2 * 1.9 * x^^1;
178     alias d2f = (x) => 2 * 1.9;
179 
180     assert(poly(3.3, x).approxEqual(f(3.3)));
181     assert(poly(7.2, x).approxEqual(f(7.2)));
182 
183     assert(poly!1(3.3, x).approxEqual(df(3.3)));
184     assert(poly!1(7.2, x).approxEqual(df(7.2)));
185 
186     assert(poly!2(3.3, x).approxEqual(d2f(3.3)));
187     assert(poly!2(7.2, x).approxEqual(d2f(7.2)));
188 }
189 
190 // Check coefficient.length = 2
191 version (mir_test) @safe pure nothrow unittest
192 {
193     import mir.math.common: approxEqual;
194 
195     double[] x = [3.0, 4.5];
196 
197     alias   f = (x) => 3.0 + 4.5 * x^^1;
198     alias  df = (x) => 4.5;
199     alias d2f = (x) => 0.0;
200 
201     assert(poly(3.3, x).approxEqual(f(3.3)));
202     assert(poly(7.2, x).approxEqual(f(7.2)));
203 
204     assert(poly!1(3.3, x).approxEqual(df(3.3)));
205     assert(poly!1(7.2, x).approxEqual(df(7.2)));
206 
207     assert(poly!2(3.3, x).approxEqual(d2f(3.3)));
208     assert(poly!2(7.2, x).approxEqual(d2f(7.2)));
209 }
210 
211 // Check coefficient.length = 1
212 version (mir_test) @safe pure nothrow unittest
213 {
214     import mir.math.common: approxEqual;
215 
216     double[] x = [3.0];
217 
218     alias  f = (x) => 3.0;
219     alias df = (x) => 0.0;
220 
221     assert(poly(3.3, x).approxEqual(f(3.3)));
222     assert(poly(7.2, x).approxEqual(f(7.2)));
223 
224     assert(poly!1(3.3, x).approxEqual(df(3.3)));
225     assert(poly!1(7.2, x).approxEqual(df(7.2)));
226 }