View Javadoc
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  
19  import java.util.function.DoubleUnaryOperator;
20  
21  /**
22   * This class implements the <a href="http://mathworld.wolfram.com/BrentsMethod.html">
23   * Brent algorithm</a> for finding zeros of real univariate functions.
24   * The function should be continuous but not necessarily smooth.
25   * The {@code solve} method returns a zero {@code x} of the function {@code f}
26   * in the given interval {@code [a, b]} to within a tolerance
27   * {@code 2 eps abs(x) + t} where {@code eps} is the relative accuracy and
28   * {@code t} is the absolute accuracy.
29   * <p>The given interval must bracket the root.</p>
30   * <p>
31   *  The reference implementation is given in chapter 4 of
32   *  <blockquote>
33   *   <b>Algorithms for Minimization Without Derivatives</b>,
34   *   <em>Richard P. Brent</em>,
35   *   Dover, 2002
36   *  </blockquote>
37   */
38  public class BrentSolver {
39      /** Relative accuracy. */
40      private final double relativeAccuracy;
41      /** Absolute accuracy. */
42      private final double absoluteAccuracy;
43      /** Function accuracy. */
44      private final double functionValueAccuracy;
45  
46      /**
47       * Construct a solver.
48       *
49       * @param relativeAccuracy Relative accuracy.
50       * @param absoluteAccuracy Absolute accuracy.
51       * @param functionValueAccuracy Function value accuracy.
52       */
53      public BrentSolver(double relativeAccuracy,
54                         double absoluteAccuracy,
55                         double functionValueAccuracy) {
56          this.relativeAccuracy = relativeAccuracy;
57          this.absoluteAccuracy = absoluteAccuracy;
58          this.functionValueAccuracy = functionValueAccuracy;
59      }
60  
61      /**
62       * Search the function's zero within the given interval.
63       *
64       * @param func Function to solve.
65       * @param min Lower bound.
66       * @param max Upper bound.
67       * @return the root.
68       * @throws IllegalArgumentException if {@code min > max}.
69       * @throws IllegalArgumentException if the given interval does
70       * not bracket the root.
71       */
72      public double findRoot(DoubleUnaryOperator func,
73                             double min,
74                             double max) {
75          // Avoid overflow computing the initial value: 0.5 * (min + max)
76          // Note: This sum is invalid if min == max == Double.MIN_VALUE
77          // so detect this edge case. It will raise a bracketing exception
78          // if min is not the root within the configured function accuracy;
79          // otherwise min is returned.
80          final double initial = min == max ? min : 0.5 * min + 0.5 * max;
81          return findRoot(func, min, initial, max);
82      }
83  
84      /**
85       * Search the function's zero within the given interval,
86       * starting from the given estimate.
87       *
88       * @param func Function to solve.
89       * @param min Lower bound.
90       * @param initial Initial guess.
91       * @param max Upper bound.
92       * @return the root.
93       * @throws IllegalArgumentException if {@code min > max} or
94       * {@code initial} is not in the {@code [min, max]} interval.
95       * @throws IllegalArgumentException if the given interval does
96       * not bracket the root.
97       */
98      public double findRoot(DoubleUnaryOperator func,
99                             double min,
100                            double initial,
101                            double max) {
102         if (min > max) {
103             throw new SolverException(SolverException.TOO_LARGE, min, max);
104         }
105         if (initial < min ||
106             initial > max) {
107             throw new SolverException(SolverException.OUT_OF_RANGE, initial, min, max);
108         }
109 
110         // Return the initial guess if it is good enough.
111         final double yInitial = func.applyAsDouble(initial);
112         if (Math.abs(yInitial) <= functionValueAccuracy) {
113             return initial;
114         }
115 
116         // Return the first endpoint if it is good enough.
117         final double yMin = func.applyAsDouble(min);
118         if (Math.abs(yMin) <= functionValueAccuracy) {
119             return min;
120         }
121 
122         // Reduce interval if min and initial bracket the root.
123         if (Double.compare(yInitial * yMin, 0.0) < 0) {
124             return brent(func, min, initial, yMin, yInitial);
125         }
126 
127         // Return the second endpoint if it is good enough.
128         final double yMax = func.applyAsDouble(max);
129         if (Math.abs(yMax) <= functionValueAccuracy) {
130             return max;
131         }
132 
133         // Reduce interval if initial and max bracket the root.
134         if (Double.compare(yInitial * yMax, 0.0) < 0) {
135             return brent(func, initial, max, yInitial, yMax);
136         }
137 
138         throw new SolverException(SolverException.BRACKETING, min, yMin, max, yMax);
139     }
140 
141     /**
142      * Search for a zero inside the provided interval.
143      * This implementation is based on the algorithm described at page 58 of
144      * the book
145      * <blockquote>
146      *  <b>Algorithms for Minimization Without Derivatives</b>,
147      *  <i>Richard P. Brent</i>,
148      *  Dover 0-486-41998-3
149      * </blockquote>
150      *
151      * @param func Function to solve.
152      * @param lo Lower bound of the search interval.
153      * @param hi Higher bound of the search interval.
154      * @param fLo Function value at the lower bound of the search interval.
155      * @param fHi Function value at the higher bound of the search interval.
156      * @return the value where the function is zero.
157      */
158     private double brent(DoubleUnaryOperator func,
159                          double lo, double hi,
160                          double fLo, double fHi) {
161         double a = lo;
162         double fa = fLo;
163         double b = hi;
164         double fb = fHi;
165         double c = a;
166         double fc = fa;
167         double d = b - a;
168         double e = d;
169 
170         final double t = absoluteAccuracy;
171         final double eps = relativeAccuracy;
172 
173         while (true) {
174             if (Math.abs(fc) < Math.abs(fb)) {
175                 a = b;
176                 b = c;
177                 c = a;
178                 fa = fb;
179                 fb = fc;
180                 fc = fa;
181             }
182 
183             final double tol = 2 * eps * Math.abs(b) + t;
184             final double m = 0.5 * (c - b);
185 
186             if (Math.abs(m) <= tol ||
187                 equalsZero(fb))  {
188                 return b;
189             }
190             if (Math.abs(e) < tol ||
191                 Math.abs(fa) <= Math.abs(fb)) {
192                 // Force bisection.
193                 d = m;
194                 e = d;
195             } else {
196                 final double s = fb / fa;
197                 double p;
198                 double q;
199                 // The equality test (a == c) is intentional,
200                 // it is part of the original Brent's method and
201                 // it should NOT be replaced by proximity test.
202                 if (a == c) {
203                     // Linear interpolation.
204                     p = 2 * m * s;
205                     q = 1 - s;
206                 } else {
207                     // Inverse quadratic interpolation.
208                     q = fa / fc;
209                     final double r = fb / fc;
210                     p = s * (2 * m * q * (q - r) - (b - a) * (r - 1));
211                     q = (q - 1) * (r - 1) * (s - 1);
212                 }
213                 if (p > 0) {
214                     q = -q;
215                 } else {
216                     p = -p;
217                 }
218                 if (p >= 1.5 * m * q - Math.abs(tol * q) ||
219                     p >= Math.abs(0.5 * e * q)) {
220                     // Inverse quadratic interpolation gives a value
221                     // in the wrong direction, or progress is slow.
222                     // Fall back to bisection.
223                     d = m;
224                     e = d;
225                 } else {
226                     e = d;
227                     d = p / q;
228                 }
229             }
230             a = b;
231             fa = fb;
232 
233             if (Math.abs(d) > tol) {
234                 b += d;
235             } else if (m > 0) {
236                 b += tol;
237             } else {
238                 b -= tol;
239             }
240             fb = func.applyAsDouble(b);
241             if ((fb > 0 && fc > 0) ||
242                 (fb <= 0 && fc <= 0)) {
243                 c = a;
244                 fc = fa;
245                 d = b - a;
246                 e = d;
247             }
248         }
249     }
250 
251     /**
252      * Return true if the value is within 1 ULP of zero.
253      *
254      * @param value Value
255      * @return true if zero within a 1 ULP tolerance
256      */
257     private static boolean equalsZero(double value) {
258         return Math.abs(value) <= Double.MIN_VALUE;
259     }
260 }