// // Math.NET Numerics, part of the Math.NET Project // http://numerics.mathdotnet.com // http://github.com/mathnet/mathnet-numerics // // Copyright (c) 2009-2020 Math.NET // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // using System; using IStation.Numerics.LinearAlgebra; using IStation.Numerics.LinearAlgebra.Double; namespace IStation.Numerics.RootFinding { /// /// Algorithm by Broyden. /// Implementation inspired by Press, Teukolsky, Vetterling, and Flannery, "Numerical Recipes in C", 2nd edition, Cambridge University Press /// public static class Broyden { /// Find a solution of the equation f(x)=0. /// The function to find roots from. /// Initial guess of the root. /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Default 1e-8. Must be greater than 0. /// Maximum number of iterations. Default 100. /// Relative step size for calculating the Jacobian matrix at first step. Default 1.0e-4 /// Returns the root with the specified accuracy. /// public static double[] FindRoot(Func f, double[] initialGuess, double accuracy = 1e-8, int maxIterations = 100, double jacobianStepSize = 1.0e-4) { double[] root; if (TryFindRootWithJacobianStep(f, initialGuess, accuracy, maxIterations, jacobianStepSize, out root)) { return root; } throw new NonConvergenceException("The algorithm has failed, exceeded the number of iterations allowed or there is no root within the provided bounds."); } /// Find a solution of the equation f(x)=0. /// The function to find roots from. /// Initial guess of the root. /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Must be greater than 0. /// Maximum number of iterations. Usually 100. /// Relative step size for calculating the Jacobian matrix at first step. /// The root that was found, if any. Undefined if the function returns false. /// True if a root with the specified accuracy was found, else false. public static bool TryFindRootWithJacobianStep(Func f, double[] initialGuess, double accuracy, int maxIterations, double jacobianStepSize, out double[] root) { if (accuracy <= 0) { throw new ArgumentOutOfRangeException(nameof(accuracy), "Must be greater than zero."); } var x = new DenseVector(initialGuess); double[] y0 = f(initialGuess); var y = new DenseVector(y0); double g = y.L2Norm(); Matrix B = CalculateApproximateJacobian(f, initialGuess, y0, jacobianStepSize); try { for (int i = 0; i <= maxIterations; i++) { var dx = (DenseVector) (-B.LU().Solve(y)); var xnew = x + dx; var ynew = new DenseVector(f(xnew.Values)); double gnew = ynew.L2Norm(); if (gnew > g) { double g2 = g*g; double scale = g2/(g2 + gnew*gnew); if (scale == 0.0) { scale = 1.0e-4; } dx = scale*dx; xnew = x + dx; ynew = new DenseVector(f(xnew.Values)); gnew = ynew.L2Norm(); } if (gnew < accuracy) { root = xnew.Values; return true; } // update Jacobian B DenseVector dF = ynew - y; Matrix dB = (dF - B.Multiply(dx)).ToColumnMatrix() * dx.Multiply(1.0 / Math.Pow(dx.L2Norm(), 2)).ToRowMatrix(); B = B + dB; x = xnew; y = ynew; g = gnew; } } catch (InvalidParameterException) { root = null; return false; } root = null; return false; } /// Find a solution of the equation f(x)=0. /// The function to find roots from. /// Initial guess of the root. /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Must be greater than 0. /// Maximum number of iterations. Usually 100. /// The root that was found, if any. Undefined if the function returns false. /// True if a root with the specified accuracy was found, else false. public static bool TryFindRoot(Func f, double[] initialGuess, double accuracy, int maxIterations, out double[] root) { return TryFindRootWithJacobianStep(f, initialGuess, accuracy, maxIterations, 1.0e-4, out root); } /// /// Helper method to calculate an approximation of the Jacobian. /// /// The function. /// The argument (initial guess). /// The result (of initial guess). /// Relative step size for calculating the Jacobian. static Matrix CalculateApproximateJacobian(Func f, double[] x0, double[] y0, double jacobianStepSize) { int dim = x0.Length; var B = new DenseMatrix(dim); var x = new double[dim]; Array.Copy(x0, 0, x, 0, dim); for (int j = 0; j < dim; j++) { double h = (1.0+Math.Abs(x0[j]))*jacobianStepSize; var xj = x[j]; x[j] = xj + h; double[] y = f(x); x[j] = xj; for (int i = 0; i < dim; i++) { B.At(i, j, (y[i] - y0[i])/h); } } return B; } } }