The OpenD Programming Language

1 module mir.interpolate.utility;
2 
3 import mir.ndslice.slice;
4 import std.traits;
5 import mir.math.common: fmamath;
6 
7 package template DeepType(T)
8 {
9     static if (is(T : E[N], E, size_t N))
10         alias DeepType = DeepType!E;
11     else
12         alias DeepType = T;
13 }
14 
15 /++
16 Quadratic function structure
17 +/
18 struct ParabolaKernel(T)
19 {
20     ///
21     T a = 0;
22     ///
23     T b = 0;
24     ///
25     T c = 0;
26 
27 @fmamath:
28 
29     ///
30     this(T a, T b, T c)
31     {
32         this.a = a;
33         this.b = b;
34         this.c = c;
35     }
36 
37     /// Builds parabola given three points
38     this(T x0, T x1, T x2, T y0, T y1, T y2)
39     {
40         auto b1 = x1 - x0;
41         auto b2 = x2 - x1;
42         auto d1 = y1 - y0;
43         auto d2 = y2 - y1;
44         auto a1 = b1 * (x1 + x0);
45         auto a2 = b2 * (x2 + x1);
46         auto bm = - (b2 / b1);
47         auto a3 = bm * a1 + a2;
48         auto d3 = bm * d1 + d2;
49         a = d3 / a3;
50         b = (d1 - a1 * a) / b1;
51         c = y1 - x1 * (a * x1 + b);
52     }
53 
54     /++
55     Params:
56         x0 = `x0`
57         x1 = `x1`
58         y0 = `f(x0)`
59         y1 = `f(x1)`
60         d1 = `f'(x1)`
61     +/
62     static ParabolaKernel fromFirstDerivative(T x0, T x1, T y0, T y1, T d1)
63     {
64         auto dy = y1 - y0;
65         auto dx = x1 - x0;
66         auto dd = dy / dx;
67         auto a = (d1 - dd) / dx;
68         auto b = dd - a * (x0 + x1);
69         auto c = y1 - (a * x1 + b) * x1;
70         return ParabolaKernel(a, b, c);
71     }
72 
73     ///
74     auto opCall(uint derivative = 0)(T x) const
75         if (derivative <= 2)
76     {
77         auto y = (a * x + b) * x + c;
78         static if (derivative == 0)
79            return y;
80         else
81         {
82             auto y1 = 2 * a * x + b;
83             static if (derivative == 1)
84                 return cast(T[2])[y, y1];
85             else
86                 return cast(T[3])[y, y1, 2 * a];
87         }
88     }
89 
90     ///
91     alias withDerivative = opCall!1;
92     ///
93     alias withTwoDerivatives = opCall!2;
94 }
95 
96 /// ditto
97 ParabolaKernel!(Unqual!(typeof(X.init - Y.init))) parabolaKernel(X, Y)(in X x0, in X x1, in X x2, const Y y0, const Y y1, const Y y2)
98 {
99     return typeof(return)(x0, x1, x2, y0, y1, y2);
100 }
101 
102 /++
103 Returns: `[f'(x0), f'(x1), f'(x2)]`
104 +/
105 Unqual!(typeof(X.init - Y.init))[3] parabolaDerivatives(X, Y)(in X x0, in X x1, in X x2, const Y y0, const Y y1, const Y y2)
106 {
107     auto d0 = (y2 - y1) / (x2 - x1);
108     auto d1 = (y0 - y2) / (x0 - x2);
109     auto d2 = (y1 - y0) / (x1 - x0);
110     return [d1 + d2 - d0, d0 + d2 - d1, d0 + d1 - d2];
111 }
112 
113 version(mir_test)
114 ///
115 unittest
116 {
117     import mir.math.common: approxEqual;
118 
119     alias f = (double x) => 3 * x ^^ 2 + 7 * x + 5;
120     auto x0 = 4;
121     auto x1 = 9;
122     auto x2 = 20;
123     auto p = parabolaKernel(x0, x1, x2, f(x0), f(x1), f(x2));
124 
125     assert(p.a.approxEqual(3));
126     assert(p.b.approxEqual(7));
127     assert(p.c.approxEqual(5));
128     assert(p(10).approxEqual(f(10)));
129 }
130 
131 
132 version(mir_test)
133 ///
134 unittest
135 {
136     import mir.math.common: approxEqual;
137 
138     alias f = (double x) => 3 * x ^^ 2 + 7 * x + 5;
139     alias d = (double x) => 2 * 3 * x + 7;
140     auto x0 = 4;
141     auto x1 = 9;
142     auto p = ParabolaKernel!double.fromFirstDerivative(x0, x1, f(x0), f(x1), d(x1));
143 
144     assert(p.a.approxEqual(3));
145     assert(p.b.approxEqual(7));
146     assert(p.c.approxEqual(5));
147 }
148 
149 /++
150 Cubic function structure
151 +/
152 struct CubicKernel(T)
153 {
154     ///
155     T a = 0;
156     ///
157     T b = 0;
158     ///
159     T c = 0;
160     ///
161     T d = 0;
162 
163 @fmamath:
164 
165     ///
166     this(T a, T b, T c, T d)
167     {
168         this.a = a;
169         this.b = b;
170         this.c = c;
171         this.d = d;
172     }
173 
174     /++
175     Params:
176         x0 = `x0`
177         x1 = `x1`
178         y0 = `f(x0)`
179         y1 = `f(x1)`
180         dd0 = `f''(x0)`
181         d1 = `f'(x1)`
182     +/
183     static CubicKernel fromSecondAndFirstDerivative(T x0, T x1, T y0, T y1, T dd0, T d1)
184     {
185         auto hdd0 = 0.5f * dd0;
186         auto dy = y1 - y0;
187         auto dx = x1 - x0;
188         auto dd = dy / dx;
189         auto a =  0.5f * ((d1 - dd) / dx - hdd0) / dx;
190         auto b = hdd0 - 3 * a * x0;
191         auto c = d1 - (3 * a * x1 + 2 * b) * x1;
192         auto d = y1 - ((a * x1 + b) * x1 + c) * x1;
193         return CubicKernel!T(a, b, c, d);
194     }
195 
196     ///
197     auto opCall(uint derivative = 0)(T x) const
198         if (derivative <= 3)
199     {
200         auto y = ((a * x + b) * x + c) * x + d;
201         static if (derivative == 0)
202            return y;
203         else
204         {
205             T[derivative + 1] ret;
206             ret[0] = y;
207             ret[1] = (3 * a * x + 2 * b) * x + c;
208             static if (derivative > 1)
209             {
210                 ret[2] = 6 * a * x + 2 * b;
211                 static if (derivative > 2)
212                     ret[3] = 6 * a;
213             }
214             return ret;
215         }
216     }
217 
218     ///
219     alias withDerivative = opCall!1;
220     ///
221     alias withTwoDerivatives = opCall!2;
222 }
223 
224 version(mir_test)
225 ///
226 unittest
227 {
228     import mir.math.common: approxEqual;
229 
230     alias f = (double x) => 3 * x ^^ 3 + 7 * x ^^ 2 + 5 * x + 10;
231     alias d = (double x) => 3 * 3 * x ^^ 2 + 2 * 7 * x + 5;
232     alias s = (double x) => 6 * 3 * x + 2 * 7;
233     auto x0 = 4;
234     auto x1 = 9;
235     auto p = CubicKernel!double.fromSecondAndFirstDerivative(x0, x1, f(x0), f(x1), s(x0), d(x1));
236 
237     assert(p.a.approxEqual(3));
238     assert(p.b.approxEqual(7));
239     assert(p.c.approxEqual(5));
240     assert(p.d.approxEqual(10));
241     assert(p(13).approxEqual(f(13)));
242     assert(p.opCall!1(13)[1].approxEqual(d(13)));
243     assert(p.opCall!2(13)[2].approxEqual(s(13)));
244     assert(p.opCall!3(13)[3].approxEqual(18));
245 }
246 
247 
248 package auto pchipTail(T)(in T step0, in T step1, in T diff0, in T diff1)
249 {
250     import mir.math.common: copysign, fabs;
251     if (!diff0)
252     {
253         return 0;
254     }
255     auto slope = ((step0 * 2 + step1) * diff0 - step0 * diff1) / (step0 + step1);
256     if (copysign(1f, slope) != copysign(1f, diff0))
257     {
258         return 0;
259     }
260     if ((copysign(1f, diff0) != copysign(1f, diff1)) && (fabs(slope) > fabs(diff0 * 3)))
261     {
262         return diff0 * 3;
263     }
264     return slope;
265 }