예제 #1
0
  /**
   * 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);
    }
  }
예제 #2
0
/**
 * 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;
  }
}