The OpenD Programming Language

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 }