/* * Copyright 2013 Chris Pheby * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jadira.quant.coremath; import org.jadira.quant.api.CompositeFunction; import org.jadira.quant.api.QuantitativeFunction; import org.jadira.quant.exception.IntervalBisectionOutOfRangeException; /** * This class can be used to solve the equation f(x) = 0. It will determine the double value of x, * where f is a continuous function defined on an interval between [a, b] and f(a) and f(b) have opposite * signs. [a, b] are said to bracket the root since there must be at least one root between a and b that * yields f(x) = 0. * * For each iteration, the approach divides the interval in two by determining the midpoint of the interval, c, * such that c = (a+b)/2. The method then determines the interval that brackets the root, this being either * [a, c] or [c, b]. This is used as the interval for the next iteration. The process is terminated either * if the maximum number of iterations is reached, the gap between this result and the previous iteration * result is sufficiently small (if configured with {@link PrecisionStrategy#BETWEEN_RESULTS}) or the value is * within the desired distance of root (if configured with {@link PrecisionStrategy#TO_INTERVAL}. * * PrecisionStrategy defaults to {@link PrecisionStrategy#TO_INTERVAL}, the method defaults to 20 iterations, * and an accuracy distance of 1e-3 (0.001). * * Inspired by the example that appears in Java Methods for Financial Engineering (Philip Barker), * this implementation includes error handling, a functional programming model and two precision * strategies. */ public class IntervalBisection implements CompositeFunction<Double, QuantitativeFunction<Double, Double>> { private final double precision; private final int iterations; private final double lowerBound; private final double higherBound; private PrecisionStrategy precisionStrategy; public enum PrecisionStrategy { TO_INTERVAL, BETWEEN_RESULTS; } public IntervalBisection(double lowerBound, double higherBound) { this(lowerBound, higherBound, 20); } /** * Create a new instance with the given number of iterations and precision * @param lowerBound The minimum bound for the range to converge from * @param higherBound The maximum bound for the range to converge from * @param iterations The number of iterations to perform */ public IntervalBisection(double lowerBound, double higherBound, int iterations) { // Defaults precision strategy to 1e-3 this(lowerBound, higherBound, iterations, 0.001D, PrecisionStrategy.TO_INTERVAL); } /** * Create a new instance with the given number of iterations and precision * @param lowerBound The minimum bound for the range to converge from * @param higherBound The maximum bound for the range to converge from * @param iterations The number of iterations to perform * @param precision The requested precision * @param precisionStrategy Indicates approach for managing precision */ public IntervalBisection(double lowerBound, double higherBound, int iterations, double precision, PrecisionStrategy precisionStrategy) { if (iterations < 1) { throw new IllegalArgumentException("iterations must be greater than 1"); } if (Double.compare(precision, 0.0D) < 0) { throw new IllegalArgumentException("precision must be a positive number"); } if (precisionStrategy == null) { throw new IllegalArgumentException("precisionStrategy may not be null"); } this.lowerBound = lowerBound; this.higherBound = higherBound; this.iterations = iterations; this.precision = precision; this.precisionStrategy = precisionStrategy; } public double getLowerBound() { return lowerBound; } public double getHigherBound() { return higherBound; } public int getIterations() { return iterations; } public double getPrecision() { return precision; } @Override public Double apply(QuantitativeFunction<Double, Double> computeFunction) { final double resultA = computeFunction.apply(lowerBound); final double resultB = computeFunction.apply(higherBound); if (resultA != 0.0D && resultB != 0.0D && (Double.compare(resultA, 0.0D) ^ Double.compare(0.0D, resultB)) != 0) { throw new IntervalBisectionOutOfRangeException(lowerBound, higherBound); } double currentLower = lowerBound; double currentHigher = higherBound; double currentMiddle = Double.NaN; double midValueResult = Double.NaN; double preceedingMidValueResult = Double.NaN; for (int i = 0; i < iterations; i++) { preceedingMidValueResult = midValueResult; currentMiddle = currentLower + 0.5 * (currentHigher - currentLower); midValueResult = computeFunction.apply(currentMiddle); if (resultA * midValueResult < 0) { currentHigher = currentMiddle; } else if (resultA * midValueResult > 0) { currentLower = currentMiddle; } if (PrecisionStrategy.TO_INTERVAL == precisionStrategy) { if (Math.abs(midValueResult) <= precision) { break; } } else if ((PrecisionStrategy.BETWEEN_RESULTS == precisionStrategy || null == precisionStrategy) && (Math.abs(midValueResult - preceedingMidValueResult) <= precision)) { break; } } return currentMiddle; } }