Skip to content

Commit 20df7f2

Browse files
authored
Merge pull request #1110 from diluculo/makima
Add Makima (Modified Akima) Interpolation to CubicSpline
2 parents dbcd7a9 + c74fa44 commit 20df7f2

2 files changed

Lines changed: 229 additions & 0 deletions

File tree

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// <copyright file="MakimaSplineTest.cs" company="Math.NET">
2+
// Math.NET Numerics, part of the Math.NET Project
3+
// https://numerics.mathdotnet.com
4+
// https://github.com/mathnet/mathnet-numerics
5+
//
6+
// Copyright (c) 2009-$CURRENT_YEAR$ Math.NET
7+
//
8+
// Permission is hereby granted, free of charge, to any person
9+
// obtaining a copy of this software and associated documentation
10+
// files (the "Software"), to deal in the Software without
11+
// restriction, including without limitation the rights to use,
12+
// copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
// copies of the Software, and to permit persons to whom the
14+
// Software is furnished to do so, subject to the following
15+
// conditions:
16+
//
17+
// The above copyright notice and this permission notice shall be
18+
// included in all copies or substantial portions of the Software.
19+
//
20+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22+
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23+
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25+
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27+
// OTHER DEALINGS IN THE SOFTWARE.
28+
// </copyright>
29+
30+
using MathNet.Numerics.Interpolation;
31+
using NUnit.Framework;
32+
33+
namespace MathNet.Numerics.Tests.InterpolationTests
34+
{
35+
[TestFixture, Category("Interpolation")]
36+
public class MakimaSplineTest
37+
{
38+
// Test sample data
39+
readonly double[] _t = { -2.0, -1.0, 0.0, 1.0, 2.0 };
40+
readonly double[] _y = { 1.0, 2.0, -1.0, 0.0, 1.0 };
41+
42+
/// <summary>
43+
/// Verifies that the Makima interpolation exactly fits the sample points.
44+
/// </summary>
45+
[Test]
46+
public void FitsAtSamplePoints()
47+
{
48+
IInterpolation it = CubicSpline.InterpolateMakima(_t, _y);
49+
for (var i = 0; i < _y.Length; i++)
50+
{
51+
Assert.AreEqual(_y[i], it.Interpolate(_t[i]), 1e-15, "Exact point " + i);
52+
}
53+
}
54+
55+
/// <summary>
56+
/// Verifies that at arbitrary points, the interpolation matches the reference values.
57+
/// (Note: Replace the expected values with verified reference values.)
58+
/// </summary>
59+
/// <param name="t">The x-value to interpolate.</param>
60+
/// <param name="expected">The expected interpolated value.</param>
61+
/// <param name="maxAbsoluteError">Maximum allowed absolute error.</param>
62+
[TestCase(-2.4, 0.14266666666666683, 1e-10)]
63+
[TestCase(-0.9, 1.805, 1e-10)]
64+
[TestCase(-0.5, 0.29166666666666674, 1e-10)]
65+
[TestCase(-0.1, -0.955, 1e-10)]
66+
[TestCase(0.1, -0.954, 1e-10)]
67+
[TestCase(0.4, -0.696, 1e-10)]
68+
[TestCase(1.2, 0.2, 1e-10)]
69+
[TestCase(10.0, 9.0, 1e-10)]
70+
[TestCase(-10.0, 527.0, 1e-10)]
71+
public void FitsAtArbitraryPoints(double t, double expected, double maxAbsoluteError)
72+
{
73+
IInterpolation it = CubicSpline.InterpolateMakima(_t, _y);
74+
Assert.AreEqual(expected, it.Interpolate(t), maxAbsoluteError, "Interpolation at {0}", t);
75+
}
76+
77+
/// <summary>
78+
/// Verifies that Makima interpolation handles linear data correctly.
79+
/// </summary>
80+
/// <param name="samples">The number of sample points.</param>
81+
[TestCase(5)]
82+
[TestCase(7)]
83+
[TestCase(15)]
84+
public void SupportsLinearCase(int samples)
85+
{
86+
double[] x, y, xtest, ytest;
87+
// LinearInterpolationCase.Build is a utility to generate linear test data.
88+
LinearInterpolationCase.Build(out x, out y, out xtest, out ytest, samples);
89+
IInterpolation it = CubicSpline.InterpolateMakima(x, y);
90+
for (var i = 0; i < xtest.Length; i++)
91+
{
92+
Assert.AreEqual(ytest[i], it.Interpolate(xtest[i]), 1e-15, "Linear case with {0} samples, sample {1}", samples, i);
93+
}
94+
}
95+
96+
/// <summary>
97+
/// Verifies that the interpolation throws an exception when provided with too few samples.
98+
/// </summary>
99+
[Test]
100+
public void FewSamples()
101+
{
102+
Assert.That(() => CubicSpline.InterpolateMakima(new double[0], new double[0]), Throws.ArgumentException);
103+
Assert.That(() => CubicSpline.InterpolateMakima(new double[4], new double[4]), Throws.ArgumentException);
104+
Assert.That(CubicSpline.InterpolateMakima(new[] { 1.0, 2.0, 3.0, 4.0, 5.0 },
105+
new[] { 2.0, 2.0, 2.0, 2.0, 2.0 })
106+
.Interpolate(1.0), Is.EqualTo(2.0));
107+
}
108+
}
109+
}

