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