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  import org.apache.commons.math4.legacy.analysis.UnivariateFunction;
20  import org.apache.commons.math4.legacy.exception.NoBracketingException;
21  import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
22  import org.apache.commons.math4.legacy.exception.NullArgumentException;
23  import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
24  import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
25  import org.apache.commons.math4.core.jdkmath.JdkMath;
26  
27  /**
28   * Utility routines for {@link UnivariateSolver} objects.
29   *
30   */
31  public final class UnivariateSolverUtils {
32      /**
33       * Class contains only static methods.
34       */
35      private UnivariateSolverUtils() {}
36  
37      /**
38       * Convenience method to find a zero of a univariate real function.  A default
39       * solver is used.
40       *
41       * @param function Function.
42       * @param x0 Lower bound for the interval.
43       * @param x1 Upper bound for the interval.
44       * @return a value where the function is zero.
45       * @throws NoBracketingException if the function has the same sign at the
46       * endpoints.
47       * @throws NullArgumentException if {@code function} is {@code null}.
48       */
49      public static double solve(UnivariateFunction function, double x0, double x1)
50          throws NullArgumentException,
51                 NoBracketingException {
52          if (function == null) {
53              throw new NullArgumentException(LocalizedFormats.FUNCTION);
54          }
55          final UnivariateSolver solver = new BrentSolver();
56          return solver.solve(Integer.MAX_VALUE, function, x0, x1);
57      }
58  
59      /**
60       * Convenience method to find a zero of a univariate real function.  A default
61       * solver is used.
62       *
63       * @param function Function.
64       * @param x0 Lower bound for the interval.
65       * @param x1 Upper bound for the interval.
66       * @param absoluteAccuracy Accuracy to be used by the solver.
67       * @return a value where the function is zero.
68       * @throws NoBracketingException if the function has the same sign at the
69       * endpoints.
70       * @throws NullArgumentException if {@code function} is {@code null}.
71       */
72      public static double solve(UnivariateFunction function,
73                                 double x0, double x1,
74                                 double absoluteAccuracy)
75          throws NullArgumentException,
76                 NoBracketingException {
77          if (function == null) {
78              throw new NullArgumentException(LocalizedFormats.FUNCTION);
79          }
80          final UnivariateSolver solver = new BrentSolver(absoluteAccuracy);
81          return solver.solve(Integer.MAX_VALUE, function, x0, x1);
82      }
83  
84      /**
85       * Force a root found by a non-bracketing solver to lie on a specified side,
86       * as if the solver were a bracketing one.
87       *
88       * @param maxEval maximal number of new evaluations of the function
89       * (evaluations already done for finding the root should have already been subtracted
90       * from this number)
91       * @param f function to solve
92       * @param bracketing bracketing solver to use for shifting the root
93       * @param baseRoot original root found by a previous non-bracketing solver
94       * @param min minimal bound of the search interval
95       * @param max maximal bound of the search interval
96       * @param allowedSolution the kind of solutions that the root-finding algorithm may
97       * accept as solutions.
98       * @return a root approximation, on the specified side of the exact root
99       * @throws NoBracketingException if the function has the same sign at the
100      * endpoints.
101      */
102     public static double forceSide(final int maxEval, final UnivariateFunction f,
103                                    final BracketedUnivariateSolver<UnivariateFunction> bracketing,
104                                    final double baseRoot, final double min, final double max,
105                                    final AllowedSolution allowedSolution)
106         throws NoBracketingException {
107 
108         if (allowedSolution == AllowedSolution.ANY_SIDE) {
109             // no further bracketing required
110             return baseRoot;
111         }
112 
113         // find a very small interval bracketing the root
114         final double step = JdkMath.max(bracketing.getAbsoluteAccuracy(),
115                                          JdkMath.abs(baseRoot * bracketing.getRelativeAccuracy()));
116         double xLo        = JdkMath.max(min, baseRoot - step);
117         double fLo        = f.value(xLo);
118         double xHi        = JdkMath.min(max, baseRoot + step);
119         double fHi        = f.value(xHi);
120         int remainingEval = maxEval - 2;
121         while (remainingEval > 0) {
122 
123             if ((fLo >= 0 && fHi <= 0) || (fLo <= 0 && fHi >= 0)) {
124                 // compute the root on the selected side
125                 return bracketing.solve(remainingEval, f, xLo, xHi, baseRoot, allowedSolution);
126             }
127 
128             // try increasing the interval
129             boolean changeLo = false;
130             boolean changeHi = false;
131             if (fLo < fHi) {
132                 // increasing function
133                 if (fLo >= 0) {
134                     changeLo = true;
135                 } else {
136                     changeHi = true;
137                 }
138             } else if (fLo > fHi) {
139                 // decreasing function
140                 if (fLo <= 0) {
141                     changeLo = true;
142                 } else {
143                     changeHi = true;
144                 }
145             } else {
146                 // unknown variation
147                 changeLo = true;
148                 changeHi = true;
149             }
150 
151             // update the lower bound
152             if (changeLo) {
153                 xLo = JdkMath.max(min, xLo - step);
154                 fLo  = f.value(xLo);
155                 remainingEval--;
156             }
157 
158             // update the higher bound
159             if (changeHi) {
160                 xHi = JdkMath.min(max, xHi + step);
161                 fHi  = f.value(xHi);
162                 remainingEval--;
163             }
164         }
165 
166         throw new NoBracketingException(LocalizedFormats.FAILED_BRACKETING,
167                                         xLo, xHi, fLo, fHi,
168                                         maxEval - remainingEval, maxEval, baseRoot,
169                                         min, max);
170     }
171 
172     /**
173      * This method simply calls {@link #bracket(UnivariateFunction, double, double, double,
174      * double, double, int) bracket(function, initial, lowerBound, upperBound, q, r, maximumIterations)}
175      * with {@code q} and {@code r} set to 1.0 and {@code maximumIterations} set to {@code Integer.MAX_VALUE}.
176      * <p>
177      * <strong>Note: </strong> this method can take {@code Integer.MAX_VALUE}
178      * iterations to throw a {@code ConvergenceException.}  Unless you are
179      * confident that there is a root between {@code lowerBound} and
180      * {@code upperBound} near {@code initial}, it is better to use
181      * {@link #bracket(UnivariateFunction, double, double, double, double,double, int)
182      * bracket(function, initial, lowerBound, upperBound, q, r, maximumIterations)},
183      * explicitly specifying the maximum number of iterations.</p>
184      *
185      * @param function Function.
186      * @param initial Initial midpoint of interval being expanded to
187      * bracket a root.
188      * @param lowerBound Lower bound (a is never lower than this value)
189      * @param upperBound Upper bound (b never is greater than this
190      * value).
191      * @return a two-element array holding a and b.
192      * @throws NoBracketingException if a root cannot be bracketted.
193      * @throws NotStrictlyPositiveException if {@code maximumIterations <= 0}.
194      * @throws NullArgumentException if {@code function} is {@code null}.
195      */
196     public static double[] bracket(UnivariateFunction function,
197                                    double initial,
198                                    double lowerBound, double upperBound)
199         throws NullArgumentException,
200                NotStrictlyPositiveException,
201                NoBracketingException {
202         return bracket(function, initial, lowerBound, upperBound, 1.0, 1.0, Integer.MAX_VALUE);
203     }
204 
205      /**
206      * This method simply calls {@link #bracket(UnivariateFunction, double, double, double,
207      * double, double, int) bracket(function, initial, lowerBound, upperBound, q, r, maximumIterations)}
208      * with {@code q} and {@code r} set to 1.0.
209      * @param function Function.
210      * @param initial Initial midpoint of interval being expanded to
211      * bracket a root.
212      * @param lowerBound Lower bound (a is never lower than this value).
213      * @param upperBound Upper bound (b never is greater than this
214      * value).
215      * @param maximumIterations Maximum number of iterations to perform
216      * @return a two element array holding a and b.
217      * @throws NoBracketingException if the algorithm fails to find a and b
218      * satisfying the desired conditions.
219      * @throws NotStrictlyPositiveException if {@code maximumIterations <= 0}.
220      * @throws NullArgumentException if {@code function} is {@code null}.
221      */
222     public static double[] bracket(UnivariateFunction function,
223                                    double initial,
224                                    double lowerBound, double upperBound,
225                                    int maximumIterations)
226         throws NullArgumentException,
227                NotStrictlyPositiveException,
228                NoBracketingException {
229         return bracket(function, initial, lowerBound, upperBound, 1.0, 1.0, maximumIterations);
230     }
231 
232     /**
233      * This method attempts to find two values a and b satisfying <ul>
234      * <li> {@code lowerBound <= a < initial < b <= upperBound} </li>
235      * <li> {@code f(a) * f(b) <= 0} </li>
236      * </ul>
237      * If {@code f} is continuous on {@code [a,b]}, this means that {@code a}
238      * and {@code b} bracket a root of {@code f}.
239      * <p>
240      * The algorithm checks the sign of \( f(l_k) \) and \( f(u_k) \) for increasing
241      * values of k, where \( l_k = max(lower, initial - \delta_k) \),
242      * \( u_k = min(upper, initial + \delta_k) \), using recurrence
243      * \( \delta_{k+1} = r \delta_k + q, \delta_0 = 0\) and starting search with \( k=1 \).
244      * The algorithm stops when one of the following happens: <ul>
245      * <li> at least one positive and one negative value have been found --  success!</li>
246      * <li> both endpoints have reached their respective limits -- NoBracketingException </li>
247      * <li> {@code maximumIterations} iterations elapse -- NoBracketingException </li></ul>
248      * <p>
249      * If different signs are found at first iteration ({@code k=1}), then the returned
250      * interval will be \( [a, b] = [l_1, u_1] \). If different signs are found at a later
251      * iteration {@code k>1}, then the returned interval will be either
252      * \( [a, b] = [l_{k+1}, l_{k}] \) or \( [a, b] = [u_{k}, u_{k+1}] \). A root solver called
253      * with these parameters will therefore start with the smallest bracketing interval known
254      * at this step.
255      * </p>
256      * <p>
257      * Interval expansion rate is tuned by changing the recurrence parameters {@code r} and
258      * {@code q}. When the multiplicative factor {@code r} is set to 1, the sequence is a
259      * simple arithmetic sequence with linear increase. When the multiplicative factor {@code r}
260      * is larger than 1, the sequence has an asymptotically exponential rate. Note than the
261      * additive parameter {@code q} should never be set to zero, otherwise the interval would
262      * degenerate to the single initial point for all values of {@code k}.
263      * </p>
264      * <p>
265      * As a rule of thumb, when the location of the root is expected to be approximately known
266      * within some error margin, {@code r} should be set to 1 and {@code q} should be set to the
267      * order of magnitude of the error margin. When the location of the root is really a wild guess,
268      * then {@code r} should be set to a value larger than 1 (typically 2 to double the interval
269      * length at each iteration) and {@code q} should be set according to half the initial
270      * search interval length.
271      * </p>
272      * <p>
273      * As an example, if we consider the trivial function {@code f(x) = 1 - x} and use
274      * {@code initial = 4}, {@code r = 1}, {@code q = 2}, the algorithm will compute
275      * {@code f(4-2) = f(2) = -1} and {@code f(4+2) = f(6) = -5} for {@code k = 1}, then
276      * {@code f(4-4) = f(0) = +1} and {@code f(4+4) = f(8) = -7} for {@code k = 2}. Then it will
277      * return the interval {@code [0, 2]} as the smallest one known to be bracketing the root.
278      * As shown by this example, the initial value (here {@code 4}) may lie outside of the returned
279      * bracketing interval.
280      * </p>
281      * @param function function to check
282      * @param initial Initial midpoint of interval being expanded to
283      * bracket a root.
284      * @param lowerBound Lower bound (a is never lower than this value).
285      * @param upperBound Upper bound (b never is greater than this
286      * value).
287      * @param q additive offset used to compute bounds sequence (must be strictly positive)
288      * @param r multiplicative factor used to compute bounds sequence
289      * @param maximumIterations Maximum number of iterations to perform
290      * @return a two element array holding the bracketing values.
291      * @exception NoBracketingException if function cannot be bracketed in the search interval
292      */
293     public static double[] bracket(final UnivariateFunction function, final double initial,
294                                    final double lowerBound, final double upperBound,
295                                    final double q, final double r, final int maximumIterations)
296         throws NoBracketingException {
297 
298         if (function == null) {
299             throw new NullArgumentException(LocalizedFormats.FUNCTION);
300         }
301         if (q <= 0)  {
302             throw new NotStrictlyPositiveException(q);
303         }
304         if (maximumIterations <= 0)  {
305             throw new NotStrictlyPositiveException(LocalizedFormats.INVALID_MAX_ITERATIONS, maximumIterations);
306         }
307         verifySequence(lowerBound, initial, upperBound);
308 
309         // initialize the recurrence
310         double a     = initial;
311         double b     = initial;
312         double fa    = Double.NaN;
313         double fb    = Double.NaN;
314         double delta = 0;
315 
316         for (int numIterations = 0;
317              numIterations < maximumIterations && (a > lowerBound || b < upperBound);
318              ++numIterations) {
319 
320             final double previousA  = a;
321             final double previousFa = fa;
322             final double previousB  = b;
323             final double previousFb = fb;
324 
325             delta = r * delta + q;
326             a     = JdkMath.max(initial - delta, lowerBound);
327             b     = JdkMath.min(initial + delta, upperBound);
328             fa    = function.value(a);
329             fb    = function.value(b);
330 
331             if (numIterations == 0) {
332                 // at first iteration, we don't have a previous interval
333                 // we simply compare both sides of the initial interval
334                 if (fa * fb <= 0) {
335                     // the first interval already brackets a root
336                     return new double[] { a, b };
337                 }
338             } else {
339                 // we have a previous interval with constant sign and expand it,
340                 // we expect sign changes to occur at boundaries
341                 if (fa * previousFa <= 0) {
342                     // sign change detected at near lower bound
343                     return new double[] { a, previousA };
344                 } else if (fb * previousFb <= 0) {
345                     // sign change detected at near upper bound
346                     return new double[] { previousB, b };
347                 }
348             }
349         }
350 
351         // no bracketing found
352         throw new NoBracketingException(a, b, fa, fb);
353     }
354 
355     /**
356      * Compute the midpoint of two values.
357      *
358      * @param a first value.
359      * @param b second value.
360      * @return the midpoint.
361      */
362     public static double midpoint(double a, double b) {
363         return (a + b) * 0.5;
364     }
365 
366     /**
367      * Check whether the interval bounds bracket a root. That is, if the
368      * values at the endpoints are not equal to zero, then the function takes
369      * opposite signs at the endpoints.
370      *
371      * @param function Function.
372      * @param lower Lower endpoint.
373      * @param upper Upper endpoint.
374      * @return {@code true} if the function values have opposite signs at the
375      * given points.
376      * @throws NullArgumentException if {@code function} is {@code null}.
377      */
378     public static boolean isBracketing(UnivariateFunction function,
379                                        final double lower,
380                                        final double upper)
381         throws NullArgumentException {
382         if (function == null) {
383             throw new NullArgumentException(LocalizedFormats.FUNCTION);
384         }
385         final double fLo = function.value(lower);
386         final double fHi = function.value(upper);
387         return (fLo >= 0 && fHi <= 0) || (fLo <= 0 && fHi >= 0);
388     }
389 
390     /**
391      * Check whether the arguments form a (strictly) increasing sequence.
392      *
393      * @param start First number.
394      * @param mid Second number.
395      * @param end Third number.
396      * @return {@code true} if the arguments form an increasing sequence.
397      */
398     public static boolean isSequence(final double start,
399                                      final double mid,
400                                      final double end) {
401         return start < mid && mid < end;
402     }
403 
404     /**
405      * Check that the endpoints specify an interval.
406      *
407      * @param lower Lower endpoint.
408      * @param upper Upper endpoint.
409      * @throws NumberIsTooLargeException if {@code lower >= upper}.
410      */
411     public static void verifyInterval(final double lower,
412                                       final double upper)
413         throws NumberIsTooLargeException {
414         if (lower >= upper) {
415             throw new NumberIsTooLargeException(LocalizedFormats.ENDPOINTS_NOT_AN_INTERVAL,
416                                                 lower, upper, false);
417         }
418     }
419 
420     /**
421      * Check that {@code lower < initial < upper}.
422      *
423      * @param lower Lower endpoint.
424      * @param initial Initial value.
425      * @param upper Upper endpoint.
426      * @throws NumberIsTooLargeException if {@code lower >= initial} or
427      * {@code initial >= upper}.
428      */
429     public static void verifySequence(final double lower,
430                                       final double initial,
431                                       final double upper)
432         throws NumberIsTooLargeException {
433         verifyInterval(lower, initial);
434         verifyInterval(initial, upper);
435     }
436 
437     /**
438      * Check that the endpoints specify an interval and the end points
439      * bracket a root.
440      *
441      * @param function Function.
442      * @param lower Lower endpoint.
443      * @param upper Upper endpoint.
444      * @throws NoBracketingException if the function has the same sign at the
445      * endpoints.
446      * @throws NullArgumentException if {@code function} is {@code null}.
447      */
448     public static void verifyBracketing(UnivariateFunction function,
449                                         final double lower,
450                                         final double upper)
451         throws NullArgumentException,
452                NoBracketingException {
453         if (function == null) {
454             throw new NullArgumentException(LocalizedFormats.FUNCTION);
455         }
456         verifyInterval(lower, upper);
457         if (!isBracketing(function, lower, upper)) {
458             throw new NoBracketingException(lower, upper,
459                                             function.value(lower),
460                                             function.value(upper));
461         }
462     }
463 }