BrentSolver.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.numbers.rootfinder;

  18. import java.util.function.DoubleUnaryOperator;

  19. /**
  20.  * This class implements the <a href="http://mathworld.wolfram.com/BrentsMethod.html">
  21.  * Brent algorithm</a> for finding zeros of real univariate functions.
  22.  * The function should be continuous but not necessarily smooth.
  23.  * The {@code solve} method returns a zero {@code x} of the function {@code f}
  24.  * in the given interval {@code [a, b]} to within a tolerance
  25.  * {@code 2 eps abs(x) + t} where {@code eps} is the relative accuracy and
  26.  * {@code t} is the absolute accuracy.
  27.  * <p>The given interval must bracket the root.</p>
  28.  * <p>
  29.  *  The reference implementation is given in chapter 4 of
  30.  *  <blockquote>
  31.  *   <b>Algorithms for Minimization Without Derivatives</b>,
  32.  *   <em>Richard P. Brent</em>,
  33.  *   Dover, 2002
  34.  *  </blockquote>
  35.  */
  36. public class BrentSolver {
  37.     /** Relative accuracy. */
  38.     private final double relativeAccuracy;
  39.     /** Absolute accuracy. */
  40.     private final double absoluteAccuracy;
  41.     /** Function accuracy. */
  42.     private final double functionValueAccuracy;

  43.     /**
  44.      * Construct a solver.
  45.      *
  46.      * @param relativeAccuracy Relative accuracy.
  47.      * @param absoluteAccuracy Absolute accuracy.
  48.      * @param functionValueAccuracy Function value accuracy.
  49.      */
  50.     public BrentSolver(double relativeAccuracy,
  51.                        double absoluteAccuracy,
  52.                        double functionValueAccuracy) {
  53.         this.relativeAccuracy = relativeAccuracy;
  54.         this.absoluteAccuracy = absoluteAccuracy;
  55.         this.functionValueAccuracy = functionValueAccuracy;
  56.     }

  57.     /**
  58.      * Search the function's zero within the given interval.
  59.      *
  60.      * @param func Function to solve.
  61.      * @param min Lower bound.
  62.      * @param max Upper bound.
  63.      * @return the root.
  64.      * @throws IllegalArgumentException if {@code min > max}.
  65.      * @throws IllegalArgumentException if the given interval does
  66.      * not bracket the root.
  67.      */
  68.     public double findRoot(DoubleUnaryOperator func,
  69.                            double min,
  70.                            double max) {
  71.         // Avoid overflow computing the initial value: 0.5 * (min + max)
  72.         // Note: This sum is invalid if min == max == Double.MIN_VALUE
  73.         // so detect this edge case. It will raise a bracketing exception
  74.         // if min is not the root within the configured function accuracy;
  75.         // otherwise min is returned.
  76.         final double initial = min == max ? min : 0.5 * min + 0.5 * max;
  77.         return findRoot(func, min, initial, max);
  78.     }

  79.     /**
  80.      * Search the function's zero within the given interval,
  81.      * starting from the given estimate.
  82.      *
  83.      * @param func Function to solve.
  84.      * @param min Lower bound.
  85.      * @param initial Initial guess.
  86.      * @param max Upper bound.
  87.      * @return the root.
  88.      * @throws IllegalArgumentException if {@code min > max} or
  89.      * {@code initial} is not in the {@code [min, max]} interval.
  90.      * @throws IllegalArgumentException if the given interval does
  91.      * not bracket the root.
  92.      */
  93.     public double findRoot(DoubleUnaryOperator func,
  94.                            double min,
  95.                            double initial,
  96.                            double max) {
  97.         if (min > max) {
  98.             throw new SolverException(SolverException.TOO_LARGE, min, max);
  99.         }
  100.         if (initial < min ||
  101.             initial > max) {
  102.             throw new SolverException(SolverException.OUT_OF_RANGE, initial, min, max);
  103.         }

  104.         // Return the initial guess if it is good enough.
  105.         final double yInitial = func.applyAsDouble(initial);
  106.         if (Math.abs(yInitial) <= functionValueAccuracy) {
  107.             return initial;
  108.         }

  109.         // Return the first endpoint if it is good enough.
  110.         final double yMin = func.applyAsDouble(min);
  111.         if (Math.abs(yMin) <= functionValueAccuracy) {
  112.             return min;
  113.         }

  114.         // Reduce interval if min and initial bracket the root.
  115.         if (Double.compare(yInitial * yMin, 0.0) < 0) {
  116.             return brent(func, min, initial, yMin, yInitial);
  117.         }

  118.         // Return the second endpoint if it is good enough.
  119.         final double yMax = func.applyAsDouble(max);
  120.         if (Math.abs(yMax) <= functionValueAccuracy) {
  121.             return max;
  122.         }

  123.         // Reduce interval if initial and max bracket the root.
  124.         if (Double.compare(yInitial * yMax, 0.0) < 0) {
  125.             return brent(func, initial, max, yInitial, yMax);
  126.         }

  127.         throw new SolverException(SolverException.BRACKETING, min, yMin, max, yMax);
  128.     }

  129.     /**
  130.      * Search for a zero inside the provided interval.
  131.      * This implementation is based on the algorithm described at page 58 of
  132.      * the book
  133.      * <blockquote>
  134.      *  <b>Algorithms for Minimization Without Derivatives</b>,
  135.      *  <i>Richard P. Brent</i>,
  136.      *  Dover 0-486-41998-3
  137.      * </blockquote>
  138.      *
  139.      * @param func Function to solve.
  140.      * @param lo Lower bound of the search interval.
  141.      * @param hi Higher bound of the search interval.
  142.      * @param fLo Function value at the lower bound of the search interval.
  143.      * @param fHi Function value at the higher bound of the search interval.
  144.      * @return the value where the function is zero.
  145.      */
  146.     private double brent(DoubleUnaryOperator func,
  147.                          double lo, double hi,
  148.                          double fLo, double fHi) {
  149.         double a = lo;
  150.         double fa = fLo;
  151.         double b = hi;
  152.         double fb = fHi;
  153.         double c = a;
  154.         double fc = fa;
  155.         double d = b - a;
  156.         double e = d;

  157.         final double t = absoluteAccuracy;
  158.         final double eps = relativeAccuracy;

  159.         while (true) {
  160.             if (Math.abs(fc) < Math.abs(fb)) {
  161.                 a = b;
  162.                 b = c;
  163.                 c = a;
  164.                 fa = fb;
  165.                 fb = fc;
  166.                 fc = fa;
  167.             }

  168.             final double tol = 2 * eps * Math.abs(b) + t;
  169.             final double m = 0.5 * (c - b);

  170.             if (Math.abs(m) <= tol ||
  171.                 equalsZero(fb))  {
  172.                 return b;
  173.             }
  174.             if (Math.abs(e) < tol ||
  175.                 Math.abs(fa) <= Math.abs(fb)) {
  176.                 // Force bisection.
  177.                 d = m;
  178.                 e = d;
  179.             } else {
  180.                 final double s = fb / fa;
  181.                 double p;
  182.                 double q;
  183.                 // The equality test (a == c) is intentional,
  184.                 // it is part of the original Brent's method and
  185.                 // it should NOT be replaced by proximity test.
  186.                 if (a == c) {
  187.                     // Linear interpolation.
  188.                     p = 2 * m * s;
  189.                     q = 1 - s;
  190.                 } else {
  191.                     // Inverse quadratic interpolation.
  192.                     q = fa / fc;
  193.                     final double r = fb / fc;
  194.                     p = s * (2 * m * q * (q - r) - (b - a) * (r - 1));
  195.                     q = (q - 1) * (r - 1) * (s - 1);
  196.                 }
  197.                 if (p > 0) {
  198.                     q = -q;
  199.                 } else {
  200.                     p = -p;
  201.                 }
  202.                 if (p >= 1.5 * m * q - Math.abs(tol * q) ||
  203.                     p >= Math.abs(0.5 * e * q)) {
  204.                     // Inverse quadratic interpolation gives a value
  205.                     // in the wrong direction, or progress is slow.
  206.                     // Fall back to bisection.
  207.                     d = m;
  208.                     e = d;
  209.                 } else {
  210.                     e = d;
  211.                     d = p / q;
  212.                 }
  213.             }
  214.             a = b;
  215.             fa = fb;

  216.             if (Math.abs(d) > tol) {
  217.                 b += d;
  218.             } else if (m > 0) {
  219.                 b += tol;
  220.             } else {
  221.                 b -= tol;
  222.             }
  223.             fb = func.applyAsDouble(b);
  224.             if ((fb > 0 && fc > 0) ||
  225.                 (fb <= 0 && fc <= 0)) {
  226.                 c = a;
  227.                 fc = fa;
  228.                 d = b - a;
  229.                 e = d;
  230.             }
  231.         }
  232.     }

  233.     /**
  234.      * Return true if the value is within 1 ULP of zero.
  235.      *
  236.      * @param value Value
  237.      * @return true if zero within a 1 ULP tolerance
  238.      */
  239.     private static boolean equalsZero(double value) {
  240.         return Math.abs(value) <= Double.MIN_VALUE;
  241.     }
  242. }