All Downloads are FREE. Search and download functionalities are using the official Maven repository.

umcg.genetica.math.interpolation.CubicSpline Maven / Gradle / Ivy

There is a newer version: 1.0.7
Show newest version
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package umcg.genetica.math.interpolation;

import umcg.genetica.math.Fmath;
import umcg.genetica.util.Primitives;

/**
 * ********************************************************
 *
 * Class CubicSpline
 *
 * Class for performing an interpolation using a cubic spline setTabulatedArrays
 * and interpolate adapted, with modification to an object-oriented approach,
 * from Numerical Recipes in C (http://www.nr.com/)
 *
 *
 * WRITTEN BY: Dr Michael Thomas Flanagan
 *
 * DATE:	May 2002 UPDATE: 29 April 2005, 17 February 2006, 21 September 2006, 4
 * December 2007 24 March 2008 (Thanks to Peter Neuhaus, Florida Institute for
 * Human and Machine Cognition) 21 September 2008 14 January 2009 - point
 * deletion and check for 3 points reordered (Thanks to Jan Sacha, Vrije
 * Universiteit Amsterdam) 31 October 2009, 11-14 January 2010 31 August 2010 -
 * overriding natural spline error, introduced in earlier update, corrected
 * (Thanks to Dagmara Oszkiewicz, University of Helsinki) 2 February 2011
 *
 * DOCUMENTATION: See Michael Thomas Flanagan's Java library on-line web page:
 * http://www.ee.ucl.ac.uk/~mflanaga/java/CubicSpline.html
 * http://www.ee.ucl.ac.uk/~mflanaga/java/
 *
 * Copyright (c) 2002 - 2010 Michael Thomas Flanagan
 *
 * PERMISSION TO COPY:
 *
 * Permission to use, copy and modify this software and its documentation for
 * NON-COMMERCIAL purposes is granted, without fee, provided that an
 * acknowledgement to the author, Dr Michael Thomas Flanagan at
 * www.ee.ucl.ac.uk/~mflanaga, appears in all copies and associated
 * documentation or publications.
 *
 * Redistributions of the source code of this source code, or parts of the
 * source codes, must retain the above copyright notice, this list of conditions
 * and the following disclaimer and requires written permission from the Michael
 * Thomas Flanagan:
 *
 * Redistribution in binary form of all or parts of this class must reproduce
 * the above copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided with the
 * distribution and requires written permission from the Michael Thomas
 * Flanagan:
 *
 * Dr Michael Thomas Flanagan makes no representations about the suitability or
 * fitness of the software for any or for a particular purpose. Dr Michael
 * Thomas Flanagan shall not be liable for any damages suffered as a result of
 * using, modifying or distributing this software or its derivatives.
 *
 * *************************************************************************************
 */
public class CubicSpline {

    private int nPoints = 0;                                    // no. of tabulated points
    private int nPointsOriginal = 0;                            // no. of tabulated points after any deletions of identical points
    private double[] y = null;                                  // y=f(x) tabulated function
    private double[] x = null;                                  // x in tabulated function f(x)
    private double yy = Double.NaN;                             // interpolated value of y
    private double dydx = Double.NaN;                           // interpolated value of the first derivative, dy/dx
    private int[] newAndOldIndices;                              // record of indices on ordering x into ascending order
    private double xMin = Double.NaN;                           // minimum x value
    private double xMax = Double.NaN;                           // maximum x value
    private double range = Double.NaN;                          // xMax - xMin
    private double[] d2ydx2 = null;                             // second derivatives of y
    private double yp1 = Double.NaN;                            // second derivative at point one
    // default value = NaN (natural spline)
    private double ypn = Double.NaN;                            // second derivative at point n
    // default value = NaN (natural spline)
    private boolean derivCalculated = false;                    // = true when the derivatives have been calculated
    private boolean checkPoints = false;                        // = true when points checked for identical values
    private static boolean supress = false;                     // if true: warning messages supressed
    private static boolean averageIdenticalAbscissae = false;   // if true: the the ordinate values for identical abscissae are averaged
    // if false: the abscissae values are separated by 0.001 of the total abscissae range;
    private static double potentialRoundingError = 5e-15;       // potential rounding error used in checking wheter a value lies within the interpolation bounds
    private static boolean roundingCheck = true;                // = true: points outside the interpolation bounds by less than the potential rounding error rounded to the bounds limit

