1 /++
2 $(H2 Generic Piecewise Interpolant)
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.generic;
15 
16 @optmath:
17 
18 ///
19 version(mir_test)
20 @safe pure @nogc unittest
21 {
22     import mir.ndslice;
23     import mir.math.common: approxEqual;
24 
25     struct PieceInterpolant
26     {
27         int value;
28 
29         this()(int value)
30         {
31             this.value = value;
32         }
33 
34         int opCall(uint derivative : 0, X)(int x0, int x1, X x) const
35         {
36             return value;
37         }
38 
39         enum uint derivativeOrder = 0;
40     }
41 
42     alias S = PieceInterpolant;
43     static immutable x = [0, 1, 2, 3]; // can be also an array of floating point numbers
44     static immutable y = [S(10), S(20), S(30)];
45 
46     auto interpolant = generic(x.rcslice, y.rcslice!(const S));
47 
48     assert(interpolant(-1) == 10);
49     assert(interpolant(0) == 10);
50     assert(interpolant(0.5) == 10);
51 
52     assert(interpolant(1) == 20);
53     assert(interpolant(1.5) == 20);
54 
55     assert(interpolant(2) == 30);
56     assert(interpolant(3) == 30);
57     assert(interpolant(3.4) == 30);
58     assert(interpolant(3) == 30);
59     assert(interpolant(4) == 30);
60 }
61 
62 
63 import core.lifetime: move; 
64 import mir.internal.utility;
65 import mir.functional;
66 import mir.interpolate;
67 import mir.math.common: optmath;
68 import mir.ndslice.slice;
69 import mir.primitives;
70 import mir.rc.array;
71 import mir.utility: min, max;
72 import std.meta: AliasSeq, staticMap;
73 import std.traits;
74 public import mir.interpolate: atInterval;
75 
76 /++
77 Constructs multivariate generic interpolant with nodes on rectilinear grid.
78 
79 Params:
80     grid = `x` values for interpolant
81     values = `f(x)` values for interpolant
82 
83 Constraints:
84     `grid`, `values` must have the same length >= 1
85 
86 Returns: $(LREF Generic)
87 +/
88 Generic!(X, F) generic(X, F)
89     (Slice!(RCI!(immutable X)) grid, Slice!(RCI!(const F)) values)
90 {
91     return typeof(return)(forward!grid, values.move);
92 }
93 
94 /++
95 Multivariate generic interpolant with nodes on rectilinear grid.
96 +/
97 struct Generic(X, F)
98 {
99 @optmath:
100 
101     /// Aligned buffer allocated with `mir.internal.memory`. $(RED For internal use.)
102     Slice!(RCI!(const F)) _data;
103     /// Grid iterators. $(RED For internal use.)
104     RCI!(immutable X) _grid;
105 
106 extern(D):
107 
108     /++
109     +/
110     this(Slice!(RCI!(immutable X)) grid, Slice!(RCI!(const F)) data) @safe @nogc
111     {
112         import core.lifetime: move;
113         enum  msg_min =  "generic interpolant: minimal allowed length for the grid equals 2.";
114         enum  msg_eq =  "generic interpolant: X length and Y values length + 1 should be equal.";
115         version(D_Exceptions)
116         {
117             static immutable exc_min = new Exception(msg_min);
118             static immutable exc_eq = new Exception(msg_eq);
119         }
120         if (grid.length < 2)
121         {
122             version(D_Exceptions) throw exc_min;
123             else assert(0, msg_min);
124         }
125         if (grid.length != data._lengths[0] + 1)
126         {
127             version(D_Exceptions) throw exc_eq;
128             else assert(0, msg_eq);
129         }
130         _grid = move(grid._iterator);
131         _data = move(data);
132     }
133 
134 @trusted:
135 
136     ///
137     Generic lightConst()() const @property { return *cast(Generic*)&this; }
138 
139     ///
140     Slice!(RCI!(immutable X)) grid(size_t dimension = 0)() scope return const @property
141         if (dimension == 0)
142     {
143         return _grid.lightConst.sliced(_data._lengths[0]);
144     }
145 
146     ///
147     immutable(X)[] gridScopeView(size_t dimension = 0)() scope return const @property @trusted
148         if (dimension == 0)
149     {
150         return _grid._iterator[0 .. _data._lengths[0]];
151     }
152 
153     /++
154     Returns: intervals count.
155     +/
156     size_t intervalCount(size_t dimension = 0)() scope const @property
157         if (dimension == 0)
158     {
159         assert(_data._lengths[0] > 1);
160         return _data._lengths[0] - 0;
161     }
162 
163     ///
164     size_t[1] gridShape()() scope const @property
165     {
166         return _data.shape;
167     }
168 
169     ///
170     enum uint derivativeOrder = F.derivativeOrder;
171 
172     ///
173     template opCall(uint derivative = 0)
174         if (derivative == 0)
175     {
176         /++
177         `(x)` operator.
178         Complexity:
179             `O(log(grid.length))`
180         +/
181         auto opCall(X)(in X x) const
182         {
183             return opCall!(X)(RefTuple!(size_t, X)(this.findInterval(x), x));
184         }
185 
186         ///
187         auto opCall(X)(RefTuple!(size_t, X) tuple) const
188         {
189             X x = tuple[1];
190             size_t idx = tuple[0];
191             return _data[idx].opCall!derivative(_grid[idx], _grid[idx + 1], x);
192         }
193     }
194 }