001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.math4.legacy.analysis.solvers; 018 019 020import org.apache.commons.math4.legacy.analysis.UnivariateFunction; 021import org.apache.commons.math4.legacy.exception.MathInternalError; 022import org.apache.commons.math4.legacy.exception.NoBracketingException; 023import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException; 024import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException; 025import org.apache.commons.math4.legacy.exception.TooManyEvaluationsException; 026import org.apache.commons.math4.core.jdkmath.JdkMath; 027import org.apache.commons.numbers.core.Precision; 028 029/** 030 * This class implements a modification of the <a 031 * href="http://mathworld.wolfram.com/BrentsMethod.html"> Brent algorithm</a>. 032 * <p> 033 * The changes with respect to the original Brent algorithm are: 034 * <ul> 035 * <li>the returned value is chosen in the current interval according 036 * to user specified {@link AllowedSolution},</li> 037 * <li>the maximal order for the invert polynomial root search is 038 * user-specified instead of being invert quadratic only</li> 039 * </ul><p> 040 * The given interval must bracket the root.</p> 041 * 042 */ 043public class BracketingNthOrderBrentSolver 044 extends AbstractUnivariateSolver 045 implements BracketedUnivariateSolver<UnivariateFunction> { 046 047 /** Default absolute accuracy. */ 048 private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6; 049 050 /** Default maximal order. */ 051 private static final int DEFAULT_MAXIMAL_ORDER = 5; 052 053 /** Maximal aging triggering an attempt to balance the bracketing interval. */ 054 private static final int MAXIMAL_AGING = 2; 055 056 /** Reduction factor for attempts to balance the bracketing interval. */ 057 private static final double REDUCTION_FACTOR = 1.0 / 16.0; 058 059 /** Maximal order. */ 060 private final int maximalOrder; 061 062 /** The kinds of solutions that the algorithm may accept. */ 063 private AllowedSolution allowed; 064 065 /** 066 * Construct a solver with default accuracy and maximal order (1e-6 and 5 respectively). 067 */ 068 public BracketingNthOrderBrentSolver() { 069 this(DEFAULT_ABSOLUTE_ACCURACY, DEFAULT_MAXIMAL_ORDER); 070 } 071 072 /** 073 * Construct a solver. 074 * 075 * @param absoluteAccuracy Absolute accuracy. 076 * @param maximalOrder maximal order. 077 * @exception NumberIsTooSmallException if maximal order is lower than 2 078 */ 079 public BracketingNthOrderBrentSolver(final double absoluteAccuracy, 080 final int maximalOrder) 081 throws NumberIsTooSmallException { 082 super(absoluteAccuracy); 083 if (maximalOrder < 2) { 084 throw new NumberIsTooSmallException(maximalOrder, 2, true); 085 } 086 this.maximalOrder = maximalOrder; 087 this.allowed = AllowedSolution.ANY_SIDE; 088 } 089 090 /** 091 * Construct a solver. 092 * 093 * @param relativeAccuracy Relative accuracy. 094 * @param absoluteAccuracy Absolute accuracy. 095 * @param maximalOrder maximal order. 096 * @exception NumberIsTooSmallException if maximal order is lower than 2 097 */ 098 public BracketingNthOrderBrentSolver(final double relativeAccuracy, 099 final double absoluteAccuracy, 100 final int maximalOrder) 101 throws NumberIsTooSmallException { 102 super(relativeAccuracy, absoluteAccuracy); 103 if (maximalOrder < 2) { 104 throw new NumberIsTooSmallException(maximalOrder, 2, true); 105 } 106 this.maximalOrder = maximalOrder; 107 this.allowed = AllowedSolution.ANY_SIDE; 108 } 109 110 /** 111 * Construct a solver. 112 * 113 * @param relativeAccuracy Relative accuracy. 114 * @param absoluteAccuracy Absolute accuracy. 115 * @param functionValueAccuracy Function value accuracy. 116 * @param maximalOrder maximal order. 117 * @exception NumberIsTooSmallException if maximal order is lower than 2 118 */ 119 public BracketingNthOrderBrentSolver(final double relativeAccuracy, 120 final double absoluteAccuracy, 121 final double functionValueAccuracy, 122 final int maximalOrder) 123 throws NumberIsTooSmallException { 124 super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy); 125 if (maximalOrder < 2) { 126 throw new NumberIsTooSmallException(maximalOrder, 2, true); 127 } 128 this.maximalOrder = maximalOrder; 129 this.allowed = AllowedSolution.ANY_SIDE; 130 } 131 132 /** Get the maximal order. 133 * @return maximal order 134 */ 135 public int getMaximalOrder() { 136 return maximalOrder; 137 } 138 139 /** 140 * {@inheritDoc} 141 */ 142 @Override 143 protected double doSolve() 144 throws TooManyEvaluationsException, 145 NumberIsTooLargeException, 146 NoBracketingException { 147 // prepare arrays with the first points 148 final double[] x = new double[maximalOrder + 1]; 149 final double[] y = new double[maximalOrder + 1]; 150 x[0] = getMin(); 151 x[1] = getStartValue(); 152 x[2] = getMax(); 153 verifySequence(x[0], x[1], x[2]); 154 155 // evaluate initial guess 156 y[1] = computeObjectiveValue(x[1]); 157 if (Precision.equals(y[1], 0.0, 1)) { 158 // return the initial guess if it is a perfect root. 159 return x[1]; 160 } 161 162 // evaluate first endpoint 163 y[0] = computeObjectiveValue(x[0]); 164 if (Precision.equals(y[0], 0.0, 1)) { 165 // return the first endpoint if it is a perfect root. 166 return x[0]; 167 } 168 169 int nbPoints; 170 int signChangeIndex; 171 if (y[0] * y[1] < 0) { 172 173 // reduce interval if it brackets the root 174 nbPoints = 2; 175 signChangeIndex = 1; 176 } else { 177 178 // evaluate second endpoint 179 y[2] = computeObjectiveValue(x[2]); 180 if (Precision.equals(y[2], 0.0, 1)) { 181 // return the second endpoint if it is a perfect root. 182 return x[2]; 183 } 184 185 if (y[1] * y[2] < 0) { 186 // use all computed point as a start sampling array for solving 187 nbPoints = 3; 188 signChangeIndex = 2; 189 } else { 190 throw new NoBracketingException(x[0], x[2], y[0], y[2]); 191 } 192 } 193 194 // prepare a work array for inverse polynomial interpolation 195 final double[] tmpX = new double[x.length]; 196 197 // current tightest bracketing of the root 198 double xA = x[signChangeIndex - 1]; 199 double yA = y[signChangeIndex - 1]; 200 double absYA = JdkMath.abs(yA); 201 int agingA = 0; 202 double xB = x[signChangeIndex]; 203 double yB = y[signChangeIndex]; 204 double absYB = JdkMath.abs(yB); 205 int agingB = 0; 206 207 // search loop 208 while (true) { 209 210 // check convergence of bracketing interval 211 final double xTol = getAbsoluteAccuracy() + 212 getRelativeAccuracy() * JdkMath.max(JdkMath.abs(xA), JdkMath.abs(xB)); 213 if (xB - xA <= xTol || JdkMath.max(absYA, absYB) < getFunctionValueAccuracy()) { 214 switch (allowed) { 215 case ANY_SIDE : 216 return absYA < absYB ? xA : xB; 217 case LEFT_SIDE : 218 return xA; 219 case RIGHT_SIDE : 220 return xB; 221 case BELOW_SIDE : 222 return (yA <= 0) ? xA : xB; 223 case ABOVE_SIDE : 224 return (yA < 0) ? xB : xA; 225 default : 226 // this should never happen 227 throw new MathInternalError(); 228 } 229 } 230 231 // target for the next evaluation point 232 double targetY; 233 if (agingA >= MAXIMAL_AGING) { 234 // we keep updating the high bracket, try to compensate this 235 final int p = agingA - MAXIMAL_AGING; 236 final double weightA = (1 << p) - 1; 237 final double weightB = p + 1; 238 targetY = (weightA * yA - weightB * REDUCTION_FACTOR * yB) / (weightA + weightB); 239 } else if (agingB >= MAXIMAL_AGING) { 240 // we keep updating the low bracket, try to compensate this 241 final int p = agingB - MAXIMAL_AGING; 242 final double weightA = p + 1; 243 final double weightB = (1 << p) - 1; 244 targetY = (weightB * yB - weightA * REDUCTION_FACTOR * yA) / (weightA + weightB); 245 } else { 246 // bracketing is balanced, try to find the root itself 247 targetY = 0; 248 } 249 250 // make a few attempts to guess a root, 251 double nextX; 252 int start = 0; 253 int end = nbPoints; 254 do { 255 256 // guess a value for current target, using inverse polynomial interpolation 257 System.arraycopy(x, start, tmpX, start, end - start); 258 nextX = guessX(targetY, tmpX, y, start, end); 259 260 if (!(nextX > xA && nextX < xB)) { 261 // the guessed root is not strictly inside of the tightest bracketing interval 262 263 // the guessed root is either not strictly inside the interval or it 264 // is a NaN (which occurs when some sampling points share the same y) 265 // we try again with a lower interpolation order 266 if (signChangeIndex - start >= end - signChangeIndex) { 267 // we have more points before the sign change, drop the lowest point 268 ++start; 269 } else { 270 // we have more points after sign change, drop the highest point 271 --end; 272 } 273 274 // we need to do one more attempt 275 nextX = Double.NaN; 276 } 277 } while (Double.isNaN(nextX) && end - start > 1); 278 279 if (Double.isNaN(nextX)) { 280 // fall back to bisection 281 nextX = xA + 0.5 * (xB - xA); 282 start = signChangeIndex - 1; 283 end = signChangeIndex; 284 } 285 286 // evaluate the function at the guessed root 287 final double nextY = computeObjectiveValue(nextX); 288 if (Precision.equals(nextY, 0.0, 1)) { 289 // we have found an exact root, since it is not an approximation 290 // we don't need to bother about the allowed solutions setting 291 return nextX; 292 } 293 294 if (nbPoints > 2 && end - start != nbPoints) { 295 296 // we have been forced to ignore some points to keep bracketing, 297 // they are probably too far from the root, drop them from now on 298 nbPoints = end - start; 299 System.arraycopy(x, start, x, 0, nbPoints); 300 System.arraycopy(y, start, y, 0, nbPoints); 301 signChangeIndex -= start; 302 } else if (nbPoints == x.length) { 303 304 // we have to drop one point in order to insert the new one 305 nbPoints--; 306 307 // keep the tightest bracketing interval as centered as possible 308 if (signChangeIndex >= (x.length + 1) / 2) { 309 // we drop the lowest point, we have to shift the arrays and the index 310 System.arraycopy(x, 1, x, 0, nbPoints); 311 System.arraycopy(y, 1, y, 0, nbPoints); 312 --signChangeIndex; 313 } 314 } 315 316 // insert the last computed point 317 //(by construction, we know it lies inside the tightest bracketing interval) 318 System.arraycopy(x, signChangeIndex, x, signChangeIndex + 1, nbPoints - signChangeIndex); 319 x[signChangeIndex] = nextX; 320 System.arraycopy(y, signChangeIndex, y, signChangeIndex + 1, nbPoints - signChangeIndex); 321 y[signChangeIndex] = nextY; 322 ++nbPoints; 323 324 // update the bracketing interval 325 if (nextY * yA <= 0) { 326 // the sign change occurs before the inserted point 327 xB = nextX; 328 yB = nextY; 329 absYB = JdkMath.abs(yB); 330 ++agingA; 331 agingB = 0; 332 } else { 333 // the sign change occurs after the inserted point 334 xA = nextX; 335 yA = nextY; 336 absYA = JdkMath.abs(yA); 337 agingA = 0; 338 ++agingB; 339 340 // update the sign change index 341 signChangeIndex++; 342 } 343 } 344 } 345 346 /** Guess an x value by n<sup>th</sup> order inverse polynomial interpolation. 347 * <p> 348 * The x value is guessed by evaluating polynomial Q(y) at y = targetY, where Q 349 * is built such that for all considered points (x<sub>i</sub>, y<sub>i</sub>), 350 * Q(y<sub>i</sub>) = x<sub>i</sub>. 351 * </p> 352 * @param targetY target value for y 353 * @param x reference points abscissas for interpolation, 354 * note that this array <em>is</em> modified during computation 355 * @param y reference points ordinates for interpolation 356 * @param start start index of the points to consider (inclusive) 357 * @param end end index of the points to consider (exclusive) 358 * @return guessed root (will be a NaN if two points share the same y) 359 */ 360 private double guessX(final double targetY, final double[] x, final double[] y, 361 final int start, final int end) { 362 363 // compute Q Newton coefficients by divided differences 364 for (int i = start; i < end - 1; ++i) { 365 final int delta = i + 1 - start; 366 for (int j = end - 1; j > i; --j) { 367 x[j] = (x[j] - x[j-1]) / (y[j] - y[j - delta]); 368 } 369 } 370 371 // evaluate Q(targetY) 372 double x0 = 0; 373 for (int j = end - 1; j >= start; --j) { 374 x0 = x[j] + x0 * (targetY - y[j]); 375 } 376 377 return x0; 378 } 379 380 /** {@inheritDoc} */ 381 @Override 382 public double solve(int maxEval, UnivariateFunction f, double min, 383 double max, AllowedSolution allowedSolution) 384 throws TooManyEvaluationsException, 385 NumberIsTooLargeException, 386 NoBracketingException { 387 this.allowed = allowedSolution; 388 return super.solve(maxEval, f, min, max); 389 } 390 391 /** {@inheritDoc} */ 392 @Override 393 public double solve(int maxEval, UnivariateFunction f, double min, 394 double max, double startValue, 395 AllowedSolution allowedSolution) 396 throws TooManyEvaluationsException, 397 NumberIsTooLargeException, 398 NoBracketingException { 399 this.allowed = allowedSolution; 400 return super.solve(maxEval, f, min, max, startValue); 401 } 402}