//
// 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;
}
}
}