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.analysis.UnivariateFunction;
21  import org.apache.commons.math4.legacy.exception.MathInternalError;
22  import org.apache.commons.math4.legacy.exception.NoBracketingException;
23  import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
24  import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
25  import org.apache.commons.math4.legacy.exception.TooManyEvaluationsException;
26  import org.apache.commons.math4.core.jdkmath.JdkMath;
27  import org.apache.commons.numbers.core.Precision;
28  
29  /**
30   * This class implements a modification of the <a
31   * href="http://mathworld.wolfram.com/BrentsMethod.html"> Brent algorithm</a>.
32   * <p>
33   * The changes with respect to the original Brent algorithm are:
34   * <ul>
35   *   <li>the returned value is chosen in the current interval according
36   *   to user specified {@link AllowedSolution},</li>
37   *   <li>the maximal order for the invert polynomial root search is
38   *   user-specified instead of being invert quadratic only</li>
39   * </ul><p>
40   * The given interval must bracket the root.</p>
41   *
42   */
43  public class BracketingNthOrderBrentSolver
44      extends AbstractUnivariateSolver
45      implements BracketedUnivariateSolver<UnivariateFunction> {
46  
47      /** Default absolute accuracy. */
48      private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
49  
50      /** Default maximal order. */
51      private static final int DEFAULT_MAXIMAL_ORDER = 5;
52  
53      /** Maximal aging triggering an attempt to balance the bracketing interval. */
54      private static final int MAXIMAL_AGING = 2;
55  
56      /** Reduction factor for attempts to balance the bracketing interval. */
57      private static final double REDUCTION_FACTOR = 1.0 / 16.0;
58  
59      /** Maximal order. */
60      private final int maximalOrder;
61  
62      /** The kinds of solutions that the algorithm may accept. */
63      private AllowedSolution allowed;
64  
65      /**
66       * Construct a solver with default accuracy and maximal order (1e-6 and 5 respectively).
67       */
68      public BracketingNthOrderBrentSolver() {
69          this(DEFAULT_ABSOLUTE_ACCURACY, DEFAULT_MAXIMAL_ORDER);
70      }
71  
72      /**
73       * Construct a solver.
74       *
75       * @param absoluteAccuracy Absolute accuracy.
76       * @param maximalOrder maximal order.
77       * @exception NumberIsTooSmallException if maximal order is lower than 2
78       */
79      public BracketingNthOrderBrentSolver(final double absoluteAccuracy,
80                                           final int maximalOrder)
81          throws NumberIsTooSmallException {
82          super(absoluteAccuracy);
83          if (maximalOrder < 2) {
84              throw new NumberIsTooSmallException(maximalOrder, 2, true);
85          }
86          this.maximalOrder = maximalOrder;
87          this.allowed = AllowedSolution.ANY_SIDE;
88      }
89  
90      /**
91       * Construct a solver.
92       *
93       * @param relativeAccuracy Relative accuracy.
94       * @param absoluteAccuracy Absolute accuracy.
95       * @param maximalOrder maximal order.
96       * @exception NumberIsTooSmallException if maximal order is lower than 2
97       */
98      public BracketingNthOrderBrentSolver(final double relativeAccuracy,
99                                           final double absoluteAccuracy,
100                                          final int maximalOrder)
101         throws NumberIsTooSmallException {
102         super(relativeAccuracy, absoluteAccuracy);
103         if (maximalOrder < 2) {
104             throw new NumberIsTooSmallException(maximalOrder, 2, true);
105         }
106         this.maximalOrder = maximalOrder;
107         this.allowed = AllowedSolution.ANY_SIDE;
108     }
109 
110     /**
111      * Construct a solver.
112      *
113      * @param relativeAccuracy Relative accuracy.
114      * @param absoluteAccuracy Absolute accuracy.
115      * @param functionValueAccuracy Function value accuracy.
116      * @param maximalOrder maximal order.
117      * @exception NumberIsTooSmallException if maximal order is lower than 2
118      */
119     public BracketingNthOrderBrentSolver(final double relativeAccuracy,
120                                          final double absoluteAccuracy,
121                                          final double functionValueAccuracy,
122                                          final int maximalOrder)
123         throws NumberIsTooSmallException {
124         super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
125         if (maximalOrder < 2) {
126             throw new NumberIsTooSmallException(maximalOrder, 2, true);
127         }
128         this.maximalOrder = maximalOrder;
129         this.allowed = AllowedSolution.ANY_SIDE;
130     }
131 
132     /** Get the maximal order.
133      * @return maximal order
134      */
135     public int getMaximalOrder() {
136         return maximalOrder;
137     }
138 
139     /**
140      * {@inheritDoc}
141      */
142     @Override
143     protected double doSolve()
144         throws TooManyEvaluationsException,
145                NumberIsTooLargeException,
146                NoBracketingException {
147         // prepare arrays with the first points
148         final double[] x = new double[maximalOrder + 1];
149         final double[] y = new double[maximalOrder + 1];
150         x[0] = getMin();
151         x[1] = getStartValue();
152         x[2] = getMax();
153         verifySequence(x[0], x[1], x[2]);
154 
155         // evaluate initial guess
156         y[1] = computeObjectiveValue(x[1]);
157         if (Precision.equals(y[1], 0.0, 1)) {
158             // return the initial guess if it is a perfect root.
159             return x[1];
160         }
161 
162         // evaluate first  endpoint
163         y[0] = computeObjectiveValue(x[0]);
164         if (Precision.equals(y[0], 0.0, 1)) {
165             // return the first endpoint if it is a perfect root.
166             return x[0];
167         }
168 
169         int nbPoints;
170         int signChangeIndex;
171         if (y[0] * y[1] < 0) {
172 
173             // reduce interval if it brackets the root
174             nbPoints        = 2;
175             signChangeIndex = 1;
176         } else {
177 
178             // evaluate second endpoint
179             y[2] = computeObjectiveValue(x[2]);
180             if (Precision.equals(y[2], 0.0, 1)) {
181                 // return the second endpoint if it is a perfect root.
182                 return x[2];
183             }
184 
185             if (y[1] * y[2] < 0) {
186                 // use all computed point as a start sampling array for solving
187                 nbPoints        = 3;
188                 signChangeIndex = 2;
189             } else {
190                 throw new NoBracketingException(x[0], x[2], y[0], y[2]);
191             }
192         }
193 
194         // prepare a work array for inverse polynomial interpolation
195         final double[] tmpX = new double[x.length];
196 
197         // current tightest bracketing of the root
198         double xA    = x[signChangeIndex - 1];
199         double yA    = y[signChangeIndex - 1];
200         double absYA = JdkMath.abs(yA);
201         int agingA   = 0;
202         double xB    = x[signChangeIndex];
203         double yB    = y[signChangeIndex];
204         double absYB = JdkMath.abs(yB);
205         int agingB   = 0;
206 
207         // search loop
208         while (true) {
209 
210             // check convergence of bracketing interval
211             final double xTol = getAbsoluteAccuracy() +
212                                 getRelativeAccuracy() * JdkMath.max(JdkMath.abs(xA), JdkMath.abs(xB));
213             if (xB - xA <= xTol || JdkMath.max(absYA, absYB) < getFunctionValueAccuracy()) {
214                 switch (allowed) {
215                 case ANY_SIDE :
216                     return absYA < absYB ? xA : xB;
217                 case LEFT_SIDE :
218                     return xA;
219                 case RIGHT_SIDE :
220                     return xB;
221                 case BELOW_SIDE :
222                     return (yA <= 0) ? xA : xB;
223                 case ABOVE_SIDE :
224                     return (yA <  0) ? xB : xA;
225                 default :
226                     // this should never happen
227                     throw new MathInternalError();
228                 }
229             }
230 
231             // target for the next evaluation point
232             double targetY;
233             if (agingA >= MAXIMAL_AGING) {
234                 // we keep updating the high bracket, try to compensate this
235                 final int p = agingA - MAXIMAL_AGING;
236                 final double weightA = (1 << p) - 1;
237                 final double weightB = p + 1;
238                 targetY = (weightA * yA - weightB * REDUCTION_FACTOR * yB) / (weightA + weightB);
239             } else if (agingB >= MAXIMAL_AGING) {
240                 // we keep updating the low bracket, try to compensate this
241                 final int p = agingB - MAXIMAL_AGING;
242                 final double weightA = p + 1;
243                 final double weightB = (1 << p) - 1;
244                 targetY = (weightB * yB - weightA * REDUCTION_FACTOR * yA) / (weightA + weightB);
245             } else {
246                 // bracketing is balanced, try to find the root itself
247                 targetY = 0;
248             }
249 
250             // make a few attempts to guess a root,
251             double nextX;
252             int start = 0;
253             int end   = nbPoints;
254             do {
255 
256                 // guess a value for current target, using inverse polynomial interpolation
257                 System.arraycopy(x, start, tmpX, start, end - start);
258                 nextX = guessX(targetY, tmpX, y, start, end);
259 
260                 if (!(nextX > xA && nextX < xB)) {
261                     // the guessed root is not strictly inside of the tightest bracketing interval
262 
263                     // the guessed root is either not strictly inside the interval or it
264                     // is a NaN (which occurs when some sampling points share the same y)
265                     // we try again with a lower interpolation order
266                     if (signChangeIndex - start >= end - signChangeIndex) {
267                         // we have more points before the sign change, drop the lowest point
268                         ++start;
269                     } else {
270                         // we have more points after sign change, drop the highest point
271                         --end;
272                     }
273 
274                     // we need to do one more attempt
275                     nextX = Double.NaN;
276                 }
277             } while (Double.isNaN(nextX) && end - start > 1);
278 
279             if (Double.isNaN(nextX)) {
280                 // fall back to bisection
281                 nextX = xA + 0.5 * (xB - xA);
282                 start = signChangeIndex - 1;
283                 end   = signChangeIndex;
284             }
285 
286             // evaluate the function at the guessed root
287             final double nextY = computeObjectiveValue(nextX);
288             if (Precision.equals(nextY, 0.0, 1)) {
289                 // we have found an exact root, since it is not an approximation
290                 // we don't need to bother about the allowed solutions setting
291                 return nextX;
292             }
293 
294             if (nbPoints > 2 && end - start != nbPoints) {
295 
296                 // we have been forced to ignore some points to keep bracketing,
297                 // they are probably too far from the root, drop them from now on
298                 nbPoints = end - start;
299                 System.arraycopy(x, start, x, 0, nbPoints);
300                 System.arraycopy(y, start, y, 0, nbPoints);
301                 signChangeIndex -= start;
302             } else  if (nbPoints == x.length) {
303 
304                 // we have to drop one point in order to insert the new one
305                 nbPoints--;
306 
307                 // keep the tightest bracketing interval as centered as possible
308                 if (signChangeIndex >= (x.length + 1) / 2) {
309                     // we drop the lowest point, we have to shift the arrays and the index
310                     System.arraycopy(x, 1, x, 0, nbPoints);
311                     System.arraycopy(y, 1, y, 0, nbPoints);
312                     --signChangeIndex;
313                 }
314             }
315 
316             // insert the last computed point
317             //(by construction, we know it lies inside the tightest bracketing interval)
318             System.arraycopy(x, signChangeIndex, x, signChangeIndex + 1, nbPoints - signChangeIndex);
319             x[signChangeIndex] = nextX;
320             System.arraycopy(y, signChangeIndex, y, signChangeIndex + 1, nbPoints - signChangeIndex);
321             y[signChangeIndex] = nextY;
322             ++nbPoints;
323 
324             // update the bracketing interval
325             if (nextY * yA <= 0) {
326                 // the sign change occurs before the inserted point
327                 xB = nextX;
328                 yB = nextY;
329                 absYB = JdkMath.abs(yB);
330                 ++agingA;
331                 agingB = 0;
332             } else {
333                 // the sign change occurs after the inserted point
334                 xA = nextX;
335                 yA = nextY;
336                 absYA = JdkMath.abs(yA);
337                 agingA = 0;
338                 ++agingB;
339 
340                 // update the sign change index
341                 signChangeIndex++;
342             }
343         }
344     }
345 
346     /** Guess an x value by n<sup>th</sup> order inverse polynomial interpolation.
347      * <p>
348      * The x value is guessed by evaluating polynomial Q(y) at y = targetY, where Q
349      * is built such that for all considered points (x<sub>i</sub>, y<sub>i</sub>),
350      * Q(y<sub>i</sub>) = x<sub>i</sub>.
351      * </p>
352      * @param targetY target value for y
353      * @param x reference points abscissas for interpolation,
354      * note that this array <em>is</em> modified during computation
355      * @param y reference points ordinates for interpolation
356      * @param start start index of the points to consider (inclusive)
357      * @param end end index of the points to consider (exclusive)
358      * @return guessed root (will be a NaN if two points share the same y)
359      */
360     private double guessX(final double targetY, final double[] x, final double[] y,
361                           final int start, final int end) {
362 
363         // compute Q Newton coefficients by divided differences
364         for (int i = start; i < end - 1; ++i) {
365             final int delta = i + 1 - start;
366             for (int j = end - 1; j > i; --j) {
367                 x[j] = (x[j] - x[j-1]) / (y[j] - y[j - delta]);
368             }
369         }
370 
371         // evaluate Q(targetY)
372         double x0 = 0;
373         for (int j = end - 1; j >= start; --j) {
374             x0 = x[j] + x0 * (targetY - y[j]);
375         }
376 
377         return x0;
378     }
379 
380     /** {@inheritDoc} */
381     @Override
382     public double solve(int maxEval, UnivariateFunction f, double min,
383                         double max, AllowedSolution allowedSolution)
384         throws TooManyEvaluationsException,
385                NumberIsTooLargeException,
386                NoBracketingException {
387         this.allowed = allowedSolution;
388         return super.solve(maxEval, f, min, max);
389     }
390 
391     /** {@inheritDoc} */
392     @Override
393     public double solve(int maxEval, UnivariateFunction f, double min,
394                         double max, double startValue,
395                         AllowedSolution allowedSolution)
396         throws TooManyEvaluationsException,
397                NumberIsTooLargeException,
398                NoBracketingException {
399         this.allowed = allowedSolution;
400         return super.solve(maxEval, f, min, max, startValue);
401     }
402 }