Skip to content

Commit 2f39770

Browse files
NetPro69cdrnet
authored andcommitted
Renamed to StationaryPoints and Extrema, optimized Extrema and added large data set unit test
1 parent af511c1 commit 2f39770

2 files changed

Lines changed: 88 additions & 26 deletions

File tree

src/Numerics.Tests/InterpolationTests/CubicSplineTest.cs

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ public void InterpolateAkimaSorted_MustBeThreadSafe_GitHub219([Values(8, 32, 256
213213
public void NaturalSplineGetHorizontalDerivativeTValues()
214214
{
215215
CubicSpline it = CubicSpline.InterpolateBoundaries(_x, _z, SplineBoundaryCondition.SecondDerivative, -4.0, SplineBoundaryCondition.SecondDerivative, 4.0);
216-
var horizontalDerivatives = it.GetHorizontalDerivativeTValues();
216+
var horizontalDerivatives = it.StationaryPoints();
217217
//readonly double[] _x = { -4, -3, -2, -1, 0, 1, 2, 3, 4 };
218218
//readonly double[] _z = { -7, 2, 5, 0, -3, -1, -4, 0, 6 };
219219
Assert.AreEqual(4, horizontalDerivatives.Length, "Incorrect number of points with derivative value equal to 0");
@@ -231,11 +231,70 @@ public void NaturalSplineGetHorizontalDerivativeTValues()
231231
public void NaturalSplineGetMinMaxTvalues()
232232
{
233233
CubicSpline it = CubicSpline.InterpolateBoundaries(_x, _z, SplineBoundaryCondition.SecondDerivative, -4.0, SplineBoundaryCondition.SecondDerivative, 4.0);
234-
var minMax = it.GetMinMaxTValues();
234+
var minMax = it.Extrema();
235235
Assert.AreEqual(-4, minMax.Item1, "Spline returns wrong t value for global minimum");
236236
Assert.AreEqual(4, minMax.Item2, "Spline returns wrong t value for global maximum");
237237
Console.WriteLine("GetMinMaxTValues checked out ok for cubic spline.");
238+
}
238239

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+
}
239271
}
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+
240299
}
241300
}

src/Numerics/Interpolation/CubicSpline.cs

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -626,9 +626,10 @@ int LeftSegmentIndex(double t)
626626

627627
/// <summary>
628628
/// Gets all the t values where the derivative is 0
629+
/// see: https://mathworld.wolfram.com/StationaryPoint.html
629630
/// </summary>
630-
/// <returns>An array of t values (in the domain of teh function) where the derivative of the spline is 0</returns>
631-
public double[] GetHorizontalDerivativeTValues()
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()
632633
{
633634
List<double> points = new List<double>();
634635
for (int index = 0; index < _x.Length - 1; index++)
@@ -667,29 +668,31 @@ public double[] GetHorizontalDerivativeTValues()
667668
/// Returns the t values in the domain of the spline for which it takes the minimum and maximum value.
668669
/// </summary>
669670
/// <returns>A tuple containing the t value for which the spline is minimum in the first component and maximum in the second component </returns>
670-
public Tuple<double, double> GetMinMaxTValues()
671+
public Tuple<double, double> Extrema()
671672
{
672-
double max = double.MinValue;
673-
double min = double.MaxValue;
674-
double minT = 0;
675-
double maxT = 0;
676-
//go through the functions points to check if one of them has a higher value
677-
foreach (double p in _x)
678-
{
679-
double y = Interpolate(p);
680-
if (y > max)
681-
{
682-
max = y;
683-
maxT = p;
684-
}
685-
if (y < min)
686-
{
687-
min = y;
688-
minT = p;
689-
}
690-
}
691-
//go through the inflexion, local minimums and local maximums
692-
foreach (double p in GetHorizontalDerivativeTValues())
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)
693696
{
694697
double y = Interpolate(p);
695698
if (y > max)

0 commit comments

Comments
 (0)