    // Constructors
    // Constructor with data arrays initialised to arrays x and y
    public CubicSpline(double[] x, double[] y) {
        this.nPoints = x.length;
        this.nPointsOriginal = this.nPoints;
        if (this.nPoints != y.length) {
            throw new IllegalArgumentException("Arrays x and y are of different length " + this.nPoints + " " + y.length);
        }
        if (this.nPoints < 3) {
            throw new IllegalArgumentException("A minimum of three data points is needed");
        }
        this.x = new double[nPoints];
        this.y = new double[nPoints];
        this.d2ydx2 = new double[nPoints];
        for (int i = 0; i < this.nPoints; i++) {
            this.x[i] = x[i];
            this.y[i] = y[i];
        }
        this.orderPoints();
        this.checkForIdenticalPoints();
        this.calcDeriv();

    }

    // Constructor with data arrays initialised to zero
    // Primarily for use by BiCubicSpline
    public CubicSpline(int nPoints) {
        this.nPoints = nPoints;
        this.nPointsOriginal = this.nPoints;
        if (this.nPoints < 3) {
            throw new IllegalArgumentException("A minimum of three data points is needed");
        }
        this.x = new double[nPoints];
        this.y = new double[nPoints];
        this.d2ydx2 = new double[nPoints];
    }

    // METHODS
    // Reset rounding error check option
    // Default option: points outside the interpolation bounds by less than the potential rounding error rounded to the bounds limit
    // This method causes this check to be ignored and an exception to be thrown if any poit lies outside the interpolation bounds
    public static void noRoundingErrorCheck() {
        CubicSpline.roundingCheck = false;
    }

    // Reset potential rounding error value
    // Default option: points outside the interpolation bounds by less than the potential rounding error rounded to the bounds limit
    // The default value for the potential rounding error is 5e-15*times the 10^exponent of the value outside the bounds
    // This method allows the 5e-15 to be reset
    public static void potentialRoundingError(double potentialRoundingError) {
        CubicSpline.potentialRoundingError = potentialRoundingError;
    }

    // Resets the x y data arrays - primarily for use in BiCubicSpline
    public void resetData(double[] x, double[] y) {
        this.nPoints = this.nPointsOriginal;
        if (x.length != y.length) {
            throw new IllegalArgumentException("Arrays x and y are of different length");
        }
        if (this.nPoints != x.length) {
            throw new IllegalArgumentException("Original array length not matched by new array length");
        }

        for (int i = 0; i < this.nPoints; i++) {
            this.x[i] = x[i];
            this.y[i] = y[i];
        }
        this.orderPoints();
        this.checkForIdenticalPoints();
    }

    // Reset the default handing of identical abscissae with different ordinates
    // from the default option of separating the two relevant abscissae by 0.001 of the range
    // to averaging the relevant ordinates
    public static void averageIdenticalAbscissae() {
        CubicSpline.averageIdenticalAbscissae = true;
    }

    // Sort points into an ascending abscissa order
    public void orderPoints() {
        double[] dummy = new double[nPoints];
        this.newAndOldIndices = new int[nPoints];
        // Sort x into ascending order storing indices changes
        Fmath.selectionSort(this.x, dummy, this.newAndOldIndices);
        // Sort x into ascending order and make y match the new order storing both new x and new y
        Fmath.selectionSort(this.x, this.y, this.x, this.y);

        // Minimum and maximum values and range
        this.xMin = Primitives.min(this.x);
        this.xMax = Primitives.max(this.x);
        range = xMax - xMin;
    }

    // get the maximum value
    public double getXmax() {
        return this.xMax;
    }

    // get the minimum value
    public double getXmin() {
        return this.xMin;
    }

    // get the limits of x
    public double[] getLimits() {
        double[] limits = {this.xMin, this.xMax};
        return limits;
    }

    // print to screen the limis of x
    public void displayLimits() {
        System.out.println("\nThe limits of the abscissae (x-values) are " + this.xMin + " and " + this.xMax + "\n");
    }

