1 /++ 2 $(H2 Linear Interpolation) 3 4 See_also: $(REF_ALTTEXT $(TT interp1), interp1, mir, interpolate) 5 6 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) 7 Copyright: 2020 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments 8 Authors: Ilya Yaroshenko 9 10 Macros: 11 SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, interpolate, $1)$(NBSP) 12 T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) 13 +/ 14 module mir.interpolate.linear; 15 16 import core.lifetime: move; 17 import mir.functional; 18 import mir.internal.utility; 19 import mir.interpolate; 20 import mir.math.common: optmath; 21 import mir.ndslice.slice; 22 import mir.primitives; 23 import mir.rc.array; 24 import mir.utility: min, max; 25 import std.meta: AliasSeq, staticMap; 26 import std.traits; 27 public import mir.interpolate: atInterval; 28 29 @optmath: 30 31 /++ 32 Constructs multivariate linear interpolant with nodes on rectilinear grid. 33 34 Params: 35 grid = `x` values for interpolant 36 values = `f(x)` values for interpolant 37 38 Constraints: 39 `grid`, `values` must have the same length >= 2 40 41 Returns: $(LREF Linear) 42 +/ 43 Linear!(F, N, X) linear(F, size_t N = 1, X = F) 44 (Repeat!(N, Slice!(RCI!(immutable X))) grid, Slice!(RCI!(const F), N) values) 45 { 46 return typeof(return)(forward!grid, values.move); 47 } 48 49 /// R -> R: Linear interpolation 50 version(mir_test) 51 @safe pure @nogc unittest 52 { 53 import mir.algorithm.iteration; 54 import mir.ndslice; 55 import mir.math.common: approxEqual; 56 57 static immutable x = [0, 1, 2, 3, 5.00274, 7.00274, 10.0055, 20.0137, 30.0192]; 58 static immutable y = [0.0011, 0.0011, 0.0030, 0.0064, 0.0144, 0.0207, 0.0261, 0.0329, 0.0356,]; 59 static immutable xs = [1, 2, 3, 4.00274, 5.00274, 6.00274, 7.00274, 8.00548, 9.00548, 10.0055, 11.0055, 12.0082, 13.0082, 14.0082, 15.0082, 16.011, 17.011, 18.011, 19.011, 20.0137, 21.0137, 22.0137, 23.0137, 24.0164, 25.0164, 26.0164, 27.0164, 28.0192, 29.0192, 30.0192]; 60 61 auto interpolant = linear!double(x.rcslice!(immutable double), y.rcslice!(const double)); 62 63 static immutable data = [0.0011, 0.0030, 0.0064, 0.0104, 0.0144, 0.0176, 0.0207, 0.0225, 0.0243, 0.0261, 0.0268, 0.0274, 0.0281, 0.0288, 0.0295, 0.0302, 0.0309, 0.0316, 0.0322, 0.0329, 0.0332, 0.0335, 0.0337, 0.0340, 0.0342, 0.0345, 0.0348, 0.0350, 0.0353, 0.0356]; 64 65 assert(xs.sliced.vmap(interpolant).all!((a, b) => approxEqual(a, b, 1e-4, 1e-4))(data)); 66 } 67 68 /// R^2 -> R: Bilinear interpolation 69 version(mir_test) 70 @safe pure @nogc unittest 71 { 72 import mir.math.common: approxEqual; 73 import mir.ndslice; 74 alias appreq = (a, b) => approxEqual(a, b, 10e-10, 10e-10); 75 76 //// set test function //// 77 enum y_x0 = 2; 78 enum y_x1 = -7; 79 enum y_x0x1 = 3; 80 81 // this function should be approximated very well 82 alias f = (x0, x1) => y_x0 * x0 + y_x1 * x1 + y_x0x1 * x0 * x1 - 11; 83 84 ///// set interpolant //// 85 static immutable x0 = [-1.0, 2, 8, 15]; 86 static immutable x1 = [-4.0, 2, 5, 10, 13]; 87 88 auto grid = cartesian(x0, x1) 89 .map!f 90 .rcslice 91 .lightConst; 92 93 auto interpolant = 94 linear!(double, 2)( 95 x0.rcslice!(immutable double), 96 x1.rcslice!(immutable double), 97 grid 98 ); 99 100 ///// compute test data //// 101 auto test_grid = cartesian(x0.sliced + 1.23, x1.sliced + 3.23); 102 auto real_data = test_grid.map!f; 103 auto interp_data = test_grid.vmap(interpolant); 104 ///// verify result //// 105 assert(all!appreq(interp_data, real_data)); 106 107 //// check derivatives //// 108 auto z0 = 1.23; 109 auto z1 = 3.21; 110 auto d = interpolant.withDerivative(z0, z1); 111 assert(appreq(interpolant(z0, z1), f(z0, z1))); 112 assert(appreq(d[0][0], f(z0, z1))); 113 assert(appreq(d[1][0], y_x0 + y_x0x1 * z1)); 114 assert(appreq(d[0][1], y_x1 + y_x0x1 * z0)); 115 assert(appreq(d[1][1], y_x0x1)); 116 } 117 118 /// R^3 -> R: Trilinear interpolation 119 version(mir_test) 120 @safe pure unittest 121 { 122 import mir.math.common: approxEqual; 123 import mir.ndslice; 124 alias appreq = (a, b) => approxEqual(a, b, 10e-10, 10e-10); 125 126 ///// set test function //// 127 enum y_x0 = 2; 128 enum y_x1 = -7; 129 enum y_x2 = 5; 130 enum y_x0x1 = 10; 131 enum y_x0x1x2 = 3; 132 133 // this function should be approximated very well 134 static auto f(double x0, double x1, double x2) 135 { 136 return y_x0 * x0 + y_x1 * x1 + y_x2 * x2 + y_x0x1 * x0 * x1 + y_x0x1x2 * x0 * x1 * x2 - 11; 137 } 138 139 ///// set interpolant //// 140 static immutable x0 = [-1.0, 2, 8, 15]; 141 static immutable x1 = [-4.0, 2, 5, 10, 13]; 142 static immutable x2 = [3, 3.7, 5]; 143 auto grid = cartesian(x0, x1, x2) 144 .map!f 145 .as!(const double) 146 .rcslice; 147 148 auto interpolant = linear!(double, 3)( 149 x0.rcslice!(immutable double), 150 x1.rcslice!(immutable double), 151 x2.rcslice!(immutable double), 152 grid); 153 154 ///// compute test data //// 155 auto test_grid = cartesian(x0.sliced + 1.23, x1.sliced + 3.23, x2.sliced - 3); 156 auto real_data = test_grid.map!f; 157 auto interp_data = test_grid.vmap(interpolant); 158 ///// verify result //// 159 assert(all!appreq(interp_data, real_data)); 160 161 //// check derivatives //// 162 auto z0 = 1.23; 163 auto z1 = 3.21; 164 auto z2 = 4; 165 auto d = interpolant.withDerivative(z0, z1, z2); 166 assert(appreq(interpolant(z0, z1, z2), f(z0, z1, z2))); 167 assert(appreq(d[0][0][0], f(z0, z1, z2))); 168 assert(appreq(d[1][0][0], y_x0 + y_x0x1 * z1 + y_x0x1x2 * z1 * z2)); 169 assert(appreq(d[0][1][0], y_x1 + y_x0x1 * z0 + y_x0x1x2 * z0 * z2)); 170 assert(appreq(d[1][1][0], y_x0x1 + y_x0x1x2 * z2)); 171 assert(appreq(d[1][1][1], y_x0x1x2)); 172 } 173 174 /++ 175 Multivariate linear interpolant with nodes on rectilinear grid. 176 +/ 177 struct Linear(F, size_t N = 1, X = F) 178 if (N && N <= 6) 179 { 180 /// Aligned buffer allocated with `mir.internal.memory`. $(RED For internal use.) 181 Slice!(RCI!(const F), N) _data; 182 /// Grid iterators. $(RED For internal use.) 183 Repeat!(N, RCI!(immutable X)) _grid; 184 185 @optmath extern(D): 186 187 /++ 188 +/ 189 this(Repeat!(N, Slice!(RCI!(immutable X))) grid, Slice!(RCI!(const F), N) data) @safe @nogc 190 { 191 enum msg_min = "linear interpolant: minimal allowed length for the grid equals 2."; 192 enum msg_eq = "linear interpolant: X and Y values length should be equal."; 193 version(D_Exceptions) 194 { 195 static immutable exc_min = new Exception(msg_min); 196 static immutable exc_eq = new Exception(msg_eq); 197 } 198 foreach(i, ref x; grid) 199 { 200 if (x.length < 2) 201 { 202 version(D_Exceptions) throw exc_min; 203 else assert(0, msg_min); 204 } 205 if (x.length != data._lengths[i]) 206 { 207 version(D_Exceptions) throw exc_eq; 208 else assert(0, msg_eq); 209 } 210 _grid[i] = x._iterator.move; 211 } 212 _data = data.move; 213 } 214 215 @trusted: 216 217 /// 218 Linear lightConst()() const @property { return *cast(Linear*)&this; } 219 220 /// 221 Slice!(RCI!(immutable X)) grid(size_t dimension = 0)() scope return const @property 222 if (dimension < N) 223 { 224 return _grid[dimension].lightConst.sliced(_data._lengths[dimension]); 225 } 226 227 /// 228 immutable(X)[] gridScopeView(size_t dimension = 0)() scope return const @property @trusted 229 if (dimension < N) 230 { 231 return _grid[dimension]._iterator[0 .. _data._lengths[dimension]]; 232 } 233 234 /++ 235 Returns: intervals count. 236 +/ 237 size_t intervalCount(size_t dimension = 0)() scope const @property 238 { 239 assert(_data._lengths[dimension] > 1); 240 return _data._lengths[dimension] - 1; 241 } 242 243 /// 244 size_t[N] gridShape()() scope const @property 245 { 246 return _data.shape; 247 } 248 249 /// 250 enum uint derivativeOrder = 1; 251 252 /// 253 template opCall(uint derivative = 0) 254 if (derivative <= derivativeOrder) 255 { 256 /++ 257 `(x)` operator. 258 Complexity: 259 `O(log(grid.length))` 260 +/ 261 auto opCall(X...)(in X xs) scope const @trusted 262 if (X.length == N) 263 { 264 import mir.functional: AliasCall; 265 import mir.ndslice.topology: iota; 266 alias Kernel = AliasCall!(LinearKernel!F, "opCall", derivative); 267 268 size_t[N] indices; 269 Kernel[N] kernels; 270 271 enum rp2d = derivative; 272 273 foreach(i; Iota!N) 274 { 275 static if (isInterval!(typeof(xs[i]))) 276 { 277 indices[i] = xs[i][1]; 278 auto x = xs[i][0]; 279 } 280 else 281 { 282 alias x = xs[i]; 283 indices[i] = this.findInterval!i(x); 284 } 285 kernels[i] = LinearKernel!F(_grid[i][indices[i]], _grid[i][indices[i] + 1], x); 286 } 287 288 align(64) F[2 ^^ N][derivative + 1] local; 289 immutable strides = _data._lengths.iota.strides; 290 291 void load(sizediff_t i)(F* from, F* to) 292 { 293 version(LDC) pragma(inline, true); 294 static if (i == -1) 295 { 296 *to = *from; 297 } 298 else 299 { 300 from += strides[i] * indices[i]; 301 load!(i - 1)(from, to); 302 from += strides[i]; 303 enum s = 2 ^^ (N - 1 - i); 304 to += s; 305 load!(i - 1)(from, to); 306 } 307 } 308 309 load!(N - 1)(cast(F*) _data.ptr, cast(F*)local[0].ptr); 310 311 foreach(i; Iota!N) 312 { 313 enum P = 2 ^^ (N - 1 - i); 314 enum L = 2 ^^ (N - i * (1 - rp2d)) / 2; 315 vectorize(kernels[i], local[0][0 * L .. 1 * L], local[0][1 * L .. 2 * L], *cast(F[L][2 ^^ rp2d]*)local[rp2d].ptr); 316 static if (rp2d == 1) 317 shuffle3!1(local[1][0 .. L], local[1][L .. 2 * L], local[0][0 .. L], local[0][L .. 2 * L]); 318 static if (i + 1 == N) 319 return *cast(SplineReturnType!(F, N, 2 ^^ rp2d)*) local[0].ptr; 320 } 321 } 322 } 323 324 /// 325 alias withDerivative = opCall!1; 326 } 327 328 /// 329 struct LinearKernel(X) 330 { 331 X step = 0; 332 X w0 = 0; 333 X w1 = 0; 334 335 /// 336 auto lightConst()() const @property 337 { 338 return LinearKernel!X(step, w0, w1); 339 } 340 341 /// 342 auto lightImmutable()() immutable @property 343 { 344 return LinearKernel!X(step, w0, w1); 345 } 346 347 /// 348 this()(X x0, X x1, X x) 349 { 350 step = x1 - x0; 351 auto c0 = x - x0; 352 auto c1 = x1 - x; 353 w0 = c0 / step; 354 w1 = c1 / step; 355 } 356 357 /// 358 template opCall(uint derivative = 0) 359 if (derivative <= 1) 360 { 361 /// 362 auto opCall(Y)(in Y y0, in Y y1) 363 { 364 auto r0 = y0 * w1; 365 auto r1 = y1 * w0; 366 auto y = r0 + r1; 367 static if (derivative) 368 { 369 auto diff = y1 - y0; 370 Y[derivative + 1] ret = 0; 371 ret[0] = y; 372 ret[1] = diff / step; 373 return ret; 374 } 375 else 376 { 377 return y; 378 } 379 } 380 } 381 382 /// 383 alias withDerivative = opCall!1; 384 }