Skip to content

Commit 974162b

Browse files
committed
Merge branch 'master' of https://github.com/libmir/mir-algorithm
2 parents 24a6cae + 0bf4a08 commit 974162b

File tree

14 files changed

+692
-63
lines changed

14 files changed

+692
-63
lines changed

index.d

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,6 @@ $(BOOKTABLE ,
3333
$(TR $(TDNW $(MREF mir,combinatorics)★) $(TD Combinations, combinations with repeats, cartesian power, permutations.))
3434
$(LEADINGROW Interconnection with other languages)
3535
$(TR $(TDNW $(MREF mir,ndslice,connect,cpython)) $(TD Utilities for $(HTTPS docs.python.org/3/c-api/buffer.html, Python Buffer Protocol)))
36-
$(LEADINGROW Math)
37-
$(TR $(TDNW $(MREF mir,math,numeric)) $(TD Simple numeric algorithms))
38-
$(TR $(TDNW $(MREF mir,math,sum)★) $(TD Various precise summation algorithms))
3936
$(LEADINGROW Accessories)
4037
$(TR $(TDNW $(MREF mir,array,allocation)) $(TD `std.array` reworked for Mir))
4138
$(TR $(TDNW $(MREF mir,range)) $(TD Ranges))

source/mir/combinatorics/package.d

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -398,8 +398,8 @@ struct Permutations(T)
398398
{
399399
assert(state.length + 1 == indices.length);
400400
// iota
401-
foreach (T i, ref index; indices)
402-
index = i;
401+
foreach (i, ref index; indices)
402+
index = cast(T)i;
403403
state[] = 0;
404404

405405
this.indices = indices;

source/mir/interpolate/package.d

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ template findInterval(size_t dimension = 0)
6767
else
6868
{
6969
immutable sizediff_t len = interpolant.intervalCount - 1;
70-
auto grid = interpolant.grid[1 .. $][0 .. len];
70+
auto grid = interpolant.grid[][1 .. $][0 .. len];
7171
}
7272
assert(len >= 0);
7373
return grid.transitionIndex!"a <= b"(x);

source/mir/interpolate/polynomial.d

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
/++
2+
$(H2 Polinomial Interpolation)
3+
4+
See_also: $(REF_ALTTEXT $(TT interp1), interp1, mir, interpolate)
5+
6+
License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
7+
Copyright: Copyright © 2019, Symmetry Investments & Kaleidic Associates Advisory Limited
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.polynomial;
15+
16+
public import mir.interpolate: atInterval;
17+
import mir.functional: RefTuple;
18+
import mir.internal.utility : isFloatingPoint, Iota;
19+
import mir.interpolate: findInterval;
20+
import mir.math.common;
21+
import mir.math.numeric;
22+
import mir.math.sum;
23+
import mir.ndslice.slice;
24+
import mir.ndslice.topology: diff, map, member, iota, triplets;
25+
import mir.rcarray;
26+
import mir.utility: min, swap;
27+
import std.traits: Unqual;
28+
29+
@optmath:
30+
31+
///
32+
version(mir_test)
33+
@safe pure unittest
34+
{
35+
import mir.algorithm.iteration: all;
36+
import mir.math;
37+
import mir.ndslice;
38+
39+
auto f(double x) { return (x-2) * (x-5) * (x-9); }
40+
auto fd(double x) { return (x-5) * (x-9) + (x-2) * (x-5) + (x-2) * (x-9); }
41+
auto fd2(double x) { return (x-5) + (x-9) + (x-2) + (x-5) + (x-2) + (x-9); }
42+
double[2] fWithDerivative(double x) { return [f(x), fd(x)]; }
43+
double[3] fWithTwoDerivatives(double x) { return [f(x), fd(x), fd2(x)]; }
44+
45+
//
46+
auto x = [0.0, 3, 5, 10];
47+
auto y = x.map!f.slice.field;
48+
// `!2` adds first two derivatives: f, f', and f''
49+
// default parameter is 0
50+
auto l = x.lagrange!2(y);
51+
52+
foreach(test; x ~ [2.0, 5, 9] ~ [double(PI), double(E), 10, 100, 1e3])
53+
foreach(scale; [-1, +1, 1 + double.epsilon, 1 + double.epsilon.sqrt])
54+
foreach(shift; [0, double.min_normal/2, double.min_normal*2, double.epsilon, double.epsilon.sqrt])
55+
{
56+
auto testX = test * scale + shift;
57+
58+
auto lx = l(testX);
59+
auto l_ldx = l.withDerivative(testX);
60+
auto l_ld_ld2x = l.withTwoDerivatives(testX);
61+
62+
assert(lx.approxEqual(f(testX)));
63+
assert(l_ldx[].all!approxEqual(fWithDerivative(testX)[]));
64+
assert(l_ld_ld2x[].all!approxEqual(fWithTwoDerivatives(testX)[]));
65+
}
66+
}
67+
68+
/++
69+
Constructs barycentric lagrange interpolant.
70+
+/
71+
Lagrange!(T, maxDerivative) lagrange(uint maxDerivative = 0, T, X)(scope const T[] x, scope const X[] y)
72+
if (maxDerivative < 16)
73+
{
74+
return x.sliced.lagrange!maxDerivative(y.sliced);
75+
}
76+
77+
/// ditto
78+
Lagrange!(Unqual!(Slice!(YIterator, 1, ykind).DeepElement), maxDerivative)
79+
lagrange(uint maxDerivative = 0, XIterator, SliceKind xkind, YIterator, SliceKind ykind)(scope Slice!(XIterator, 1, xkind) x, scope Slice!(YIterator, 1, ykind) y) @trusted
80+
if (maxDerivative < 16)
81+
{
82+
alias T = Unqual!(Slice!(YIterator, 1, ykind).DeepElement);
83+
return Lagrange!(T, maxDerivative)(RCArray!(immutable T).create(x), RCArray!T.create(y));
84+
}
85+
86+
/++
87+
+/
88+
struct Lagrange(T, uint maxAdditionalFunctions = 0)
89+
if (isFloatingPoint!T && maxAdditionalFunctions < 16)
90+
{
91+
/// $(RED for internal use only.)
92+
RCArray!(immutable T) _grid;
93+
/// $(RED for internal use only.)
94+
RCArray!(immutable T) _inversedBarycentricWeights;
95+
/// $(RED for internal use only.)
96+
RCArray!T[maxAdditionalFunctions + 1] _normalizedValues;
97+
/// $(RED for internal use only.)
98+
T[maxAdditionalFunctions + 1] _asums;
99+
100+
@optmath @safe pure @nogc:
101+
102+
/++
103+
Complexity: `O(N)`
104+
+/
105+
pragma(inline, false)
106+
this(RCArray!(immutable T) grid, RCArray!T values, RCArray!(immutable T) inversedBarycentricWeights)
107+
{
108+
import mir.algorithm.iteration: all;
109+
assert(grid.length > 1);
110+
assert(grid.length == values.length);
111+
assert(grid.length == inversedBarycentricWeights.length);
112+
enum msg = "Lagrange: grid points are too close or have not finite values.";
113+
version(D_Exceptions)
114+
{
115+
enum T md = T.min_normal / T.epsilon;
116+
static immutable exc = new Exception(msg);
117+
if (!grid[].diff.all!(a => md <= a && a <= T.max))
118+
throw exc;
119+
}
120+
swap(_grid, grid);
121+
swap(_inversedBarycentricWeights, inversedBarycentricWeights);
122+
swap(_normalizedValues[0], values);
123+
auto x = _grid[].sliced;
124+
auto w = _inversedBarycentricWeights[].sliced;
125+
foreach (_; Iota!maxAdditionalFunctions)
126+
{
127+
auto y = _normalizedValues[_][].sliced;
128+
static if (_ == 0)
129+
_asums[_] = y.asumNorm;
130+
_normalizedValues[_ + 1] = RCArray!T(x.length, T.alignof, true, false);
131+
auto d = _normalizedValues[_ + 1][].sliced;
132+
polynomialDerivativeValues(d, x, y, w);
133+
_asums[_ + 1] = d.asumNorm * _asums[_];
134+
}
135+
}
136+
137+
/++
138+
Complexity: `O(N^^2)
139+
+/
140+
this(RCArray!(immutable T) grid, RCArray!T values)
141+
{
142+
import std.algorithm.mutation: move;
143+
auto weights = grid[].sliced.inversedBarycentricWeights;
144+
this(grid.move, values.move, weights.move);
145+
}
146+
147+
scope const:
148+
149+
@property
150+
{
151+
///
152+
ref const(RCArray!(immutable T)) grid() { return _grid; }
153+
///
154+
ref const(RCArray!(immutable T)) inversedBarycentricWeights() { return _inversedBarycentricWeights; }
155+
///
156+
ref const(RCArray!T)[maxAdditionalFunctions + 1] normalizedValues() { return _normalizedValues; }
157+
///
158+
ref const(T)[maxAdditionalFunctions + 1] asums() { return _asums; }
159+
///
160+
size_t intervalCount(size_t dimension = 0)()
161+
{
162+
assert(_grid.length > 1);
163+
return _grid.length - 1;
164+
}
165+
}
166+
167+
template opCall(uint derivative = 0)
168+
if (derivative <= maxAdditionalFunctions)
169+
{
170+
///
171+
pragma(inline, false)
172+
auto opCall(T x)
173+
{
174+
return opCall!derivative(RefTuple!(size_t, T)(this.findInterval(x), x));
175+
}
176+
177+
///
178+
pragma(inline, false)
179+
auto opCall(RefTuple!(size_t, T) tuple)
180+
{
181+
182+
const x = tuple[1];
183+
const idx = tuple[0];
184+
const grid = _grid[].sliced;
185+
const inversedBarycentricWeights = _inversedBarycentricWeights[].sliced;
186+
scope Slice!(const(T)*)[derivative + 1] values;
187+
foreach (i; Iota!(derivative + 1))
188+
values[i] = _normalizedValues[i][].sliced;
189+
const T[2] pd = [
190+
T(x - grid[idx + 0]).fabs,
191+
T(x - grid[idx + 1]).fabs,
192+
];
193+
ptrdiff_t fastIndex =
194+
pd[0] < T.min_normal ? idx + 0 :
195+
pd[1] < T.min_normal ? idx + 1 :
196+
-1;
197+
if (fastIndex >= 0)
198+
{
199+
static if (derivative == 0)
200+
{
201+
return values[0][fastIndex] * _asums[0];
202+
}
203+
else
204+
{
205+
T[derivative + 1] ret = 0;
206+
foreach (_; Iota!(derivative + 1))
207+
ret[_] = values[_][fastIndex] * _asums[_];
208+
return ret;
209+
}
210+
}
211+
T d = 0;
212+
T[derivative + 1] n = 0;
213+
foreach (i; 0 .. grid.length)
214+
{
215+
T w = 1 / (inversedBarycentricWeights[i] * (x - grid[i]));
216+
d += w;
217+
foreach (_; Iota!(derivative + 1))
218+
n[_] += w * values[_][i];
219+
}
220+
foreach (_; Iota!(derivative + 1))
221+
{
222+
n[_] /= d;
223+
n[_] *= asums[_];
224+
}
225+
static if (derivative == 0)
226+
return n[0];
227+
else
228+
return n;
229+
}
230+
}
231+
232+
static if (maxAdditionalFunctions)
233+
{
234+
///
235+
alias withDerivative = opCall!1;
236+
237+
static if (maxAdditionalFunctions > 1)
238+
{
239+
///
240+
alias withTwoDerivatives = opCall!2;
241+
}
242+
}
243+
}
244+
245+
246+
/++
247+
+/
248+
pragma(inline, false)
249+
@nogc
250+
RCArray!(immutable T) inversedBarycentricWeights(T)(scope Slice!(const(T)*) x)
251+
if (isFloatingPoint!T)
252+
{
253+
const n = x.length;
254+
scope prodsa = RCArray!(Prod!T)(n);
255+
scope p = prodsa[].sliced;
256+
foreach (triplet; n.iota.triplets) with(triplet)
257+
{
258+
foreach (l; left)
259+
{
260+
auto e = wipProd(x[center] - x[l]);
261+
p[l] *= -e;
262+
p[center] *= e;
263+
}
264+
}
265+
import mir.algorithm.iteration: reduce;
266+
const minExp = long.max.reduce!min(p.member!"exp");
267+
foreach (ref e; p)
268+
e = e.ldexp(1 - minExp);
269+
auto ret = RCArray!(immutable T).create(p.member!"value");
270+
return ret;
271+
}
272+
273+
/++
274+
Computes derivative values in the same points
275+
Params:
276+
d = derivative values (output)
277+
y = values
278+
x = grid
279+
w = inversed barycentric weights
280+
Returns:
281+
Derivative values in the same points
282+
+/
283+
@nogc
284+
Slice!(T*) polynomialDerivativeValues(T)(return Slice!(T*) d, Slice!(const(T)*) x, Slice!(const(T)*) y, Slice!(const(T)*) w)
285+
if (isFloatingPoint!T)
286+
{
287+
pragma(inline, false);
288+
import mir.math.sum;
289+
290+
const n = x.length;
291+
assert(n == w.length);
292+
assert(n == y.length);
293+
294+
d.fillCollapseSums!(Summation.fast,
295+
(c, s1, s2) => w[c] * s1 + y[c] * s2,
296+
(c, _) => y[_] / (w[_] * (x[c] - x[_])),
297+
(c, _) => map!"1 / a"(x[c] - x[_]));
298+
return d;
299+
}
300+
301+
///
302+
@nogc
303+
Slice!(T*) polynomialDerivativeValues(T)(return Slice!(T*) d, Slice!(const(T)*) x, Slice!(const(T)*) y)
304+
if (isFloatingPoint!T)
305+
{
306+
return .polynomialDerivativeValues(d, x, y, x.inversedBarycentricWeights[].sliced);
307+
}
308+
309+
private T asumNorm(T)(Slice!(T*) v)
310+
{
311+
pragma(inline, false);
312+
auto n = v.map!fabs.sum!"fast";
313+
v[] /= n;
314+
return n;
315+
}

0 commit comments

Comments
 (0)