    // Checks for and removes all but one of identical points
    // Checks and appropriately handles identical abscissae with differing ordinates
    public void checkForIdenticalPoints() {
        int nP = this.nPoints;
        boolean test1 = true;
        int ii = 0;
        while (test1) {
            boolean test2 = true;
            int jj = ii + 1;
            while (test2) {
                if (this.x[ii] == this.x[jj]) {
                    if (this.y[ii] == this.y[jj]) {
                        if (!CubicSpline.supress) {
                            System.out.print("CubicSpline: Two identical points, " + this.x[ii] + ", " + this.y[ii]);
                            System.out.println(", in data array at indices " + this.newAndOldIndices[ii] + " and " + this.newAndOldIndices[jj] + ", latter point removed");
                        }
                        double[] xx = new double[this.nPoints - 1];
                        double[] yy = new double[this.nPoints - 1];
                        int[] naoi = new int[this.nPoints - 1];
                        for (int i = 0; i < jj; i++) {
                            xx[i] = this.x[i];
                            yy[i] = this.y[i];
                            naoi[i] = this.newAndOldIndices[i];
                        }
                        for (int i = jj; i < this.nPoints - 1; i++) {
                            xx[i] = this.x[i + 1];
                            yy[i] = this.y[i + 1];
                            naoi[i] = this.newAndOldIndices[i + 1];
                        }
                        this.nPoints--;
                        System.arraycopy(xx, 0, this.x, 0, xx.length);
                        System.arraycopy(yy, 0, this.y, 0, yy.length);
                        System.arraycopy(naoi, 0, this.newAndOldIndices, 0, naoi.length);
//			this.x = Conv.copy(xx);
//			this.y = Conv.copy(yy);
//			this.newAndOldIndices = Conv.copy(naoi);
                    } else {
                        if (CubicSpline.averageIdenticalAbscissae == true) {
                            if (!CubicSpline.supress) {
                                System.out.print("CubicSpline: Two identical points on the absicca (x-axis) with different ordinate (y-axis) values, " + x[ii] + ": " + y[ii] + ", " + y[jj]);
                                System.out.println(", average of the ordinates taken");
                            }
                            this.y[ii] = (this.y[ii] + this.y[jj]) / 2.0D;
                            double[] xx = new double[this.nPoints - 1];
                            double[] yy = new double[this.nPoints - 1];
                            int[] naoi = new int[this.nPoints - 1];
                            for (int i = 0; i < jj; i++) {
                                xx[i] = this.x[i];
                                yy[i] = this.y[i];
                                naoi[i] = this.newAndOldIndices[i];
                            }
                            for (int i = jj; i < this.nPoints - 1; i++) {
                                xx[i] = this.x[i + 1];
                                yy[i] = this.y[i + 1];
                                naoi[i] = this.newAndOldIndices[i + 1];
                            }
                            this.nPoints--;
                            System.arraycopy(xx, 0, this.x, 0, xx.length);
                            System.arraycopy(yy, 0, this.y, 0, yy.length);
                            System.arraycopy(naoi, 0, this.newAndOldIndices, 0, naoi.length);
//			    this.x = Conv.copy(xx);
//			    this.y = Conv.copy(yy);
//			    this.newAndOldIndices = Conv.copy(naoi);
                        } else {
                            double sepn = range * 0.0005D;
                            if (!CubicSpline.supress) {
                                System.out.print("CubicSpline: Two identical points on the absicca (x-axis) with different ordinate (y-axis) values, " + x[ii] + ": " + y[ii] + ", " + y[jj]);
                            }
                            boolean check = false;
                            if (ii == 0) {
                                if (x[2] - x[1] <= sepn) {
                                    sepn = (x[2] - x[1]) / 2.0D;
                                }
                                if (this.y[0] > this.y[1]) {
                                    if (this.y[1] > this.y[2]) {
                                        check = stay(ii, jj, sepn);
                                    } else {
                                        check = swap(ii, jj, sepn);
                                    }
                                } else {
                                    if (this.y[2] <= this.y[1]) {
                                        check = swap(ii, jj, sepn);
                                    } else {
                                        check = stay(ii, jj, sepn);
                                    }
                                }
                            }
                            if (jj == this.nPoints - 1) {
                                if (x[nP - 2] - x[nP - 3] <= sepn) {
                                    sepn = (x[nP - 2] - x[nP - 3]) / 2.0D;
                                }
                                if (this.y[ii] <= this.y[jj]) {
                                    if (this.y[ii - 1] <= this.y[ii]) {
                                        check = stay(ii, jj, sepn);
                                    } else {
                                        check = swap(ii, jj, sepn);
                                    }
                                } else {
                                    if (this.y[ii - 1] <= this.y[ii]) {
                                        check = swap(ii, jj, sepn);
                                    } else {
                                        check = stay(ii, jj, sepn);
                                    }
                                }
                            }
                            if (ii != 0 && jj != this.nPoints - 1) {
                                if (x[ii] - x[ii - 1] <= sepn) {
                                    sepn = (x[ii] - x[ii - 1]) / 2;
                                }
                                if (x[jj + 1] - x[jj] <= sepn) {
                                    sepn = (x[jj + 1] - x[jj]) / 2;
                                }
                                if (this.y[ii] > this.y[ii - 1]) {
                                    if (this.y[jj] > this.y[ii]) {
                                        if (this.y[jj] > this.y[jj + 1]) {
                                            if (this.y[ii - 1] <= this.y[jj + 1]) {
                                                check = stay(ii, jj, sepn);
                                            } else {
                                                check = swap(ii, jj, sepn);
                                            }
                                        } else {
                                            check = stay(ii, jj, sepn);
                                        }
                                    } else {
                                        if (this.y[jj + 1] > this.y[jj]) {
                                            //ToDo check this. This was the original: if (this.y[jj + 1] > this.y[ii - 1] && this.y[jj + 1] > this.y[ii - 1]) {
                                            if (this.y[jj + 1] > this.y[ii - 1]) {
                                                check = stay(ii, jj, sepn);
                                            }
                                        } else {
                                            check = swap(ii, jj, sepn);
                                        }
                                    }
                                } else {
                                    if (this.y[jj] > this.y[ii]) {
                                        if (this.y[jj + 1] > this.y[jj]) {
                                            check = stay(ii, jj, sepn);
                                        }
                                    } else {
                                        if (this.y[jj + 1] > this.y[ii - 1]) {
                                            check = stay(ii, jj, sepn);
                                        } else {
                                            check = swap(ii, jj, sepn);
                                        }
                                    }
                                }
                            }

                            if (check == false) {
                                check = stay(ii, jj, sepn);
                            }
                            if (!CubicSpline.supress) {
                                System.out.println(", the two abscissae have been separated by a distance " + sepn);
                            }
                            jj++;
                        }
                    }
                    if ((this.nPoints - 1) == ii) {
                        test2 = false;
                    }
                } else {
                    jj++;
                }
                if (jj >= this.nPoints) {
                    test2 = false;
                }
            }
            ii++;
            if (ii >= this.nPoints - 1) {
                test1 = false;
            }
        }
        if (this.nPoints < 3) {
            throw new IllegalArgumentException("Removal of duplicate points has reduced the number of points to less than the required minimum of three data points");
        }

        this.checkPoints = true;
    }

