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 <
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 }