Skip to content

Commit a34100c

Browse files
authored
New cubic splines: Akima, Double-Quadratic and others (#212)
* wip * wip * wip * wip * rename enum * implement akima inner splines * add double quadratic inner slopes computation * add test for akima spline * update docs * fixup * ditto * fix travis build
1 parent 3d5e9fa commit a34100c

File tree

6 files changed

+478
-266
lines changed

6 files changed

+478
-266
lines changed

doc/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ PACKAGE_mir_cpp_export = numeric
3636
PACKAGE_mir_combinatorics = package
3737
PACKAGE_mir_container = binaryheap
3838
PACKAGE_mir_graph = tarjan package
39-
PACKAGE_mir_interpolate = package constant linear spline pchip utility polynomial
39+
PACKAGE_mir_interpolate = package constant linear spline utility polynomial
4040
PACKAGE_mir_math = sum numeric
4141
PACKAGE_mir_math_func = expdigamma
4242
PACKAGE_mir_ndslice_connect = cpython

source/mir/interpolate/package.d

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ $(BOOKTABLE $(H2 Interpolation modules),
55
$(TR $(TH Module) $(TH Interpolation kind))
66
$(T2M constant, Constant Interpolant)
77
$(T2M linear, Linear Interpolant)
8-
$(T2M pchip, Piecewise Cubic Hermite Interpolating Polynomial)
9-
$(T2M polynomial, Lagrange Barycentric Interpolation)
10-
$(T2M spline, Cubic Spline Interpolant)
8+
$(T2M polynomial, Lagrange Barycentric Interpolant)
9+
$(T2M spline, Piecewise Cubic Hermite Interpolant Spline: C2 (with contiguous second derivative), cardinal, monotone (aka PCHIP), double-quadratic, Akima)
1110
)
12-
13-
Copyright: Copyright © 2017, Kaleidic Associates Advisory Limited
11+
]
12+
Copyright: Copyright © 2019, Symmetry Investments & Kaleidic Associates Advisory Limited
1413
Authors: Ilya Yaroshenko
1514
1615
Macros:

source/mir/interpolate/pchip.d

Lines changed: 10 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/++
1+
/+
22
$(H2 Piecewise Cubic Hermite Interpolating Polynomial)
33
44
See_also: $(REF_ALTTEXT $(TT interp1), interp1, mir, interpolate)
@@ -11,22 +11,25 @@ Macros:
1111
SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, interpolate, $1)$(NBSP)
1212
T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
1313
+/
14+
deprecated("The PCHIP API was changed, the new one looks like spline!double(xSlice, ySlice, SplineType.monotone)")
1415
module mir.interpolate.pchip;
1516

1617
public import mir.interpolate.spline: Spline;
17-
18-
import std.traits;
19-
private alias AliasSeq(T...) = T;
18+
import mir.interpolate;
19+
import mir.interpolate.spline;
20+
import mir.math.common: fmamath;
2021
import mir.ndslice.slice;
2122
import mir.primitives;
22-
import mir.math.common: fmamath;
23-
import mir.ndslice.internal: ConstIfPointer;
23+
import std.traits;
24+
25+
private alias AliasSeq(T...) = T;
2426

2527
@fmamath:
2628