    // Swap method for checkForIdenticalPoints procedure
    private boolean swap(int ii, int jj, double sepn) {
        this.x[ii] += sepn;
        this.x[jj] -= sepn;
        double hold = this.x[ii];
        this.x[ii] = this.x[jj];
        this.x[jj] = hold;
        hold = this.y[ii];
        this.y[ii] = this.y[jj];
        this.y[jj] = hold;
        return true;
    }

    // Stay method for checkForIdenticalPoints procedure
    private boolean stay(int ii, int jj, double sepn) {
        this.x[ii] -= sepn;
        this.x[jj] += sepn;
        return true;
    }

    // Supress warning messages in the identifiaction of duplicate points
    public static void supress() {
        CubicSpline.supress = true;
    }

    // Unsupress warning messages in the identifiaction of duplicate points
    public static void unsupress() {
        CubicSpline.supress = false;
    }

    // Returns a new CubicSpline setting array lengths to n and all array values to zero with natural spline default
    // Primarily for use in BiCubicSpline
    public static CubicSpline zero(int n) {
        if (n < 3) {
            throw new IllegalArgumentException("A minimum of three data points is needed");
        }
        CubicSpline aa = new CubicSpline(n);
        return aa;
    }

    // Create a one dimensional array of cubic spline objects of length n each of array length m
    // Primarily for use in BiCubicSpline
    public static CubicSpline[] oneDarray(int n, int m) {
        if (m < 3) {
            throw new IllegalArgumentException("A minimum of three data points is needed");
        }
        CubicSpline[] a = new CubicSpline[n];
        for (int i = 0; i < n; i++) {
            a[i] = CubicSpline.zero(m);
        }
        return a;
    }

