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
20 import org.apache.commons.math4.legacy.core.Field;
21 import org.apache.commons.math4.legacy.core.RealFieldElement;
22 import org.apache.commons.math4.legacy.analysis.RealFieldUnivariateFunction;
23 import org.apache.commons.math4.legacy.exception.MathInternalError;
24 import org.apache.commons.math4.legacy.exception.NoBracketingException;
25 import org.apache.commons.math4.legacy.exception.NullArgumentException;
26 import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
27 import org.apache.commons.math4.legacy.core.IntegerSequence;
28 import org.apache.commons.math4.legacy.core.MathArrays;
29 import org.apache.commons.numbers.core.Precision;
30
31 /**
32 * This class implements a modification of the <a
33 * href="http://mathworld.wolfram.com/BrentsMethod.html"> Brent algorithm</a>.
34 * <p>
35 * The changes with respect to the original Brent algorithm are:
36 * <ul>
37 * <li>the returned value is chosen in the current interval according
38 * to user specified {@link AllowedSolution}</li>
39 * <li>the maximal order for the invert polynomial root search is
40 * user-specified instead of being invert quadratic only</li>
41 * </ul><p>
42 * The given interval must bracket the root.</p>
43 *
44 * @param <T> the type of the field elements
45 * @since 3.6
46 */
47 public class FieldBracketingNthOrderBrentSolver<T extends RealFieldElement<T>>
48 implements BracketedRealFieldUnivariateSolver<T> {
49
50 /** Maximal aging triggering an attempt to balance the bracketing interval. */
51 private static final int MAXIMAL_AGING = 2;
52
53 /** Field to which the elements belong. */
54 private final Field<T> field;
55
56 /** Maximal order. */
57 private final int maximalOrder;
58
59 /** Function value accuracy. */
60 private final T functionValueAccuracy;
61
62 /** Absolute accuracy. */
63 private final T absoluteAccuracy;
64
65 /** Relative accuracy. */
66 private final T relativeAccuracy;
67
68 /** Evaluations counter. */
69 private IntegerSequence.Incrementor evaluations;
70
71 /**
72 * Construct a solver.
73 *
74 * @param relativeAccuracy Relative accuracy.
75 * @param absoluteAccuracy Absolute accuracy.
76 * @param functionValueAccuracy Function value accuracy.
77 * @param maximalOrder maximal order.
78 * @exception NumberIsTooSmallException if maximal order is lower than 2
79 */
80 public FieldBracketingNthOrderBrentSolver(final T relativeAccuracy,
81 final T absoluteAccuracy,
82 final T functionValueAccuracy,
83 final int maximalOrder)
84 throws NumberIsTooSmallException {
85 if (maximalOrder < 2) {
86 throw new NumberIsTooSmallException(maximalOrder, 2, true);
87 }
88 this.field = relativeAccuracy.getField();
89 this.maximalOrder = maximalOrder;
90 this.absoluteAccuracy = absoluteAccuracy;
91 this.relativeAccuracy = relativeAccuracy;
92 this.functionValueAccuracy = functionValueAccuracy;
93 this.evaluations = IntegerSequence.Incrementor.create();
94 }
95
96 /** Get the maximal order.
97 * @return maximal order
98 */
99 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 }