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 }