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}