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.math4.legacy.analysis.solvers; 18 19 import org.apache.commons.math4.legacy.analysis.UnivariateFunction; 20 import org.apache.commons.math4.legacy.exception.NoBracketingException; 21 import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException; 22 import org.apache.commons.math4.legacy.exception.NullArgumentException; 23 import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException; 24 import org.apache.commons.math4.legacy.exception.util.LocalizedFormats; 25 import org.apache.commons.math4.core.jdkmath.JdkMath; 26 27 /** 28 * Utility routines for {@link UnivariateSolver} objects. 29 * 30 */ 31 public final class UnivariateSolverUtils { 32 /** 33 * Class contains only static methods. 34 */ 35 private UnivariateSolverUtils() {} 36 37 /** 38 * Convenience method to find a zero of a univariate real function. A default 39 * solver is used. 40 * 41 * @param function Function. 42 * @param x0 Lower bound for the interval. 43 * @param x1 Upper bound for the interval. 44 * @return a value where the function is zero. 45 * @throws NoBracketingException if the function has the same sign at the 46 * endpoints. 47 * @throws NullArgumentException if {@code function} is {@code null}. 48 */ 49 public static double solve(UnivariateFunction function, double x0, double x1) 50 throws NullArgumentException, 51 NoBracketingException { 52 if (function == null) { 53 throw new NullArgumentException(LocalizedFormats.FUNCTION); 54 } 55 final UnivariateSolver solver = new BrentSolver(); 56 return solver.solve(Integer.MAX_VALUE, function, x0, x1); 57 } 58 59 /** 60 * Convenience method to find a zero of a univariate real function. A default 61 * solver is used. 62 * 63 * @param function Function. 64 * @param x0 Lower bound for the interval. 65 * @param x1 Upper bound for the interval. 66 * @param absoluteAccuracy Accuracy to be used by the solver. 67 * @return a value where the function is zero. 68 * @throws NoBracketingException if the function has the same sign at the 69 * endpoints. 70 * @throws NullArgumentException if {@code function} is {@code null}. 71 */ 72 public static double solve(UnivariateFunction function, 73 double x0, double x1, 74 double absoluteAccuracy) 75 throws NullArgumentException, 76 NoBracketingException { 77 if (function == null) { 78 throw new NullArgumentException(LocalizedFormats.FUNCTION); 79 } 80 final UnivariateSolver solver = new BrentSolver(absoluteAccuracy); 81 return solver.solve(Integer.MAX_VALUE, function, x0, x1); 82 } 83 84 /** 85 * Force a root found by a non-bracketing solver to lie on a specified side, 86 * as if the solver were a bracketing one. 87 * 88 * @param maxEval maximal number of new evaluations of the function 89 * (evaluations already done for finding the root should have already been subtracted 90 * from this number) 91 * @param f function to solve 92 * @param bracketing bracketing solver to use for shifting the root 93 * @param baseRoot original root found by a previous non-bracketing solver 94 * @param min minimal bound of the search interval 95 * @param max maximal bound of the search interval 96 * @param allowedSolution the kind of solutions that the root-finding algorithm may 97 * accept as solutions. 98 * @return a root approximation, on the specified side of the exact root 99 * @throws NoBracketingException if the function has the same sign at the 100 * endpoints. 101 */ 102 public static double forceSide(final int maxEval, final UnivariateFunction f, 103 final BracketedUnivariateSolver<UnivariateFunction> bracketing, 104 final double baseRoot, final double min, final double max, 105 final AllowedSolution allowedSolution) 106 throws NoBracketingException { 107 108 if (allowedSolution == AllowedSolution.ANY_SIDE) { 109 // no further bracketing required 110 return baseRoot; 111 } 112 113 // find a very small interval bracketing the root 114 final double step = JdkMath.max(bracketing.getAbsoluteAccuracy(), 115 JdkMath.abs(baseRoot * bracketing.getRelativeAccuracy())); 116 double xLo = JdkMath.max(min, baseRoot - step); 117 double fLo = f.value(xLo); 118 double xHi = JdkMath.min(max, baseRoot + step); 119 double fHi = f.value(xHi); 120 int remainingEval = maxEval - 2; 121 while (remainingEval > 0) { 122 123 if ((fLo >= 0 && fHi <= 0) || (fLo <= 0 && fHi >= 0)) { 124 // compute the root on the selected side 125 return bracketing.solve(remainingEval, f, xLo, xHi, baseRoot, allowedSolution); 126 } 127 128 // try increasing the interval 129 boolean changeLo = false; 130 boolean changeHi = false; 131 if (fLo < fHi) { 132 // increasing function 133 if (fLo >= 0) { 134 changeLo = true; 135 } else { 136 changeHi = true; 137 } 138 } else if (fLo > fHi) { 139 // decreasing function 140 if (fLo <= 0) { 141 changeLo = true; 142 } else { 143 changeHi = true; 144 } 145 } else { 146 // unknown variation 147 changeLo = true; 148 changeHi = true; 149 } 150 151 // update the lower bound 152 if (changeLo) { 153 xLo = JdkMath.max(min, xLo - step); 154 fLo = f.value(xLo); 155 remainingEval--; 156 } 157 158 // update the higher bound 159 if (changeHi) { 160 xHi = JdkMath.min(max, xHi + step); 161 fHi = f.value(xHi); 162 remainingEval--; 163 } 164 } 165 166 throw new NoBracketingException(LocalizedFormats.FAILED_BRACKETING, 167 xLo, xHi, fLo, fHi, 168 maxEval - remainingEval, maxEval, baseRoot, 169 min, max); 170 } 171 172 /** 173 * This method simply calls {@link #bracket(UnivariateFunction, double, double, double, 174 * double, double, int) bracket(function, initial, lowerBound, upperBound, q, r, maximumIterations)} 175 * with {@code q} and {@code r} set to 1.0 and {@code maximumIterations} set to {@code Integer.MAX_VALUE}. 176 * <p> 177 * <strong>Note: </strong> this method can take {@code Integer.MAX_VALUE} 178 * iterations to throw a {@code ConvergenceException.} Unless you are 179 * confident that there is a root between {@code lowerBound} and 180 * {@code upperBound} near {@code initial}, it is better to use 181 * {@link #bracket(UnivariateFunction, double, double, double, double,double, int) 182 * bracket(function, initial, lowerBound, upperBound, q, r, maximumIterations)}, 183 * explicitly specifying the maximum number of iterations.</p> 184 * 185 * @param function Function. 186 * @param initial Initial midpoint of interval being expanded to 187 * bracket a root. 188 * @param lowerBound Lower bound (a is never lower than this value) 189 * @param upperBound Upper bound (b never is greater than this 190 * value). 191 * @return a two-element array holding a and b. 192 * @throws NoBracketingException if a root cannot be bracketted. 193 * @throws NotStrictlyPositiveException if {@code maximumIterations <= 0}. 194 * @throws NullArgumentException if {@code function} is {@code null}. 195 */ 196 public static double[] bracket(UnivariateFunction function, 197 double initial, 198 double lowerBound, double upperBound) 199 throws NullArgumentException, 200 NotStrictlyPositiveException, 201 NoBracketingException { 202 return bracket(function, initial, lowerBound, upperBound, 1.0, 1.0, Integer.MAX_VALUE); 203 } 204 205 /** 206 * This method simply calls {@link #bracket(UnivariateFunction, double, double, double, 207 * double, double, int) bracket(function, initial, lowerBound, upperBound, q, r, maximumIterations)} 208 * with {@code q} and {@code r} set to 1.0. 209 * @param function Function. 210 * @param initial Initial midpoint of interval being expanded to 211 * bracket a root. 212 * @param lowerBound Lower bound (a is never lower than this value). 213 * @param upperBound Upper bound (b never is greater than this 214 * value). 215 * @param maximumIterations Maximum number of iterations to perform 216 * @return a two element array holding a and b. 217 * @throws NoBracketingException if the algorithm fails to find a and b 218 * satisfying the desired conditions. 219 * @throws NotStrictlyPositiveException if {@code maximumIterations <= 0}. 220 * @throws NullArgumentException if {@code function} is {@code null}. 221 */ 222 public static double[] bracket(UnivariateFunction function, 223 double initial, 224 double lowerBound, double upperBound, 225 int maximumIterations) 226 throws NullArgumentException, 227 NotStrictlyPositiveException, 228 NoBracketingException { 229 return bracket(function, initial, lowerBound, upperBound, 1.0, 1.0, maximumIterations); 230 } 231 232 /** 233 * This method attempts to find two values a and b satisfying <ul> 234 * <li> {@code lowerBound <= a < initial < b <= upperBound} </li> 235 * <li> {@code f(a) * f(b) <= 0} </li> 236 * </ul> 237 * If {@code f} is continuous on {@code [a,b]}, this means that {@code a} 238 * and {@code b} bracket a root of {@code f}. 239 * <p> 240 * The algorithm checks the sign of \( f(l_k) \) and \( f(u_k) \) for increasing 241 * values of k, where \( l_k = max(lower, initial - \delta_k) \), 242 * \( u_k = min(upper, initial + \delta_k) \), using recurrence 243 * \( \delta_{k+1} = r \delta_k + q, \delta_0 = 0\) and starting search with \( k=1 \). 244 * The algorithm stops when one of the following happens: <ul> 245 * <li> at least one positive and one negative value have been found -- success!</li> 246 * <li> both endpoints have reached their respective limits -- NoBracketingException </li> 247 * <li> {@code maximumIterations} iterations elapse -- NoBracketingException </li></ul> 248 * <p> 249 * If different signs are found at first iteration ({@code k=1}), then the returned 250 * interval will be \( [a, b] = [l_1, u_1] \). If different signs are found at a later 251 * iteration {@code k>1}, then the returned interval will be either 252 * \( [a, b] = [l_{k+1}, l_{k}] \) or \( [a, b] = [u_{k}, u_{k+1}] \). A root solver called 253 * with these parameters will therefore start with the smallest bracketing interval known 254 * at this step. 255 * </p> 256 * <p> 257 * Interval expansion rate is tuned by changing the recurrence parameters {@code r} and 258 * {@code q}. When the multiplicative factor {@code r} is set to 1, the sequence is a 259 * simple arithmetic sequence with linear increase. When the multiplicative factor {@code r} 260 * is larger than 1, the sequence has an asymptotically exponential rate. Note than the 261 * additive parameter {@code q} should never be set to zero, otherwise the interval would 262 * degenerate to the single initial point for all values of {@code k}. 263 * </p> 264 * <p> 265 * As a rule of thumb, when the location of the root is expected to be approximately known 266 * within some error margin, {@code r} should be set to 1 and {@code q} should be set to the 267 * order of magnitude of the error margin. When the location of the root is really a wild guess, 268 * then {@code r} should be set to a value larger than 1 (typically 2 to double the interval 269 * length at each iteration) and {@code q} should be set according to half the initial 270 * search interval length. 271 * </p> 272 * <p> 273 * As an example, if we consider the trivial function {@code f(x) = 1 - x} and use 274 * {@code initial = 4}, {@code r = 1}, {@code q = 2}, the algorithm will compute 275 * {@code f(4-2) = f(2) = -1} and {@code f(4+2) = f(6) = -5} for {@code k = 1}, then 276 * {@code f(4-4) = f(0) = +1} and {@code f(4+4) = f(8) = -7} for {@code k = 2}. Then it will 277 * return the interval {@code [0, 2]} as the smallest one known to be bracketing the root. 278 * As shown by this example, the initial value (here {@code 4}) may lie outside of the returned 279 * bracketing interval. 280 * </p> 281 * @param function function to check 282 * @param initial Initial midpoint of interval being expanded to 283 * bracket a root. 284 * @param lowerBound Lower bound (a is never lower than this value). 285 * @param upperBound Upper bound (b never is greater than this 286 * value). 287 * @param q additive offset used to compute bounds sequence (must be strictly positive) 288 * @param r multiplicative factor used to compute bounds sequence 289 * @param maximumIterations Maximum number of iterations to perform 290 * @return a two element array holding the bracketing values. 291 * @exception NoBracketingException if function cannot be bracketed in the search interval 292 */ 293 public static double[] bracket(final UnivariateFunction function, final double initial, 294 final double lowerBound, final double upperBound, 295 final double q, final double r, final int maximumIterations) 296 throws NoBracketingException { 297 298 if (function == null) { 299 throw new NullArgumentException(LocalizedFormats.FUNCTION); 300 } 301 if (q <= 0) { 302 throw new NotStrictlyPositiveException(q); 303 } 304 if (maximumIterations <= 0) { 305 throw new NotStrictlyPositiveException(LocalizedFormats.INVALID_MAX_ITERATIONS, maximumIterations); 306 } 307 verifySequence(lowerBound, initial, upperBound); 308 309 // initialize the recurrence 310 double a = initial; 311 double b = initial; 312 double fa = Double.NaN; 313 double fb = Double.NaN; 314 double delta = 0; 315 316 for (int numIterations = 0; 317 numIterations < maximumIterations && (a > lowerBound || b < upperBound); 318 ++numIterations) { 319 320 final double previousA = a; 321 final double previousFa = fa; 322 final double previousB = b; 323 final double previousFb = fb; 324 325 delta = r * delta + q; 326 a = JdkMath.max(initial - delta, lowerBound); 327 b = JdkMath.min(initial + delta, upperBound); 328 fa = function.value(a); 329 fb = function.value(b); 330 331 if (numIterations == 0) { 332 // at first iteration, we don't have a previous interval 333 // we simply compare both sides of the initial interval 334 if (fa * fb <= 0) { 335 // the first interval already brackets a root 336 return new double[] { a, b }; 337 } 338 } else { 339 // we have a previous interval with constant sign and expand it, 340 // we expect sign changes to occur at boundaries 341 if (fa * previousFa <= 0) { 342 // sign change detected at near lower bound 343 return new double[] { a, previousA }; 344 } else if (fb * previousFb <= 0) { 345 // sign change detected at near upper bound 346 return new double[] { previousB, b }; 347 } 348 } 349 } 350 351 // no bracketing found 352 throw new NoBracketingException(a, b, fa, fb); 353 } 354 355 /** 356 * Compute the midpoint of two values. 357 * 358 * @param a first value. 359 * @param b second value. 360 * @return the midpoint. 361 */ 362 public static double midpoint(double a, double b) { 363 return (a + b) * 0.5; 364 } 365 366 /** 367 * Check whether the interval bounds bracket a root. That is, if the 368 * values at the endpoints are not equal to zero, then the function takes 369 * opposite signs at the endpoints. 370 * 371 * @param function Function. 372 * @param lower Lower endpoint. 373 * @param upper Upper endpoint. 374 * @return {@code true} if the function values have opposite signs at the 375 * given points. 376 * @throws NullArgumentException if {@code function} is {@code null}. 377 */ 378 public static boolean isBracketing(UnivariateFunction function, 379 final double lower, 380 final double upper) 381 throws NullArgumentException { 382 if (function == null) { 383 throw new NullArgumentException(LocalizedFormats.FUNCTION); 384 } 385 final double fLo = function.value(lower); 386 final double fHi = function.value(upper); 387 return (fLo >= 0 && fHi <= 0) || (fLo <= 0 && fHi >= 0); 388 } 389 390 /** 391 * Check whether the arguments form a (strictly) increasing sequence. 392 * 393 * @param start First number. 394 * @param mid Second number. 395 * @param end Third number. 396 * @return {@code true} if the arguments form an increasing sequence. 397 */ 398 public static boolean isSequence(final double start, 399 final double mid, 400 final double end) { 401 return start < mid && mid < end; 402 } 403 404 /** 405 * Check that the endpoints specify an interval. 406 * 407 * @param lower Lower endpoint. 408 * @param upper Upper endpoint. 409 * @throws NumberIsTooLargeException if {@code lower >= upper}. 410 */ 411 public static void verifyInterval(final double lower, 412 final double upper) 413 throws NumberIsTooLargeException { 414 if (lower >= upper) { 415 throw new NumberIsTooLargeException(LocalizedFormats.ENDPOINTS_NOT_AN_INTERVAL, 416 lower, upper, false); 417 } 418 } 419 420 /** 421 * Check that {@code lower < initial < upper}. 422 * 423 * @param lower Lower endpoint. 424 * @param initial Initial value. 425 * @param upper Upper endpoint. 426 * @throws NumberIsTooLargeException if {@code lower >= initial} or 427 * {@code initial >= upper}. 428 */ 429 public static void verifySequence(final double lower, 430 final double initial, 431 final double upper) 432 throws NumberIsTooLargeException { 433 verifyInterval(lower, initial); 434 verifyInterval(initial, upper); 435 } 436 437 /** 438 * Check that the endpoints specify an interval and the end points 439 * bracket a root. 440 * 441 * @param function Function. 442 * @param lower Lower endpoint. 443 * @param upper Upper endpoint. 444 * @throws NoBracketingException if the function has the same sign at the 445 * endpoints. 446 * @throws NullArgumentException if {@code function} is {@code null}. 447 */ 448 public static void verifyBracketing(UnivariateFunction function, 449 final double lower, 450 final double upper) 451 throws NullArgumentException, 452 NoBracketingException { 453 if (function == null) { 454 throw new NullArgumentException(LocalizedFormats.FUNCTION); 455 } 456 verifyInterval(lower, upper); 457 if (!isBracketing(function, lower, upper)) { 458 throw new NoBracketingException(lower, upper, 459 function.value(lower), 460 function.value(upper)); 461 } 462 } 463 }