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.core.Field; 021import org.apache.commons.math4.legacy.core.RealFieldElement; 022import org.apache.commons.math4.legacy.analysis.RealFieldUnivariateFunction; 023import org.apache.commons.math4.legacy.exception.MathInternalError; 024import org.apache.commons.math4.legacy.exception.NoBracketingException; 025import org.apache.commons.math4.legacy.exception.NullArgumentException; 026import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException; 027import org.apache.commons.math4.legacy.core.IntegerSequence; 028import org.apache.commons.math4.legacy.core.MathArrays; 029import org.apache.commons.numbers.core.Precision; 030 031/** 032 * This class implements a modification of the <a 033 * href="http://mathworld.wolfram.com/BrentsMethod.html"> Brent algorithm</a>. 034 * <p> 035 * The changes with respect to the original Brent algorithm are: 036 * <ul> 037 * <li>the returned value is chosen in the current interval according 038 * to user specified {@link AllowedSolution}</li> 039 * <li>the maximal order for the invert polynomial root search is 040 * user-specified instead of being invert quadratic only</li> 041 * </ul><p> 042 * The given interval must bracket the root.</p> 043 * 044 * @param <T> the type of the field elements 045 * @since 3.6 046 */ 047public class FieldBracketingNthOrderBrentSolver<T extends RealFieldElement<T>> 048 implements BracketedRealFieldUnivariateSolver<T> { 049 050 /** Maximal aging triggering an attempt to balance the bracketing interval. */ 051 private static final int MAXIMAL_AGING = 2; 052 053 /** Field to which the elements belong. */ 054 private final Field<T> field; 055 056 /** Maximal order. */ 057 private final int maximalOrder; 058 059 /** Function value accuracy. */ 060 private final T functionValueAccuracy; 061 062 /** Absolute accuracy. */ 063 private final T absoluteAccuracy; 064 065 /** Relative accuracy. */ 066 private final T relativeAccuracy; 067 068 /** Evaluations counter. */ 069 private IntegerSequence.Incrementor evaluations; 070 071 /** 072 * Construct a solver. 073 * 074 * @param relativeAccuracy Relative accuracy. 075 * @param absoluteAccuracy Absolute accuracy. 076 * @param functionValueAccuracy Function value accuracy. 077 * @param maximalOrder maximal order. 078 * @exception NumberIsTooSmallException if maximal order is lower than 2 079 */ 080 public FieldBracketingNthOrderBrentSolver(final T relativeAccuracy, 081 final T absoluteAccuracy, 082 final T functionValueAccuracy, 083 final int maximalOrder) 084 throws NumberIsTooSmallException { 085 if (maximalOrder < 2) { 086 throw new NumberIsTooSmallException(maximalOrder, 2, true); 087 } 088 this.field = relativeAccuracy.getField(); 089 this.maximalOrder = maximalOrder; 090 this.absoluteAccuracy = absoluteAccuracy; 091 this.relativeAccuracy = relativeAccuracy; 092 this.functionValueAccuracy = functionValueAccuracy; 093 this.evaluations = IntegerSequence.Incrementor.create(); 094 } 095 096 /** Get the maximal order. 097 * @return maximal order 098 */ 099 public int getMaximalOrder() { 100 return maximalOrder; 101 } 102 103 /** 104 * Get the maximal number of function evaluations. 105 * 106 * @return the maximal number of function evaluations. 107 */ 108 @Override 109 public int getMaxEvaluations() { 110 return evaluations.getMaximalCount(); 111 } 112 113 /** 114 * Get the number of evaluations of the objective function. 115 * The number of evaluations corresponds to the last call to the 116 * {@code optimize} method. It is 0 if the method has not been 117 * called yet. 118 * 119 * @return the number of evaluations of the objective function. 120 */ 121 @Override 122 public int getEvaluations() { 123 return evaluations.getCount(); 124 } 125 126 /** 127 * Get the absolute accuracy. 128 * @return absolute accuracy 129 */ 130 @Override 131 public T getAbsoluteAccuracy() { 132 return absoluteAccuracy; 133 } 134 135 /** 136 * Get the relative accuracy. 137 * @return relative accuracy 138 */ 139 @Override 140 public T getRelativeAccuracy() { 141 return relativeAccuracy; 142 } 143 144 /** 145 * Get the function accuracy. 146 * @return function accuracy 147 */ 148 @Override 149 public T getFunctionValueAccuracy() { 150 return functionValueAccuracy; 151 } 152 153 /** 154 * Solve for a zero in the given interval. 155 * A solver may require that the interval brackets a single zero root. 156 * Solvers that do require bracketing should be able to handle the case 157 * where one of the endpoints is itself a root. 158 * 159 * @param maxEval Maximum number of evaluations. 160 * @param f Function to solve. 161 * @param min Lower bound for the interval. 162 * @param max Upper bound for the interval. 163 * @param allowedSolution The kind of solutions that the root-finding algorithm may 164 * accept as solutions. 165 * @return a value where the function is zero. 166 * @exception NullArgumentException if f is null. 167 * @exception NoBracketingException if root cannot be bracketed 168 */ 169 @Override 170 public T solve(final int maxEval, final RealFieldUnivariateFunction<T> f, 171 final T min, final T max, final AllowedSolution allowedSolution) 172 throws NullArgumentException, NoBracketingException { 173 return solve(maxEval, f, min, max, min.add(max).divide(2), allowedSolution); 174 } 175 176 /** 177 * Solve for a zero in the given interval, start at {@code startValue}. 178 * A solver may require that the interval brackets a single zero root. 179 * Solvers that do require bracketing should be able to handle the case 180 * where one of the endpoints is itself a root. 181 * 182 * @param maxEval Maximum number of evaluations. 183 * @param f Function to solve. 184 * @param min Lower bound for the interval. 185 * @param max Upper bound for the interval. 186 * @param startValue Start value to use. 187 * @param allowedSolution The kind of solutions that the root-finding algorithm may 188 * accept as solutions. 189 * @return a value where the function is zero. 190 * @exception NullArgumentException if f is null. 191 * @exception NoBracketingException if root cannot be bracketed 192 */ 193 @Override 194 public T solve(final int maxEval, final RealFieldUnivariateFunction<T> f, 195 final T min, final T max, final T startValue, 196 final AllowedSolution allowedSolution) 197 throws NullArgumentException, NoBracketingException { 198 199 // Checks. 200 NullArgumentException.check(f); 201 202 // Reset. 203 evaluations = evaluations.withMaximalCount(maxEval).withStart(0); 204 T zero = field.getZero(); 205 T nan = zero.add(Double.NaN); 206 207 // prepare arrays with the first points 208 final T[] x = MathArrays.buildArray(field, maximalOrder + 1); 209 final T[] y = MathArrays.buildArray(field, maximalOrder + 1); 210 x[0] = min; 211 x[1] = startValue; 212 x[2] = max; 213 214 // evaluate initial guess 215 evaluations.increment(); 216 y[1] = f.value(x[1]); 217 if (Precision.equals(y[1].getReal(), 0.0, 1)) { 218 // return the initial guess if it is a perfect root. 219 return x[1]; 220 } 221 222 // evaluate first endpoint 223 evaluations.increment(); 224 y[0] = f.value(x[0]); 225 if (Precision.equals(y[0].getReal(), 0.0, 1)) { 226 // return the first endpoint if it is a perfect root. 227 return x[0]; 228 } 229 230 int nbPoints; 231 int signChangeIndex; 232 if (y[0].multiply(y[1]).getReal() < 0) { 233 234 // reduce interval if it brackets the root 235 nbPoints = 2; 236 signChangeIndex = 1; 237 } else { 238 239 // evaluate second endpoint 240 evaluations.increment(); 241 y[2] = f.value(x[2]); 242 if (Precision.equals(y[2].getReal(), 0.0, 1)) { 243 // return the second endpoint if it is a perfect root. 244 return x[2]; 245 } 246 247 if (y[1].multiply(y[2]).getReal() < 0) { 248 // use all computed point as a start sampling array for solving 249 nbPoints = 3; 250 signChangeIndex = 2; 251 } else { 252 throw new NoBracketingException(x[0].getReal(), x[2].getReal(), 253 y[0].getReal(), y[2].getReal()); 254 } 255 } 256 257 // prepare a work array for inverse polynomial interpolation 258 final T[] tmpX = MathArrays.buildArray(field, x.length); 259 260 // current tightest bracketing of the root 261 T xA = x[signChangeIndex - 1]; 262 T yA = y[signChangeIndex - 1]; 263 T absXA = xA.abs(); 264 T absYA = yA.abs(); 265 int agingA = 0; 266 T xB = x[signChangeIndex]; 267 T yB = y[signChangeIndex]; 268 T absXB = xB.abs(); 269 T absYB = yB.abs(); 270 int agingB = 0; 271 272 // search loop 273 while (true) { 274 275 // check convergence of bracketing interval 276 T maxX = absXA.subtract(absXB).getReal() < 0 ? absXB : absXA; 277 T maxY = absYA.subtract(absYB).getReal() < 0 ? absYB : absYA; 278 final T xTol = absoluteAccuracy.add(relativeAccuracy.multiply(maxX)); 279 if (xB.subtract(xA).subtract(xTol).getReal() <= 0 || 280 maxY.subtract(functionValueAccuracy).getReal() < 0) { 281 switch (allowedSolution) { 282 case ANY_SIDE : 283 return absYA.subtract(absYB).getReal() < 0 ? xA : xB; 284 case LEFT_SIDE : 285 return xA; 286 case RIGHT_SIDE : 287 return xB; 288 case BELOW_SIDE : 289 return yA.getReal() <= 0 ? xA : xB; 290 case ABOVE_SIDE : 291 return yA.getReal() < 0 ? xB : xA; 292 default : 293 // this should never happen 294 throw new MathInternalError(null); 295 } 296 } 297 298 // target for the next evaluation point 299 T targetY; 300 if (agingA >= MAXIMAL_AGING) { 301 // we keep updating the high bracket, try to compensate this 302 targetY = yB.divide(16).negate(); 303 } else if (agingB >= MAXIMAL_AGING) { 304 // we keep updating the low bracket, try to compensate this 305 targetY = yA.divide(16).negate(); 306 } else { 307 // bracketing is balanced, try to find the root itself 308 targetY = zero; 309 } 310 311 // make a few attempts to guess a root, 312 T nextX; 313 int start = 0; 314 int end = nbPoints; 315 do { 316 317 // guess a value for current target, using inverse polynomial interpolation 318 System.arraycopy(x, start, tmpX, start, end - start); 319 nextX = guessX(targetY, tmpX, y, start, end); 320 321 if (!(nextX.subtract(xA).getReal() > 0 && nextX.subtract(xB).getReal() < 0)) { 322 // the guessed root is not strictly inside of the tightest bracketing interval 323 324 // the guessed root is either not strictly inside the interval or it 325 // is a NaN (which occurs when some sampling points share the same y) 326 // we try again with a lower interpolation order 327 if (signChangeIndex - start >= end - signChangeIndex) { 328 // we have more points before the sign change, drop the lowest point 329 ++start; 330 } else { 331 // we have more points after sign change, drop the highest point 332 --end; 333 } 334 335 // we need to do one more attempt 336 nextX = nan; 337 } 338 } while (Double.isNaN(nextX.getReal()) && end - start > 1); 339 340 if (Double.isNaN(nextX.getReal())) { 341 // fall back to bisection 342 nextX = xA.add(xB.subtract(xA).divide(2)); 343 start = signChangeIndex - 1; 344 end = signChangeIndex; 345 } 346 347 // evaluate the function at the guessed root 348 evaluations.increment(); 349 final T nextY = f.value(nextX); 350 if (Precision.equals(nextY.getReal(), 0.0, 1)) { 351 // we have found an exact root, since it is not an approximation 352 // we don't need to bother about the allowed solutions setting 353 return nextX; 354 } 355 356 if (nbPoints > 2 && end - start != nbPoints) { 357 358 // we have been forced to ignore some points to keep bracketing, 359 // they are probably too far from the root, drop them from now on 360 nbPoints = end - start; 361 System.arraycopy(x, start, x, 0, nbPoints); 362 System.arraycopy(y, start, y, 0, nbPoints); 363 signChangeIndex -= start; 364 } else if (nbPoints == x.length) { 365 366 // we have to drop one point in order to insert the new one 367 nbPoints--; 368 369 // keep the tightest bracketing interval as centered as possible 370 if (signChangeIndex >= (x.length + 1) / 2) { 371 // we drop the lowest point, we have to shift the arrays and the index 372 System.arraycopy(x, 1, x, 0, nbPoints); 373 System.arraycopy(y, 1, y, 0, nbPoints); 374 --signChangeIndex; 375 } 376 } 377 378 // insert the last computed point 379 //(by construction, we know it lies inside the tightest bracketing interval) 380 System.arraycopy(x, signChangeIndex, x, signChangeIndex + 1, nbPoints - signChangeIndex); 381 x[signChangeIndex] = nextX; 382 System.arraycopy(y, signChangeIndex, y, signChangeIndex + 1, nbPoints - signChangeIndex); 383 y[signChangeIndex] = nextY; 384 ++nbPoints; 385 386 // update the bracketing interval 387 if (nextY.multiply(yA).getReal() <= 0) { 388 // the sign change occurs before the inserted point 389 xB = nextX; 390 yB = nextY; 391 absYB = yB.abs(); 392 ++agingA; 393 agingB = 0; 394 } else { 395 // the sign change occurs after the inserted point 396 xA = nextX; 397 yA = nextY; 398 absYA = yA.abs(); 399 agingA = 0; 400 ++agingB; 401 402 // update the sign change index 403 signChangeIndex++; 404 } 405 } 406 } 407 408 /** Guess an x value by n<sup>th</sup> order inverse polynomial interpolation. 409 * <p> 410 * The x value is guessed by evaluating polynomial Q(y) at y = targetY, where Q 411 * is built such that for all considered points (x<sub>i</sub>, y<sub>i</sub>), 412 * Q(y<sub>i</sub>) = x<sub>i</sub>. 413 * </p> 414 * @param targetY target value for y 415 * @param x reference points abscissas for interpolation, 416 * note that this array <em>is</em> modified during computation 417 * @param y reference points ordinates for interpolation 418 * @param start start index of the points to consider (inclusive) 419 * @param end end index of the points to consider (exclusive) 420 * @return guessed root (will be a NaN if two points share the same y) 421 */ 422 private T guessX(final T targetY, final T[] x, final T[] y, 423 final int start, final int end) { 424 425 // compute Q Newton coefficients by divided differences 426 for (int i = start; i < end - 1; ++i) { 427 final int delta = i + 1 - start; 428 for (int j = end - 1; j > i; --j) { 429 x[j] = x[j].subtract(x[j-1]).divide(y[j].subtract(y[j - delta])); 430 } 431 } 432 433 // evaluate Q(targetY) 434 T x0 = field.getZero(); 435 for (int j = end - 1; j >= start; --j) { 436 x0 = x[j].add(x0.multiply(targetY.subtract(y[j]))); 437 } 438 439 return x0; 440 } 441}