/** * Evaluate the derivative of a function at a given x-value with or without an approximation. * * @param x the x-value * @param useApprox whether or not to use an approximation * @return f'(x) */ public double differentiate(double x, boolean useApprox) { if (useApprox) { final double STEP = Math.pow(10, -5); double rightApprox, leftApprox; rightApprox = (evaluate(x + STEP) - evaluate(x)) / STEP; leftApprox = (evaluate(x - STEP) - evaluate(x)) / -STEP; if (Math.abs(rightApprox - leftApprox) < Math.pow(10, -2)) { return (rightApprox + leftApprox) / 2; } else { return Double.NaN; } } else { return differentiate().evaluate(x); } }
/** * A rectangular function base class. Includes methods for evaluating, finding zeros, * differentiating, and integrating. * * @author Nathan Lindquist * @version 10 March 2013 */ public abstract class Rectangular { /** Relative difference constant for use in the package. */ public static final double RELATIVE_DIFF = Double.MIN_VALUE * 10; /** Epsilon constant for comparing to zero. */ protected static final double ZERO_EPSILON = Math.pow(10, -10); /** A small step size for moving past a convergence point. */ protected static final double CONVERGENCE_STEP = Math.pow(10, -10); /** Construct a rectangular function. Only for use by subclasses. */ protected Rectangular() {} /** * Evaluates the function at a given value. * * @param x the x-value * @return the corresponding y-value */ public abstract double evaluate(double x); /** * Find the left-most x-value that corresponds to the given y-value between two bounds. If the * x-value does not exist, return Double.NaN. * * @param left the left bound * @param right the right bound * @param y the y-value * @return the x-value */ public double findX(double left, double right, double y) { return new SumFunction(this, new Constant(-y)).findZero(left, right); } /** * Find all x-values that correspond to the given y-value between two bounds. If no x-values * exist, return Double.NaN. * * @param left the left bound * @param right the right bound * @param y the y-value * @return the x-value */ public DoubleSet findAllX(double left, double right, double y) { return new SumFunction(this, new Constant(-y)).findAllZeros(left, right); } /** * Finds the left-most zero between two bounds. If the zero does not exist, return Double.NaN. * * @param left the left bound * @param right the right bound * @return an ArrayList of x-values */ public abstract double findZero(double left, double right); /** * Find all zeros between two bounds. If no zeros exist, return an empty ArrayList. * * @param left the left bound * @param right the right bound * @return an ArrayList of zeros */ public abstract FiniteDoubleSet findAllZeros(double left, double right); /** * Return a Function object representing the derivative of this Function. * * @return the derivative */ public abstract Rectangular differentiate(); /** * Evaluate the derivative at a given value. * * @param x the x-value * @return f'(x) */ public double differentiate(double x) { return differentiate(x, false); } /** * Evaluate the derivative of a function at a given x-value with or without an approximation. * * @param x the x-value * @param useApprox whether or not to use an approximation * @return f'(x) */ public double differentiate(double x, boolean useApprox) { if (useApprox) { final double STEP = Math.pow(10, -5); double rightApprox, leftApprox; rightApprox = (evaluate(x + STEP) - evaluate(x)) / STEP; leftApprox = (evaluate(x - STEP) - evaluate(x)) / -STEP; if (Math.abs(rightApprox - leftApprox) < Math.pow(10, -2)) { return (rightApprox + leftApprox) / 2; } else { return Double.NaN; } } else { return differentiate().evaluate(x); } } /** * Return a Function object representing the indefinite integral of this Function (no constant * term included). * * @return the indefinite integral */ public abstract Rectangular integrate(); /** * Return the definite integral defined by two bounds using this function's antiderivative. * * @param left the left bound * @param right the right bound * @return the definite integral on [left, right] */ public double integrate(double left, double right) { if (hasVA(left, right)) { return Double.NaN; } return integrate(left, right, false); } /** * Return the definite integral defined by two bounds, with or without an approximation. By * default, the approximation uses Simpon's method to approximate the integral with 10000 * subdivisions. * * @param left the left bound * @param right the right bound * @param useApprox whether or not to use an approximation * @return the definite integral on [left, right] */ public double integrate(double left, double right, boolean useApprox) { if (useApprox) { final int SUBDIV = 10000; return simpsons(left, right, SUBDIV); } else { return integrate().evaluate(right) - integrate().evaluate(left); } } /** * Approximate the definite integral between two bounds by the left rectangle method. * * @param left the left bound * @param right the right bound * @param subdiv the number of subdivisions * @return the lram approximation on [left, right] */ public double lram(double left, double right, int subdiv) { if (hasVA(left, right)) { return Double.NaN; // check for continuity } double x = left, step = (right - left) / subdiv, lram = 0; // multiply y-value by length of base for (int i = 0; i < subdiv; i++) { if (Double.isNaN(evaluate(x))) { return Double.NaN; // exit if not continuous } lram += evaluate(x) * step; x += step; } return lram; } /** * Approximate the definite integral between two bounds by the right rectangle method. * * @param left the left bound * @param right the right bound * @param subdiv the number of subdivisions * @return the rram approximation on [left, right] */ public double rram(double left, double right, int subdiv) { if (hasVA(left, right)) { return Double.NaN; // check for continuity } double step = (right - left) / subdiv; return lram(left + step, right + step, subdiv); } /** * Approximate the definite integral between two bounds by the middle rectangle method. * * @param left the left bound * @param right the right bound * @param subdiv the number of subdivisions * @return the mram approximation on [left, right] */ public double mram(double left, double right, int subdiv) { if (hasVA(left, right)) { return Double.NaN; // check for continuity } double step = (right - left) / subdiv; return lram(left + step / 2, right + step / 2, subdiv); } /** * Approximate the definite integral between two bounds by the trapezoid method. * * @param left the left bound * @param right the right bound * @param subdiv the number of subdivisions * @return the trapezoid approximation on [left, right] */ public double trap(double left, double right, int subdiv) { if (hasVA(left, right)) { return Double.NaN; // check for continuity TODO: better way? } double x = left, step = (right - left) / subdiv, trap = 0; // the formula is (right - left)/(2*width) * (y1 + 2*y2 + 2*y3 + ... + 2*y(n-1) + yn) for (int i = 0; i <= subdiv; i++) { if (Double.isNaN(evaluate(x))) { return Double.NaN; // exit if not continuous } if (i == 0 || i == subdiv) { trap += evaluate(x); } else { trap += evaluate(x) * 2; } x += step; } return trap * step / 2.0; } /** * Approximate the definite integral between two bounds by Simpson's method. * * @param left the left bound * @param right the right bound * @param subdiv the number of subdivisions * @return the Simpson's method approximation on [left, right] */ public double simpsons(double left, double right, int subdiv) { // number of subdivisions must be even, and function must be continuous if (subdiv % 2 != 0 || hasVA(left, right)) { return Double.NaN; } double x = left, step = (right - left) / subdiv, simpsons = 0; for (int i = 0; i <= subdiv; i++) { if (Double.isNaN(evaluate(x))) { return Double.NaN; // exit if not continuous } // formula: (right - left)/(3*width) * (y1 + 4*y2 + 2*y3 + 4*y4 + 2*y5 + ... // + 4*y(n-1) + yn) if (i == 0 || i == subdiv) { simpsons += evaluate(x); } else if (i % 2 == 1) { simpsons += evaluate(x) * 4; } else { simpsons += evaluate(x) * 2; } x += step; } return simpsons * step / 3.0; } /** * Get the function's domain bounded by two values. * * @param left the left bound * @param right the right bound * @return a FiniteDomain object representing the domain */ public abstract FiniteDomain getDomain(double left, double right); /** * Get the function's domain (unbounded). * * @return a FiniteDomain object representing the domain */ public FiniteDomain getDomain() { return getDomain(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); } /** * Check if this function and another object are the same. * * @param o the Object to be tested * @return true if they are equal */ @Override public abstract boolean equals(Object o); /** * Return a string representation of this function. * * @return the string representation */ @Override public abstract String toString(); /** * Check for convergence of two values. More restrictive than default doubles comparison. * * @param firstX the first value to be tested * @param nextX the second value to be tested * @return whether or not the two values have converged */ protected boolean detectConvergence(double firstX, double nextX) { return compareDoubles(firstX, nextX, RELATIVE_DIFF); } /** * Check if a number is positive. * * @param num the number to be tested * @return whether or not the number is greater than 0 */ protected boolean isPositive(double num) { return num > 0; } /** * Return the x-value corresponding to a vertical asymptote between two bounds. If the x-value * does not exist, return Double.NaN. * * @param left the left bound * @param right the right bound * @return the x-value */ protected double findVA(double left, double right) { double step = (right - left) / 10, firstY, nextY, firstX = left, nextX; do { nextX = firstX + step; firstY = evaluate(firstX); nextY = evaluate(nextX); if ((isPositive(firstY) != isPositive(nextY)) || (Double.isNaN(firstY) != Double.isNaN(nextY)) || (isPositive(differentiate(firstX)) != isPositive(differentiate(nextX)))) { step /= -10; } if (firstX > right) { return Double.NaN; } if (detectConvergence(firstX, nextX)) { if (Double.isNaN(Math.abs(nextY)) || Double.isNaN(Math.abs(firstY)) || Math.abs(nextY) > 100 || Math.abs(firstY) > 100) { return firstX; } else { step = (right - left) / 10; nextX = advancePastConvergence(nextX, nextY, step); } } firstX = nextX; } while (!(firstX - step > right)); return Double.NaN; } /** * Compare two doubles for equality by checking their relative difference. This method computes * the difference between two values and checks that against the larger value using the desired * relative difference. * * @param num1 the first number to be tested * @param num2 the second number to be tested * @param relDiff the fraction of the larger number that the difference must be less than * @return whether or not the two values are essentially equal */ public static boolean compareDoubles(double num1, double num2, double relDiff) { if (Double.isInfinite(num1) && Double.isInfinite(num2)) // check for infinity { return Double.isNaN(num1 - num2); // will result in NaN only if they are the same sign } double diff = Math.abs(num2 - num1); double max = (Math.abs(num2) > Math.abs(num1)) ? Math.abs(num2) : Math.abs(num1); if (max == 0.0) // if max is somehow exactly equal to zero { return compareToZero(diff, ZERO_EPSILON); } else { return diff / max < relDiff; } } /** * Compare a double to zero using an epsilon value. Intended to be less restrictive than * compareDoubles. * * @param num the number to be tested * @param epsilon the value the number must be less than * @return whether or not the number is essentially zero */ public static boolean compareToZero(double num, double epsilon) { return Math.abs(num) < epsilon; } /** * Check if the specified value is between or equal to the other two specified values. If it is, * return the value; otherwise, return Double.NaN. * * @param left the left bound * @param right the right bound * @param value the value to be tested * @return the value if in the range, Double.NaN otherwise */ protected static double valueInRange(double left, double right, double value) { if (new Subset(left, right).contains(value)) { return value; } else { return Double.NaN; } } /** * Check if a function has a vertical asymptote between two bounds. * * @param left the left bound * @param right the right bound * @return whether or not the vertical asymptote exists */ protected boolean hasVA(double left, double right) { return !Double.isNaN(findVA(left, right)); } /** * Advance the current x-value beyond the convergence point in order to avoid infinite loops. * * @param x the current x-value * @param y the current y-value * @param step how far to advance * @return the new x-value */ protected double advancePastConvergence(double x, double y, double step) { x += step; return x; } }