Home | History | Annotate | Download | only in solvers
      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.math.analysis.solvers;
     18 
     19 
     20 import org.apache.commons.math.FunctionEvaluationException;
     21 import org.apache.commons.math.MathRuntimeException;
     22 import org.apache.commons.math.MaxIterationsExceededException;
     23 import org.apache.commons.math.analysis.UnivariateRealFunction;
     24 import org.apache.commons.math.exception.util.LocalizedFormats;
     25 import org.apache.commons.math.util.FastMath;
     26 
     27 /**
     28  * Implements the <a href="http://mathworld.wolfram.com/BrentsMethod.html">
     29  * Brent algorithm</a> for  finding zeros of real univariate functions.
     30  * <p>
     31  * The function should be continuous but not necessarily smooth.</p>
     32  *
     33  * @version $Revision:670469 $ $Date:2008-06-23 10:01:38 +0200 (lun., 23 juin 2008) $
     34  */
     35 public class BrentSolver extends UnivariateRealSolverImpl {
     36 
     37     /**
     38      * Default absolute accuracy
     39      * @since 2.1
     40      */
     41     public static final double DEFAULT_ABSOLUTE_ACCURACY = 1E-6;
     42 
     43     /** Default maximum number of iterations
     44      * @since 2.1
     45      */
     46     public static final int DEFAULT_MAXIMUM_ITERATIONS = 100;
     47 
     48     /** Serializable version identifier */
     49     private static final long serialVersionUID = 7694577816772532779L;
     50 
     51     /**
     52      * Construct a solver for the given function.
     53      *
     54      * @param f function to solve.
     55      * @deprecated as of 2.0 the function to solve is passed as an argument
     56      * to the {@link #solve(UnivariateRealFunction, double, double)} or
     57      * {@link UnivariateRealSolverImpl#solve(UnivariateRealFunction, double, double, double)}
     58      * method.
     59      */
     60     @Deprecated
     61     public BrentSolver(UnivariateRealFunction f) {
     62         super(f, DEFAULT_MAXIMUM_ITERATIONS, DEFAULT_ABSOLUTE_ACCURACY);
     63     }
     64 
     65     /**
     66      * Construct a solver with default properties.
     67      * @deprecated in 2.2 (to be removed in 3.0).
     68      */
     69     @Deprecated
     70     public BrentSolver() {
     71         super(DEFAULT_MAXIMUM_ITERATIONS, DEFAULT_ABSOLUTE_ACCURACY);
     72     }
     73 
     74     /**
     75      * Construct a solver with the given absolute accuracy.
     76      *
     77      * @param absoluteAccuracy lower bound for absolute accuracy of solutions returned by the solver
     78      * @since 2.1
     79      */
     80     public BrentSolver(double absoluteAccuracy) {
     81         super(DEFAULT_MAXIMUM_ITERATIONS, absoluteAccuracy);
     82     }
     83 
     84     /**
     85      * Contstruct a solver with the given maximum iterations and absolute accuracy.
     86      *
     87      * @param maximumIterations maximum number of iterations
     88      * @param absoluteAccuracy lower bound for absolute accuracy of solutions returned by the solver
     89      * @since 2.1
     90      */
     91     public BrentSolver(int maximumIterations, double absoluteAccuracy) {
     92         super(maximumIterations, absoluteAccuracy);
     93     }
     94 
     95     /** {@inheritDoc} */
     96     @Deprecated
     97     public double solve(double min, double max)
     98         throws MaxIterationsExceededException, FunctionEvaluationException {
     99         return solve(f, min, max);
    100     }
    101 
    102     /** {@inheritDoc} */
    103     @Deprecated
    104     public double solve(double min, double max, double initial)
    105         throws MaxIterationsExceededException, FunctionEvaluationException {
    106         return solve(f, min, max, initial);
    107     }
    108 
    109     /**
    110      * Find a zero in the given interval with an initial guess.
    111      * <p>Throws <code>IllegalArgumentException</code> if the values of the
    112      * function at the three points have the same sign (note that it is
    113      * allowed to have endpoints with the same sign if the initial point has
    114      * opposite sign function-wise).</p>
    115      *
    116      * @param f function to solve.
    117      * @param min the lower bound for the interval.
    118      * @param max the upper bound for the interval.
    119      * @param initial the start value to use (must be set to min if no
    120      * initial point is known).
    121      * @return the value where the function is zero
    122      * @throws MaxIterationsExceededException the maximum iteration count is exceeded
    123      * @throws FunctionEvaluationException if an error occurs evaluating  the function
    124      * @throws IllegalArgumentException if initial is not between min and max
    125      * (even if it <em>is</em> a root)
    126      * @deprecated in 2.2 (to be removed in 3.0).
    127      */
    128     @Deprecated
    129     public double solve(final UnivariateRealFunction f,
    130                         final double min, final double max, final double initial)
    131         throws MaxIterationsExceededException, FunctionEvaluationException {
    132 
    133         clearResult();
    134         if ((initial < min) || (initial > max)) {
    135             throw MathRuntimeException.createIllegalArgumentException(
    136                   LocalizedFormats.INVALID_INTERVAL_INITIAL_VALUE_PARAMETERS,
    137                   min, initial, max);
    138         }
    139 
    140         // return the initial guess if it is good enough
    141         double yInitial = f.value(initial);
    142         if (FastMath.abs(yInitial) <= functionValueAccuracy) {
    143             setResult(initial, 0);
    144             return result;
    145         }
    146 
    147         // return the first endpoint if it is good enough
    148         double yMin = f.value(min);
    149         if (FastMath.abs(yMin) <= functionValueAccuracy) {
    150             setResult(min, 0);
    151             return result;
    152         }
    153 
    154         // reduce interval if min and initial bracket the root
    155         if (yInitial * yMin < 0) {
    156             return solve(f, min, yMin, initial, yInitial, min, yMin);
    157         }
    158 
    159         // return the second endpoint if it is good enough
    160         double yMax = f.value(max);
    161         if (FastMath.abs(yMax) <= functionValueAccuracy) {
    162             setResult(max, 0);
    163             return result;
    164         }
    165 
    166         // reduce interval if initial and max bracket the root
    167         if (yInitial * yMax < 0) {
    168             return solve(f, initial, yInitial, max, yMax, initial, yInitial);
    169         }
    170 
    171         throw MathRuntimeException.createIllegalArgumentException(
    172               LocalizedFormats.SAME_SIGN_AT_ENDPOINTS, min, max, yMin, yMax);
    173 
    174     }
    175 
    176     /**
    177      * Find a zero in the given interval with an initial guess.
    178      * <p>Throws <code>IllegalArgumentException</code> if the values of the
    179      * function at the three points have the same sign (note that it is
    180      * allowed to have endpoints with the same sign if the initial point has
    181      * opposite sign function-wise).</p>
    182      *
    183      * @param f function to solve.
    184      * @param min the lower bound for the interval.
    185      * @param max the upper bound for the interval.
    186      * @param initial the start value to use (must be set to min if no
    187      * initial point is known).
    188      * @param maxEval Maximum number of evaluations.
    189      * @return the value where the function is zero
    190      * @throws MaxIterationsExceededException the maximum iteration count is exceeded
    191      * @throws FunctionEvaluationException if an error occurs evaluating  the function
    192      * @throws IllegalArgumentException if initial is not between min and max
    193      * (even if it <em>is</em> a root)
    194      */
    195     @Override
    196     public double solve(int maxEval, final UnivariateRealFunction f,
    197                         final double min, final double max, final double initial)
    198         throws MaxIterationsExceededException, FunctionEvaluationException {
    199         setMaximalIterationCount(maxEval);
    200         return solve(f, min, max, initial);
    201     }
    202 
    203     /**
    204      * Find a zero in the given interval.
    205      * <p>
    206      * Requires that the values of the function at the endpoints have opposite
    207      * signs. An <code>IllegalArgumentException</code> is thrown if this is not
    208      * the case.</p>
    209      *
    210      * @param f the function to solve
    211      * @param min the lower bound for the interval.
    212      * @param max the upper bound for the interval.
    213      * @return the value where the function is zero
    214      * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
    215      * @throws FunctionEvaluationException if an error occurs evaluating the function
    216      * @throws IllegalArgumentException if min is not less than max or the
    217      * signs of the values of the function at the endpoints are not opposites
    218      * @deprecated in 2.2 (to be removed in 3.0).
    219      */
    220     @Deprecated
    221     public double solve(final UnivariateRealFunction f,
    222                         final double min, final double max)
    223         throws MaxIterationsExceededException, FunctionEvaluationException {
    224 
    225         clearResult();
    226         verifyInterval(min, max);
    227 
    228         double ret = Double.NaN;
    229 
    230         double yMin = f.value(min);
    231         double yMax = f.value(max);
    232 
    233         // Verify bracketing
    234         double sign = yMin * yMax;
    235         if (sign > 0) {
    236             // check if either value is close to a zero
    237             if (FastMath.abs(yMin) <= functionValueAccuracy) {
    238                 setResult(min, 0);
    239                 ret = min;
    240             } else if (FastMath.abs(yMax) <= functionValueAccuracy) {
    241                 setResult(max, 0);
    242                 ret = max;
    243             } else {
    244                 // neither value is close to zero and min and max do not bracket root.
    245                 throw MathRuntimeException.createIllegalArgumentException(
    246                         LocalizedFormats.SAME_SIGN_AT_ENDPOINTS, min, max, yMin, yMax);
    247             }
    248         } else if (sign < 0){
    249             // solve using only the first endpoint as initial guess
    250             ret = solve(f, min, yMin, max, yMax, min, yMin);
    251         } else {
    252             // either min or max is a root
    253             if (yMin == 0.0) {
    254                 ret = min;
    255             } else {
    256                 ret = max;
    257             }
    258         }
    259 
    260         return ret;
    261     }
    262 
    263     /**
    264      * Find a zero in the given interval.
    265      * <p>
    266      * Requires that the values of the function at the endpoints have opposite
    267      * signs. An <code>IllegalArgumentException</code> is thrown if this is not
    268      * the case.</p>
    269      *
    270      * @param f the function to solve
    271      * @param min the lower bound for the interval.
    272      * @param max the upper bound for the interval.
    273      * @param maxEval Maximum number of evaluations.
    274      * @return the value where the function is zero
    275      * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
    276      * @throws FunctionEvaluationException if an error occurs evaluating the function
    277      * @throws IllegalArgumentException if min is not less than max or the
    278      * signs of the values of the function at the endpoints are not opposites
    279      */
    280     @Override
    281     public double solve(int maxEval, final UnivariateRealFunction f,
    282                         final double min, final double max)
    283         throws MaxIterationsExceededException, FunctionEvaluationException {
    284         setMaximalIterationCount(maxEval);
    285         return solve(f, min, max);
    286     }
    287 
    288     /**
    289      * Find a zero starting search according to the three provided points.
    290      * @param f the function to solve
    291      * @param x0 old approximation for the root
    292      * @param y0 function value at the approximation for the root
    293      * @param x1 last calculated approximation for the root
    294      * @param y1 function value at the last calculated approximation
    295      * for the root
    296      * @param x2 bracket point (must be set to x0 if no bracket point is
    297      * known, this will force starting with linear interpolation)
    298      * @param y2 function value at the bracket point.
    299      * @return the value where the function is zero
    300      * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
    301      * @throws FunctionEvaluationException if an error occurs evaluating the function
    302      */
    303     private double solve(final UnivariateRealFunction f,
    304                          double x0, double y0,
    305                          double x1, double y1,
    306                          double x2, double y2)
    307     throws MaxIterationsExceededException, FunctionEvaluationException {
    308 
    309         double delta = x1 - x0;
    310         double oldDelta = delta;
    311 
    312         int i = 0;
    313         while (i < maximalIterationCount) {
    314             if (FastMath.abs(y2) < FastMath.abs(y1)) {
    315                 // use the bracket point if is better than last approximation
    316                 x0 = x1;
    317                 x1 = x2;
    318                 x2 = x0;
    319                 y0 = y1;
    320                 y1 = y2;
    321                 y2 = y0;
    322             }
    323             if (FastMath.abs(y1) <= functionValueAccuracy) {
    324                 // Avoid division by very small values. Assume
    325                 // the iteration has converged (the problem may
    326                 // still be ill conditioned)
    327                 setResult(x1, i);
    328                 return result;
    329             }
    330             double dx = x2 - x1;
    331             double tolerance =
    332                 FastMath.max(relativeAccuracy * FastMath.abs(x1), absoluteAccuracy);
    333             if (FastMath.abs(dx) <= tolerance) {
    334                 setResult(x1, i);
    335                 return result;
    336             }
    337             if ((FastMath.abs(oldDelta) < tolerance) ||
    338                     (FastMath.abs(y0) <= FastMath.abs(y1))) {
    339                 // Force bisection.
    340                 delta = 0.5 * dx;
    341                 oldDelta = delta;
    342             } else {
    343                 double r3 = y1 / y0;
    344                 double p;
    345                 double p1;
    346                 // the equality test (x0 == x2) is intentional,
    347                 // it is part of the original Brent's method,
    348                 // it should NOT be replaced by proximity test
    349                 if (x0 == x2) {
    350                     // Linear interpolation.
    351                     p = dx * r3;
    352                     p1 = 1.0 - r3;
    353                 } else {
    354                     // Inverse quadratic interpolation.
    355                     double r1 = y0 / y2;
    356                     double r2 = y1 / y2;
    357                     p = r3 * (dx * r1 * (r1 - r2) - (x1 - x0) * (r2 - 1.0));
    358                     p1 = (r1 - 1.0) * (r2 - 1.0) * (r3 - 1.0);
    359                 }
    360                 if (p > 0.0) {
    361                     p1 = -p1;
    362                 } else {
    363                     p = -p;
    364                 }
    365                 if (2.0 * p >= 1.5 * dx * p1 - FastMath.abs(tolerance * p1) ||
    366                         p >= FastMath.abs(0.5 * oldDelta * p1)) {
    367                     // Inverse quadratic interpolation gives a value
    368                     // in the wrong direction, or progress is slow.
    369                     // Fall back to bisection.
    370                     delta = 0.5 * dx;
    371                     oldDelta = delta;
    372                 } else {
    373                     oldDelta = delta;
    374                     delta = p / p1;
    375                 }
    376             }
    377             // Save old X1, Y1
    378             x0 = x1;
    379             y0 = y1;
    380             // Compute new X1, Y1
    381             if (FastMath.abs(delta) > tolerance) {
    382                 x1 = x1 + delta;
    383             } else if (dx > 0.0) {
    384                 x1 = x1 + 0.5 * tolerance;
    385             } else if (dx <= 0.0) {
    386                 x1 = x1 - 0.5 * tolerance;
    387             }
    388             y1 = f.value(x1);
    389             if ((y1 > 0) == (y2 > 0)) {
    390                 x2 = x0;
    391                 y2 = y0;
    392                 delta = x1 - x0;
    393                 oldDelta = delta;
    394             }
    395             i++;
    396         }
    397         throw new MaxIterationsExceededException(maximalIterationCount);
    398     }
    399 }
    400