View Javadoc
1   /*
2    * Copyright (C) 2012 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.numerical.roots;
17  
18  import com.irurueta.numerical.EvaluationException;
19  import com.irurueta.numerical.InvalidBracketRangeException;
20  import com.irurueta.numerical.LockedException;
21  import com.irurueta.numerical.NotReadyException;
22  import com.irurueta.numerical.SingleDimensionFunctionEvaluatorListener;
23  
24  /**
25   * This class searches for a single REAL root on a single dimension function
26   * (i.e. f(x) ). The root to be found must be inside a given or computed bracket
27   * of values.
28   * <p>
29   * This class uses the bisection method, which is one of the slowest but most
30   * reliable existing methods (i.e. the method doubles the accuracy on each
31   * iteration, so in 64 iterations the highest accuracy provided by a double
32   * value can be obtained).
33   * <p>
34   * In comparison to other methods, the Bisection one can find roots even in
35   * situations where isn't a zero crossing. In other words, bracket estimation
36   * might fail for this class, but even then a root might be obtained.
37   * <p>
38   * In order to find a root around a given range of values, a bracket of values
39   * can be provided. Otherwise, a bracket can be computed by attempting to find
40   * a zero-crossing while expanding an initial range of values.
41   * <p>
42   * Bracket computation estimates a larger range of values, which can later be
43   * refined in order to estimate a given root.
44   * <p>
45   * This class is based on the implementation of Numerical Recipes 3rd ed.
46   * Section 9.1.1 page 447.
47   */
48  public class BisectionSingleRootEstimator extends BracketedSingleRootEstimator {
49  
50      /**
51       * Constant defining maximum number of iterations to estimate a root.
52       */
53      public static final int JMAX = 50;
54  
55      /**
56       * Constant defining default tolerance to find a root.
57       */
58      public static final double DEFAULT_TOLERANCE = 1e-6;
59  
60      /**
61       * Minimum allowed tolerance that can be set.
62       */
63      public static final double MIN_TOLERANCE = 0.0;
64  
65      /**
66       * Tolerance to find a root. Whenever the variation of the estimated root is
67       * smaller than the provided tolerance, then the algorithm is assumed to be
68       * converged.
69       */
70      private double tolerance;
71  
72      /**
73       * Empty constructor.
74       */
75      public BisectionSingleRootEstimator() {
76          super();
77          tolerance = DEFAULT_TOLERANCE;
78      }
79  
80      /**
81       * Constructor.
82       *
83       * @param listener     Listener to evaluate a single dimension function f(x)
84       *                     to find its roots.
85       * @param minEvalPoint Smallest value inside the bracket of values where the
86       *                     root will be searched.
87       * @param maxEvalPoint Largest value inside the bracket of values where the
88       *                     root will be searched.
89       * @param tolerance    Tolerance to find a root. During the estimation of a
90       *                     root, if the variation of the estimated root is smaller than the provided
91       *                     tolerance, then the algorithm is assumed to be converged, and the root
92       *                     is ensured to have an accuracy that equals tolerance.
93       * @throws InvalidBracketRangeException Raised if minEvalPoint &lt;
94       *                                      maxEvalPoint.
95       * @throws IllegalArgumentException     Raised if provided tolerance is negative.
96       */
97      public BisectionSingleRootEstimator(
98              final SingleDimensionFunctionEvaluatorListener listener, final double minEvalPoint,
99              final double maxEvalPoint, final double tolerance) throws InvalidBracketRangeException {
100         super(listener, minEvalPoint, maxEvalPoint);
101         internalSetTolerance(tolerance);
102     }
103 
104     /**
105      * Returns tolerance to find a root. Whenever the variation of the estimated
106      * root is smaller than returned tolerance, then the algorithm is assumed to
107      * be converged, and the estimated root is ensured to have an accuracy that
108      * equals the returned tolerance.
109      *
110      * @return Tolerance to find a root.
111      */
112     public double getTolerance() {
113         return tolerance;
114     }
115 
116     /**
117      * Sets tolerance to find a root. Whenever the variation of the estimated
118      * root is smaller than provided tolerance, then the algorithm is assumed to
119      * be converged, and the estimated root is ensured to have an accuracy that
120      * equals provided tolerance.
121      *
122      * @param tolerance Tolerance to find a root.
123      * @throws LockedException          Raised if this instance is locked while doing
124      *                                  some computations.
125      * @throws IllegalArgumentException Raised if provided tolerance is negative.
126      */
127     public void setTolerance(final double tolerance) throws LockedException {
128         if (isLocked()) {
129             throw new LockedException();
130         }
131         internalSetTolerance(tolerance);
132     }
133 
134     /**
135      * Internal method to set tolerance to find a root. This method does not
136      * check whether this instance is locked.
137      * Whenever the variation of the estimated root is smaller than provided
138      * tolerance, then the algorithm is assumed to be converged, and the
139      * estimated root is ensured to have an accuracy that equals provided
140      * tolerance.
141      *
142      * @param tolerance Tolerance to find a root.
143      * @throws IllegalArgumentException Raised if provided tolerance is negative.
144      */
145     private void internalSetTolerance(final double tolerance) {
146         if (tolerance < MIN_TOLERANCE) {
147             throw new IllegalArgumentException();
148         }
149         this.tolerance = tolerance;
150     }
151 
152     /**
153      * Estimates a single root of the provided single dimension function
154      * contained within a given bracket of values.
155      *
156      * @throws LockedException         Exception raised if this instance is already
157      *                                 locked.
158      * @throws NotReadyException       Exception raised if not enough parameters have
159      *                                 been provided in order to start the estimation.
160      * @throws RootEstimationException Raised if the root estimation failed for
161      *                                 some other reason (usually inability to evaluate the function,
162      *                                 numerical instability or convergence problems, or no roots are found).
163      */
164     @Override
165     public void estimate() throws LockedException, NotReadyException, RootEstimationException {
166         if (isLocked()) {
167             throw new LockedException();
168         }
169         if (!isReady()) {
170             throw new NotReadyException();
171         }
172 
173         locked = true;
174         rootAvailable = false;
175 
176         try {
177             final var x1 = minEvalPoint;
178             final var x2 = maxEvalPoint;
179             final var xacc = tolerance;
180             double dx;
181             double xmid;
182             double rtb;
183             final var f = listener.evaluate(x1);
184             var fmid = listener.evaluate(x2);
185 
186             if (f * fmid >= 0.0) {
187                 // check that bracket contains a sign change in function
188                 locked = false;
189                 throw new RootEstimationException();
190             }
191             if (f < 0.0) {
192                 dx = x2 - x1;
193                 rtb = x1;
194             } else {
195                 dx = x1 - x2;
196                 rtb = x2;
197             }
198             for (var j = 0; j < JMAX; j++) {
199                 dx *= 0.5;
200                 xmid = rtb + dx;
201                 fmid = listener.evaluate(xmid);
202                 if (fmid <= 0.0) {
203                     rtb = xmid;
204                 }
205                 if (Math.abs(dx) < xacc || fmid == 0.0) {
206                     // result obtained
207                     root = rtb;
208                     rootAvailable = true;
209                     locked = false;
210                     return;
211                 }
212             }
213         } catch (final EvaluationException e) {
214             throw new RootEstimationException(e);
215         } finally {
216             locked = false;
217         }
218         // too many iterations and error exceeds desired tolerance
219         throw new RootEstimationException();
220     }
221 
222     /**
223      * Returns boolean indicating whether this instance is ready to compute a
224      * root.
225      * This instance is ready as soon as a listener is provided and a bracket
226      * is provided or computed.
227      *
228      * @return True if instance is ready, false otherwise
229      */
230     @Override
231     public boolean isReady() {
232         return isListenerAvailable() && isBracketAvailable();
233     }
234 }