    // Enters the first derivatives of the cubic spline at
    // the first and last point of the tabulated data
    // Overrides a natural spline
    public void setDerivLimits(double yp1, double ypn) {
        this.yp1 = yp1;
        this.ypn = ypn;
        this.calcDeriv();
    }

    // Resets a natural spline
    // Use above - this kept for backward compatibility
    public void setDerivLimits() {
        this.yp1 = Double.NaN;
        this.ypn = Double.NaN;
        this.calcDeriv();
    }

    // Enters the first derivatives of the cubic spline at
    // the first and last point of the tabulated data
    // Overrides a natural spline
    // Use setDerivLimits(double yp1, double ypn) - this kept for backward compatibility
    public void setDeriv(double yp1, double ypn) {
        this.yp1 = yp1;
        this.ypn = ypn;
        this.calcDeriv();
    }

    // Returns the internal array of second derivatives
    public double[] getDeriv() {
        if (!this.derivCalculated) {
            this.calcDeriv();
        }
        return this.d2ydx2;
    }

    // Sets the internal array of second derivatives
    // Used primarily with BiCubicSpline
    public void setDeriv(double[] deriv) {
        this.d2ydx2 = deriv;
        this.derivCalculated = true;
    }

    //  Calculates the second derivatives of the tabulated function
    //  for use by the cubic spline interpolation method (.interpolate)
    //  This method follows the procedure in Numerical Methods C language procedure for calculating second derivatives
    public void calcDeriv() {
        double p = 0.0D, qn = 0.0D, sig = 0.0D, un = 0.0D;
        double[] u = new double[nPoints];

        if (Double.isNaN(this.yp1)) {
            d2ydx2[0] = u[0] = 0.0;
        } else {
            this.d2ydx2[0] = -0.5;
            u[0] = (3.0 / (this.x[1] - this.x[0])) * ((this.y[1] - this.y[0]) / (this.x[1] - this.x[0]) - this.yp1);
        }

        for (int i = 1; i <= this.nPoints - 2; i++) {
            sig = (this.x[i] - this.x[i - 1]) / (this.x[i + 1] - this.x[i - 1]);
            p = sig * this.d2ydx2[i - 1] + 2.0;
            this.d2ydx2[i] = (sig - 1.0) / p;
            u[i] = (this.y[i + 1] - this.y[i]) / (this.x[i + 1] - this.x[i]) - (this.y[i] - this.y[i - 1]) / (this.x[i] - this.x[i - 1]);
            u[i] = (6.0 * u[i] / (this.x[i + 1] - this.x[i - 1]) - sig * u[i - 1]) / p;
        }

        if (Double.isNaN(this.ypn)) {
            qn = un = 0.0;
        } else {
            qn = 0.5;
            un = (3.0 / (this.x[nPoints - 1] - this.x[this.nPoints - 2])) * (this.ypn - (this.y[this.nPoints - 1] - this.y[this.nPoints - 2]) / (this.x[this.nPoints - 1] - x[this.nPoints - 2]));
        }

        this.d2ydx2[this.nPoints - 1] = (un - qn * u[this.nPoints - 2]) / (qn * this.d2ydx2[this.nPoints - 2] + 1.0);
        for (int k = this.nPoints - 2; k >= 0; k--) {
            this.d2ydx2[k] = this.d2ydx2[k] * this.d2ydx2[k + 1] + u[k];
        }
        this.derivCalculated = true;
    }

