001    /* ============================================================
002     * JRobin : Pure java implementation of RRDTool's functionality
003     * ============================================================
004     *
005     * Project Info:  http://www.jrobin.org
006     * Project Lead:  Sasa Markovic (saxon@jrobin.org)
007     *
008     * Developers:    Sasa Markovic (saxon@jrobin.org)
009     *
010     *
011     * (C) Copyright 2003-2005, by Sasa Markovic.
012     *
013     * This library is free software; you can redistribute it and/or modify it under the terms
014     * of the GNU Lesser General Public License as published by the Free Software Foundation;
015     * either version 2.1 of the License, or (at your option) any later version.
016     *
017     * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
018     * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
019     * See the GNU Lesser General Public License for more details.
020     *
021     * You should have received a copy of the GNU Lesser General Public License along with this
022     * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
023     * Boston, MA 02111-1307, USA.
024     */
025    package org.jrobin.data;
026    
027    import org.jrobin.core.RrdException;
028    import org.jrobin.core.Util;
029    
030    import java.util.Calendar;
031    import java.util.Date;
032    
033    /**
034     * Class used to interpolate datasource values from the collection of (timestamp, values)
035     * points using natural cubic spline interpolation.<p>
036     * <p/>
037     * <b>WARNING</b>: So far, this class cannot handle NaN datasource values
038     * (an exception will be thrown by the constructor). Future releases might change this.
039     */
040    public class CubicSplineInterpolator extends Plottable {
041            private double[] x;
042            private double[] y;
043    
044            // second derivates come here
045            private double[] y2;
046    
047            // internal spline variables
048            private int n, klo, khi;
049    
050            /**
051             * Creates cubic spline interpolator from arrays of timestamps and corresponding
052             * datasource values.
053             *
054             * @param timestamps timestamps in seconds
055             * @param values         corresponding datasource values
056             * @throws RrdException Thrown if supplied arrays do not contain at least 3 values, or if
057             *                      timestamps are not ordered, or array lengths are not equal, or some datasource value is NaN.
058             */
059            public CubicSplineInterpolator(long[] timestamps, double[] values) throws RrdException {
060                    this.x = new double[timestamps.length];
061                    for (int i = 0; i < timestamps.length; i++) {
062                            this.x[i] = timestamps[i];
063                    }
064                    this.y = values;
065                    validate();
066                    spline();
067            }
068    
069            /**
070             * Creates cubic spline interpolator from arrays of Date objects and corresponding
071             * datasource values.
072             *
073             * @param dates  Array of Date objects
074             * @param values corresponding datasource values
075             * @throws RrdException Thrown if supplied arrays do not contain at least 3 values, or if
076             *                      timestamps are not ordered, or array lengths are not equal, or some datasource value is NaN.
077             */
078            public CubicSplineInterpolator(Date[] dates, double[] values) throws RrdException {
079                    this.x = new double[dates.length];
080                    for (int i = 0; i < dates.length; i++) {
081                            this.x[i] = Util.getTimestamp(dates[i]);
082                    }
083                    this.y = values;
084                    validate();
085                    spline();
086            }
087    
088            /**
089             * Creates cubic spline interpolator from arrays of GregorianCalendar objects and corresponding
090             * datasource values.
091             *
092             * @param dates  Array of GregorianCalendar objects
093             * @param values corresponding datasource values
094             * @throws RrdException Thrown if supplied arrays do not contain at least 3 values, or if
095             *                      timestamps are not ordered, or array lengths are not equal, or some datasource value is NaN.
096             */
097            public CubicSplineInterpolator(Calendar[] dates, double[] values) throws RrdException {
098                    this.x = new double[dates.length];
099                    for (int i = 0; i < dates.length; i++) {
100                            this.x[i] = Util.getTimestamp(dates[i]);
101                    }
102                    this.y = values;
103                    validate();
104                    spline();
105            }
106    
107            /**
108             * Creates cubic spline interpolator for an array of 2D-points.
109             *
110             * @param x x-axis point coordinates
111             * @param y y-axis point coordinates
112             * @throws RrdException Thrown if supplied arrays do not contain at least 3 values, or if
113             *                      timestamps are not ordered, or array lengths are not equal, or some datasource value is NaN.
114             */
115            public CubicSplineInterpolator(double[] x, double[] y) throws RrdException {
116                    this.x = x;
117                    this.y = y;
118                    validate();
119                    spline();
120            }
121    
122            private void validate() throws RrdException {
123                    boolean ok = true;
124                    if (x.length != y.length || x.length < 3) {
125                            ok = false;
126                    }
127                    for (int i = 0; i < x.length - 1 && ok; i++) {
128                            if (x[i] >= x[i + 1] || Double.isNaN(y[i])) {
129                                    ok = false;
130                            }
131                    }
132                    if (!ok) {
133                            throw new RrdException("Invalid plottable data supplied");
134                    }
135            }
136    
137            private void spline() {
138                    n = x.length;
139                    y2 = new double[n];
140                    double[] u = new double[n - 1];
141                    y2[0] = y2[n - 1] = 0.0;
142                    u[0] = 0.0; // natural spline
143                    for (int i = 1; i <= n - 2; i++) {
144                            double sig = (x[i] - x[i - 1]) / (x[i + 1] - x[i - 1]);
145                            double p = sig * y2[i - 1] + 2.0;
146                            y2[i] = (sig - 1.0) / p;
147                            u[i] = (y[i + 1] - y[i]) / (x[i + 1] - x[i]) - (y[i] - y[i - 1]) / (x[i] - x[i - 1]);
148                            u[i] = (6.0 * u[i] / (x[i + 1] - x[i - 1]) - sig * u[i - 1]) / p;
149                    }
150                    for (int k = n - 2; k >= 0; k--) {
151                            y2[k] = y2[k] * y2[k + 1] + u[k];
152                    }
153                    // prepare everything for getValue()
154                    klo = 0;
155                    khi = n - 1;
156            }
157    
158            /**
159             * Calculates spline-interpolated y-value for the corresponding x-value. Call
160             * this if you need spline-interpolated values in your code.
161             *
162             * @param xval x-value
163             * @return inteprolated y-value
164             */
165            public double getValue(double xval) {
166                    if (xval < x[0] || xval > x[n - 1]) {
167                            return Double.NaN;
168                    }
169                    if (xval < x[klo] || xval > x[khi]) {
170                            // out of bounds
171                            klo = 0;
172                            khi = n - 1;
173                    }
174                    while (khi - klo > 1) {
175                            // find bounding interval using bisection method
176                            int k = (khi + klo) / 2;
177                            if (x[k] > xval) {
178                                    khi = k;
179                            }
180                            else {
181                                    klo = k;
182                            }
183                    }
184                    double h = x[khi] - x[klo];
185                    double a = (x[khi] - xval) / h;
186                    double b = (xval - x[klo]) / h;
187                    return a * y[klo] + b * y[khi] +
188                                    ((a * a * a - a) * y2[klo] + (b * b * b - b) * y2[khi]) * (h * h) / 6.0;
189            }
190    
191            /**
192             * Method overriden from the base class. This method will be called by the framework. Call
193             * this method only if you need spline-interpolated values in your code.
194             *
195             * @param timestamp timestamp in seconds
196             * @return inteprolated datasource value
197             */
198            public double getValue(long timestamp) {
199                    return getValue((double) timestamp);
200            }
201    
202    }