View Javadoc
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 }