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 }