2729
/++
2830
Constructs piecewise cubic hermite interpolating polynomial with nodes on rectilinear grid.
2931
+/
32+
3033
template pchip(T, size_t N = 1, FirstGridIterator = immutable(T)*, NextGridIterators = Repeat!(N - 1, FirstGridIterator))
3134
if (isFloatingPoint!T && is(T == Unqual!T) && N <= 6)
3235
{
@@ -48,162 +51,6 @@ template pchip(T, size_t N = 1, FirstGridIterator = immutable(T)*, NextGridItera
4851
GridVectors grid,
4952
Slice!(yIterator, N, ykind) values) @safe
5053
{
51-
static if (__VERSION__ >= 2085) import core.lifetime: move; else import std.algorithm.mutation: move;
52-
auto ret = Spline!T(grid);
53-
ret._values = values;
54-
pchipSlopes(grid, values, typeof(return).pickDataSubslice(ret._data, 1));
55-
return ret.move;
56-
}
57-
}
58-
59-
///
60-
version(mir_test)
61-
@safe unittest
62-
{
63-
import mir.math.common: approxEqual;
64-
import mir.algorithm.iteration: all;
65-
import mir.ndslice.allocation: slice;
66-
import mir.ndslice.slice: sliced;
67-
import mir.ndslice.topology: vmap;
68-
69-
auto x = [1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22].idup.sliced;
70-
auto y = [17.0, 0, 16, 4, 10, 15, 19, 5, 18, 6].idup.sliced;
71-
auto interpolant = pchip!double(x, y);
72-
73-
auto xs = x[0 .. $ - 1] + 0.5;
74-
75-
() @trusted {
76-
auto ys = xs.vmap(interpolant);
77-
78-
assert(ys.all!approxEqual([
79-
5.333333333333334,
80-
2.500000000000000,
81-
10.000000000000000,
82-
4.288971807628524,
83-
11.202580845771145,
84-
16.250000000000000,
85-
17.962962962962962,
86-
5.558593750000000,
87-
17.604662698412699,
88-
]));
89-
}();
90-
}
91-
92-
// Check direction equality
93-
version(mir_test)
94-
@safe unittest
95-
{
96-
import mir.math.common: approxEqual;
97-
import mir.ndslice.slice: sliced;
98-
import mir.ndslice.allocation: slice;
99-
import mir.ndslice.topology: retro, map;
100-
101-
auto points = [1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22].idup.sliced;
102-
auto values = [17.0, 0, 16, 4, 10, 15, 19, 5, 18, 6].idup.sliced;
103-
104-
auto results = [
105-
5.333333333333334,
106-
2.500000000000000,
107-
10.000000000000000,
108-
4.288971807628524,
109-
11.202580845771145,
110-
16.250000000000000,
111-
17.962962962962962,
112-
5.558593750000000,
113-
17.604662698412699,
114-
];
115-
auto interpolant = pchip!double(points, values);
116-
117-
auto pointsR = slice(-points.retro);
118-
auto valuesR = values.retro.slice;
119-
auto interpolantR = pchip!double(pointsR, valuesR);
120-
121-
version(X86_64)
122-
assert(map!interpolant(points[0 .. $ - 1] + 0.5) == map!interpolantR(pointsR.retro[0 .. $ - 1] - 0.5));
123-
}
124-
125-
126-
/++
127-
Computes slopes for piecewise spline hermite interpolating polynomial.
128-
Params:
129-
points = `x` values for interpolant
130-
values = `f(x)` values for interpolant
131-
slopes = uninitialized ndslice to write slopes into
132-
Constraints:
133-
`points`, `values`, and `slopes` must have the same length >= 3
134-
+/
135-
void pchipSlopes(IG, IV, IS, SliceKind gkind, SliceKind vkind, SliceKind skind)(
136-
Slice!(IG, 1, gkind) points,
137-
Slice!(IV, 1, vkind) values,
138-
Slice!(IS, 1, skind) slopes) @trusted
139-
{
140-
if (points.length < 3)
141-
assert(0);
142-
if (points.length != values.length)
143-
assert(0);
144-
if (points.length != slopes.length)
145-
assert(0);
146-
147-
auto step0 = cast()(points[1] - points[0]);
148-
auto step1 = cast()(points[2] - points[1]);
149-
auto diff0 = cast()(values[1] - values[0]);
150-
auto diff1 = cast()(values[2] - values[1]);
151-
diff0 /= step0;
152-
diff1 /= step1;
153-
154-
slopes.front = pchipTail(step0, step1, diff0, diff1);
155-
for(size_t i = 1;;)
156-
{
157-
import mir.math.common: copysign;
158-
if (diff0 && diff1 && copysign(1f, diff0) == copysign(1f, diff1))
159-
{
160-
auto w0 = step1 * 2 + step0;
161-
auto w1 = step0 * 2 + step1;
162-
slopes[i] = (w0 + w1) / (w0 / diff0 + w1 / diff1);
163-
}
164-
else
165-
{
166-
slopes[i] = 0;
167-
}
168-
if (++i == slopes.length - 1)
169-
{
170-
break;
171-
}
172-
step0 = step1;
173-
diff0 = diff1;
174-
step1 = points[i + 1] - points[i + 0];
175-
diff1 = values[i + 1] - values[i + 0];
176-
diff1 /= step1;
177-
}
178-
slopes.back = pchipTail(step1, step0, diff1, diff0);
179-
}
180-
181-
auto pchipTail(T)(in T step0, in T step1, in T diff0, in T diff1)
182-
{
183-
import mir.math.common: copysign, fabs;
184-
if (!diff0)
185-
{
186-
return 0;
187-
}
188-
auto slope = ((step0 * 2 + step1) * diff0 - step0 * diff1) / (step0 + step1);
189-
if (copysign(1f, slope) != copysign(1f, diff0))
190-
{
191-
return 0;
192-
}
193-
if ((copysign(1f, diff0) != copysign(1f, diff1)) && (fabs(slope) > fabs(diff0 * 3)))
194-
{
195-
return diff0 * 3;
196-
}
197-
return slope;
198-
}
199-
200-
template Repeat(ulong n, L...)
201-
{
202-
static if (n)
203-
{
204-
import std.meta: Repeat;
205-
alias Repeat = std.meta.Repeat!(n, L);
54+
return spline!T(grid, values, SplineType.monotone);
20655
}
207-
else
208-
alias Repeat = L[0 .. 0];
20956
}

0 commit comments

Comments
 (0)