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