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 }