The OpenD Programming Language

1 /++
2 $(H2 Constant 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 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.constant;
15 
16 @fmamath:
17 
18 ///
19 version(mir_test)
20 @safe pure @nogc unittest
21 {
22     import mir.ndslice;
23     import mir.math.common: approxEqual;
24 
25     static immutable x = [0, 1, 2, 3];
26     static immutable y = [10, 20, 30, 40];
27 
28     auto interpolant = constant!int(x.rcslice, y.rcslice!(const int));
29 
30     assert(interpolant(-1) == 10);
31     assert(interpolant(0) == 10);
32     assert(interpolant(0.5) == 10);
33 
34     assert(interpolant(1) == 20);
35     
36     assert(interpolant(3) == 40);
37     assert(interpolant(4) == 40);
38 }
39 
40 
41 import core.lifetime: move; 
42 import mir.internal.utility;
43 import mir.functional;
44 import mir.interpolate;
45 import mir.math.common: fmamath;
46 import mir.ndslice.slice;
47 import mir.primitives;
48 import mir.rc.array;
49 import mir.utility: min, max;
50 import std.meta: AliasSeq, staticMap;
51 import std.traits;
52 
53 ///
54 public import mir.interpolate: atInterval;
55 
56 /++
57 Constructs multivariate constant interpolant with nodes on rectilinear grid.
58 
59 Params:
60     grid = `x` values for interpolant
61     values = `f(x)` values for interpolant
62 
63 Constraints:
64     `grid`, `values` must have the same length >= 1
65 
66 Returns: $(LREF Constant)
67 +/
68 Constant!(F, N, X) constant(F, size_t N = 1, X = F)
69     (Repeat!(N, Slice!(RCI!(immutable X))) grid, Slice!(RCI!(const F), N) values)
70 {
71     return typeof(return)(forward!grid, values.move);
72 }
73 
74 /++
75 Multivariate constant interpolant with nodes on rectilinear grid.
76 +/
77 struct Constant(F, size_t N = 1, X = F)
78     if (N && N <= 6)
79 {
80 @fmamath:
81 
82     /// Aligned buffer allocated with `mir.internal.memory`. $(RED For internal use.)
83     Slice!(RCI!(const F), N) _data;
84     /// Grid iterators. $(RED For internal use.)
85     Repeat!(N, RCI!(immutable X)) _grid;
86 
87 extern(D):
88 
89     bool opEquals()(auto ref scope const typeof(this) rhs) scope const @trusted pure nothrow @nogc
90     {
91         if (rhs._data != this._data)
92             return false;
93         static foreach (d; 0 .. N)
94             if (gridScopeView!d != rhs.gridScopeView!d)
95                 return false;
96         return true;
97     }
98 
99     /++
100     +/
101     this(Repeat!(N, Slice!(RCI!(immutable X))) grid, Slice!(RCI!(const F), N) data) @safe @nogc
102     {
103         enum  msg_min =  "constant interpolant: minimal allowed length for the grid equals 1.";
104         enum  msg_eq =  "constant interpolant: X and Y values length should be equal.";
105         version(D_Exceptions)
106         {
107             static immutable exc_min = new Exception(msg_min);
108             static immutable exc_eq = new Exception(msg_eq);
109         }
110         foreach(i, ref x; grid)
111         {
112             if (x.length < 1)
113             {
114                 version(D_Exceptions) { import mir.exception : toMutable; throw exc_min.toMutable; }
115                 else assert(0, msg_min);
116             }
117             if (x.length != data._lengths[i])
118             {
119                 version(D_Exceptions) { import mir.exception : toMutable; throw exc_eq.toMutable; }
120                 else assert(0, msg_eq);
121             }
122             _grid[i] = x._iterator;
123         }
124         _data = data;
125     }
126 
127 @trusted:
128 
129     ///
130     Constant lightConst()() const @property { return *cast(Constant*)&this; }
131 
132     ///
133     Slice!(RCI!(immutable X)) grid(size_t dimension = 0)() return scope const @property
134         if (dimension < N)
135     {
136         return _grid[dimension].lightConst.sliced(_data._lengths[dimension]);
137     }
138 
139     ///
140     immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted
141         if (dimension < N)
142     {
143         return _grid[dimension]._iterator[0 .. _data._lengths[dimension]];
144     }
145 
146     /++
147     Returns: intervals count.
148     +/
149     size_t intervalCount(size_t dimension = 0)() scope const @property
150     {
151         assert(_data._lengths[dimension] > 0);
152         return _data._lengths[dimension] - 0;
153     }
154 
155     ///
156     size_t[N] gridShape()() scope const @property
157     {
158         return _data.shape;
159     }
160 
161     ///
162     enum uint derivativeOrder = 0;
163 
164     ///
165     enum uint dimensionCount = N;
166 
167     ///
168     template opCall(uint derivative = 0)
169         // if (derivative <= derivativeOrder)
170     {
171         /++
172         `(x)` operator.
173         Complexity:
174             `O(log(grid.length))`
175         +/
176         auto opCall(X...)(in X xs) scope const @trusted
177             if (X.length == N)
178         {
179             size_t[N] indices;
180             foreach(i; Iota!N)
181             {
182                 static if (isInterval!(typeof(xs[i])))
183                     indices[i] = xs[i][1];
184                 else
185                     indices[i] = _data._lengths[i] > 1 ? this.findInterval!i(xs[i]) : 0;
186             }
187             static if (derivative == 0)
188             {
189                 return _data[indices];
190             }
191             else
192             {
193                 F[derivative + 1] ret = 0;
194                 ret[0] = _data[indices];
195                 return ret;
196             }
197         }
198     }
199 }
200 
201 /++
202 Single value interpolation
203 +/
204 SingleConstant!F singleConstant(F)(const F value)
205 {
206     return typeof(return)(value);
207 }
208 
209 /// ditto
210 struct SingleConstant(F, X = F)
211 {
212     ///
213     enum uint derivativeOrder = 0;
214 
215     ///
216     enum uint dimensionCount = 1;
217 
218     ///
219     F value;
220 
221     ///
222     this(F value)
223     {
224         this.value = value;
225     }
226 
227 
228     ///
229     immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted
230         if (dimension == 0)
231     {
232         return null;
233     }
234 
235     ///
236     template opCall(uint derivative = 0)
237     {
238         /++
239         `(x)` operator.
240         Complexity:
241             `O(1)`
242         +/
243         auto opCall(X...)(in X xs) scope const @trusted
244         {
245             static if (derivative == 0)
246             {
247                 return F(value);
248             }
249             else
250             {
251                 F[derivative + 1] ret = 0;
252                 ret[0] = value;
253                 return ret;
254             }
255         }
256     }
257 }
258 
259 ///
260 @safe pure nothrow
261 version (mir_test)
262 unittest
263 {
264     auto sc = singleConstant(34.1);
265     assert(sc(100) == 34.1);
266     assert(sc.opCall!2(100) == [34.1, 0, 0]);
267 }
268 
269 /++
270 Interpolator used for non-rectiliner trapezoid-like greeds.
271 Params:
272     grid = rc-array of interpolation grid
273     data = rc-array of interpolator-like structures
274 +/
275 auto metaSingleConstant(T)(T data)
276 {
277     import core.lifetime: move;
278     return MetaSingleConstant!T(data.move);
279 }
280 
281 /// ditto
282 struct MetaSingleConstant(T, X = double)
283     // if (T.derivativeOrder >= 1)
284 {
285     import mir.interpolate.utility: DeepType;
286 
287     ///
288     T data;
289 
290     ///
291     this(T data)
292     {
293         import core.lifetime: move;
294         this.data = data.move;
295     }
296 
297     ///
298     MetaSingleConstant lightConst()() const @property { return *cast(MetaSingleConstant*)&this; }
299 
300     ///
301     immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted
302         if (dimension == 0)
303     {
304         return null;
305     }
306 
307     ///
308     enum uint derivativeOrder = 1;
309 
310     static if (__traits(compiles, {enum N = T.dimensionCount;}))
311     ///
312     enum uint dimensionCount = T.dimensionCount + 1;
313 
314     ///
315     template opCall(uint derivative = 0)
316     {
317         /++
318         `(x)` operator.
319         Complexity:
320             `O(log(grid.length))`
321         +/
322         auto opCall(X...)(const X xs) scope const @trusted
323             if (xs.length >= 1)
324         {
325             static if (derivative == 0)
326             {
327                 return data(xs[1.. $]);
328             }
329             else
330             {
331                 auto iv = data.opCall!derivative(xs[1.. $]);
332                 typeof(iv)[derivative + 1] ret = 0;
333                 ret[0] = iv;
334                 return ret;
335             }
336         }
337     }
338 }
339 
340 /// Ignores the first dimension parameter
341 version(mir_test)
342 unittest
343 {
344     import mir.interpolate.linear;
345 
346     auto x = [0.0, 1, 2, 3, 5];
347     auto y = [4.0, 0, 9, 23, 40];
348 
349     auto g = [7.0, 10, 15];
350 
351     import mir.ndslice.allocation: rcslice;
352 
353     auto d = linear!double(x.rcslice!(immutable double), y.rcslice!(const double));
354 
355     auto ignoresFirstDim = d.metaSingleConstant;
356 
357     assert(ignoresFirstDim(9.0, 1.8) == d(1.8));
358     assert(ignoresFirstDim.opCall!1(9.0, 1.8) == [d.opCall!1(1.8), [0.0, 0.0]]);
359 }