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