src/Numerics/Interpolation/CubicSpline.cs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,126 @@ public static CubicSpline InterpolateAkima(IEnumerable<double> x, IEnumerable<do
210210
return InterpolateAkimaInplace(x.ToArray(), y.ToArray());
211211
}
212212

213+
/// <summary>
214+
/// Creates a modified Akima (makima) cubic spline interpolation from a set of (x,y) value pairs,
215+
/// sorted ascendingly by x.
216+
/// </summary>
217+
/// <param name="x">Sample points (must be sorted ascendingly)</param>
218+
/// <param name="y">Sample values corresponding to x</param>
219+
/// <returns>A CubicSpline instance using the makima method</returns>
220+
public static CubicSpline InterpolateMakimaSorted(double[] x, double[] y)
221+
{
222+
if (x.Length != y.Length)
223+
{
224+
throw new ArgumentException("All vectors must have the same dimensionality.");
225+
}
226+
if (x.Length < 5)
227+
{
228+
throw new ArgumentException("The given array is too small. It must be at least 5 long.", nameof(x));
229+
}
230+
231+
var n = x.Length;
232+
var dx = new double[n - 1];
233+
for (var i = 0; i < n - 1; i++)
234+
{
235+
dx[i] = x[i + 1] - x[i];
236+
}
237+
238+
// Allocate m with padding: length = n + 3
239+
var mLen = n + 3;
240+
var m = new double[mLen];
241+
// m[2] .. m[n+1] : slopes between adjacent points.
242+
for (var i = 0; i < n - 1; i++)
243+
{
244+
m[i + 2] = (y[i + 1] - y[i]) / dx[i];
245+
}
246+
247+
// Extrapolate two additional points on the left.
248+
m[1] = 2 * m[2] - m[3];
249+
m[0] = 2 * m[1] - m[2];
250+
251+
// Extrapolate two additional points on the right.
252+
m[n + 1] = 2 * m[n] - m[n - 1];
253+
m[n + 2] = 2 * m[n + 1] - m[n];
254+
255+
// Compute differences (dm) and sums (pm) for makima weights.
256+
var dmLen = mLen - 1;
257+
var dm = new double[dmLen];
258+
var pm = new double[dmLen];
259+
for (var i = 0; i < dmLen; i++)
260+
{
261+
dm[i] = Math.Abs(m[i + 1] - m[i]);
262+
pm[i] = Math.Abs(m[i + 1] + m[i]);
263+
}
264+
265+
// Compute modified weights: f1 and f2 for indices 0 .. n-1.
266+
var f1 = new double[n];
267+
var f2 = new double[n];
268+
for (var i = 0; i < n; i++)
269+
{
270+
f1[i] = dm[i + 2] + 0.5 * pm[i + 2];
271+
f2[i] = dm[i] + 0.5 * pm[i];
272+
}
273+
var f12 = new double[n];
274+
for (var i = 0; i < n; i++)
275+
{
276+
f12[i] = f1[i] + f2[i];
277+
}
278+
279+
// Initial derivative estimates: t[i] = 0.5*(m[i+3] + m[i])
280+
var t = new double[n];
281+
for (var i = 0; i < n; i++)
282+
{
283+
t[i] = 0.5 * (m[i + 3] + m[i]);
284+
}
285+
286+
// Determine threshold from maximum of f12.
287+
var maxF12 = 0.0;
288+
for (var i = 0; i < n; i++)
289+
{
290+
if (f12[i] > maxF12)
291+
maxF12 = f12[i];
292+
}
293+
var threshold = 1e-9 * maxF12;
294+
295+
// For indices where f12 is significant, update t[i] using weighted average.
296+
for (var i = 0; i < n; i++)
297+
{
298+
if (f12[i] > threshold)
299+
{
300+
t[i] = (f1[i] * m[i + 1] + f2[i] * m[i + 2]) / f12[i];
301+
}
302+
}
303+
304+
// Construct the Hermite cubic spline using the computed derivatives.
305+
return InterpolateHermiteSorted(x, y, t);
306+
}
307+
308+
/// <summary>
309+
/// Creates a modified Akima (makima) cubic spline interpolation from an unsorted set of (x,y) value pairs.
310+
/// WARNING: This method works in-place and will sort the input arrays.
311+
/// </summary>
312+
/// <param name="x">Sample points</param>
313+
/// <param name="y">Sample values corresponding to x</param>
314+
/// <returns>A CubicSpline instance using the makima method</returns>
315+
public static CubicSpline InterpolateMakimaInplace(double[] x, double[] y)
316+
{
317+
// Sorting.Sort is assumed to sort x and apply the same permutation to y.
318+
Sorting.Sort(x, y);
319+
return InterpolateMakimaSorted(x, y);
320+
}
321+
322+
/// <summary>
323+
/// Creates a modified Akima (makima) cubic spline interpolation from an unsorted set of (x,y) value pairs.
324+
/// </summary>
325+
/// <param name="x">Sample points as an IEnumerable</param>
326+
/// <param name="y">Sample values corresponding to x as an IEnumerable</param>
327+
/// <returns>A CubicSpline instance using the makima method</returns>
328+
public static CubicSpline InterpolateMakima(IEnumerable<double> x, IEnumerable<double> y)
329+
{
330+
return InterpolateMakimaInplace(x.ToArray(), y.ToArray());
331+
}
332+
213333
/// <summary>
214334
/// Create a piecewise cubic Hermite interpolating polynomial from an unsorted set of (x,y) value pairs.
215335
/// Monotone-preserving interpolation with continuous first derivative.

0 commit comments

Comments
 (0)