1 /++ 2 $(H2 Extrapolators) 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 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments 8 Authors: Ilia Ki 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.extrapolate; 15 16 /++ 17 Constant extrapolator 18 +/ 19 ConstantExtrapolator!T constantExtrapolator(T)(T interpolator) 20 { 21 import core.lifetime: move; 22 return typeof(return)(interpolator.move); 23 } 24 25 26 /// ditto 27 struct ConstantExtrapolator(T) 28 if (__traits(hasMember, T, "gridScopeView")) 29 { 30 /// 31 T data; 32 33 /// 34 this(T data) 35 { 36 import core.lifetime: move; 37 this.data = data.move; 38 } 39 40 /// 41 ConstantExtrapolator lightConst()() const @property { return *cast(ConstantExtrapolator*)&this; } 42 43 /// 44 auto gridScopeView(size_t dimension = 0)() return scope const @property @trusted 45 { 46 return data.gridScopeView!dimension; 47 } 48 49 /// 50 enum uint derivativeOrder = 1; 51 52 static if (__traits(compiles, {enum N = T.dimensionCount;})) 53 /// 54 enum uint dimensionCount = T.dimensionCount + 1; 55 56 /// 57 template opCall(uint derivative = 0) 58 { 59 /++ 60 `(x)` operator. 61 Complexity: 62 `O(log(grid.length))` 63 +/ 64 auto opCall(X...)(const X xs) scope const @trusted 65 if (X.length >= 1) 66 { 67 import mir.internal.utility: Iota; 68 import mir.math.common: fmin, fmax; 69 import std.meta: staticMap; 70 71 static if (derivative) 72 bool[X.length] extrpolated; 73 74 auto mod(size_t i)() 75 { 76 static if (__traits(compiles, gridScopeView!i)) 77 { 78 auto grid = gridScopeView!i; 79 static if (derivative) 80 extrpolated[i] = grid.length != 0 && (xs[i] < grid[0] || grid[$ - 1] < xs[i]); 81 return grid.length ? xs[i].fmax(grid[0]).fmin(grid[$ - 1]) : xs[i]; 82 } 83 else 84 { 85 return xs[i]; 86 } 87 } 88 89 alias xm = staticMap!(mod, Iota!(X.length)); 90 91 static if (derivative == 0) 92 return data(xm); 93 else 94 { 95 static assert (X.length <= 4, "multidimensional constant exrapolation with derivatives isn't implemented"); 96 auto ret = data.opCall!derivative(xm); 97 98 static if (X.length >= 1) 99 { 100 if (extrpolated[0]) 101 foreach (ref a; ret[1 .. $]) 102 a = 0; 103 } 104 105 static if (X.length >= 2) 106 { 107 if (extrpolated[1]) 108 foreach (ref a; ret) 109 foreach (ref b; a[1 .. $]) 110 b = 0; 111 } 112 113 static if (X.length >= 3) 114 { 115 if (extrpolated[2]) 116 foreach (ref a; ret) 117 foreach (ref b; a) 118 foreach (ref c; b[1 .. $]) 119 c = 0; 120 } 121 122 static if (X.length >= 4) 123 { 124 if (extrpolated[2]) 125 foreach (ref a; ret) 126 foreach (ref b; a) 127 foreach (ref c; b) 128 foreach (ref d; c[1 .. $]) 129 d = 0; 130 } 131 132 return ret; 133 } 134 } 135 } 136 } 137 138 139 /// 140 version(mir_test) 141 unittest 142 { 143 import mir.interpolate.linear; 144 145 auto x = [0.0, 1, 2, 3, 5]; 146 auto y = [4.0, 0, 9, 23, 40]; 147 148 auto g = [7.0, 10, 15]; 149 150 import mir.ndslice.allocation: rcslice; 151 152 auto linear = linear!double( 153 x.rcslice!(immutable double), 154 y.rcslice!(const double), 155 ).constantExtrapolator; 156 157 assert(linear(2) == 9); 158 assert(linear(-1) == 4); 159 assert(linear(100) == 40); 160 161 assert(linear.opCall!1(-1) == [4, 0]); 162 163 } 164 165 /++ 166 Linear extrapolator. 167 +/ 168 LinearExtrapolator!T linearExtrapolator(T)(T interpolator) 169 { 170 import core.lifetime: move; 171 return typeof(return)(interpolator.move); 172 } 173 174 175 /// ditto 176 struct LinearExtrapolator(T) 177 if (__traits(hasMember, T, "gridScopeView")) 178 { 179 /// 180 T data; 181 182 /// 183 this(T data) 184 { 185 import core.lifetime: move; 186 this.data = data.move; 187 } 188 189 /// 190 LinearExtrapolator lightConst()() const @property { return *cast(LinearExtrapolator*)&this; } 191 192 /// 193 auto gridScopeView(size_t dimension = 0)() return scope const @property @trusted 194 { 195 return data.gridScopeView!dimension; 196 } 197 198 /// 199 enum uint derivativeOrder = 1; 200 201 static if (__traits(compiles, {enum N = T.dimensionCount;})) 202 /// 203 enum uint dimensionCount = T.dimensionCount + 1; 204 205 /// 206 template opCall(uint derivative = 0) 207 { 208 /++ 209 `(x)` operator. 210 Complexity: 211 `O(log(grid.length))` 212 +/ 213 auto opCall(X...)(const X xs) scope const @trusted 214 if (X.length >= 1) 215 { 216 import mir.internal.utility: Iota; 217 import mir.math.common: fmin, fmax; 218 import std.meta: staticMap; 219 220 bool[X.length] extrpolated; 221 222 auto mod(size_t i)() 223 { 224 static if (__traits(compiles, gridScopeView!i)) 225 { 226 auto grid = gridScopeView!i; 227 extrpolated[i] = grid.length != 0 && (xs[i] < grid[0] || grid[$ - 1] < xs[i]); 228 return grid.length ? xs[i].fmax(grid[0]).fmin(grid[$ - 1]) : xs[i]; 229 } 230 else 231 { 232 return xs[i]; 233 } 234 } 235 236 alias xm = staticMap!(mod, Iota!(X.length)); 237 238 import mir.utility: max; 239 240 static assert (X.length <= 2, "multidimensional linear exrapolation with derivatives isn't implemented"); 241 auto ret = data.opCall!(derivative.max(1u))(xm); 242 243 static if (X.length >= 1) 244 { 245 if (extrpolated[0]) 246 { 247 ret[0] += ret[1] * (xs[0] - xm[0]); 248 foreach (ref a; ret[2 .. $]) 249 a = 0; 250 } 251 } 252 253 static if (derivative == 0) 254 return ret[0]; 255 else 256 return ret; 257 } 258 } 259 } 260 261 262 /// 263 version(mir_test) 264 unittest 265 { 266 import mir.test; 267 import mir.interpolate.linear; 268 269 auto x = [0.0, 1, 2, 3, 5]; 270 auto y = [4.0, 0, 9, 23, 40]; 271 272 auto g = [7.0, 10, 15]; 273 274 import mir.ndslice.allocation: rcslice; 275 276 auto linear = linear!double( 277 x.rcslice!(immutable double), 278 y.rcslice!(const double), 279 ); 280 281 auto linearLinear = linear.linearExtrapolator; 282 283 linearLinear(2).should == linear(2); 284 linearLinear(-1).should == linear(-1); 285 linearLinear.opCall!1(-1).should == [8, -4]; 286 linearLinear(100).shouldApprox == linear(100); 287 }