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   * Computes a root for a single dimension function inside a given bracket of
26   * values, in other words, root will only be searched within provided minimum
27   * and maximum evaluation points.
28   * This class searches for REAL roots only!
29   * This implementation is based on Numerical Recipes 3rd ed. Section 9.4, page
30   * 456.
31   */
32  public class SafeNewtonRaphsonSingleRootEstimator extends DerivativeSingleRootEstimator {
33  
34      /**
35       * Maximum number of iterations.
36       */
37      public static final int MAXIT = 100;
38  
39      /**
40       * Constant defining default accuracy of the estimated root.
41       */
42      public static final double DEFAULT_TOLERANCE = 1e-6;
43  
44      /**
45       * Constant defining minimum allowed tolerance.
46       */
47      public static final double MIN_TOLERANCE = 0.0;
48  
49      /**
50       * Tolerance value. The algorithm will iterate until the result converges
51       * below this value of accuracy or until the maximum number of iterations is
52       * achieved (and in such case, convergence will be assumed to have failed).
53       */
54      private double tolerance;
55  
56      /**
57       * Empty constructor.
58       */
59      public SafeNewtonRaphsonSingleRootEstimator() {
60          super();
61          tolerance = DEFAULT_TOLERANCE;
62      }
63  
64      /**
65       * Constructor.
66       *
67       * @param listener     Listener to evaluate a single dimension function f(x)
68       *                     to find its roots.
69       * @param minEvalPoint Smallest value inside the bracket of values where the
70       *                     root will be searched.
71       * @param maxEvalPoint Largest value inside the bracket of values where the
72       *                     root will be searched.
73       * @param tolerance    Tolerance to be achieved in the estimated root.
74       * @throws InvalidBracketRangeException Raised if minEvalPoint <
75       *                                      maxEvalPoint.
76       * @throws IllegalArgumentException     Raised if tolerance is negative.
77       */
78      public SafeNewtonRaphsonSingleRootEstimator(
79              final SingleDimensionFunctionEvaluatorListener listener, final double minEvalPoint,
80              final double maxEvalPoint, final double tolerance) throws InvalidBracketRangeException {
81          super(listener, minEvalPoint, maxEvalPoint);
82          internalSetTolerance(tolerance);
83      }
84  
85      /**
86       * Constructor.
87       *
88       * @param listener           Listener to evaluate a single dimension function f(x)
89       *                           to find its roots.
90       * @param derivativeListener Listener to evaluate the function's derivative
91       * @param minEvalPoint       Smallest value inside the bracket of values where the
92       *                           root will be searched.
93       * @param maxEvalPoint       Largest value inside the bracket of values where the
94       *                           root will be searched.
95       * @param tolerance          Tolerance to be achieved in the estimated root.
96       * @throws InvalidBracketRangeException Raised if minEvalPoint <
97       *                                      maxEvalPoint.
98       * @throws IllegalArgumentException     Raised if tolerance is negative.
99       */
100     public SafeNewtonRaphsonSingleRootEstimator(
101             final SingleDimensionFunctionEvaluatorListener listener,
102             final SingleDimensionFunctionEvaluatorListener derivativeListener, final double minEvalPoint,
103             final double maxEvalPoint, final double tolerance) throws InvalidBracketRangeException {
104         super(listener, derivativeListener, minEvalPoint, maxEvalPoint);
105         internalSetTolerance(tolerance);
106     }
107 
108     /**
109      * Returns tolerance value.
110      * Tolerance is the accuracy to be achieved when estimating a root.
111      * If a root is found by this class, it is ensured to have an accuracy below
112      * the tolerance value.
113      *
114      * @return Tolerance value.
115      */
116     public double getTolerance() {
117         return tolerance;
118     }
119 
120     /**
121      * Internal method to set tolerance value.
122      * Tolerance is the accuracy to be achieved when estimating a root.
123      * If a root is found by this class, it is ensured to have an accuracy below
124      * provided tolerance value.
125      * This method does not check whether this instance is locked or not.
126      *
127      * @param tolerance Tolerance value.
128      * @throws IllegalArgumentException Raised if provided tolerance value is
129      *                                  negative.
130      */
131     private void internalSetTolerance(final double tolerance) {
132         if (tolerance < MIN_TOLERANCE) {
133             throw new IllegalArgumentException();
134         }
135         this.tolerance = tolerance;
136     }
137 
138     /**
139      * Sets tolerance value.
140      * Tolerance is the accuracy to be achieved when estimating a root.
141      * If a root is found by this class, it is ensured to have an accuracy below
142      * provided tolerance value.
143      *
144      * @param tolerance Tolerance value.
145      * @throws LockedException          Raised if this instance is locked.
146      * @throws IllegalArgumentException Raised if provided tolerance value is
147      *                                  negative.
148      */
149     public void setTolerance(final double tolerance) throws LockedException {
150         if (isLocked()) {
151             throw new LockedException();
152         }
153         internalSetTolerance(tolerance);
154     }
155 
156     /**
157      * Estimates a local root for a given single dimension function being
158      * evaluated by provided listener.
159      *
160      * @throws LockedException         Exception raised if this instance is already
161      *                                 locked.
162      * @throws NotReadyException       Exception raised if either a listener has not
163      *                                 yet been provided or a bracket has not been provided or computed.
164      * @throws RootEstimationException Raised if the root estimation failed for
165      *                                 some other reason (usually inability to evaluate the function,
166      *                                 numerical instability or convergence problems, or no roots are found).
167      */
168     @Override
169     @SuppressWarnings("Duplicates")
170     public void estimate() throws LockedException, NotReadyException, RootEstimationException {
171 
172         if (isLocked()) {
173             throw new LockedException();
174         }
175         if (!isReady()) {
176             throw new NotReadyException();
177         }
178 
179         locked = true;
180         rootAvailable = false;
181 
182         final var x1 = minEvalPoint;
183         final var x2 = maxEvalPoint;
184         final var xacc = tolerance;
185 
186         double xh;
187         double xl;
188         final double fl;
189         final double fh;
190         try {
191             fl = listener.evaluate(x1);
192             fh = listener.evaluate(x2);
193         } catch (final EvaluationException e) {
194             throw new RootEstimationException(e);
195         }
196 
197         if ((fl > 0.0 && fh > 0.0) || (fl < 0.0 && fh < 0.0)) {
198             // root must be bracketed
199             locked = false;
200             throw new RootEstimationException();
201         }
202         if (fl == 0.0) {
203             root = x1;
204             rootAvailable = true;
205             locked = false;
206             return;
207         }
208         if (fh == 0.0) {
209             root = x2;
210             rootAvailable = true;
211             locked = false;
212             return;
213         }
214         if (fl < 0.0) {
215             xl = x1;
216             xh = x2;
217         } else {
218             xh = x1;
219             xl = x2;
220         }
221         var rts = 0.5 * (x1 + x2);
222         var dxold = Math.abs(x2 - x1);
223         var dx = dxold;
224         double f;
225         double df;
226         try {
227             f = listener.evaluate(rts);
228             df = derivativeListener.evaluate(rts);
229         } catch (final EvaluationException e) {
230             throw new RootEstimationException(e);
231         }
232 
233         for (var j = 0; j < MAXIT; j++) {
234             if ((((rts - xh) * df - f) * ((rts - xl) * df - f) > 0.0) || (Math.abs(2.0 * f) > Math.abs(dxold * df))) {
235                 dxold = dx;
236                 dx = 0.5 * (xh - xl);
237                 rts = xl + dx;
238                 if (xl == rts) {
239                     root = rts;
240                     rootAvailable = true;
241                     locked = false;
242                     return;
243                 }
244             } else {
245                 dxold = dx;
246                 dx = f / df;
247                 final var temp = rts;
248                 rts -= dx;
249                 if (temp == rts) {
250                     root = rts;
251                     rootAvailable = true;
252                     locked = false;
253                     return;
254                 }
255             }
256             if (Math.abs(dx) < xacc) {
257                 root = rts;
258                 rootAvailable = true;
259                 locked = false;
260                 return;
261             }
262 
263             try {
264                 f = listener.evaluate(rts);
265                 df = derivativeListener.evaluate(rts);
266             } catch (final EvaluationException e) {
267                 throw new RootEstimationException(e);
268             }
269 
270             if (f < 0.0) {
271                 xl = rts;
272             } else {
273                 xh = rts;
274             }
275         }
276         // maximum number of iterations exceeded
277         locked = false;
278         throw new RootEstimationException();
279     }
280 }