View Javadoc
1   /*
2    *  Copyright 2013 Chris Pheby
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 org.jadira.quant.coremath;
17  
18  import org.jadira.quant.api.CompositeFunction;
19  import org.jadira.quant.api.QuantitativeFunction;
20  import org.jadira.quant.exception.IntervalBisectionOutOfRangeException;
21  
22  /**
23   * This class can be used to solve the equation f(x) = 0. It will determine the double value of x,
24   * where f is a continuous function defined on an interval between [a, b] and f(a) and f(b) have opposite
25   * signs. [a, b] are said to bracket the root since there must be at least one root between a and b that
26   * yields f(x) = 0.
27   *
28   * For each iteration, the approach divides the interval in two by determining the midpoint of the interval, c,
29   * such that c = (a+b)/2. The method then determines the interval that brackets the root, this being either
30   * [a, c] or [c, b]. This is used as the interval for the next iteration. The process is terminated either
31   * if the maximum number of iterations is reached, the gap between this result and the previous iteration
32   * result is sufficiently small (if configured with {@link PrecisionStrategy#BETWEEN_RESULTS}) or the value is
33   * within the desired distance of root (if configured with {@link PrecisionStrategy#TO_INTERVAL}.
34   *
35   * PrecisionStrategy defaults to {@link PrecisionStrategy#TO_INTERVAL}, the method defaults to 20 iterations,
36   * and an accuracy distance of 1e-3 (0.001).
37   *
38   * Inspired by the example that appears in Java Methods for Financial Engineering (Philip Barker),
39   * this implementation includes error handling, a functional programming model and two precision
40   * strategies.
41   */
42  public class IntervalBisection implements CompositeFunction<Double, QuantitativeFunction<Double, Double>> {
43  
44  	private final double precision;
45  	private final int iterations;
46  	private final double lowerBound;
47  	private final double higherBound;
48  	private PrecisionStrategy precisionStrategy;
49  
50  	public enum PrecisionStrategy {
51  		TO_INTERVAL, BETWEEN_RESULTS;
52  	}
53  
54  	public IntervalBisection(double lowerBound, double higherBound) {
55  		this(lowerBound, higherBound, 20);
56  	}
57  
58  	/**
59  	 * Create a new instance with the given number of iterations and precision
60  	 * @param lowerBound The minimum bound for the range to converge from
61  	 * @param higherBound The maximum bound for the range to converge from
62  	 * @param iterations The number of iterations to perform
63  	 */
64  	public IntervalBisection(double lowerBound, double higherBound, int iterations) {
65  		// Defaults precision strategy to 1e-3
66  		this(lowerBound, higherBound, iterations, 0.001D, PrecisionStrategy.TO_INTERVAL);
67  	}
68  
69  	/**
70  	 * Create a new instance with the given number of iterations and precision
71  	 * @param lowerBound The minimum bound for the range to converge from
72  	 * @param higherBound The maximum bound for the range to converge from
73  	 * @param iterations The number of iterations to perform
74  	 * @param precision The requested precision
75  	 * @param precisionStrategy Indicates approach for managing precision
76  	 */
77  	public IntervalBisection(double lowerBound, double higherBound, int iterations, double precision, PrecisionStrategy precisionStrategy) {
78  
79  		if (iterations < 1) {
80  			throw new IllegalArgumentException("iterations must be greater than 1");
81  		}
82  		if (Double.compare(precision, 0.0D)  < 0) {
83  			throw new IllegalArgumentException("precision must be a positive number");
84  		}
85  		if (precisionStrategy == null) {
86  			throw new IllegalArgumentException("precisionStrategy may not be null");
87  		}
88  
89  		this.lowerBound = lowerBound;
90  		this.higherBound = higherBound;
91  		this.iterations = iterations;
92  		this.precision = precision;
93  		this.precisionStrategy = precisionStrategy;
94  	}
95  
96  	public double getLowerBound() {
97  		return lowerBound;
98  	}
99  
100 	public double getHigherBound() {
101 		return higherBound;
102 	}
103 
104 	public int getIterations() {
105 		return iterations;
106 	}
107 
108 	public double getPrecision() {
109 		return precision;
110 	}
111 
112 	@Override
113 	public Double apply(QuantitativeFunction<Double, Double> computeFunction) {
114 
115 		final double resultA = computeFunction.apply(lowerBound);
116 		final double resultB = computeFunction.apply(higherBound);
117 
118 		if (resultA != 0.0D && resultB != 0.0D
119 				&& (Double.compare(resultA, 0.0D) ^ Double.compare(0.0D, resultB)) != 0) {
120 			throw new IntervalBisectionOutOfRangeException(lowerBound, higherBound);
121 		}
122 
123 		double currentLower = lowerBound;
124 		double currentHigher = higherBound;
125 
126 		double currentMiddle = Double.NaN;
127 		double midValueResult = Double.NaN;
128 		double preceedingMidValueResult = Double.NaN;
129 
130 		for (int i = 0; i < iterations; i++) {
131 
132 			preceedingMidValueResult = midValueResult;
133 
134 			currentMiddle = currentLower + 0.5 * (currentHigher - currentLower);
135 			midValueResult = computeFunction.apply(currentMiddle);
136 
137 			if (resultA * midValueResult < 0) {
138 				currentHigher = currentMiddle;
139 			} else if (resultA * midValueResult > 0) {
140 				currentLower = currentMiddle;
141 			}
142 
143 			if (PrecisionStrategy.TO_INTERVAL == precisionStrategy) {
144 				if (Math.abs(midValueResult) <= precision) {
145 					break;
146 				}
147 			} else if ((PrecisionStrategy.BETWEEN_RESULTS == precisionStrategy || null == precisionStrategy)
148 					&& (Math.abs(midValueResult - preceedingMidValueResult) <= precision)) {
149 					break;
150 			}
151 		}
152 		return currentMiddle;
153 	}
154 }