public void FindExtremaNegativeGamma() { // https://en.wikipedia.org/wiki/Particular_values_of_the_Gamma_function#Other_constants Tuple <double, double>[] extrema = new Tuple <double, double>[] { Tuple.Create(-0.5040830082644554092582693045, -3.5446436111550050891219639933), Tuple.Create(-1.5734984731623904587782860437, 2.3024072583396801358235820396), Tuple.Create(-2.6107208684441446500015377157, -0.8881363584012419200955280294), Tuple.Create(-3.6352933664369010978391815669, 0.2451275398343662504382300889) }; foreach (Tuple <double, double> extremum in extrema) { // We use brackets since we know the extremum must lie between the singularities. // We should be able to use the actual singularities at endpoints, but this doesn't work; look into it. Interval bracket = Interval.FromEndpoints(Math.Floor(extremum.Item1) + 0.01, Math.Ceiling(extremum.Item1) - 0.01); Extremum result; if (extremum.Item2 < 0.0) { result = FunctionMath.FindMaximum(AdvancedMath.Gamma, bracket); } else { result = FunctionMath.FindMinimum(AdvancedMath.Gamma, bracket); } Assert.IsTrue(result.Bracket.OpenContains(extremum.Item1)); Assert.IsTrue(result.Bracket.OpenContains(result.Location)); Assert.IsTrue(TestUtilities.IsNearlyEqual(result.Value, extremum.Item2)); } }
public void FindMinimaFromPoint() { foreach (TestExtremum testExtremum in testMinima) { Extremum extremum = FunctionMath.FindMinimum(testExtremum.Function, testExtremum.Interval.Midpoint); Assert.IsTrue(TestUtilities.IsNearlyEqual(extremum.Location, testExtremum.Location, Math.Sqrt(TestUtilities.TargetPrecision))); Assert.IsTrue(TestUtilities.IsNearlyEqual(extremum.Value, testExtremum.Value, TestUtilities.TargetPrecision)); //if (!Double.IsNaN(testMinimum.Curvature)) Assert.IsTrue(TestUtilities.IsNearlyEqual(minimum.Curvature, testMinimum.Curvature, 0.05)); } }
public void FindMinimumOfGamma() { int count = 0; ExtremumSettings settings = new ExtremumSettings() { Listener = r => { count++; } }; Func <double, double> f = new Func <double, double>(AdvancedMath.Gamma); Extremum minimum = FunctionMath.FindMinimum(f, 1.5, settings); Assert.IsTrue(TestUtilities.IsNearlyEqual(minimum.Location, 1.46163214496836234126, Math.Sqrt(TestUtilities.TargetPrecision))); Assert.IsTrue(TestUtilities.IsNearlyEqual(minimum.Value, 0.88560319441088870028)); //Assert.IsTrue(TestUtilities.IsNearlyEqual(minimum.Curvature, Math.Sqrt(Math.Sqrt(0.8569736317111708)))); Assert.IsTrue(count > 0); }
/// <summary> /// Fits the data to an arbitrary parameterized function. /// </summary> /// <param name="function">The fit function.</param> /// <param name="start">An initial guess at the parameters.</param> /// <returns>A fit result containing the best-fitting function parameters /// and a χ<sup>2</sup> test of the quality of the fit.</returns> /// <exception cref="ArgumentNullException"><paramref name="function"/> or <paramref name="start"/> are <see langword="null"/>.</exception> /// <exception cref="InsufficientDataException">There are fewer data points than fit parameters.</exception> /// <exception cref="DivideByZeroException">The curvature matrix is singular, indicating that the data is independent of /// one or more parameters, or that two or more parameters are linearly dependent.</exception> public FitResult FitToFunction(Func <double[], T, double> function, double[] start) { if (function == null) { throw new ArgumentNullException(nameof(function)); } if (start == null) { throw new ArgumentNullException(nameof(start)); } // you can't do a fit with less data than parameters if (this.Count < start.Length) { throw new InsufficientDataException(); } /* * Func<IList<double>, double> function0 = (IList<double> x0) => { * double[] x = new double[x0.Count]; * x0.CopyTo(x, 0); * return(function(x)); * }; * MultiExtremum minimum0 = MultiFunctionMath.FindMinimum(function0, start); */ // create a chi^2 fit metric and minimize it FitMetric <T> metric = new FitMetric <T>(this, function); SpaceExtremum minimum = FunctionMath.FindMinimum(new Func <double[], double>(metric.Evaluate), start); // compute the covariance (Hessian) matrix by inverting the curvature matrix SymmetricMatrix A = 0.5 * minimum.Curvature(); CholeskyDecomposition CD = A.CholeskyDecomposition(); // should not return null if we were at a minimum if (CD == null) { throw new DivideByZeroException(); } SymmetricMatrix C = CD.Inverse(); // package up the results and return them TestResult test = new TestResult("ChiSquare", minimum.Value, TestType.RightTailed, new ChiSquaredDistribution(this.Count - minimum.Dimension)); FitResult fit = new FitResult(minimum.Location(), C, test); return(fit); }
/// <summary> /// Fits the data to an arbitrary parameterized function. /// </summary> /// <param name="function">The fit function.</param> /// <param name="start">An initial guess at the parameters.</param> /// <returns>A fit result containing the best-fitting function parameters /// and a χ<sup>2</sup> test of the quality of the fit.</returns> /// <exception cref="ArgumentNullException"><paramref name="function"/> or <paramref name="start"/> are <see langword="null"/>.</exception> /// <exception cref="InsufficientDataException">There are fewer data points than fit parameters.</exception> /// <exception cref="DivideByZeroException">The curvature matrix is singular, indicating that the data is independent of /// one or more parameters, or that two or more parameters are linearly dependent.</exception> public UncertainMeasurementFitResult FitToFunction(Func <double[], T, double> function, double[] start) { if (function == null) { throw new ArgumentNullException(nameof(function)); } if (start == null) { throw new ArgumentNullException(nameof(start)); } // you can't do a fit with less data than parameters if (this.Count < start.Length) { throw new InsufficientDataException(); } // create a chi^2 fit metric and minimize it FitMetric <T> metric = new FitMetric <T>(this, function); SpaceExtremum minimum = FunctionMath.FindMinimum(new Func <double[], double>(metric.Evaluate), start); // compute the covariance (Hessian) matrix by inverting the curvature matrix SymmetricMatrix A = 0.5 * minimum.Curvature(); CholeskyDecomposition CD = A.CholeskyDecomposition(); // should not return null if we were at a minimum if (CD == null) { throw new DivideByZeroException(); } SymmetricMatrix C = CD.Inverse(); // package up the results and return them TestResult test = new TestResult("χ²", minimum.Value, new ChiSquaredDistribution(this.Count - minimum.Dimension), TestType.RightTailed); ParameterCollection parameters = new ParameterCollection(NumberNames(start.Length), new ColumnVector(minimum.Location(), 0, 1, start.Length, true), C); return(new UncertainMeasurementFitResult(parameters, test)); }
/// <summary> /// Fits an MA(1) model to the time series. /// </summary> /// <param name="series">The data series.</param> /// <returns>The fit, with parameters lag-1 coefficient, mean, and standard deviation.</returns> public static MA1FitResult FitToMA1(this IReadOnlyList <double> series) { if (series == null) { throw new ArgumentNullException(nameof(series)); } if (series.Count < 4) { throw new InsufficientDataException(); } // MA(1) model is // y_t - \mu = u_t + \beta u_{t-1} // where u_t ~ N(0, \sigma) are IID. // It's easy to show that // m = E(y_t) = \mu // c_0 = V(y_t) = E((y_t - m)^2) = (1 + \beta^2) \sigma^2 // c_1 = V(y_t, y_{t-1}) = \beta \sigma^2 // So the method of moments gives // \beta = \frac{1 - \sqrt{1 - (2 g_1)^2}}{2} // \mu = m // \sigma^2 = \frac{c_0}{1 + \beta^2} // It turns out these are very poor (high bias, high variance) estimators, // but they do illustrate the basic requirement that g_1 < 1/2. // The MLE estimator int n = series.Count; double m = series.Mean(); Func <double, double> fnc = (double theta) => { double s = 0.0; double uPrevious = 0.0; for (int i = 0; i < series.Count; i++) { double u = series[i] - m - theta * uPrevious; s += u * u; uPrevious = u; } ; return(s); }; Extremum minimum = FunctionMath.FindMinimum(fnc, Interval.FromEndpoints(-1.0, 1.0)); double beta = minimum.Location; double sigma2 = minimum.Value / (n - 3); // While there is significant evidence that the MLE value for \beta is biased // for small-n, I know of no analytic correction. //double[] parameters = new double[] { beta, m, Math.Sqrt(sigma2) }; // The calculation of the variance for \mu can be improved over the MLE // result by plugging the values for \gamma_0 and \gamma_1 into the // exact formula. double varBeta; if (minimum.Curvature > 0.0) { varBeta = sigma2 / minimum.Curvature; } else { varBeta = MoreMath.Sqr(1.0 - beta * beta) / n; } double varMu = sigma2 * (MoreMath.Sqr(1.0 + beta) - 2.0 * beta / n) / n; double varSigma = sigma2 / 2.0 / n; List <double> residuals = new List <double>(n); double u1 = 0.0; for (int i = 0; i < n; i++) { double u0 = series[i] - m - beta * u1; residuals.Add(u0); u1 = u0; } ; return(new MA1FitResult( new UncertainValue(m, Math.Sqrt(varMu)), new UncertainValue(beta, Math.Sqrt(varBeta)), new UncertainValue(Math.Sqrt(sigma2), Math.Sqrt(varSigma)), residuals )); }
/// <summary> /// Fits an MA(1) model to the time series. /// </summary> /// <returns>The fit with parameters lag-1 coefficient, mean, and standard deviation.</returns> public FitResult FitToMA1() { if (data.Count < 4) { throw new InsufficientDataException(); } // MA(1) model is // y_t - \mu = u_t + \beta u_{t-1} // where u_t ~ N(0, \sigma) are IID. // It's easy to show that // m = E(y_t) = \mu // c_0 = V(y_t) = E((y_t - m)^2) = (1 + \beta^2) \sigma^2 // c_1 = V(y_t, y_{t-1}) = \beta \sigma^2 // So the method of moments gives // \beta = \frac{1 - \sqrt{1 - (2 g_1)^2}}{2} // \mu = m // \sigma^2 = \frac{c_0}{1 + \beta^2} // It turns out these are very poor (high bias, high variance) estimators, // but they illustrate a basic requirement that g_1 < 1/2. // The MLE estimator int n = data.Count; double m = data.Mean; Func <double, double> fnc = (double theta) => { double s = 0.0; double uPrevious = 0.0; for (int i = 0; i < data.Count; i++) { double u = data[i] - m - theta * uPrevious; s += u * u; uPrevious = u; } ; return(s); }; Extremum minimum = FunctionMath.FindMinimum(fnc, Interval.FromEndpoints(-1.0, 1.0)); double beta = minimum.Location; double sigma2 = minimum.Value / (data.Count - 3); // While there is significant evidence that the MLE value for \beta is biased // for small-n, I know of no anlytic correction. double[] parameters = new double[] { beta, m, Math.Sqrt(sigma2) }; // The calculation of the variance for \mu can be improved over the MLE // result by plugging the values for \gamma_0 and \gamma_1 into the // exact formula. SymmetricMatrix covariances = new SymmetricMatrix(3); if (minimum.Curvature > 0.0) { covariances[0, 0] = sigma2 / minimum.Curvature; } else { covariances[0, 0] = MoreMath.Sqr(1.0 - beta * beta) / n; } covariances[1, 1] = sigma2 * (MoreMath.Sqr(1.0 + beta) - 2.0 * beta / n) / n; covariances[2, 2] = sigma2 / 2.0 / n; TimeSeries residuals = new TimeSeries(); double u1 = 0.0; for (int i = 0; i < data.Count; i++) { double u0 = data[i] - m - beta * u1; residuals.Add(u0); u1 = u0; } ; TestResult test = residuals.LjungBoxTest(); FitResult result = new FitResult( parameters, covariances, test ); return(result); }
public static void Optimization() { Interval range = Interval.FromEndpoints(0.0, 6.28); Extremum min = FunctionMath.FindMinimum(x => Math.Sin(x), range); Console.WriteLine($"Found minimum of {min.Value} at {min.Location}."); Console.WriteLine($"Required {min.EvaluationCount} evaluations."); MultiExtremum rosenbrock = MultiFunctionMath.FindLocalMinimum( x => MoreMath.Sqr(2.0 - x[0]) + 100.0 * MoreMath.Sqr(x[1] - x[0] * x[0]), new ColumnVector(0.0, 0.0) ); ColumnVector xm = rosenbrock.Location; Console.WriteLine($"Found minimum of {rosenbrock.Value} at ({xm[0]},{xm[1]})."); Console.WriteLine($"Required {rosenbrock.EvaluationCount} evaluations."); Func <IReadOnlyList <double>, double> leviFunction = z => { double x = z[0]; double y = z[1]; return( MoreMath.Sqr(MoreMath.Sin(3.0 * Math.PI * x)) + MoreMath.Sqr(x - 1.0) * (1.0 + MoreMath.Sqr(MoreMath.Sin(3.0 * Math.PI * y))) + MoreMath.Sqr(y - 1.0) * (1.0 + MoreMath.Sqr(MoreMath.Sin(2.0 * Math.PI * y))) ); }; Interval[] leviRegion = new Interval[] { Interval.FromEndpoints(-10.0, +10.0), Interval.FromEndpoints(-10.0, +10.0) }; MultiExtremum levi = MultiFunctionMath.FindGlobalMinimum(leviFunction, leviRegion); ColumnVector zm = levi.Location; Console.WriteLine($"Found minimum of {levi.Value} at ({zm[0]},{zm[1]})."); Console.WriteLine($"Required {levi.EvaluationCount} evaluations."); // Select a dimension int n = 6; // Define a function that takes 2n polar coordinates in the form // phi_1, theta_1, phi_2, theta_2, ..., phi_n, theta_n, computes // the sum of the potential energy 1/d for all pairs. Func <IReadOnlyList <double>, double> thompson = (IReadOnlyList <double> v) => { double e = 0.0; // iterate over all distinct pairs of points for (int i = 0; i < n; i++) { for (int j = 0; j < i; j++) { // compute the chord length between points i and j double dx = Math.Cos(v[2 * j]) * Math.Cos(v[2 * j + 1]) - Math.Cos(v[2 * i]) * Math.Cos(v[2 * i + 1]); double dy = Math.Cos(v[2 * j]) * Math.Sin(v[2 * j + 1]) - Math.Cos(v[2 * i]) * Math.Sin(v[2 * i + 1]); double dz = Math.Sin(v[2 * j]) - Math.Sin(v[2 * i]); double d = Math.Sqrt(dx * dx + dy * dy + dz * dz); e += 1.0 / d; } } return(e); }; // Limit all polar coordinates to their standard ranges. Interval[] box = new Interval[2 * n]; for (int i = 0; i < n; i++) { box[2 * i] = Interval.FromEndpoints(-Math.PI, Math.PI); box[2 * i + 1] = Interval.FromEndpoints(-Math.PI / 2.0, Math.PI / 2.0); } // Use settings to monitor proress toward a rough solution. MultiExtremumSettings roughSettings = new MultiExtremumSettings() { RelativePrecision = 0.05, AbsolutePrecision = 0.0, Listener = r => { Console.WriteLine($"Minimum {r.Value} after {r.EvaluationCount} evaluations."); } }; MultiExtremum roughThompson = MultiFunctionMath.FindGlobalMinimum(thompson, box, roughSettings); // Use settings to monitor proress toward a refined solution. MultiExtremumSettings refinedSettings = new MultiExtremumSettings() { RelativePrecision = 1.0E-5, AbsolutePrecision = 0.0, Listener = r => { Console.WriteLine($"Minimum {r.Value} after {r.EvaluationCount} evaluations."); } }; MultiExtremum refinedThompson = MultiFunctionMath.FindLocalMinimum(thompson, roughThompson.Location, refinedSettings); Console.WriteLine($"Minimum potential energy {refinedThompson.Value}."); Console.WriteLine($"Required {roughThompson.EvaluationCount} + {refinedThompson.EvaluationCount} evaluations."); /* * // Define a function that takes 2n coordinates x1, y1, x2, y2, ... xn, yn * // and finds the smallest distance between two coordinate pairs. * Func<IReadOnlyList<double>, double> function = (IReadOnlyList<double> x) => { * double sMin = Double.MaxValue; * for (int i = 0; i < n; i++) { * for (int j = 0; j < i; j++) { * double s = MoreMath.Hypot(x[2 * i] - x[2 * j], x[2 * i + 1] - x[2 * j + 1]); * if (s < sMin) sMin = s; * } * } * return (sMin); * }; * * // Limit all coordinates to the unit box. * Interval[] box = new Interval[2 * n]; * for (int i = 0; i < box.Length; i++) box[i] = Interval.FromEndpoints(0.0, 1.0); * * // Use settings to monitor proress toward a rough solution. * MultiExtremumSettings roughSettings = new MultiExtremumSettings() { * RelativePrecision = 1.0E-2, AbsolutePrecision = 0.0, * Listener = r => { * Console.WriteLine($"Minimum {r.Value} after {r.EvaluationCount} evaluations."); * } * }; * MultiExtremum roughMaximum = MultiFunctionMath.FindGlobalMaximum(function, box, roughSettings); * * // Use settings to monitor proress toward a rough solution. * MultiExtremumSettings refinedSettings = new MultiExtremumSettings() { * RelativePrecision = 1.0E-8, AbsolutePrecision = 0.0, * Listener = r => { * Console.WriteLine($"Minimum {r.Value} after {r.EvaluationCount} evaluations."); * } * }; * MultiExtremum refinedMaximum = MultiFunctionMath.FindLocalMaximum(function, roughMaximum.Location, refinedSettings); */ }