001/* An interpolator for a specified array of indexes and values.
002
003 Copyright (c) 1998-2013 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.math;
029
030///////////////////////////////////////////////////////////////////
031//// Interpolation
032
033/**
034 This class provides algorithms to do interpolation. Currently, zero,
035 first, and third order interpolations are supported. These are the
036 interpolation orders most often used in practice. zero order interpolation
037 holds the last reference value; first order does linear interpolation;
038 and third order interpolation is based on the Hermite curves in chapter
039 11 of "Computer Graphic, Principles and Practice", by Foley, van Dam, Feiner
040 and Hughes, 2nd ed. in C, 1996.
041 <p>
042 The setValues() method specifies the reference values as a double
043 array. setIndexes() specifies the indexes of those values as an
044 int array. These two arrays must have the same length, and the indexes
045 must be increasing and non-negative; otherwise an exception will be thrown.
046 The values are periodic if a positive period is set by setPeriod(). In
047 this case, the period must be greater than the largest index, and
048 values within the index range 0 to (period-1) are repeated indefinitely.
049 If the period is zero, the values are not periodic, and the values
050 outside the range of the indexes are considered to be 0.0.
051 The interpolation order is set by setOrder().
052 <p>
053 The default reference values are {1.0, 0.0} and the indexes are {0, 1}.
054 The default period is 2 and the order is 0.
055 <p>
056
057 @author Sarah Packman, Yuhong Xiong
058 @version $Id$
059 @since Ptolemy II 0.4
060 @Pt.ProposedRating Yellow (yuhong)
061 @Pt.AcceptedRating red (cxh)
062 */
063public class Interpolation {
064    /** Construct an instance of Interpolation using the default parameters.
065     */
066    public Interpolation() {
067    }
068
069    ///////////////////////////////////////////////////////////////////
070    ////                         public methods                    ////
071
072    /** Return the reference indexes.
073     *  @return An int array.
074     *  @see #setIndexes(int[])
075     */
076    public int[] getIndexes() {
077        return _indexes;
078    }
079
080    /** Return the interpolation order.
081     *  @return An int.
082     *  @see #setOrder(int)
083     */
084    public int getOrder() {
085        return _order;
086    }
087
088    /** Return the value repetition period.
089     *  @return An int.
090     *  @see #setPeriod(int)
091     */
092    public int getPeriod() {
093        return _period;
094    }
095
096    /** Return the reference values.
097     *  @return An double array.
098     *  @see #setValues(double[])
099     */
100    public double[] getValues() {
101        return _values;
102    }
103
104    /** Return the interpolation result for the specified index.
105     *  @param index The point of interpolation. Can be negative
106     *  @return A double.
107     *  @exception IllegalStateException If the index and value arrays
108     *   do not have the same length, or the period is not 0 and not
109     *   greater than the largest index.
110     */
111    public double interpolate(int index) {
112        int numRefPoints = _indexes.length;
113
114        if (numRefPoints != _values.length) {
115            throw new IllegalStateException("Interpolation.interpolate(): "
116                    + "The index and value arrays do "
117                    + "not have the same length.");
118        }
119
120        int largestIndex = _indexes[numRefPoints - 1];
121
122        if (_period != 0 && _period <= largestIndex) {
123            throw new IllegalStateException("Interpolation.interpolate(): "
124                    + "The period is not 0 and not " + "greater than the "
125                    + "largest index.");
126        }
127
128        if (index < 0 || index > largestIndex) {
129            if (_period == 0) {
130                return 0.0;
131            } else {
132                // convert index to a value within [0, period-1]
133                if (index < 0) {
134                    index += (-index / _period + 1) * _period;
135                }
136
137                index %= _period;
138            }
139        }
140
141        // index is now within [0, period-1]. If it is outside the range of
142        // the smallest and the largest index, values must be periodic.
143        // Handle a special case where the number of reference points is
144        // 1. The code for order 3 later won't work for this case.
145        if (numRefPoints == 1) {
146            return _values[0];
147        }
148
149        // indexIndexStart is the index to _indexes whose entry is the
150        // index to the left of the interpolation point.
151        int indexIndexStart = -1;
152
153        // search though all indexes to find iStart.
154        for (int i = 0; i < numRefPoints; i++) {
155            if (_indexes[i] == index) {
156                return _values[i];
157            } else if (_indexes[i] < index) {
158                indexIndexStart = i;
159            } else {
160                break;
161            }
162        }
163
164        // Perform interpolation
165        if (_order == 0) {
166            if (indexIndexStart != -1) {
167                return _values[indexIndexStart];
168            } else {
169                return _values[numRefPoints - 1];
170            }
171        }
172
173        // order must be 1 or 3, need at least the two points surrounding
174        // the interpolation point.
175        int iStart;
176
177        // order must be 1 or 3, need at least the two points surrounding
178        // the interpolation point.
179        int iEnd;
180        double vStart;
181        double vEnd;
182
183        if (indexIndexStart == -1) {
184            iStart = _indexes[numRefPoints - 1] - _period;
185            vStart = _values[numRefPoints - 1];
186        } else {
187            iStart = _indexes[indexIndexStart];
188            vStart = _values[indexIndexStart];
189        }
190
191        if (indexIndexStart == numRefPoints - 1) {
192            iEnd = _indexes[0] + _period;
193            vEnd = _values[0];
194        } else {
195            iEnd = _indexes[indexIndexStart + 1];
196            vEnd = _values[indexIndexStart + 1];
197        }
198
199        if (_order == 1) {
200            return vStart
201                    + (index - iStart) * (vEnd - vStart) / (iEnd - iStart);
202        }
203
204        // order is 3. Need the points before Start and the point after End
205        // to compute the tangent at Start and End.
206        int iBeforeStart;
207
208        // order is 3. Need the points before Start and the point after End
209        // to compute the tangent at Start and End.
210        int iAfterEnd;
211        double vBeforeStart;
212        double vAfterEnd;
213
214        if (indexIndexStart == -1) {
215            iBeforeStart = _indexes[numRefPoints - 2] - _period;
216            vBeforeStart = _values[numRefPoints - 2];
217        } else if (indexIndexStart == 0) {
218            if (_period > 0) {
219                iBeforeStart = _indexes[numRefPoints - 1] - _period;
220                vBeforeStart = _values[numRefPoints - 1];
221            } else {
222                // Not periodic
223                iBeforeStart = _indexes[0] - 1;
224                vBeforeStart = 0.0;
225            }
226        } else {
227            iBeforeStart = _indexes[indexIndexStart - 1];
228            vBeforeStart = _values[indexIndexStart - 1];
229        }
230
231        if (indexIndexStart == numRefPoints - 1) {
232            iAfterEnd = _indexes[1] + _period;
233            vAfterEnd = _values[1];
234        } else if (indexIndexStart == numRefPoints - 2) {
235            if (_period > 0) {
236                iAfterEnd = _indexes[0] + _period;
237                vAfterEnd = _values[0];
238            } else {
239                // Not periodic
240                iAfterEnd = _indexes[numRefPoints - 1] + 1;
241                vAfterEnd = 0.0;
242            }
243        } else {
244            iAfterEnd = _indexes[indexIndexStart + 2];
245            vAfterEnd = _values[indexIndexStart + 2];
246        }
247
248        // computer the tangent at Start and End.
249        double tanBefore2Start = (vStart - vBeforeStart)
250                / (iStart - iBeforeStart);
251        double tanStart2End = (vEnd - vStart) / (iEnd - iStart);
252        double tanEnd2After = (vAfterEnd - vEnd) / (iAfterEnd - iEnd);
253
254        double tanStart = 0.5 * (tanBefore2Start + tanStart2End);
255        double tanEnd = 0.5 * (tanStart2End + tanEnd2After);
256
257        return _hermite(index, iStart, vStart, tanStart, iEnd, vEnd, tanEnd);
258    }
259
260    /** Set the reference indexes.
261     *  @param indexes An int array.
262     *  @exception IllegalArgumentException If the argument array is
263     *   not increasing and non-negative.
264     *  @see #getIndexes()
265     */
266    public void setIndexes(int[] indexes) {
267        int prev = -1;
268
269        for (int indexe : indexes) {
270            if (indexe <= prev) {
271                throw new IllegalArgumentException("Interpolation.setIndexes"
272                        + " index array is not increasing and non-negative.");
273            }
274
275            prev = indexe;
276        }
277
278        _indexes = indexes;
279    }
280
281    /** Set the interpolation order.
282     *  @param order An int.
283     *  @exception IllegalArgumentException If the order is not 0, 1, or 3.
284     *  @see #getOrder()
285     */
286    public void setOrder(int order) {
287        if (order != 0 && order != 1 && order != 3) {
288            throw new IllegalArgumentException("Interpolation.setOrder: "
289                    + "The order " + order + " is not valid.");
290        }
291
292        _order = order;
293    }
294
295    /** Set the value repetition period.
296     *  @param period An int.
297     *  @exception IllegalArgumentException If the period is negative.
298     *  @see #getPeriod()
299     */
300    public void setPeriod(int period) {
301        if (period < 0) {
302            throw new IllegalArgumentException(
303                    "Interpolation.setPeriod: " + "The period is negative.");
304        }
305
306        _period = period;
307    }
308
309    /** Set the reference values.
310     *  @param values A double array.
311     *  @see #getValues()
312     */
313    public void setValues(double[] values) {
314        _values = values;
315    }
316
317    ///////////////////////////////////////////////////////////////////
318    ////                         private methods                   ////
319    // Return the Hermite curve interpolation. The arguments are: the
320    // interpolation point index, the index/value/tangent of the starting
321    // reference point, the index/value/tangent of the ending reference
322    // point.
323    private double _hermite(int index, int iStart, double vStart,
324            double tanStart, int iEnd, double vEnd, double tanEnd) {
325        // forming the Hermite matrix M
326        double[][] M = new double[4][4];
327        double iStartSqr = iStart * iStart;
328        double iEndSqr = iEnd * iEnd;
329        M[0][0] = iStartSqr * iStart;
330        M[0][1] = iStartSqr;
331        M[0][2] = iStart;
332        M[0][3] = 1;
333
334        M[1][0] = iEndSqr * iEnd;
335        M[1][1] = iEndSqr;
336        M[1][2] = iEnd;
337        M[1][3] = 1;
338
339        M[2][0] = 3 * iStartSqr;
340        M[2][1] = 2 * iStart;
341        M[2][2] = 1;
342        M[2][3] = 0;
343
344        M[3][0] = 3 * iEndSqr;
345        M[3][1] = 2 * iEnd;
346        M[3][2] = 1;
347        M[3][3] = 0;
348
349        double[][] MInverse = DoubleMatrixMath.inverse(M);
350
351        // forming the column vector of values and tangents
352        double[] Gh = new double[4];
353        Gh[0] = vStart;
354        Gh[1] = vEnd;
355        Gh[2] = tanStart;
356        Gh[3] = tanEnd;
357
358        // compute the coefficients vector coef[a, b, c, d] or the 3rd order
359        // curve.
360        double[] coef = DoubleMatrixMath.multiply(Gh, MInverse);
361
362        // compute the interpolated value
363        double indexSqr = index * index;
364        return coef[0] * indexSqr * index + coef[1] * indexSqr + coef[2] * index
365                + coef[3];
366    }
367
368    ///////////////////////////////////////////////////////////////////
369    ////                         private variables                 ////
370    private int[] _indexes = { 0, 1 };
371
372    private double[] _values = { 1.0, 0.0 };
373
374    private int _period = 2;
375
376    private int _order = 0;
377}