001/* 002 * Copyright 2013 Chris Pheby 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.jadira.quant.coremath; 017 018import org.jadira.quant.api.CompositeFunction; 019import org.jadira.quant.api.QuantitativeFunction; 020import org.jadira.quant.exception.IntervalBisectionOutOfRangeException; 021 022/** 023 * This class can be used to solve the equation f(x) = 0. It will determine the double value of x, 024 * where f is a continuous function defined on an interval between [a, b] and f(a) and f(b) have opposite 025 * signs. [a, b] are said to bracket the root since there must be at least one root between a and b that 026 * yields f(x) = 0. 027 * 028 * For each iteration, the approach divides the interval in two by determining the midpoint of the interval, c, 029 * such that c = (a+b)/2. The method then determines the interval that brackets the root, this being either 030 * [a, c] or [c, b]. This is used as the interval for the next iteration. The process is terminated either 031 * if the maximum number of iterations is reached, the gap between this result and the previous iteration 032 * result is sufficiently small (if configured with {@link PrecisionStrategy#BETWEEN_RESULTS}) or the value is 033 * within the desired distance of root (if configured with {@link PrecisionStrategy#TO_INTERVAL}. 034 * 035 * PrecisionStrategy defaults to {@link PrecisionStrategy#TO_INTERVAL}, the method defaults to 20 iterations, 036 * and an accuracy distance of 1e-3 (0.001). 037 * 038 * Inspired by the example that appears in Java Methods for Financial Engineering (Philip Barker), 039 * this implementation includes error handling, a functional programming model and two precision 040 * strategies. 041 */ 042public class IntervalBisection implements CompositeFunction<Double, QuantitativeFunction<Double, Double>> { 043 044 private final double precision; 045 private final int iterations; 046 private final double lowerBound; 047 private final double higherBound; 048 private PrecisionStrategy precisionStrategy; 049 050 public enum PrecisionStrategy { 051 TO_INTERVAL, BETWEEN_RESULTS; 052 } 053 054 public IntervalBisection(double lowerBound, double higherBound) { 055 this(lowerBound, higherBound, 20); 056 } 057 058 /** 059 * Create a new instance with the given number of iterations and precision 060 * @param lowerBound The minimum bound for the range to converge from 061 * @param higherBound The maximum bound for the range to converge from 062 * @param iterations The number of iterations to perform 063 */ 064 public IntervalBisection(double lowerBound, double higherBound, int iterations) { 065 // Defaults precision strategy to 1e-3 066 this(lowerBound, higherBound, iterations, 0.001D, PrecisionStrategy.TO_INTERVAL); 067 } 068 069 /** 070 * Create a new instance with the given number of iterations and precision 071 * @param lowerBound The minimum bound for the range to converge from 072 * @param higherBound The maximum bound for the range to converge from 073 * @param iterations The number of iterations to perform 074 * @param precision The requested precision 075 * @param precisionStrategy Indicates approach for managing precision 076 */ 077 public IntervalBisection(double lowerBound, double higherBound, int iterations, double precision, PrecisionStrategy precisionStrategy) { 078 079 if (iterations < 1) { 080 throw new IllegalArgumentException("iterations must be greater than 1"); 081 } 082 if (Double.compare(precision, 0.0D) < 0) { 083 throw new IllegalArgumentException("precision must be a positive number"); 084 } 085 if (precisionStrategy == null) { 086 throw new IllegalArgumentException("precisionStrategy may not be null"); 087 } 088 089 this.lowerBound = lowerBound; 090 this.higherBound = higherBound; 091 this.iterations = iterations; 092 this.precision = precision; 093 this.precisionStrategy = precisionStrategy; 094 } 095 096 public double getLowerBound() { 097 return lowerBound; 098 } 099 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}