From 8cf834e137e9529de60fd9b599bb1ec413c4a8f9 Mon Sep 17 00:00:00 2001 From: Christoph Ruegg Date: Thu, 5 Jun 2014 13:40:03 +0200 Subject: [PATCH] Interpolation: common api should not sort original data; alternative if data is already sorted #219 --- src/Numerics/Interpolate.cs | 100 +++++--- src/Numerics/Interpolation/Barycentric.cs | 151 ++++++++--- .../BulirschStoerRationalInterpolation.cs | 54 ++-- src/Numerics/Interpolation/CubicSpline.cs | 239 ++++++++++++------ src/Numerics/Interpolation/LinearSpline.cs | 47 ++-- .../NevillePolynomialInterpolation.cs | 60 +++-- .../BulirschStoerRationalTest.cs | 4 +- .../InterpolationTests/CubicSplineTest.cs | 23 ++ 8 files changed, 474 insertions(+), 204 deletions(-) diff --git a/src/Numerics/Interpolate.cs b/src/Numerics/Interpolate.cs index f3c4250b..f074b6cb 100644 --- a/src/Numerics/Interpolate.cs +++ b/src/Numerics/Interpolate.cs @@ -4,7 +4,7 @@ // http://github.com/mathnet/mathnet-numerics // http://mathnetnumerics.codeplex.com // -// Copyright (c) 2009-2013 Math.NET +// Copyright (c) 2009-2014 Math.NET // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation @@ -41,13 +41,18 @@ namespace MathNet.Numerics /// /// Creates an interpolation based on arbitrary points. /// - /// The sample points t. Supports both lists and arrays. - /// The sample point values x(t). Supports both lists and arrays. + /// The sample points t. + /// The sample point values x(t). /// /// An interpolation scheme optimized for the given sample points and values, /// which can then be used to compute interpolations and extrapolations /// on arbitrary points. /// + /// + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.Barycentric.InterpolateRationalFloaterHormannSorted + /// instead, which is more efficient. + /// public static IInterpolation Common(IEnumerable points, IEnumerable values) { return Barycentric.InterpolateRationalFloaterHormann(points, values); @@ -56,46 +61,57 @@ namespace MathNet.Numerics /// /// Create a floater hormann rational pole-free interpolation based on arbitrary points. /// - /// The sample points t. Supports both lists and arrays. - /// The sample point values x(t). Supports both lists and arrays. + /// The sample points t. + /// The sample point values x(t). /// /// An interpolation scheme optimized for the given sample points and values, /// which can then be used to compute interpolations and extrapolations /// on arbitrary points. /// + /// + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.Barycentric.InterpolateRationalFloaterHormannSorted + /// instead, which is more efficient. + /// public static IInterpolation RationalWithoutPoles(IEnumerable points, IEnumerable values) { return Barycentric.InterpolateRationalFloaterHormann(points, values); } /// - /// Create a burlisch stoer rational interpolation based on arbitrary points. + /// Create a Bulirsch Stoer rational interpolation based on arbitrary points. /// - /// The sample points t. Optimized for arrays.. - /// The sample point values x(t). Optimized for arrays. + /// The sample points t. + /// The sample point values x(t). /// /// An interpolation scheme optimized for the given sample points and values, /// which can then be used to compute interpolations and extrapolations /// on arbitrary points. /// + /// + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.BulirschStoerRationalInterpolation.InterpolateSorted + /// instead, which is more efficient. + /// public static IInterpolation RationalWithPoles(IEnumerable points, IEnumerable values) { - return new BulirschStoerRationalInterpolation(points, values); + return BulirschStoerRationalInterpolation.Interpolate(points, values); } /// /// Create a barycentric polynomial interpolation where the given sample points are equidistant. /// - /// The sample points t, must be equidistant. Optimized for arrays. - /// The sample point values x(t). Optimized for arrays. + /// The sample points t, must be equidistant. + /// The sample point values x(t). /// /// An interpolation scheme optimized for the given sample points and values, /// which can then be used to compute interpolations and extrapolations /// on arbitrary points. /// /// - /// The value pairs do not have to be sorted, but if they are not sorted ascendingly - /// and the passed x and y arguments are arrays, they will be sorted inplace and thus modified. + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.Barycentric.InterpolatePolynomialEquidistantSorted + /// instead, which is more efficient. /// public static IInterpolation PolynomialEquidistant(IEnumerable points, IEnumerable values) { @@ -103,35 +119,41 @@ namespace MathNet.Numerics } /// - /// Create a neville polynomial interpolation based on arbitrary points. + /// Create a Neville polynomial interpolation based on arbitrary points. /// If the points happen to be equidistant, consider to use the much more robust PolynomialEquidistant instead. /// Otherwise, consider whether RationalWithoutPoles would not be a more robust alternative. /// - /// The sample points t. Optimized for arrays. - /// The sample point values x(t). Optimized for arrays. + /// The sample points t. + /// The sample point values x(t). /// /// An interpolation scheme optimized for the given sample points and values, /// which can then be used to compute interpolations and extrapolations /// on arbitrary points. /// + /// + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.NevillePolynomialInterpolation.InterpolateSorted + /// instead, which is more efficient. + /// public static IInterpolation Polynomial(IEnumerable points, IEnumerable values) { - return new NevillePolynomialInterpolation(points, values); + return NevillePolynomialInterpolation.Interpolate(points, values); } /// /// Create a piecewise linear spline interpolation based on arbitrary points. /// - /// The sample points t. Optimized for arrays. - /// The sample point values x(t). Optimized for arrays. + /// The sample points t. + /// The sample point values x(t). /// /// An interpolation scheme optimized for the given sample points and values, /// which can then be used to compute interpolations and extrapolations /// on arbitrary points. /// /// - /// The value pairs do not have to be sorted, but if they are not sorted ascendingly - /// and the passed x and y arguments are arrays, they will be sorted inplace and thus modified. + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.LinearSpline.InterpolateSorted + /// instead, which is more efficient. /// public static IInterpolation LinearSpline(IEnumerable points, IEnumerable values) { @@ -139,18 +161,20 @@ namespace MathNet.Numerics } /// - /// Create an piecewise natural cubic spline interpolation based on arbitrary points, with zero secondary derivatives at the boundaries. + /// Create an piecewise natural cubic spline interpolation based on arbitrary points, + /// with zero secondary derivatives at the boundaries. /// - /// The sample points t. Optimized for arrays. - /// The sample point values x(t). Optimized for arrays. + /// The sample points t. + /// The sample point values x(t). /// /// An interpolation scheme optimized for the given sample points and values, /// which can then be used to compute interpolations and extrapolations /// on arbitrary points. /// /// - /// The value pairs do not have to be sorted, but if they are not sorted ascendingly - /// and the passed x and y arguments are arrays, they will be sorted inplace and thus modified. + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.CubicSpline.InterpolateNaturalSorted + /// instead, which is more efficient. /// public static IInterpolation CubicSpline(IEnumerable points, IEnumerable values) { @@ -158,18 +182,20 @@ namespace MathNet.Numerics } /// - /// Create an piecewise cubic Akima spline interpolation based on arbitrary points. Akima splines are robust to outliers. + /// Create an piecewise cubic Akima spline interpolation based on arbitrary points. + /// Akima splines are robust to outliers. /// - /// The sample points t. Optimized for arrays. - /// The sample point values x(t). Optimized for arrays. + /// The sample points t. + /// The sample point values x(t). /// /// An interpolation scheme optimized for the given sample points and values, /// which can then be used to compute interpolations and extrapolations /// on arbitrary points. /// /// - /// The value pairs do not have to be sorted, but if they are not sorted ascendingly - /// and the passed x and y arguments are arrays, they will be sorted inplace and thus modified. + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.CubicSpline.InterpolateAkimaSorted + /// instead, which is more efficient. /// public static IInterpolation CubicSplineRobust(IEnumerable points, IEnumerable values) { @@ -177,10 +203,11 @@ namespace MathNet.Numerics } /// - /// Create a piecewise cubic Hermite spline interpolation based on arbitrary points and their slopes/first derivative. + /// Create a piecewise cubic Hermite spline interpolation based on arbitrary points + /// and their slopes/first derivative. /// - /// The sample points t. Optimized for arrays. - /// The sample point values x(t). Optimized for arrays. + /// The sample points t. + /// The sample point values x(t). /// The slope at the sample points. Optimized for arrays. /// /// An interpolation scheme optimized for the given sample points and values, @@ -188,8 +215,9 @@ namespace MathNet.Numerics /// on arbitrary points. /// /// - /// The value pairs do not have to be sorted, but if they are not sorted ascendingly - /// and the passed x and y arguments are arrays, they will be sorted inplace and thus modified. + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.CubicSpline.InterpolateHermiteSorted + /// instead, which is more efficient. /// public static IInterpolation CubicSplineWithDerivatives(IEnumerable points, IEnumerable values, IEnumerable firstDerivatives) { diff --git a/src/Numerics/Interpolation/Barycentric.cs b/src/Numerics/Interpolation/Barycentric.cs index b5145b59..29369c5b 100644 --- a/src/Numerics/Interpolation/Barycentric.cs +++ b/src/Numerics/Interpolation/Barycentric.cs @@ -4,7 +4,7 @@ // http://github.com/mathnet/mathnet-numerics // http://mathnetnumerics.codeplex.com // -// Copyright (c) 2009-2013 Math.NET +// Copyright (c) 2009-2014 Math.NET // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation @@ -45,9 +45,9 @@ namespace MathNet.Numerics.Interpolation readonly double[] _y; readonly double[] _w; - /// Sample points (N), no sorting assumed. - /// Sample values (N). - /// Barycentric weights (N). + /// Sample points (N), sorted ascendingly. + /// Sample values (N), sorted ascendingly by x. + /// Barycentric weights (N), sorted ascendingly by x. public Barycentric(double[] x, double[] y, double[] w) { if (x.Length != y.Length || x.Length != w.Length) @@ -66,67 +66,80 @@ namespace MathNet.Numerics.Interpolation } /// - /// Create a barycentric polynomial interpolation from a set of (x,y) value pairs with equidistant x. No sorting is assumed. + /// Create a barycentric polynomial interpolation from a set of (x,y) value pairs with equidistant x, sorted ascendingly by x. /// - /// - /// The value pairs do not have to be sorted, but if they are not sorted ascendingly - /// and the passed x and y arguments are arrays, they will be sorted inplace and thus modified. - /// - public static Barycentric InterpolatePolynomialEquidistant(IEnumerable x, IEnumerable y) + public static Barycentric InterpolatePolynomialEquidistantSorted(double[] x, double[] y) { - var xx = (x as double[]) ?? x.ToArray(); - var yy = (y as double[]) ?? y.ToArray(); - - if (xx.Length != yy.Length) + if (x.Length != y.Length) { throw new ArgumentException(Resources.ArgumentVectorsSameLength); } - if (xx.Length < 1) + if (x.Length < 1) { throw new ArgumentOutOfRangeException("x"); } - Sorting.Sort(xx, yy); - - var weights = new double[xx.Length]; + var weights = new double[x.Length]; weights[0] = 1.0; for (int i = 1; i < weights.Length; i++) { weights[i] = -(weights[i - 1]*(weights.Length - i))/i; } - return new Barycentric(xx, yy, weights); + return new Barycentric(x, y, weights); + } + + /// + /// Create a barycentric polynomial interpolation from an unordered set of (x,y) value pairs with equidistant x. + /// WARNING: Works in-place and can thus causes the data array to be reordered. + /// + public static Barycentric InterpolatePolynomialEquidistantInplace(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (x.Length < 1) + { + throw new ArgumentOutOfRangeException("x"); + } + + Sorting.Sort(x, y); + return InterpolatePolynomialEquidistantSorted(x, y); + } + + /// + /// Create a barycentric polynomial interpolation from an unsorted set of (x,y) value pairs with equidistant x. + /// + public static Barycentric InterpolatePolynomialEquidistant(IEnumerable x, IEnumerable y) + { + // note: we must make a copy, even if the input was arrays already + return InterpolatePolynomialEquidistantInplace(x.ToArray(), y.ToArray()); } /// /// Create a barycentric polynomial interpolation from a set of values related to linearly/equidistant spaced points within an interval. /// - /// - /// The value pairs do not have to be sorted, but if they are not sorted ascendingly - /// and the passed x and y arguments are arrays, they will be sorted inplace and thus modified. - /// public static Barycentric InterpolatePolynomialEquidistant(double leftBound, double rightBound, IEnumerable y) { var yy = (y as double[]) ?? y.ToArray(); var xx = Generate.LinearSpaced(yy.Length, leftBound, rightBound); - return InterpolatePolynomialEquidistant(xx, yy); + return InterpolatePolynomialEquidistantSorted(xx, yy); } /// /// Create a barycentric rational interpolation without poles, using Mike Floater and Kai Hormann's Algorithm. + /// The values are assumed to be sorted ascendingly by x. /// - /// Sample points (N), no sorting assumed. Optimized for arrays. - /// Sample values (N). Optimized for arrays. + /// Sample points (N), sorted ascendingly. + /// Sample values (N), sorted ascendingly by x. /// /// Order of the interpolation scheme, 0 <= order <= N. /// In most cases a value between 3 and 8 gives good results. /// - /// - /// The value pairs do not have to be sorted, but if they are not sorted ascendingly - /// and the passed x and y arguments are arrays, they will be sorted inplace and thus modified. - /// - public static Barycentric InterpolateRationalFloaterHormann(IEnumerable x, IEnumerable y, int order) + public static Barycentric InterpolateRationalFloaterHormannSorted(double[] x, double[] y, int order) { var xx = (x as double[]) ?? x.ToArray(); var yy = (y as double[]) ?? y.ToArray(); @@ -175,18 +188,78 @@ namespace MathNet.Numerics.Interpolation /// /// Create a barycentric rational interpolation without poles, using Mike Floater and Kai Hormann's Algorithm. + /// WARNING: Works in-place and can thus causes the data array to be reordered. /// - /// Sample points (N), no sorting assumed. Optimized for arrays. - /// Sample values (N). Optimized for arrays. - /// - /// The value pairs do not have to be sorted, but if they are not sorted ascendingly - /// and the passed x and y arguments are arrays, they will be sorted inplace and thus modified. - /// + /// Sample points (N), no sorting assumed. + /// Sample values (N). + /// + /// Order of the interpolation scheme, 0 <= order <= N. + /// In most cases a value between 3 and 8 gives good results. + /// + public static Barycentric InterpolateRationalFloaterHormannInplace(double[] x, double[] y, int order) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (0 > order || x.Length <= order) + { + throw new ArgumentOutOfRangeException("order"); + } + + Sorting.Sort(x, y); + return InterpolateRationalFloaterHormannSorted(x, y, order); + } + + /// + /// Create a barycentric rational interpolation without poles, using Mike Floater and Kai Hormann's Algorithm. + /// + /// Sample points (N), no sorting assumed. + /// Sample values (N). + /// + /// Order of the interpolation scheme, 0 <= order <= N. + /// In most cases a value between 3 and 8 gives good results. + /// + public static Barycentric InterpolateRationalFloaterHormann(IEnumerable x, IEnumerable y, int order) + { + // note: we must make a copy, even if the input was arrays already + return InterpolateRationalFloaterHormannInplace(x.ToArray(), y.ToArray(), order); + } + + /// + /// Create a barycentric rational interpolation without poles, using Mike Floater and Kai Hormann's Algorithm. + /// The values are assumed to be sorted ascendingly by x. + /// + /// Sample points (N), sorted ascendingly. + /// Sample values (N), sorted ascendingly by x. + public static Barycentric InterpolateRationalFloaterHormannSorted(double[] x, double[] y) + { + return InterpolateRationalFloaterHormannSorted(x, y, Math.Min(3, x.Length - 1)); + } + + /// + /// Create a barycentric rational interpolation without poles, using Mike Floater and Kai Hormann's Algorithm. + /// WARNING: Works in-place and can thus causes the data array to be reordered. + /// + /// Sample points (N), no sorting assumed. + /// Sample values (N). + public static Barycentric InterpolateRationalFloaterHormannInplace(double[] x, double[] y) + { + return InterpolateRationalFloaterHormannInplace(x, y, Math.Min(3, x.Length - 1)); + } + + /// + /// Create a barycentric rational interpolation without poles, using Mike Floater and Kai Hormann's Algorithm. + /// + /// Sample points (N), no sorting assumed. + /// Sample values (N). public static Barycentric InterpolateRationalFloaterHormann(IEnumerable x, IEnumerable y) { - var xx = (x as double[]) ?? x.ToArray(); + // note: we must make a copy, even if the input was arrays already + var xx = x.ToArray(); var order = Math.Min(3, xx.Length - 1); - return InterpolateRationalFloaterHormann(xx, y, order); + return InterpolateRationalFloaterHormannInplace(xx, y.ToArray(), order); } /// diff --git a/src/Numerics/Interpolation/BulirschStoerRationalInterpolation.cs b/src/Numerics/Interpolation/BulirschStoerRationalInterpolation.cs index a31d910b..bc4495d6 100644 --- a/src/Numerics/Interpolation/BulirschStoerRationalInterpolation.cs +++ b/src/Numerics/Interpolation/BulirschStoerRationalInterpolation.cs @@ -4,7 +4,7 @@ // http://github.com/mathnet/mathnet-numerics // http://mathnetnumerics.codeplex.com // -// Copyright (c) 2009-2013 Math.NET +// Copyright (c) 2009-2014 Math.NET // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation @@ -48,30 +48,54 @@ namespace MathNet.Numerics.Interpolation readonly double[] _x; readonly double[] _y; - /// - /// Initializes a new instance of the BulirschStoerRationalInterpolation class. - /// - /// Sample Points t - /// Sample Values x(t) - public BulirschStoerRationalInterpolation(IEnumerable x, IEnumerable y) + /// Sample Points t, sorted ascendingly. + /// Sample Values x(t), sorted ascendingly by x. + public BulirschStoerRationalInterpolation(double[] x, double[] y) { - var xx = (x as double[]) ?? x.ToArray(); - var yy = (y as double[]) ?? y.ToArray(); - - if (xx.Length != yy.Length) + if (x.Length != y.Length) { throw new ArgumentException(Resources.ArgumentVectorsSameLength); } - if (xx.Length < 1) + if (x.Length < 1) { throw new ArgumentOutOfRangeException("x"); } - Sorting.Sort(xx, yy); + _x = x; + _y = y; + } - _x = xx; - _y = yy; + /// + /// Create a Bulirsch-Stoer rational interpolation from a set of (x,y) value pairs, sorted ascendingly by x. + /// + public static BulirschStoerRationalInterpolation InterpolateSorted(double[] x, double[] y) + { + return new BulirschStoerRationalInterpolation(x, y); + } + + /// + /// Create a Bulirsch-Stoer rational interpolation from an unsorted set of (x,y) value pairs. + /// WARNING: Works in-place and can thus causes the data array to be reordered. + /// + public static BulirschStoerRationalInterpolation InterpolateInplace(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + Sorting.Sort(x, y); + return InterpolateSorted(x, y); + } + + /// + /// Create a Bulirsch-Stoer rational interpolation from an unsorted set of (x,y) value pairs. + /// + public static BulirschStoerRationalInterpolation Interpolate(IEnumerable x, IEnumerable y) + { + // note: we must make a copy, even if the input was arrays already + return InterpolateInplace(x.ToArray(), y.ToArray()); } /// diff --git a/src/Numerics/Interpolation/CubicSpline.cs b/src/Numerics/Interpolation/CubicSpline.cs index 8c8ea2bf..5152cc43 100644 --- a/src/Numerics/Interpolation/CubicSpline.cs +++ b/src/Numerics/Interpolation/CubicSpline.cs @@ -4,7 +4,7 @@ // http://github.com/mathnet/mathnet-numerics // http://mathnetnumerics.codeplex.com // -// Copyright (c) 2009-2013 Math.NET +// Copyright (c) 2009-2014 Math.NET // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation @@ -69,79 +69,90 @@ namespace MathNet.Numerics.Interpolation } /// - /// Create a hermite cubic spline interpolation from a set of (x,y) value pairs and their slope (first derivative). + /// Create a hermite cubic spline interpolation from a set of (x,y) value pairs and their slope (first derivative), sorted ascendingly by x. /// - /// - /// The value pairs do not have to be sorted, but if they are not sorted ascendingly - /// and the passed x and y arguments are arrays, they will be sorted inplace and thus modified. - /// - public static CubicSpline InterpolateHermite(IEnumerable x, IEnumerable y, IEnumerable firstDerivatives) + public static CubicSpline InterpolateHermiteSorted(double[] x, double[] y, double[] firstDerivatives) { - var xx = (x as double[]) ?? x.ToArray(); - var yy = (y as double[]) ?? y.ToArray(); - var dd = (firstDerivatives as double[]) ?? firstDerivatives.ToArray(); - - if (xx.Length != yy.Length || xx.Length != dd.Length) + if (x.Length != y.Length || x.Length != firstDerivatives.Length) { throw new ArgumentException(Resources.ArgumentVectorsSameLength); } - if (xx.Length < 2) + if (x.Length < 2) { throw new ArgumentOutOfRangeException("x"); } - Sorting.Sort(xx, yy, dd); - - var c0 = new double[xx.Length - 1]; - var c1 = new double[xx.Length - 1]; - var c2 = new double[xx.Length - 1]; - var c3 = new double[xx.Length - 1]; + var c0 = new double[x.Length - 1]; + var c1 = new double[x.Length - 1]; + var c2 = new double[x.Length - 1]; + var c3 = new double[x.Length - 1]; for (int i = 0; i < c1.Length; i++) { - double w = xx[i + 1] - xx[i]; + double w = x[i + 1] - x[i]; double w2 = w*w; - c0[i] = yy[i]; - c1[i] = dd[i]; - c2[i] = (3*(yy[i + 1] - yy[i])/w - 2*dd[i] - dd[i + 1])/w; - c3[i] = (2*(yy[i] - yy[i + 1])/w + dd[i] + dd[i + 1])/w2; + c0[i] = y[i]; + c1[i] = firstDerivatives[i]; + c2[i] = (3*(y[i + 1] - y[i])/w - 2*firstDerivatives[i] - firstDerivatives[i + 1])/w; + c3[i] = (2*(y[i] - y[i + 1])/w + firstDerivatives[i] + firstDerivatives[i + 1])/w2; } - return new CubicSpline(xx, c0, c1, c2, c3); + return new CubicSpline(x, c0, c1, c2, c3); } /// - /// Create an Akima cubic spline interpolation from a set of (x,y) value pairs. Akima splines are robust to outliers. + /// Create a hermite cubic spline interpolation from an unsorted set of (x,y) value pairs and their slope (first derivative). + /// WARNING: Works in-place and can thus causes the data array to be reordered. /// - /// - /// The value pairs do not have to be sorted, but if they are not sorted ascendingly - /// and the passed x and y arguments are arrays, they will be sorted inplace and thus modified. - /// - public static CubicSpline InterpolateAkima(IEnumerable x, IEnumerable y) + public static CubicSpline InterpolateHermiteInplace(double[] x, double[] y, double[] firstDerivatives) { - var xx = (x as double[]) ?? x.ToArray(); - var yy = (y as double[]) ?? y.ToArray(); - - if (xx.Length != yy.Length) + if (x.Length != y.Length || x.Length != firstDerivatives.Length) { throw new ArgumentException(Resources.ArgumentVectorsSameLength); } - if (xx.Length < 5) + if (x.Length < 2) { throw new ArgumentOutOfRangeException("x"); } - Sorting.Sort(xx, yy); + Sorting.Sort(x, y, firstDerivatives); + return InterpolateHermiteSorted(x, y, firstDerivatives); + } + + /// + /// Create a hermite cubic spline interpolation from an unsorted set of (x,y) value pairs and their slope (first derivative). + /// + public static CubicSpline InterpolateHermite(IEnumerable x, IEnumerable y, IEnumerable firstDerivatives) + { + // note: we must make a copy, even if the input was arrays already + return InterpolateHermiteInplace(x.ToArray(), y.ToArray(), firstDerivatives.ToArray()); + } + + /// + /// Create an Akima cubic spline interpolation from a set of (x,y) value pairs, sorted ascendingly by x. + /// Akima splines are robust to outliers. + /// + public static CubicSpline InterpolateAkimaSorted(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (x.Length < 5) + { + throw new ArgumentOutOfRangeException("x"); + } /* Prepare divided differences (diff) and weights (w) */ - var diff = new double[xx.Length - 1]; - var weights = new double[xx.Length - 1]; + var diff = new double[x.Length - 1]; + var weights = new double[x.Length - 1]; for (int i = 0; i < diff.Length; i++) { - diff[i] = (yy[i + 1] - yy[i])/(xx[i + 1] - xx[i]); + diff[i] = (y[i + 1] - y[i])/(x[i + 1] - x[i]); } for (int i = 1; i < weights.Length; i++) @@ -151,64 +162,75 @@ namespace MathNet.Numerics.Interpolation /* Prepare Hermite interpolation scheme */ - var dd = new double[xx.Length]; + var dd = new double[x.Length]; for (int i = 2; i < dd.Length - 2; i++) { dd[i] = weights[i - 1].AlmostEqual(0.0) && weights[i + 1].AlmostEqual(0.0) - ? (((xx[i + 1] - xx[i])*diff[i - 1]) + ((xx[i] - xx[i - 1])*diff[i]))/(xx[i + 1] - xx[i - 1]) + ? (((x[i + 1] - x[i])*diff[i - 1]) + ((x[i] - x[i - 1])*diff[i]))/(x[i + 1] - x[i - 1]) : ((weights[i + 1]*diff[i - 1]) + (weights[i - 1]*diff[i]))/(weights[i + 1] + weights[i - 1]); } - dd[0] = DifferentiateThreePoint(xx, yy, 0, 0, 1, 2); - dd[1] = DifferentiateThreePoint(xx, yy, 1, 0, 1, 2); - dd[xx.Length - 2] = DifferentiateThreePoint(xx, yy, xx.Length - 2, xx.Length - 3, xx.Length - 2, xx.Length - 1); - dd[xx.Length - 1] = DifferentiateThreePoint(xx, yy, xx.Length - 1, xx.Length - 3, xx.Length - 2, xx.Length - 1); + dd[0] = DifferentiateThreePoint(x, y, 0, 0, 1, 2); + dd[1] = DifferentiateThreePoint(x, y, 1, 0, 1, 2); + dd[x.Length - 2] = DifferentiateThreePoint(x, y, x.Length - 2, x.Length - 3, x.Length - 2, x.Length - 1); + dd[x.Length - 1] = DifferentiateThreePoint(x, y, x.Length - 1, x.Length - 3, x.Length - 2, x.Length - 1); /* Build Akima spline using Hermite interpolation scheme */ - return InterpolateHermite(xx, yy, dd); + return InterpolateHermiteSorted(x, y, dd); } /// - /// Create a natural cubic spline interpolation from a set of (x,y) value pairs and zero second derivatives at the two boundaries. + /// Create an Akima cubic spline interpolation from an unsorted set of (x,y) value pairs. + /// Akima splines are robust to outliers. + /// WARNING: Works in-place and can thus causes the data array to be reordered. /// - /// - /// The value pairs do not have to be sorted, but if they are not sorted ascendingly - /// and the passed x and y arguments are arrays, they will be sorted inplace and thus modified. - /// - public static CubicSpline InterpolateNatural(IEnumerable x, IEnumerable y) + public static CubicSpline InterpolateAkimaInplace(double[] x, double[] y) { - return InterpolateBoundaries(x, y, SplineBoundaryCondition.SecondDerivative, 0.0, SplineBoundaryCondition.SecondDerivative, 0.0); + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (x.Length < 5) + { + throw new ArgumentOutOfRangeException("x"); + } + + Sorting.Sort(x, y); + return InterpolateAkimaSorted(x, y); } /// - /// Create a cubic spline interpolation from a set of (x,y) value pairs and custom boundary/termination conditions. + /// Create an Akima cubic spline interpolation from an unsorted set of (x,y) value pairs. + /// Akima splines are robust to outliers. /// - /// - /// The value pairs do not have to be sorted, but if they are not sorted ascendingly - /// and the passed x and y arguments are arrays, they will be sorted inplace and thus modified. - /// - public static CubicSpline InterpolateBoundaries(IEnumerable x, IEnumerable y, + public static CubicSpline InterpolateAkima(IEnumerable x, IEnumerable y) + { + // note: we must make a copy, even if the input was arrays already + return InterpolateAkimaInplace(x.ToArray(), y.ToArray()); + } + + /// + /// Create a cubic spline interpolation from a set of (x,y) value pairs, sorted ascendingly by x, + /// and custom boundary/termination conditions. + /// + public static CubicSpline InterpolateBoundariesSorted(double[] x, double[] y, SplineBoundaryCondition leftBoundaryCondition, double leftBoundary, SplineBoundaryCondition rightBoundaryCondition, double rightBoundary) { - var xx = (x as double[]) ?? x.ToArray(); - var yy = (y as double[]) ?? y.ToArray(); - - if (xx.Length != yy.Length) + if (x.Length != y.Length) { throw new ArgumentException(Resources.ArgumentVectorsSameLength); } - if (xx.Length < 2) + if (x.Length < 2) { throw new ArgumentOutOfRangeException("x"); } - Sorting.Sort(xx, yy); - - int n = xx.Length; + int n = x.Length; // normalize special cases if ((n == 2) @@ -245,7 +267,7 @@ namespace MathNet.Numerics.Interpolation a1[0] = 0; a2[0] = 1; a3[0] = 1; - b[0] = 2*(yy[1] - yy[0])/(xx[1] - xx[0]); + b[0] = 2*(y[1] - y[0])/(x[1] - x[0]); break; case SplineBoundaryCondition.FirstDerivative: a1[0] = 0; @@ -257,19 +279,19 @@ namespace MathNet.Numerics.Interpolation a1[0] = 0; a2[0] = 2; a3[0] = 1; - b[0] = (3*((yy[1] - yy[0])/(xx[1] - xx[0]))) - (0.5*leftBoundary*(xx[1] - xx[0])); + b[0] = (3*((y[1] - y[0])/(x[1] - x[0]))) - (0.5*leftBoundary*(x[1] - x[0])); break; default: throw new NotSupportedException(Resources.InvalidLeftBoundaryCondition); } // Central Conditions - for (int i = 1; i < xx.Length - 1; i++) + for (int i = 1; i < x.Length - 1; i++) { - a1[i] = xx[i + 1] - xx[i]; - a2[i] = 2*(xx[i + 1] - xx[i - 1]); - a3[i] = xx[i] - xx[i - 1]; - b[i] = (3*(yy[i] - yy[i - 1])/(xx[i] - xx[i - 1])*(xx[i + 1] - xx[i])) + (3*(yy[i + 1] - yy[i])/(xx[i + 1] - xx[i])*(xx[i] - xx[i - 1])); + a1[i] = x[i + 1] - x[i]; + a2[i] = 2*(x[i + 1] - x[i - 1]); + a3[i] = x[i] - x[i - 1]; + b[i] = (3*(y[i] - y[i - 1])/(x[i] - x[i - 1])*(x[i + 1] - x[i])) + (3*(y[i + 1] - y[i])/(x[i + 1] - x[i])*(x[i] - x[i - 1])); } // Right Boundary @@ -279,7 +301,7 @@ namespace MathNet.Numerics.Interpolation a1[n - 1] = 1; a2[n - 1] = 1; a3[n - 1] = 0; - b[n - 1] = 2*(yy[n - 1] - yy[n - 2])/(xx[n - 1] - xx[n - 2]); + b[n - 1] = 2*(y[n - 1] - y[n - 2])/(x[n - 1] - x[n - 2]); break; case SplineBoundaryCondition.FirstDerivative: a1[n - 1] = 0; @@ -291,7 +313,7 @@ namespace MathNet.Numerics.Interpolation a1[n - 1] = 1; a2[n - 1] = 2; a3[n - 1] = 0; - b[n - 1] = (3*(yy[n - 1] - yy[n - 2])/(xx[n - 1] - xx[n - 2])) + (0.5*rightBoundary*(xx[n - 1] - xx[n - 2])); + b[n - 1] = (3*(y[n - 1] - y[n - 2])/(x[n - 1] - x[n - 2])) + (0.5*rightBoundary*(x[n - 1] - x[n - 2])); break; default: throw new NotSupportedException(Resources.InvalidRightBoundaryCondition); @@ -299,7 +321,68 @@ namespace MathNet.Numerics.Interpolation // Build Spline double[] dd = SolveTridiagonal(a1, a2, a3, b); - return InterpolateHermite(xx, yy, dd); + return InterpolateHermiteSorted(x, y, dd); + } + + /// + /// Create a cubic spline interpolation from an unsorted set of (x,y) value pairs and custom boundary/termination conditions. + /// WARNING: Works in-place and can thus causes the data array to be reordered. + /// + public static CubicSpline InterpolateBoundariesInplace(double[] x, double[] y, + SplineBoundaryCondition leftBoundaryCondition, double leftBoundary, + SplineBoundaryCondition rightBoundaryCondition, double rightBoundary) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (x.Length < 2) + { + throw new ArgumentOutOfRangeException("x"); + } + + Sorting.Sort(x, y); + return InterpolateBoundariesSorted(x, y, leftBoundaryCondition, leftBoundary, rightBoundaryCondition, rightBoundary); + } + + /// + /// Create a cubic spline interpolation from an unsorted set of (x,y) value pairs and custom boundary/termination conditions. + /// + public static CubicSpline InterpolateBoundaries(IEnumerable x, IEnumerable y, + SplineBoundaryCondition leftBoundaryCondition, double leftBoundary, + SplineBoundaryCondition rightBoundaryCondition, double rightBoundary) + { + // note: we must make a copy, even if the input was arrays already + return InterpolateBoundariesInplace(x.ToArray(), y.ToArray(), leftBoundaryCondition, leftBoundary, rightBoundaryCondition, rightBoundary); + } + + /// + /// Create a natural cubic spline interpolation from a set of (x,y) value pairs + /// and zero second derivatives at the two boundaries, sorted ascendingly by x. + /// + public static CubicSpline InterpolateNaturalSorted(double[] x, double[] y) + { + return InterpolateBoundariesSorted(x, y, SplineBoundaryCondition.SecondDerivative, 0.0, SplineBoundaryCondition.SecondDerivative, 0.0); + } + + /// + /// Create a natural cubic spline interpolation from an unsorted set of (x,y) value pairs + /// and zero second derivatives at the two boundaries. + /// WARNING: Works in-place and can thus causes the data array to be reordered. + /// + public static CubicSpline InterpolateNaturalInplace(double[] x, double[] y) + { + return InterpolateBoundariesInplace(x, y, SplineBoundaryCondition.SecondDerivative, 0.0, SplineBoundaryCondition.SecondDerivative, 0.0); + } + + /// + /// Create a natural cubic spline interpolation from an unsorted set of (x,y) value pairs + /// and zero second derivatives at the two boundaries. + /// + public static CubicSpline InterpolateNatural(IEnumerable x, IEnumerable y) + { + return InterpolateBoundaries(x, y, SplineBoundaryCondition.SecondDerivative, 0.0, SplineBoundaryCondition.SecondDerivative, 0.0); } /// diff --git a/src/Numerics/Interpolation/LinearSpline.cs b/src/Numerics/Interpolation/LinearSpline.cs index eaaaf1f5..fb9277f7 100644 --- a/src/Numerics/Interpolation/LinearSpline.cs +++ b/src/Numerics/Interpolation/LinearSpline.cs @@ -4,7 +4,7 @@ // http://github.com/mathnet/mathnet-numerics // http://mathnetnumerics.codeplex.com // -// Copyright (c) 2009-2013 Math.NET +// Copyright (c) 2009-2014 Math.NET // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation @@ -63,31 +63,46 @@ namespace MathNet.Numerics.Interpolation } /// - /// Create a linear spline interpolation from a set of (x,y) value pairs. + /// Create a linear spline interpolation from a set of (x,y) value pairs, sorted ascendingly by x. /// - /// - /// The value pairs do not have to be sorted, but if they are not sorted ascendingly - /// and the passed x and y arguments are arrays, they will be sorted inplace and thus modified. - /// - public static LinearSpline Interpolate(IEnumerable x, IEnumerable y) + public static LinearSpline InterpolateSorted(double[] x, double[] y) { - var xx = (x as double[]) ?? x.ToArray(); - var yy = (y as double[]) ?? y.ToArray(); - - if (xx.Length != yy.Length) + if (x.Length != y.Length) { throw new ArgumentException(Resources.ArgumentVectorsSameLength); } - Sorting.Sort(xx, yy); - - var c1 = new double[xx.Length - 1]; + var c1 = new double[x.Length - 1]; for (int i = 0; i < c1.Length; i++) { - c1[i] = (yy[i + 1] - yy[i])/(xx[i + 1] - xx[i]); + c1[i] = (y[i + 1] - y[i])/(x[i + 1] - x[i]); + } + + return new LinearSpline(x, y, c1); + } + + /// + /// Create a linear spline interpolation from an unsorted set of (x,y) value pairs. + /// WARNING: Works in-place and can thus causes the data array to be reordered. + /// + public static LinearSpline InterpolateInplace(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); } - return new LinearSpline(xx, yy, c1); + Sorting.Sort(x, y); + return InterpolateSorted(x, y); + } + + /// + /// Create a linear spline interpolation from an unsorted set of (x,y) value pairs. + /// + public static LinearSpline Interpolate(IEnumerable x, IEnumerable y) + { + // note: we must make a copy, even if the input was arrays already + return InterpolateInplace(x.ToArray(), y.ToArray()); } /// diff --git a/src/Numerics/Interpolation/NevillePolynomialInterpolation.cs b/src/Numerics/Interpolation/NevillePolynomialInterpolation.cs index baf69009..9f2ad234 100644 --- a/src/Numerics/Interpolation/NevillePolynomialInterpolation.cs +++ b/src/Numerics/Interpolation/NevillePolynomialInterpolation.cs @@ -4,7 +4,7 @@ // http://github.com/mathnet/mathnet-numerics // http://mathnetnumerics.codeplex.com // -// Copyright (c) 2009-2013 Math.NET +// Copyright (c) 2009-2014 Math.NET // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation @@ -53,38 +53,62 @@ namespace MathNet.Numerics.Interpolation readonly double[] _x; readonly double[] _y; - /// - /// Initializes a new instance of the NevillePolynomialInterpolation class. - /// - /// Sample Points t. Optimized for arrays. - /// Sample Values x(t). Optimized for arrays. - public NevillePolynomialInterpolation(IEnumerable x, IEnumerable y) + /// Sample Points t, sorted ascendingly. + /// Sample Values x(t), sorted ascendingly by x. + public NevillePolynomialInterpolation(double[] x, double[] y) { - var xx = (x as double[]) ?? x.ToArray(); - var yy = (y as double[]) ?? y.ToArray(); - - if (xx.Length != yy.Length) + if (x.Length != y.Length) { throw new ArgumentException(Resources.ArgumentVectorsSameLength); } - if (xx.Length < 1) + if (x.Length < 1) { throw new ArgumentOutOfRangeException("x"); } - Sorting.Sort(xx, yy); - - for (var i = 1; i < xx.Length; ++i) + for (var i = 1; i < x.Length; ++i) { - if (xx[i] == xx[i - 1]) + if (x[i] == x[i - 1]) { throw new ArgumentException(Resources.Interpolation_Initialize_SamplePointsNotUnique, "x"); } } - _x = xx; - _y = yy; + _x = x; + _y = y; + } + + /// + /// Create a Neville polynomial interpolation from a set of (x,y) value pairs, sorted ascendingly by x. + /// + public static NevillePolynomialInterpolation InterpolateSorted(double[] x, double[] y) + { + return new NevillePolynomialInterpolation(x, y); + } + + /// + /// Create a Neville polynomial interpolation from an unsorted set of (x,y) value pairs. + /// WARNING: Works in-place and can thus causes the data array to be reordered. + /// + public static NevillePolynomialInterpolation InterpolateInplace(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + Sorting.Sort(x, y); + return InterpolateSorted(x, y); + } + + /// + /// Create a Neville polynomial interpolation from an unsorted set of (x,y) value pairs. + /// + public static NevillePolynomialInterpolation Interpolate(IEnumerable x, IEnumerable y) + { + // note: we must make a copy, even if the input was arrays already + return InterpolateInplace(x.ToArray(), y.ToArray()); } /// diff --git a/src/UnitTests/InterpolationTests/BulirschStoerRationalTest.cs b/src/UnitTests/InterpolationTests/BulirschStoerRationalTest.cs index 3db86cd6..2370a46e 100644 --- a/src/UnitTests/InterpolationTests/BulirschStoerRationalTest.cs +++ b/src/UnitTests/InterpolationTests/BulirschStoerRationalTest.cs @@ -55,7 +55,7 @@ namespace MathNet.Numerics.UnitTests.InterpolationTests [Test] public void FitsAtSamplePoints() { - IInterpolation interpolation = new BulirschStoerRationalInterpolation(_t, _x); + IInterpolation interpolation = BulirschStoerRationalInterpolation.Interpolate(_t, _x); for (int i = 0; i < _x.Length; i++) { @@ -87,7 +87,7 @@ namespace MathNet.Numerics.UnitTests.InterpolationTests [TestCase(-10.0, -3.6017584308603731307, 1e-13)] public void FitsAtArbitraryPointsWithMaple(double t, double x, double maxAbsoluteError) { - IInterpolation interpolation = new BulirschStoerRationalInterpolation(_t, _x); + IInterpolation interpolation = BulirschStoerRationalInterpolation.Interpolate(_t, _x); Assert.AreEqual(x, interpolation.Interpolate(t), maxAbsoluteError, "Interpolation at {0}", t); } diff --git a/src/UnitTests/InterpolationTests/CubicSplineTest.cs b/src/UnitTests/InterpolationTests/CubicSplineTest.cs index eba63f64..b12dbd2a 100644 --- a/src/UnitTests/InterpolationTests/CubicSplineTest.cs +++ b/src/UnitTests/InterpolationTests/CubicSplineTest.cs @@ -28,6 +28,9 @@ // OTHER DEALINGS IN THE SOFTWARE. // +using System; +using System.Linq; +using System.Threading.Tasks; using MathNet.Numerics.Interpolation; using NUnit.Framework; @@ -173,5 +176,25 @@ namespace MathNet.Numerics.UnitTests.InterpolationTests Assert.AreEqual(ytest[i], it.Interpolate(xtest[i]), 1e-15, "Linear with {0} samples, sample {1}", samples, i); } } + + [Test] + public void InterpolateAkimaSorted_MustBeThreadSafe_GitHub219([Values(8, 32, 256, 1024)] int samples) + { + var x = Generate.LinearSpaced(samples + 1, 0.0, 2.0*Math.PI); + var y = new double[samples][]; + for (var i = 0; i < samples; ++i) + { + y[i] = x.Select(xx => Math.Sin(xx)/(i + 1)).ToArray(); + } + + var yipol = new double[samples]; + Parallel.For(0, samples, i => + { + var spline = CubicSpline.InterpolateAkimaSorted(x, y[i]); + yipol[i] = spline.Interpolate(1.0); + }); + + CollectionAssert.DoesNotContain(yipol, Double.NaN); + } } }