    //  INTERPOLATE
    //  Returns an interpolated value of y for a value of x from a tabulated function y=f(x)
    //  after the data has been entered via a constructor.
    //  The derivatives are calculated, bt calcDeriv(), on the first call to this method ands are
    //  then stored for use on all subsequent calls
    public double interpolate(double xx) {

        // Check for violation of interpolation bounds
        if (xx < this.x[0]) {
            // if violation is less than potntial rounding error - amend to lie with bounds
            if (CubicSpline.roundingCheck && Math.abs(x[0] - xx) <= Math.pow(10, Math.floor(Math.log10(Math.abs(this.x[0])))) * CubicSpline.potentialRoundingError) {
                xx = x[0];
            } else {
                throw new IllegalArgumentException("x (" + xx + ") is outside the range of data points (" + x[0] + " to " + x[this.nPoints - 1] + ")");
            }
        }
        if (xx > this.x[this.nPoints - 1]) {
            if (CubicSpline.roundingCheck && Math.abs(xx - this.x[this.nPoints - 1]) <= Math.pow(10, Math.floor(Math.log10(Math.abs(this.x[this.nPoints - 1])))) * CubicSpline.potentialRoundingError) {
                xx = this.x[this.nPoints - 1];
            } else {
                throw new IllegalArgumentException("x (" + xx + ") is outside the range of data points (" + x[0] + " to " + x[this.nPoints - 1] + ")");
            }
        }

        double h = 0.0D, b = 0.0D, a = 0.0D, yy = 0.0D;
        int k = 0;
        int klo = 0;
        int khi = this.nPoints - 1;
        while (khi - klo > 1) {
            k = (khi + klo) >> 1;
            if (this.x[k] > xx) {
                khi = k;
            } else {
                klo = k;
            }
        }
        h = this.x[khi] - this.x[klo];

        if (h == 0.0) {
            throw new IllegalArgumentException("Two values of x are identical: point " + klo + " (" + this.x[klo] + ") and point " + khi + " (" + this.x[khi] + ")");
        } else {
            a = (this.x[khi] - xx) / h;
            b = (xx - this.x[klo]) / h;
            this.yy = a * this.y[klo] + b * this.y[khi] + ((a * a * a - a) * this.d2ydx2[klo] + (b * b * b - b) * this.d2ydx2[khi]) * (h * h) / 6.0;
            this.dydx = (y[khi] - y[klo]) / h - ((3 * a * a - 1.0) * this.d2ydx2[klo] - (3 * b * b - 1.0) * this.d2ydx2[khi]) * h / 6.0;
        }
        return this.yy;
    }

    //  Returns an interpolated value of y  and of the first derivative dy/dx for a value of x from a tabulated function y=f(x)
    //  after the data has been entered via a constructor.
    public double[] interpolate_for_y_and_dydx(double xx) {
        this.interpolate(xx);
        double[] ret = {this.yy, this.dydx};
        return ret;
    }

    //  Returns an interpolated value of y for a value of x (xx) from a tabulated function y=f(x)
    //  after the derivatives (deriv) have been calculated independently of calcDeriv().
    public static double interpolate(double xx, double[] x, double[] y, double[] deriv) {

        if (((x.length != y.length) || (x.length != deriv.length)) || (y.length != deriv.length)) {
            throw new IllegalArgumentException("array lengths are not all equal");
        }
        int n = x.length;
        double h = 0.0D, b = 0.0D, a = 0.0D, yy = 0.0D;

        int k = 0;
        int klo = 0;
        int khi = n - 1;
        while (khi - klo > 1) {
            k = (khi + klo) >> 1;
            if (x[k] > xx) {
                khi = k;
            } else {
                klo = k;
            }
        }
        h = x[khi] - x[klo];

        if (h == 0.0) {
            throw new IllegalArgumentException("Two values of x are identical");
        } else {
            a = (x[khi] - xx) / h;
            b = (xx - x[klo]) / h;
            yy = a * y[klo] + b * y[khi] + ((a * a * a - a) * deriv[klo] + (b * b * b - b) * deriv[khi]) * (h * h) / 6.0;
        }
        return yy;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy