package umontreal.iro.lecuyer.functions;

import umontreal.iro.lecuyer.util.Misc;


/**
 * Provides utility methods for computing
 * derivatives and integrals of functions.
 * 
 */
public class MathFunctionUtil {


   /**
    * Step length in x to compute derivatives. Default: 10-6.
    * 
    */
   public static double H = 1e-6;

   private MathFunctionUtil() {}

   // For Gauss-Lobatto: nodes Cg and weights Wg
   private static final double[] Cg = { 0, 0.17267316464601142812, 0.5,
                                           0.82732683535398857188, 1 };
   private static final double[] Wg = { 0.05, 0.27222222222222222222,
                  0.35555555555555555555, 0.27222222222222222222, 0.05 };


   private static double[] fixBounds (MathFunction func, double a,
                                      double b, int numIntervals) {
   // For functions which are 0 on parts of [a, b], shorten the interval
   // [a, b] to the non-zero part of f(x). Returns the shortened interval.

       final double h = (b - a)/numIntervals;
       double x = b;
       while ((0 == func.evaluate (x)) && (x > a))
           x -= h;
       if (x < b)
           b = x + h;

       x = a;
       while ((0 == func.evaluate (x)) && (x < b))
           x += h;
       if (x > a)
          a = x - h;
       double[] D = {a, b};
       return D;
   }

   /**
    * Default number of intervals for Simpson's integral.
    * 
    */
   public static int NUMINTERVALS = 1024;


   /**
    * Returns the first derivative of the function func
    *  evaluated at x. If the given function implements
    *  {@link MathFunctionWithFirstDerivative}, this method calls
    * {@link MathFunctionWithFirstDerivative#derivative MathFunctionWithFirstDerivative.derivative} (double).
    *  Otherwise, if the function implements {@link MathFunctionWithDerivative},
    *  this method calls
    * {@link MathFunctionWithDerivative#derivative MathFunctionWithDerivative.derivative} (double, int).
    * If the function does not implement any of these two interfaces, the method
    *  uses {@link #finiteCenteredDifferenceDerivative finiteCenteredDifferenceDerivative} (MathFunction,
    *  double, double) to obtain an estimate of the derivative.
    * 
    * @param func the function to derivate.
    * 
    *    @param x the evaluation point.
    * 
    *    @return the first derivative.
    * 
    */
   public static double derivative (MathFunction func, double x) {
      if (func instanceof MathFunctionWithFirstDerivative)
         return ((MathFunctionWithFirstDerivative)func).derivative (x);
      else if (func instanceof MathFunctionWithDerivative)
         return ((MathFunctionWithDerivative)func).derivative (x, 1);
      else
         return finiteCenteredDifferenceDerivative (func, x, H);
   }


   /**
    * Returns the nth derivative of function
    *  func evaluated at x.
    *  If n = 0, this returns f (x).
    *  If n = 1, this calls
    *  {@link #derivative derivative} (MathFunction, double)
    *  and returns the resulting first derivative.
    *  Otherwise,
    *  if the function implements
    *  {@link MathFunctionWithDerivative},
    *  this method calls
    * {@link MathFunctionWithDerivative#derivative MathFunctionWithDerivative.derivative} (double, int).
    *  If the function does not implement this
    *  interface,
    *  the method uses
    *  {@link #finiteCenteredDifferenceDerivative finiteCenteredDifferenceDerivative} (MathFunction, double, int, double)
    *  if n is even, or
    *  {@link #finiteDifferenceDerivative finiteDifferenceDerivative} (MathFunction, double, int, double)
    *  if n is odd,
    *  to obtain a numerical approximation of the derivative.
    * 
    * @param func the function to derivate.
    * 
    *    @param x the evaluation point.
    * 
    *    @param n the order of the derivative.
    * 
    *    @return the nth derivative.
    * 
    */
   public static double derivative (MathFunction func, double x, int n) {
      if (n == 0)
         return func.evaluate (x);
      else if (n == 1)
         return derivative (func, x);
      else if (func instanceof MathFunctionWithDerivative)
         return ((MathFunctionWithDerivative)func).derivative (x, n);
      else if (n % 2 == 0)
         return finiteCenteredDifferenceDerivative (func, x, n, H);
      else
         return finiteDifferenceDerivative (func, x, n, H);
   }


   /**
    * Computes and returns an estimate
    *  of the nth derivative of the
    *  function f (x).
    *  This method estimates
    *  

*

*
* $\displaystyle {\frac{{d^nf(x)}}{{dx^n}}}$, *

* the nth derivative of f (x) * evaluated at x. * This method first computes * * fi = f (x + iε), for * i = 0,…, n, with * * ε = h1/n. * The estimate is then given by * * Δnf0/h, where * * Δnfi = Δn-1fi+1 - Δn-1fi, and * * Δfi = fi+1 - fi. * * @param func the function to derivate. * * @param x the evaluation point. * * @param n the order of the derivative. * * @param h the error. * * @return the estimate of the derivative. * */ public static double finiteDifferenceDerivative ( MathFunction func, double x, int n, double h) { if (n < 0) throw new IllegalArgumentException ("n must not be negative"); if (n == 0) return func.evaluate (x); final double err = Math.pow (h, 1.0 / n); final double[] values = new double[n+1]; for (int i = 0; i < values.length; i++) values[i] = func.evaluate (x + i*err); for (int j = 0; j < n; j++) { for (int i = 0; i < n - j; i++) values[i] = values[i + 1] - values[i]; } return values[0] / h; } /** * Returns * (f (x + h) - f (x - h))/(2h), * an estimate of the first derivative of f (x) * using centered differences. * * @param func the function to derivate. * * @param x the evaluation point. * * @param h the error. * * @return the estimate of the first derivative. * */ public static double finiteCenteredDifferenceDerivative ( MathFunction func, double x, double h) { final double fplus = func.evaluate (x + h); final double fminus = func.evaluate (x - h); return (fplus - fminus) / (2*h); } /** * Computes and returns an estimate of the nth derivative of the * function f (x) using finite centered differences. * If n is even, this method returns * {@link #finiteDifferenceDerivative(MathFunction, double, int, double) finiteDifferenceDerivative} (func, x - ε*n/2, n, h), with * h = εn. * * @param func the function to derivate. * * @param x the evaluation point. * * @param n the order of the derivative. * * @param h the error. * * @return the estimate of the derivative. * */ public static double finiteCenteredDifferenceDerivative ( MathFunction func, double x, int n, double h) { if (n < 0) throw new IllegalArgumentException ("n must not be negative"); if (n == 0) return func.evaluate (x); if (n % 2 == 1) throw new IllegalArgumentException ("n must be even"); final double err = Math.pow (h, 1.0 / n); return finiteDifferenceDerivative (func, x - n*err / 2, n, h); } /** * Removes any point (NaN, y) or (x, NaN) from * x and y, and returns a 2D array containing the filtered * points. This method filters each pair (x[i], y[i]) * containing at least one NaN element. It constructs a 2D array containing * the two filtered arrays, whose size is smaller than or equal to * x.length. * * @param x the X coordinates. * * @param y the Y coordinates. * * @return the filtered X and Y arrays. * */ public static double[][] removeNaNs (double[] x, double[] y) { if (x.length != y.length) throw new IllegalArgumentException(); int numNaNs = 0; for (int i = 0; i < x.length; i++) if (Double.isNaN (x[i]) || Double.isNaN (y[i])) ++numNaNs; if (numNaNs == 0) return new double[][] { x, y }; final double[] nx = new double[x.length - numNaNs]; final double[] ny = new double[y.length - numNaNs]; int j = 0; for (int i = 0; i < x.length; i++) if (!Double.isNaN (x[i]) && !Double.isNaN (y[i])) { nx[j] = x[i]; ny[j++] = y[i]; } return new double[][] { nx, ny }; } /** * Returns the integral of the function func over [a, b]. * If the given function implements {@link MathFunctionWithIntegral}, * this returns * {@link umontreal.iro.lecuyer.functions.MathFunctionWithIntegral#integral MathFunctionWithIntegral.integral} (double, double). * Otherwise, this calls * {@link #simpsonIntegral simpsonIntegral} (MathFunction, double, double, int) * with {@link #NUMINTERVALS NUMINTERVALS} intervals. * * @param func the function to integrate. * * @param a the lower bound. * * @param b the upper bound. * * @return the value of the integral. * */ public static double integral (MathFunction func, double a, double b) { if (func instanceof MathFunctionWithIntegral) return ((MathFunctionWithIntegral)func).integral (a, b); else return simpsonIntegral (func, a, b, NUMINTERVALS); } /** * Computes and returns an approximation of the integral of func over * [a, b], using the Simpson's 1/3 method with numIntervals * intervals. This method estimates *

*

*
* ∫abf (x)dx, *

* where f (x) is the function defined by func evaluated at x, * by dividing [a, b] in n = numIntervals intervals of length * * h = (b - a)/n. The integral is estimated by *

*

*
* $\displaystyle {\frac{{h}}{{3}}}$(f (a) + 4f (a + h) + 2f (a + 2h) + 4f (a + 3h) + ... + f (b)) *

* This method assumes that * a <= b < ∞, and n is even. * * @param func the function being integrated. * * @param a the left bound * * @param b the right bound. * * @param numIntervals the number of intervals. * * @return the approximate value of the integral. * */ public static double simpsonIntegral (MathFunction func, double a, double b, int numIntervals) { if (numIntervals % 2 != 0) throw new IllegalArgumentException ("numIntervals must be an even number"); if (Double.isInfinite (a) || Double.isInfinite (b) || Double.isNaN (a) || Double.isNaN (b)) throw new IllegalArgumentException ("a and b must not be infinite or NaN"); if (b < a) throw new IllegalArgumentException ("b < a"); if (a == b) return 0; double[] D = fixBounds (func, a, b, numIntervals); a = D[0]; b = D[1]; final double h = (b - a) / numIntervals; final double h2 = 2*h; final int m = numIntervals / 2; double sum = 0; for (int i = 0; i < m - 1; i++) { final double x = a + h + h2*i; sum += 4*func.evaluate (x) + 2*func.evaluate (x + h); } sum += func.evaluate (a) + func.evaluate (b) + 4*func.evaluate (b - h); return sum * h / 3; } /** * Computes and returns a numerical approximation of the integral of * f (x) over [a, b], using Gauss-Lobatto adaptive quadrature with * 5 nodes, with tolerance tol. This method estimates *

*

*
* ∫abf (x)dx, *

* where f (x) is the function defined by func. * Whenever the estimated error is larger than tol, the interval * [a, b] will be halved in two smaller intervals, and the method will * recursively call itself on the two smaller intervals until the estimated * error is smaller than tol. * * @param func the function being integrated. * * @param a the left bound * * @param b the right bound. * * @param tol error. * * @return the approximate value of the integral. * */ public static double gaussLobatto (MathFunction func, double a, double b, double tol) { if (b < a) throw new IllegalArgumentException ("b < a"); if (Double.isInfinite (a) || Double.isInfinite (b) || Double.isNaN (a) || Double.isNaN (b)) throw new IllegalArgumentException ("a or b is infinite or NaN"); if (a == b) return 0; double r0 = simpleGaussLob (func, a, b); final double h = (b - a)/2; double r1 = simpleGaussLob (func, a, a + h) + simpleGaussLob (func, a + h, b); if (Math.abs(r0 - r1) <= tol) return r1; return gaussLobatto (func, a, a + h, tol) + gaussLobatto (func, a + h, b, tol); } private static double simpleGaussLob (MathFunction func, double a, double b) { // Gauss-Lobatto integral over [a, b] with 5 nodes if (a == b) return 0; final double h = b - a; double sum = 0; for (int i = 0; i < 5; i++) { sum += Wg[i] * func.evaluate(a + h*Cg[i]); } return h*sum; } /** * Similar to method * {@link #gaussLobatto gaussLobatto}(MathFunction, double, double, double), but * also returns in T[0] the subintervals of integration, and in * T[1], the partial values of the integral over the corresponding * subintervals. Thus T[0][0] = x0 = a and T[0][n] * = xn = b; T[1][i] contains the value of the integral over * the subinterval * [xi-1, xi]; we also have T[1][0] = 0. * The sum over all T[1][i], for * i = 1,…, n gives the * value of the integral over [a, b], which is the value returned by this * method. WARNING: The user must reserve the 2 elements * of the first dimension (T[0] and T[1]) * before calling this method. * * @param func function being integrated * * @param a left bound of interval * * @param b right bound of interval * * @param tol error * * @param T (x, y) = (values of partial intervals,partial values of integral) * * @return value of the integral * */ public static double gaussLobatto (MathFunction func, double a, double b, double tol, double[][] T) { if (b < a) throw new IllegalArgumentException ("b < a"); if (a == b) { T[0] = new double [1]; T[1] = new double [1]; T[0][0] = a; T[1][0] = 0; return 0; } int n = 1; // initial capacity T[0] = new double [n]; T[1] = new double [n]; T[0][0] = a; T[1][0] = 0; int[] s = {0}; // actual number of intervals double res = innerGaussLob (func, a, b, tol, T, s); n = s[0] + 1; double[] temp = new double[n]; System.arraycopy (T[0], 0, temp, 0, n); T[0] = temp; temp = new double[n]; System.arraycopy (T[1], 0, temp, 0, n); T[1] = temp; return res; } private static double innerGaussLob (MathFunction func, double a, double b, double tol, double[][] T, int[] s) { double r0 = simpleGaussLob (func, a, b); final double h = (b - a) / 2; double r1 = simpleGaussLob (func, a, a + h) + simpleGaussLob (func, a + h, b); if (Math.abs(r0 - r1) <= tol) { ++s[0]; int len = s[0]; if (len >= T[0].length) { double[] temp = new double[2 * len]; System.arraycopy (T[0], 0, temp, 0, len); T[0] = temp; temp = new double[2 * len]; System.arraycopy (T[1], 0, temp, 0, len); T[1] = temp; } T[0][len] = b; T[1][len] = r1; return r1; } return innerGaussLob (func, a, a + h, tol, T, s) + innerGaussLob (func, a + h, b, tol, T, s); } }

Ads help maintain this website.