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 }