// // 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; namespace IStation.Numerics.RootFinding { /// /// Robust Newton-Raphson root-finding algorithm that falls back to bisection when overshooting or converging too slow, or to subdivision on lacking bracketing. /// /// public static class RobustNewtonRaphson { /// Find a solution of the equation f(x)=0. /// The function to find roots from. /// The first derivative of the function to find roots from. /// The low value of the range where the root is supposed to be. /// The high value of the range where the root is supposed to be. /// 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. /// How many parts an interval should be split into for zero crossing scanning in case of lacking bracketing. Default 20. /// Returns the root with the specified accuracy. /// public static double FindRoot(Func f, Func df, double lowerBound, double upperBound, double accuracy = 1e-8, int maxIterations = 100, int subdivision = 20) { double root; if (TryFindRoot(f, df, lowerBound, upperBound, accuracy, maxIterations, subdivision, 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. /// The first derivative of the function to find roots from. /// The low value of the range where the root is supposed to be. /// The high value of the range where the root is supposed to be. /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Example: 1e-14. Must be greater than 0. /// Maximum number of iterations. Example: 100. /// How many parts an interval should be split into for zero crossing scanning in case of lacking bracketing. Example: 20. /// 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, Func df, double lowerBound, double upperBound, double accuracy, int maxIterations, int subdivision, out double root) { if (accuracy <= 0) { throw new ArgumentOutOfRangeException(nameof(accuracy), "Must be greater than zero."); } double fmin = f(lowerBound); double fmax = f(upperBound); if (Math.Abs(fmin) < accuracy) { root = lowerBound; return true; } if (Math.Abs(fmax) < accuracy) { root = upperBound; return true; } root = 0.5*(lowerBound + upperBound); double fx = f(root); double lastStep = Math.Abs(upperBound - lowerBound); for (int i = 0; i < maxIterations; i++) { double dfx = df(root); // Netwon-Raphson step double step = fx/dfx; root -= step; if (Math.Abs(step) < accuracy && Math.Abs(fx) < accuracy) { return true; } bool overshoot = root > upperBound, undershoot = root < lowerBound; if (overshoot || undershoot || Math.Abs(2*fx) > Math.Abs(lastStep*dfx)) { // Newton-Raphson step failed // If same signs, try subdivision to scan for zero crossing intervals if (Math.Sign(fmin) == Math.Sign(fmax) && TryScanForCrossingsWithRoots(f, df, lowerBound, upperBound, accuracy, maxIterations - i - 1, subdivision, out root)) { return true; } // Bisection root = 0.5*(upperBound + lowerBound); fx = f(root); lastStep = 0.5*Math.Abs(upperBound - lowerBound); if (Math.Sign(fx) == Math.Sign(fmin)) { lowerBound = root; fmin = fx; if (overshoot) { root = upperBound; fx = fmax; } } else { upperBound = root; fmax = fx; if (undershoot) { root = lowerBound; fx = fmin; } } continue; } // Evaluation fx = f(root); lastStep = step; // Update bounds if (Math.Sign(fx) != Math.Sign(fmin)) { upperBound = root; fmax = fx; } else if (Math.Sign(fx) != Math.Sign(fmax)) { lowerBound = root; fmin = fx; } else if (Math.Sign(fmin) != Math.Sign(fmax) && Math.Abs(fx) < accuracy) { return true; } } return false; } static bool TryScanForCrossingsWithRoots(Func f, Func df, double lowerBound, double upperBound, double accuracy, int maxIterations, int subdivision, out double root) { var zeroCrossings = ZeroCrossingBracketing.FindIntervalsWithin(f, lowerBound, upperBound, subdivision); foreach (Tuple bounds in zeroCrossings) { if (TryFindRoot(f, df, bounds.Item1, bounds.Item2, accuracy, maxIterations, subdivision, out root)) { return true; } } root = double.NaN; return false; } } }