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 * https://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
18 /*
19 * This is not the original file distributed by the Apache Software Foundation
20 * It has been modified by the Hipparchus project
21 */
22 package org.hipparchus.analysis.solvers;
23
24
25 import org.hipparchus.CalculusFieldElement;
26 import org.hipparchus.Field;
27 import org.hipparchus.analysis.CalculusFieldUnivariateFunction;
28 import org.hipparchus.exception.LocalizedCoreFormats;
29 import org.hipparchus.exception.MathIllegalArgumentException;
30 import org.hipparchus.exception.MathIllegalStateException;
31 import org.hipparchus.exception.NullArgumentException;
32 import org.hipparchus.util.Incrementor;
33 import org.hipparchus.util.MathArrays;
34 import org.hipparchus.util.MathUtils;
35
36 /**
37 * This class implements a modification of the <a
38 * href="http://mathworld.wolfram.com/BrentsMethod.html"> Brent algorithm</a>.
39 * <p>
40 * The changes with respect to the original Brent algorithm are:
41 * <ul>
42 * <li>the returned value is chosen in the current interval according
43 * to user specified {@link AllowedSolution}</li>
44 * <li>the maximal order for the invert polynomial root search is
45 * user-specified instead of being invert quadratic only</li>
46 * </ul><p>
47 * The given interval must bracket the root.</p>
48 *
49 * @param <T> the type of the field elements
50 */
51 public class FieldBracketingNthOrderBrentSolver<T extends CalculusFieldElement<T>>
52 implements BracketedRealFieldUnivariateSolver<T> {
53
54 /** Maximal aging triggering an attempt to balance the bracketing interval. */
55 private static final int MAXIMAL_AGING = 2;
56
57 /** Field to which the elements belong. */
58 private final Field<T> field;
59
60 /** Maximal order. */
61 private final int maximalOrder;
62
63 /** Function value accuracy. */
64 private final T functionValueAccuracy;
65
66 /** Absolute accuracy. */
67 private final T absoluteAccuracy;
68
69 /** Relative accuracy. */
70 private final T relativeAccuracy;
71
72 /** Evaluations counter. */
73 private Incrementor evaluations;
74
75 /**
76 * Construct a solver.
77 *
78 * @param relativeAccuracy Relative accuracy.
79 * @param absoluteAccuracy Absolute accuracy.
80 * @param functionValueAccuracy Function value accuracy.
81 * @param maximalOrder maximal order.
82 * @exception MathIllegalArgumentException if maximal order is lower than 2
83 */
84 public FieldBracketingNthOrderBrentSolver(final T relativeAccuracy,
85 final T absoluteAccuracy,
86 final T functionValueAccuracy,
87 final int maximalOrder)
88 throws MathIllegalArgumentException {
89 if (maximalOrder < 2) {
90 throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
91 maximalOrder, 2);
92 }
93 this.field = relativeAccuracy.getField();
94 this.maximalOrder = maximalOrder;
95 this.absoluteAccuracy = absoluteAccuracy;
96 this.relativeAccuracy = relativeAccuracy;
97 this.functionValueAccuracy = functionValueAccuracy;
98 this.evaluations = new Incrementor();
99 }
100
101 /** Get the maximal order.
102 * @return maximal order
103 */
104 public int getMaximalOrder() {
105 return maximalOrder;
106 }
107
108 /**
109 * Get the maximal number of function evaluations.
110 *
111 * @return the maximal number of function evaluations.
112 */
113 @Override
114 public int getMaxEvaluations() {
115 return evaluations.getMaximalCount();
116 }
117
118 /**
119 * Get the number of evaluations of the objective function.
120 * The number of evaluations corresponds to the last call to the
121 * {@code optimize} method. It is 0 if the method has not been
122 * called yet.
123 *
124 * @return the number of evaluations of the objective function.
125 */
126 @Override
127 public int getEvaluations() {
128 return evaluations.getCount();
129 }
130
131 /**
132 * Get the absolute accuracy.
133 * @return absolute accuracy
134 */
135 @Override
136 public T getAbsoluteAccuracy() {
137 return absoluteAccuracy;
138 }
139
140 /**
141 * Get the relative accuracy.
142 * @return relative accuracy
143 */
144 @Override
145 public T getRelativeAccuracy() {
146 return relativeAccuracy;
147 }
148
149 /**
150 * Get the function accuracy.
151 * @return function accuracy
152 */
153 @Override
154 public T getFunctionValueAccuracy() {
155 return functionValueAccuracy;
156 }
157
158 /**
159 * Solve for a zero in the given interval.
160 * A solver may require that the interval brackets a single zero root.
161 * Solvers that do require bracketing should be able to handle the case
162 * where one of the endpoints is itself a root.
163 *
164 * @param maxEval Maximum number of evaluations.
165 * @param f Function to solve.
166 * @param min Lower bound for the interval.
167 * @param max Upper bound for the interval.
168 * @param allowedSolution The kind of solutions that the root-finding algorithm may
169 * accept as solutions.
170 * @return a value where the function is zero.
171 * @exception NullArgumentException if f is null.
172 * @exception MathIllegalArgumentException if root cannot be bracketed
173 */
174 @Override
175 public T solve(final int maxEval, final CalculusFieldUnivariateFunction<T> f,
176 final T min, final T max, final AllowedSolution allowedSolution)
177 throws MathIllegalArgumentException, NullArgumentException {
178 return solve(maxEval, f, min, max, min.add(max).divide(2), allowedSolution);
179 }
180
181 /**
182 * Solve for a zero in the given interval, start at {@code startValue}.
183 * A solver may require that the interval brackets a single zero root.
184 * Solvers that do require bracketing should be able to handle the case
185 * where one of the endpoints is itself a root.
186 *
187 * @param maxEval Maximum number of evaluations.
188 * @param f Function to solve.
189 * @param min Lower bound for the interval.
190 * @param max Upper bound for the interval.
191 * @param startValue Start value to use.
192 * @param allowedSolution The kind of solutions that the root-finding algorithm may
193 * accept as solutions.
194 * @return a value where the function is zero.
195 * @exception NullArgumentException if f is null.
196 * @exception MathIllegalArgumentException if root cannot be bracketed
197 */
198 @Override
199 public T solve(final int maxEval, final CalculusFieldUnivariateFunction<T> f,
200 final T min, final T max, final T startValue,
201 final AllowedSolution allowedSolution)
202 throws MathIllegalArgumentException, NullArgumentException {
203 // find interval containing root
204 return solveInterval(maxEval, f, min, max, startValue).getSide(allowedSolution);
205 }
206
207 /** {@inheritDoc} */
208 @Override
209 public Interval<T> solveInterval(int maxEval,
210 CalculusFieldUnivariateFunction<T> f,
211 T min,
212 T max,
213 T startValue)
214 throws MathIllegalArgumentException, MathIllegalStateException {
215
216 // Checks.
217 MathUtils.checkNotNull(f);
218
219 // Reset.
220 evaluations = evaluations.withMaximalCount(maxEval);
221 T zero = field.getZero();
222 T nan = zero.add(Double.NaN);
223
224 // prepare arrays with the first points
225 final T[] x = MathArrays.buildArray(field, maximalOrder + 1);
226 final T[] y = MathArrays.buildArray(field, maximalOrder + 1);
227 x[0] = min;
228 x[1] = startValue;
229 x[2] = max;
230
231 // evaluate initial guess
232 evaluations.increment();
233 y[1] = f.value(x[1]);
234 if (y[1].getReal() == 0.0) {
235 // return the initial guess if it is a perfect root.
236 return new Interval<>(x[1], y[1], x[1], y[1]);
237 }
238
239 // evaluate first endpoint
240 evaluations.increment();
241 y[0] = f.value(x[0]);
242 if (y[0].getReal() == 0.0) {
243 // return the first endpoint if it is a perfect root.
244 return new Interval<>(x[0], y[0], x[0], y[0]);
245 }
246
247 int nbPoints;
248 int signChangeIndex;
249 if (y[0].multiply(y[1]).getReal() < 0) {
250
251 // reduce interval if it brackets the root
252 nbPoints = 2;
253 signChangeIndex = 1;
254
255 } else {
256
257 // evaluate second endpoint
258 evaluations.increment();
259 y[2] = f.value(x[2]);
260 if (y[2].getReal() == 0.0) {
261 // return the second endpoint if it is a perfect root.
262 return new Interval<>(x[2], y[2], x[2], y[2]);
263 }
264
265 if (y[1].multiply(y[2]).getReal() < 0) {
266 // use all computed point as a start sampling array for solving
267 nbPoints = 3;
268 signChangeIndex = 2;
269 } else {
270 throw new MathIllegalArgumentException(LocalizedCoreFormats.NOT_BRACKETING_INTERVAL,
271 x[0].getReal(), x[2].getReal(),
272 y[0].getReal(), y[2].getReal());
273 }
274
275 }
276
277 // prepare a work array for inverse polynomial interpolation
278 final T[] tmpX = MathArrays.buildArray(field, x.length);
279
280 // current tightest bracketing of the root
281 T xA = x[signChangeIndex - 1];
282 T yA = y[signChangeIndex - 1];
283 T absXA = xA.abs();
284 T absYA = yA.abs();
285 int agingA = 0;
286 T xB = x[signChangeIndex];
287 T yB = y[signChangeIndex];
288 T absXB = xB.abs();
289 T absYB = yB.abs();
290 int agingB = 0;
291
292 // search loop
293 while (true) {
294
295 // check convergence of bracketing interval
296 T maxX = absXA.subtract(absXB).getReal() < 0 ? absXB : absXA;
297 T maxY = absYA.subtract(absYB).getReal() < 0 ? absYB : absYA;
298 final T xTol = absoluteAccuracy.add(relativeAccuracy.multiply(maxX));
299 final T midpoint = xA.add(xB.subtract(xA).divide(2));
300 if (xB.subtract(xA).subtract(xTol).getReal() <= 0 ||
301 maxY.subtract(functionValueAccuracy).getReal() < 0 ||
302 xA.equals(midpoint) || xB.equals(midpoint)) {
303 return new Interval<>(xA, yA, xB, yB);
304 }
305
306 // target for the next evaluation point
307 T targetY;
308 if (agingA >= MAXIMAL_AGING) {
309 // we keep updating the high bracket, try to compensate this
310 targetY = yB.divide(16).negate();
311 } else if (agingB >= MAXIMAL_AGING) {
312 // we keep updating the low bracket, try to compensate this
313 targetY = yA.divide(16).negate();
314 } else {
315 // bracketing is balanced, try to find the root itself
316 targetY = zero;
317 }
318
319 // make a few attempts to guess a root,
320 T nextX;
321 int start = 0;
322 int end = nbPoints;
323 do {
324
325 // guess a value for current target, using inverse polynomial interpolation
326 System.arraycopy(x, start, tmpX, start, end - start);
327 nextX = guessX(targetY, tmpX, y, start, end);
328
329 if (!((nextX.subtract(xA).getReal() > 0) && (nextX.subtract(xB).getReal() < 0))) {
330 // the guessed root is not strictly inside of the tightest bracketing interval
331
332 // the guessed root is either not strictly inside the interval or it
333 // is a NaN (which occurs when some sampling points share the same y)
334 // we try again with a lower interpolation order
335 if (signChangeIndex - start >= end - signChangeIndex) {
336 // we have more points before the sign change, drop the lowest point
337 ++start;
338 } else {
339 // we have more points after sign change, drop the highest point
340 --end;
341 }
342
343 // we need to do one more attempt
344 nextX = nan;
345
346 }
347
348 } while (Double.isNaN(nextX.getReal()) && (end - start > 1));
349
350 if (Double.isNaN(nextX.getReal())) {
351 // fall back to bisection
352 nextX = xA.add(xB.subtract(xA).divide(2));
353 start = signChangeIndex - 1;
354 end = signChangeIndex;
355 }
356
357 // evaluate the function at the guessed root
358 evaluations.increment();
359 final T nextY = f.value(nextX);
360 if (nextY.getReal() == 0.0) {
361 // we have found an exact root, since it is not an approximation
362 // we don't need to bother about the allowed solutions setting
363 return new Interval<>(nextX, nextY, nextX, nextY);
364 }
365
366 if ((nbPoints > 2) && (end - start != nbPoints)) {
367
368 // we have been forced to ignore some points to keep bracketing,
369 // they are probably too far from the root, drop them from now on
370 nbPoints = end - start;
371 System.arraycopy(x, start, x, 0, nbPoints);
372 System.arraycopy(y, start, y, 0, nbPoints);
373 signChangeIndex -= start;
374
375 } else if (nbPoints == x.length) {
376
377 // we have to drop one point in order to insert the new one
378 nbPoints--;
379
380 // keep the tightest bracketing interval as centered as possible
381 if (signChangeIndex >= (x.length + 1) / 2) {
382 // we drop the lowest point, we have to shift the arrays and the index
383 System.arraycopy(x, 1, x, 0, nbPoints);
384 System.arraycopy(y, 1, y, 0, nbPoints);
385 --signChangeIndex;
386 }
387
388 }
389
390 // insert the last computed point
391 //(by construction, we know it lies inside the tightest bracketing interval)
392 System.arraycopy(x, signChangeIndex, x, signChangeIndex + 1, nbPoints - signChangeIndex);
393 x[signChangeIndex] = nextX;
394 System.arraycopy(y, signChangeIndex, y, signChangeIndex + 1, nbPoints - signChangeIndex);
395 y[signChangeIndex] = nextY;
396 ++nbPoints;
397
398 // update the bracketing interval
399 if (nextY.multiply(yA).getReal() <= 0) {
400 // the sign change occurs before the inserted point
401 xB = nextX;
402 yB = nextY;
403 absYB = yB.abs();
404 ++agingA;
405 agingB = 0;
406 } else {
407 // the sign change occurs after the inserted point
408 xA = nextX;
409 yA = nextY;
410 absYA = yA.abs();
411 agingA = 0;
412 ++agingB;
413
414 // update the sign change index
415 signChangeIndex++;
416
417 }
418
419 }
420
421 }
422
423 /** Guess an x value by n<sup>th</sup> order inverse polynomial interpolation.
424 * <p>
425 * The x value is guessed by evaluating polynomial Q(y) at y = targetY, where Q
426 * is built such that for all considered points (x<sub>i</sub>, y<sub>i</sub>),
427 * Q(y<sub>i</sub>) = x<sub>i</sub>.
428 * </p>
429 * @param targetY target value for y
430 * @param x reference points abscissas for interpolation,
431 * note that this array <em>is</em> modified during computation
432 * @param y reference points ordinates for interpolation
433 * @param start start index of the points to consider (inclusive)
434 * @param end end index of the points to consider (exclusive)
435 * @return guessed root (will be a NaN if two points share the same y)
436 */
437 private T guessX(final T targetY, final T[] x, final T[] y,
438 final int start, final int end) {
439
440 // compute Q Newton coefficients by divided differences
441 for (int i = start; i < end - 1; ++i) {
442 final int delta = i + 1 - start;
443 for (int j = end - 1; j > i; --j) {
444 x[j] = x[j].subtract(x[j-1]).divide(y[j].subtract(y[j - delta]));
445 }
446 }
447
448 // evaluate Q(targetY)
449 T x0 = field.getZero();
450 for (int j = end - 1; j >= start; --j) {
451 x0 = x[j].add(x0.multiply(targetY.subtract(y[j])));
452 }
453
454 return x0;
455
456 }
457
458 }