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    *      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 }