From a88e042a15e895d2496ba1f4fe2eba522c97834e Mon Sep 17 00:00:00 2001 From: Christoph Ruegg Date: Sun, 14 Oct 2018 15:46:58 +0200 Subject: [PATCH] Fit.Curve: non-linear least-squares curve fitting (scalar) #597 --- MathNet.Numerics.sln.DotSettings | 1 + src/Numerics.Tests/FitTests.cs | 19 +++++++++++++++++++ src/Numerics/FindMinimum.cs | 11 +++++++++++ src/Numerics/Fit.cs | 20 ++++++++++++++++++++ 4 files changed, 51 insertions(+) diff --git a/MathNet.Numerics.sln.DotSettings b/MathNet.Numerics.sln.DotSettings index 5bbf624a..14b3d43d 100644 --- a/MathNet.Numerics.sln.DotSettings +++ b/MathNet.Numerics.sln.DotSettings @@ -81,5 +81,6 @@ OTHER DEALINGS IN THE SOFTWARE. True True True + True diff --git a/src/Numerics.Tests/FitTests.cs b/src/Numerics.Tests/FitTests.cs index e4e4eb70..c17c225f 100644 --- a/src/Numerics.Tests/FitTests.cs +++ b/src/Numerics.Tests/FitTests.cs @@ -320,5 +320,24 @@ namespace MathNet.Numerics.UnitTests Assert.AreEqual(4.02159*Math.Sin(z) - 1.46962*Math.Cos(z) - 0.287476, resf(z), 1e-4); } } + + [Test] + public void FitsCurveToBestLineThroughOrigin() + { + // Mathematica: Fit[{{1,4.986},{2,2.347},{3,2.061},{4,-2.995},{5,-2.352},{6,-5.782}}, {x}, x] + // -> -0.467791 x + + var x = Enumerable.Range(1, 6).Select(Convert.ToDouble).ToArray(); + var y = new[] { 4.986, 2.347, 2.061, -2.995, -2.352, -5.782 }; + + var resp = Fit.Curve(x, y, (s,t) => s*t, -1.0); + Assert.AreEqual(-0.467791, resp, 1e-4); + + var resf = Fit.CurveFunc(x, y, (s, t) => s * t, -1.0, 1e-10); + foreach (var z in Enumerable.Range(-3, 10)) + { + Assert.AreEqual(-0.467791 * z, resf(z), 1e-4); + } + } } } diff --git a/src/Numerics/FindMinimum.cs b/src/Numerics/FindMinimum.cs index b37bcfcd..76b02944 100644 --- a/src/Numerics/FindMinimum.cs +++ b/src/Numerics/FindMinimum.cs @@ -46,6 +46,17 @@ namespace MathNet.Numerics return result.MinimizingPoint; } + /// + /// Find vector x that minimizes the function f(x) using the Nelder-Mead Simplex algorithm. + /// For more options and diagnostics consider to use directly. + /// + public static double OfScalarFunction(Func function, double initialGuess, double tolerance = 1e-8, int maxIterations = 1000) + { + var objective = ObjectiveFunction.Value(v => function(v[0])); + var result = NelderMeadSimplex.Minimum(objective, CreateVector.Dense(new[] { initialGuess }), tolerance, maxIterations); + return result.MinimizingPoint[0]; + } + /// /// Find vector x that minimizes the function f(x) using the Nelder-Mead Simplex algorithm. /// For more options and diagnostics consider to use directly. diff --git a/src/Numerics/Fit.cs b/src/Numerics/Fit.cs index 34d8a0cf..8116a5f5 100644 --- a/src/Numerics/Fit.cs +++ b/src/Numerics/Fit.cs @@ -32,6 +32,7 @@ using System.Linq; using MathNet.Numerics.LinearAlgebra; using MathNet.Numerics.LinearRegression; using MathNet.Numerics.Providers.LinearAlgebra; +using MathNet.Numerics.Statistics; namespace MathNet.Numerics { @@ -334,5 +335,24 @@ namespace MathNet.Numerics var parameters = LinearGeneric(x, y, method, functions); return z => functions.Zip(parameters, (f, p) => p * f(z)).Sum(); } + + /// + /// Non-linear least-squares fitting the points (x,y) to an arbitrary function y : x -> f(p, x), + /// returning its best fitting parameter p. + /// + public static double Curve(double[] x, double[] y, Func f, double initialGuess, double tolerance = 1e-8, int maxIterations = 1000) + { + return FindMinimum.OfScalarFunction(p => Distance.Euclidean(Generate.Map(x, t => f(p, t)), y), initialGuess, tolerance, maxIterations); + } + + /// + /// Least-Squares fitting the points (x,y) to a line y : x -> a+b*x, + /// returning a function y' for the best fitting line. + /// + public static Func CurveFunc(double[] x, double[] y, Func f, double initialGuess, double tolerance = 1e-8, int maxIterations = 1000) + { + var parameters = Curve(x, y, f, initialGuess, tolerance, maxIterations); + return z => f(parameters, z); + } } }