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; 18 19 20 import org.apache.commons.math.FunctionEvaluationException; 21 import org.apache.commons.math.MaxIterationsExceededException; 22 23 /** 24 * Implements the <a href="http://mathworld.wolfram.com/BrentsMethod.html"> 25 * Brent algorithm</a> for finding zeros of real univariate functions. 26 * <p> 27 * The function should be continuous but not necessarily smooth.</p> 28 * 29 * @version $Revision: 617826 $ $Date: 2008-02-02 09:39:18 -0700 (Sat, 02 Feb 2008) $ 30 */ 31 public class BrentSolver extends UnivariateRealSolverImpl { 32 33 /** Serializable version identifier */ 34 private static final long serialVersionUID = -2136672307739067002L; 35 36 /** 37 * Construct a solver for the given function. 38 * 39 * @param f function to solve. 40 */ 41 public BrentSolver(UnivariateRealFunction f) { 42 super(f, 100, 1E-6); 43 } 44 45 /** 46 * Find a zero in the given interval with an initial guess. 47 * <p>Throws <code>IllegalArgumentException</code> if the values of the 48 * function at the three points have the same sign (note that it is 49 * allowed to have endpoints with the same sign if the initial point has 50 * opposite sign function-wise).</p> 51 * 52 * @param min the lower bound for the interval. 53 * @param max the upper bound for the interval. 54 * @param initial the start value to use (must be set to min if no 55 * initial point is known). 56 * @return the value where the function is zero 57 * @throws MaxIterationsExceededException the maximum iteration count 58 * is exceeded 59 * @throws FunctionEvaluationException if an error occurs evaluating 60 * the function 61 * @throws IllegalArgumentException if initial is not between min and max 62 * (even if it <em>is</em> a root) 63 */ 64 public double solve(double min, double max, double initial) 65 throws MaxIterationsExceededException, FunctionEvaluationException { 66 67 if (((initial - min) * (max -initial)) < 0) { 68 throw new IllegalArgumentException("Initial guess is not in search" + 69 " interval." + " Initial: " + initial + 70 " Endpoints: [" + min + "," + max + "]"); 71 } 72 73 // return the initial guess if it is good enough 74 double yInitial = f.value(initial); 75 if (Math.abs(yInitial) <= functionValueAccuracy) { 76 setResult(initial, 0); 77 return result; 78 } 79 80 // return the first endpoint if it is good enough 81 double yMin = f.value(min); 82 if (Math.abs(yMin) <= functionValueAccuracy) { 83 setResult(yMin, 0); 84 return result; 85 } 86 87 // reduce interval if min and initial bracket the root 88 if (yInitial * yMin < 0) { 89 return solve(min, yMin, initial, yInitial, min, yMin); 90 } 91 92 // return the second endpoint if it is good enough 93 double yMax = f.value(max); 94 if (Math.abs(yMax) <= functionValueAccuracy) { 95 setResult(yMax, 0); 96 return result; 97 } 98 99 // reduce interval if initial and max bracket the root 100 if (yInitial * yMax < 0) { 101 return solve(initial, yInitial, max, yMax, initial, yInitial); 102 } 103 104 // full Brent algorithm starting with provided initial guess 105 return solve(min, yMin, max, yMax, initial, yInitial); 106 107 } 108 109 /** 110 * Find a zero in the given interval. 111 * <p> 112 * Requires that the values of the function at the endpoints have opposite 113 * signs. An <code>IllegalArgumentException</code> is thrown if this is not 114 * the case.</p> 115 * 116 * @param min the lower bound for the interval. 117 * @param max the upper bound for the interval. 118 * @return the value where the function is zero 119 * @throws MaxIterationsExceededException if the maximum iteration count is exceeded 120 * @throws FunctionEvaluationException if an error occurs evaluating the 121 * function 122 * @throws IllegalArgumentException if min is not less than max or the 123 * signs of the values of the function at the endpoints are not opposites 124 */ 125 public double solve(double min, double max) throws MaxIterationsExceededException, 126 FunctionEvaluationException { 127 128 clearResult(); 129 verifyInterval(min, max); 130 131 double yMin = f.value(min); 132 double yMax = f.value(max); 133 134 // Verify bracketing 135 if (yMin * yMax >= 0) { 136 throw new IllegalArgumentException 137 ("Function values at endpoints do not have different signs." + 138 " Endpoints: [" + min + "," + max + "]" + 139 " Values: [" + yMin + "," + yMax + "]"); 140 } 141 142 // solve using only the first endpoint as initial guess 143 return solve(min, yMin, max, yMax, min, yMin); 144 145 } 146 147 /** 148 * Find a zero starting search according to the three provided points. 149 * @param x0 old approximation for the root 150 * @param y0 function value at the approximation for the root 151 * @param x1 last calculated approximation for the root 152 * @param y1 function value at the last calculated approximation 153 * for the root 154 * @param x2 bracket point (must be set to x0 if no bracket point is 155 * known, this will force starting with linear interpolation) 156 * @param y2 function value at the bracket point. 157 * @return the value where the function is zero 158 * @throws MaxIterationsExceededException if the maximum iteration count 159 * is exceeded 160 * @throws FunctionEvaluationException if an error occurs evaluating 161 * the function 162 */ 163 private double solve(double x0, double y0, 164 double x1, double y1, 165 double x2, double y2) 166 throws MaxIterationsExceededException, FunctionEvaluationException { 167 168 double delta = x1 - x0; 169 double oldDelta = delta; 170 171 int i = 0; 172 while (i < maximalIterationCount) { 173 if (Math.abs(y2) < Math.abs(y1)) { 174 // use the bracket point if is better than last approximation 175 x0 = x1; 176 x1 = x2; 177 x2 = x0; 178 y0 = y1; 179 y1 = y2; 180 y2 = y0; 181 } 182 if (Math.abs(y1) <= functionValueAccuracy) { 183 // Avoid division by very small values. Assume 184 // the iteration has converged (the problem may 185 // still be ill conditioned) 186 setResult(x1, i); 187 return result; 188 } 189 double dx = (x2 - x1); 190 double tolerance = 191 Math.max(relativeAccuracy * Math.abs(x1), absoluteAccuracy); 192 if (Math.abs(dx) <= tolerance) { 193 setResult(x1, i); 194 return result; 195 } 196 if ((Math.abs(oldDelta) < tolerance) || 197 (Math.abs(y0) <= Math.abs(y1))) { 198 // Force bisection. 199 delta = 0.5 * dx; 200 oldDelta = delta; 201 } else { 202 double r3 = y1 / y0; 203 double p; 204 double p1; 205 // the equality test (x0 == x2) is intentional, 206 // it is part of the original Brent's method, 207 // it should NOT be replaced by proximity test 208 if (x0 == x2) { 209 // Linear interpolation. 210 p = dx * r3; 211 p1 = 1.0 - r3; 212 } else { 213 // Inverse quadratic interpolation. 214 double r1 = y0 / y2; 215 double r2 = y1 / y2; 216 p = r3 * (dx * r1 * (r1 - r2) - (x1 - x0) * (r2 - 1.0)); 217 p1 = (r1 - 1.0) * (r2 - 1.0) * (r3 - 1.0); 218 } 219 if (p > 0.0) { 220 p1 = -p1; 221 } else { 222 p = -p; 223 } 224 if (2.0 * p >= 1.5 * dx * p1 - Math.abs(tolerance * p1) || 225 p >= Math.abs(0.5 * oldDelta * p1)) { 226 // Inverse quadratic interpolation gives a value 227 // in the wrong direction, or progress is slow. 228 // Fall back to bisection. 229 delta = 0.5 * dx; 230 oldDelta = delta; 231 } else { 232 oldDelta = delta; 233 delta = p / p1; 234 } 235 } 236 // Save old X1, Y1 237 x0 = x1; 238 y0 = y1; 239 // Compute new X1, Y1 240 if (Math.abs(delta) > tolerance) { 241 x1 = x1 + delta; 242 } else if (dx > 0.0) { 243 x1 = x1 + 0.5 * tolerance; 244 } else if (dx <= 0.0) { 245 x1 = x1 - 0.5 * tolerance; 246 } 247 y1 = f.value(x1); 248 if ((y1 > 0) == (y2 > 0)) { 249 x2 = x0; 250 y2 = y0; 251 delta = x1 - x0; 252 oldDelta = delta; 253 } 254 i++; 255 } 256 throw new MaxIterationsExceededException(maximalIterationCount); 257 } 258 }