com.opengamma.strata.math.impl.rootfinding.NewtonRaphsonSingleRootFinder Maven / Gradle / Ivy
Show all versions of strata-math Show documentation
/*
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.math.impl.rootfinding;
import java.util.function.Function;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.math.MathException;
import com.opengamma.strata.math.impl.function.DoubleFunction1D;
/**
* Class for finding the real root of a function within a range of $x$-values using the one-dimensional version of Newton's method.
*
* For a function $f(x)$, the Taylor series expansion is given by:
* $$
* \begin{align*}
* f(x + \delta) \approx f(x) + f'(x)\delta + \frac{f''(x)}{2}\delta^2 + \cdots
* \end{align*}
* $$
* As delta approaches zero (and if the function is well-behaved), this gives
* $$
* \begin{align*}
* \delta = -\frac{f(x)}{f'(x)}
* \end{align*}
* $$
* when $f(x + \delta) = 0$.
*
* There are several well-known problems with Newton's method, in particular when the range of values given includes a local
* maximum or minimum. In this situation, the next iterative step can shoot off to $\pm\infty$. This implementation
* currently does not attempt to correct for this: if the value of $x$ goes beyond the initial range of values $x_{low}$
* and $x_{high}$, an exception is thrown.
*
* If the function that is provided does not override the {@link com.opengamma.strata.math.impl.function.DoubleFunction1D#derivative()}
* method, then the derivative is approximated using finite difference. This is undesirable for several reasons:
* (i) the extra function evaluations will lead to slower convergence; and
* (ii) the choice of shift size is very important (too small and the result will be dominated by rounding errors, too large
* and convergence will be even slower). Use of another root-finder is recommended in this case.
*/
// CSOFF: JavadocMethod
public class NewtonRaphsonSingleRootFinder extends RealSingleRootFinder {
private static final int MAX_ITER = 10000;
private final double _accuracy;
/**
* Default constructor. Sets accuracy to 1e-12.
*/
public NewtonRaphsonSingleRootFinder() {
this(1e-12);
}
/**
* Takes the accuracy of the root as a parameter - this is the maximum difference
* between the true root and the returned value that is allowed.
* If this is negative, then the absolute value is used.
*
* @param accuracy The accuracy
*/
public NewtonRaphsonSingleRootFinder(double accuracy) {
_accuracy = Math.abs(accuracy);
}
/**
* {@inheritDoc}
* @throws MathException If the root is not found in 1000 attempts; if the Newton
* step takes the estimate for the root outside the original bounds.
*/
@Override
public Double getRoot(Function function, Double x1, Double x2) {
ArgChecker.notNull(function, "function");
return getRoot(DoubleFunction1D.from(function), x1, x2);
}
//-------------------------------------------------------------------------
public Double getRoot(Function function, Double x) {
ArgChecker.notNull(function, "function");
ArgChecker.notNull(x, "x");
DoubleFunction1D f = DoubleFunction1D.from(function);
return getRoot(f, f.derivative(), x);
}
/**
* Uses the {@link DoubleFunction1D#derivative()} method. x1 and
* x2 do not have to be increasing.
*
* @param function The function, not null
* @param x1 The first bound of the root, not null
* @param x2 The second bound of the root, not null
* @return The root
* @throws MathException If the root is not found in 1000 attempts; if the Newton
* step takes the estimate for the root outside the original bounds.
*/
public Double getRoot(DoubleFunction1D function, Double x1, Double x2) {
ArgChecker.notNull(function, "function");
return getRoot(function, function.derivative(), x1, x2);
}
/**
* Uses the {@link DoubleFunction1D#derivative()} method. This method uses an initial
* guess for the root, rather than bounds.
*
* @param function The function, not null
* @param x The initial guess for the root, not null
* @return The root
* @throws MathException If the root is not found in 1000 attempts.
*/
public Double getRoot(DoubleFunction1D function, Double x) {
ArgChecker.notNull(function, "function");
return getRoot(function, function.derivative(), x);
}
/**
* Uses the function and its derivative.
* @param function The function, not null
* @param derivative The derivative, not null
* @param x1 The first bound of the root, not null
* @param x2 The second bound of the root, not null
* @return The root
* @throws MathException If the root is not found in 1000 attempts; if the Newton
* step takes the estimate for the root outside the original bounds.
*/
public Double getRoot(Function function, Function derivative, Double x1, Double x2) {
checkInputs(function, x1, x2);
ArgChecker.notNull(derivative, "derivative");
return getRoot(DoubleFunction1D.from(function), DoubleFunction1D.from(derivative), x1, x2);
}
/**
* Uses the function and its derivative. This method uses an initial guess for the root, rather than bounds.
* @param function The function, not null
* @param derivative The derivative, not null
* @param x The initial guess for the root, not null
* @return The root
* @throws MathException If the root is not found in 1000 attempts.
*/
public Double getRoot(Function function, Function derivative, Double x) {
return getRoot(DoubleFunction1D.from(function), DoubleFunction1D.from(derivative), x);
}
/**
* Uses the function and its derivative.
* @param function The function, not null
* @param derivative The derivative, not null
* @param x1 The first bound of the root, not null
* @param x2 The second bound of the root, not null
* @return The root
* @throws MathException If the root is not found in 1000 attempts; if the Newton
* step takes the estimate for the root outside the original bounds.
*/
public Double getRoot(DoubleFunction1D function, DoubleFunction1D derivative, Double x1, Double x2) {
checkInputs(function, x1, x2);
ArgChecker.notNull(derivative, "derivative function");
double y1 = function.applyAsDouble(x1);
if (Math.abs(y1) < _accuracy) {
return x1;
}
double y2 = function.applyAsDouble(x2);
if (Math.abs(y2) < _accuracy) {
return x2;
}
double x = (x1 + x2) / 2;
double x3 = y2 < 0 ? x2 : x1;
double x4 = y2 < 0 ? x1 : x2;
double xLower = x1 > x2 ? x2 : x1;
double xUpper = x1 > x2 ? x1 : x2;
for (int i = 0; i < MAX_ITER; i++) {
double y = function.applyAsDouble(x);
double dy = derivative.applyAsDouble(x);
double dx = -y / dy;
if (Math.abs(dx) <= _accuracy) {
return x + dx;
}
x += dx;
if (x < xLower || x > xUpper) {
dx = (x4 - x3) / 2;
x = x3 + dx;
}
if (y < 0) {
x3 = x;
} else {
x4 = x;
}
}
throw new MathException("Could not find root in " + MAX_ITER + " attempts");
}
/**
* Uses the function and its derivative. This method uses an initial guess for the root, rather than bounds.
* @param function The function, not null
* @param derivative The derivative, not null
* @param x The initial guess for the root, not null
* @return The root
* @throws MathException If the root is not found in 1000 attempts.
*/
public Double getRoot(DoubleFunction1D function, DoubleFunction1D derivative, Double x) {
ArgChecker.notNull(function, "function");
ArgChecker.notNull(derivative, "derivative function");
ArgChecker.notNull(x, "x");
double root = x;
for (int i = 0; i < MAX_ITER; i++) {
double y = function.applyAsDouble(root);
double dy = derivative.applyAsDouble(root);
double dx = y / dy;
if (Math.abs(dx) <= _accuracy) {
return root - dx;
}
root -= dx;
}
throw new MathException("Could not find root in " + MAX_ITER + " attempts");
}
}