Skip to content

Commit 07bfe8b

Browse files
authored
Merge pull request #888 from NetPro69/cubic_spline_max
Added function go get points where cubic spline derivative is 0 and Get Max value for cubic spline in domain
2 parents 5c077f7 + 2f39770 commit 07bfe8b

File tree

2 files changed

+181
-2
lines changed

2 files changed

+181
-2
lines changed

src/Numerics.Tests/InterpolationTests/CubicSplineTest.cs

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ public class CubicSplineTest
3939
{
4040
readonly double[] _t = { -2.0, -1.0, 0.0, 1.0, 2.0 };
4141
readonly double[] _y = { 1.0, 2.0, -1.0, 0.0, 1.0 };
42-
42+
//test data for min max values
43+
readonly double[] _x = { -4, -3, -2, -1, 0, 1, 2, 3, 4 };
44+
readonly double[] _z = { -7, 2, 5, 0, -3, -1, -4, 0, 6 };
45+
4346
/// <summary>
4447
/// Verifies that the interpolation matches the given value at all the provided sample points.
4548
/// </summary>
@@ -202,5 +205,96 @@ public void InterpolateAkimaSorted_MustBeThreadSafe_GitHub219([Values(8, 32, 256
202205

203206
CollectionAssert.DoesNotContain(yipol, Double.NaN);
204207
}
208+
209+
/// <summary>
210+
/// Tests that the cubic spline returns the correct t values where the derivative is 0
211+
/// </summary>
212+
[Test]
213+
public void NaturalSplineGetHorizontalDerivativeTValues()
214+
{
215+
CubicSpline it = CubicSpline.InterpolateBoundaries(_x, _z, SplineBoundaryCondition.SecondDerivative, -4.0, SplineBoundaryCondition.SecondDerivative, 4.0);
216+
var horizontalDerivatives = it.StationaryPoints();
217+
//readonly double[] _x = { -4, -3, -2, -1, 0, 1, 2, 3, 4 };
218+
//readonly double[] _z = { -7, 2, 5, 0, -3, -1, -4, 0, 6 };
219+
Assert.AreEqual(4, horizontalDerivatives.Length, "Incorrect number of points with derivative value equal to 0");
220+
Assert.IsTrue(horizontalDerivatives[0]>=-3 && horizontalDerivatives[0] <= -2,"Spline returns wrong t value: "+horizontalDerivatives[0]+" for first point");
221+
Assert.IsTrue(horizontalDerivatives[1] >= -1 && horizontalDerivatives[1] <= 0, "Spline returns wrong t value: " + horizontalDerivatives[1] + " for second point");
222+
Assert.IsTrue(horizontalDerivatives[2] >= 0 && horizontalDerivatives[2] <= 1, "Spline returns wrong t value: " + horizontalDerivatives[2] + " for third point");
223+
Assert.IsTrue(horizontalDerivatives[3] >= 2 && horizontalDerivatives[3] <= 3, "Spline returns wrong t value: " + horizontalDerivatives[3] + " for fourth point");
224+
Console.WriteLine("GetHorizontalDerivativeTValues checked out ok for cubic spline.");
225+
}
226+
227+
/// <summary>
228+
/// Tests that the min and max values for the natural spline are correct
229+
/// </summary>
230+
[Test]
231+
public void NaturalSplineGetMinMaxTvalues()
232+
{
233+
CubicSpline it = CubicSpline.InterpolateBoundaries(_x, _z, SplineBoundaryCondition.SecondDerivative, -4.0, SplineBoundaryCondition.SecondDerivative, 4.0);
234+
var minMax = it.Extrema();
235+
Assert.AreEqual(-4, minMax.Item1, "Spline returns wrong t value for global minimum");
236+
Assert.AreEqual(4, minMax.Item2, "Spline returns wrong t value for global maximum");
237+
Console.WriteLine("GetMinMaxTValues checked out ok for cubic spline.");
238+
}
239+
240+
/// <summary>
241+
/// This tests that the min and max values for a natural spline interpolated on an oscilating decaying function are correct and
242+
/// spints out the time it takes to do the calculation for 100 thousand points
243+
/// </summary>
244+
[Test]
245+
public void CheckNaturalSplineMinMaxValuesPerformance()
246+
{
247+
//first generate the test data and spline
248+
double amplitude = 100;
249+
double ofset = 0;
250+
double period = 10;
251+
double decay = 0.1;
252+
double minX = -50;
253+
double maxX = 50;
254+
int points = 100000;
255+
var data = GenerateSplineData(amplitude, period, decay, minX, maxX, ofset, points);
256+
CubicSpline it = CubicSpline.InterpolateBoundaries(data.Item1, data.Item2, SplineBoundaryCondition.Natural, 0, SplineBoundaryCondition.Natural, 0);
257+
//start the time and calculate the min max values
258+
var t = DateTime.Now;
259+
var minMax = it.Extrema();
260+
Assert.IsTrue(minMax.Item2.AlmostEqual(ofset, 0.3), "Expexted max value near ofset.");
261+
Assert.IsTrue(minMax.Item1.AlmostEqual(ofset+period/2, 0.3) || minMax.Item1.AlmostEqual(ofset - period / 2, 0.3), "Expexted min value near ofset +- period/2.");
262+
//spit out the time it took to calculate
263+
Console.WriteLine("Extrema took: " + (DateTime.Now - t).TotalMilliseconds.ToString("000.00")+" ms for "+points.ToString()+" points.");
264+
//determine if the values are correct
265+
var sp = it.StationaryPoints();
266+
foreach (var x in sp)
267+
{
268+
//check that the stationary point falls roughly at a half period
269+
Assert.IsTrue(Math.Abs((Math.Abs((x - ofset) * 2 / period) - Math.Round(Math.Abs(x - ofset) * 2 / period, 0))).AlmostEqual(0,0.3), "Stationary point found outside of period/2 for x="+x.ToString());
270+
}
271+
}
272+
273+
/// <summary>
274+
/// Generates a set of points representing an oscilating decaying function
275+
/// </summary>
276+
/// <param name="amplitude">The max amplitude</param>
277+
/// <param name="period">The period of oscilation</param>
278+
/// <param name="decay">The decaying exponent, the larger the value the faster the functioon decays</param>
279+
/// <param name="minX">The min value for X</param>
280+
/// <param name="maxX">The max value for X</param>
281+
/// <param name="ofset">The x - ofset of the max value of the function</param>
282+
/// <param name="points">The number of points to generate, must be greater than 1</param>
283+
/// <returns></returns>
284+
private Tuple<double[],double[]> GenerateSplineData(double amplitude, double period, double decay, double minX, double maxX, double ofset, int points)
285+
{
286+
double delta = (maxX-minX)/(points-1);
287+
double[] _x = new double[points];
288+
double[] _y = new double[points];
289+
for (int i = 0; i < points; i++)
290+
{
291+
double x = i * delta + minX;
292+
double y = amplitude * Math.Cos((x - ofset) * 2 * Math.PI / period) * Math.Exp(-Math.Abs((x - ofset) * decay));
293+
_x[i] = x;
294+
_y[i] = y;
295+
}
296+
return Tuple.Create(_x, _y);
297+
}
298+
205299
}
206300
}

src/Numerics/Interpolation/CubicSpline.cs

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// <copyright file="CubicSpline.cs" company="Math.NET">
1+
// <copyright file="CubicSpline.cs" company="Math.NET">
22
// Math.NET Numerics, part of the Math.NET Project
33
// http://numerics.mathdotnet.com
44
// http://github.com/mathnet/mathnet-numerics
@@ -623,5 +623,90 @@ int LeftSegmentIndex(double t)
623623

624624
return Math.Min(Math.Max(index, 0), _x.Length - 2);
625625
}
626+
627+
/// <summary>
628+
/// Gets all the t values where the derivative is 0
629+
/// see: https://mathworld.wolfram.com/StationaryPoint.html
630+
/// </summary>
631+
/// <returns>An array of t values (in the domain of the function) where the derivative of the spline is 0</returns>
632+
public double[] StationaryPoints()
633+
{
634+
List<double> points = new List<double>();
635+
for (int index = 0; index < _x.Length - 1; index++)
636+
{
637+
double a = 6 * _c3[index]; //derive ax^3 and multiply by 2
638+
double b = 2 * _c2[index]; //derive bx^2
639+
double c = _c1[index];//derive cx
640+
double d = b * b - 2 * a * c;
641+
//first check if a is 0, if so its a linear function, this happens with quadratic condition
642+
if (a.AlmostEqual(0))
643+
{
644+
double x = _x[index] - c / b;
645+
//check if the result is in the domain
646+
if (_x[index] <= x && x <= _x[index + 1]) points.Add(x);
647+
}
648+
else if (d.AlmostEqual(0))//its a quadratic with a single solution
649+
{
650+
double x = _x[index] - b / a;
651+
if (_x[index] <= x && x <= _x[index + 1]) points.Add(x);
652+
}
653+
else if (d > 0)//only has a solution if d is greater than 0
654+
{
655+
d = (double)System.Math.Sqrt(d);
656+
//apply quadratic equation
657+
double x1 = _x[index] + (-b + d) / a;
658+
double x2 = _x[index] + (-b - d) / a;
659+
//Add any solution points that fall within the domain to the list
660+
if ((_x[index] <= x1) && (x1 <= _x[index + 1])) points.Add(x1);
661+
if ((_x[index] <= x2) && (x2 <= _x[index + 1])) points.Add(x2);
662+
}
663+
}
664+
return points.ToArray();
665+
}
666+
667+
/// <summary>
668+
/// Returns the t values in the domain of the spline for which it takes the minimum and maximum value.
669+
/// </summary>
670+
/// <returns>A tuple containing the t value for which the spline is minimum in the first component and maximum in the second component </returns>
671+
public Tuple<double, double> Extrema()
672+
{
673+
//Check the edges of the domain
674+
//set the initial values to the leftmost domain point
675+
double t = _x[0];
676+
double max = Interpolate(t);
677+
double min = max;
678+
double minT = t;
679+
double maxT = t;
680+
//check the rightmost domain point
681+
t = _x[_x.Length-1];
682+
var ty = Interpolate(t);
683+
if (ty > max)
684+
{
685+
max = ty;
686+
maxT = t;
687+
}
688+
if (ty < min)
689+
{
690+
min = ty;
691+
minT = t;
692+
}
693+
//check the the inflexion, local minimums and local maximums
694+
double[] pointsToCheck = StationaryPoints();
695+
foreach (double p in pointsToCheck)
696+
{
697+
double y = Interpolate(p);
698+
if (y > max)
699+
{
700+
max = y;
701+
maxT = p;
702+
}
703+
if (y < min)
704+
{
705+
min = y;
706+
minT = p;
707+
}
708+
}
709+
return new Tuple<double, double>(minT, maxT);
710+
}
626711
}
627712
}

0 commit comments

Comments
 (0)