001/* An interpolator for a specified array of times and values.
002
003 Copyright (c) 1998-2014 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.domains.continuous.lib;
029
030import ptolemy.actor.lib.DiscreteClock;
031import ptolemy.actor.util.Time;
032import ptolemy.data.DoubleToken;
033import ptolemy.data.Token;
034import ptolemy.data.expr.Parameter;
035import ptolemy.data.expr.SingletonParameter;
036import ptolemy.data.expr.StringParameter;
037import ptolemy.data.type.ArrayType;
038import ptolemy.data.type.BaseType;
039import ptolemy.kernel.CompositeEntity;
040import ptolemy.kernel.util.Attribute;
041import ptolemy.kernel.util.IllegalActionException;
042import ptolemy.kernel.util.NameDuplicationException;
043import ptolemy.math.DoubleMatrixMath;
044
045///////////////////////////////////////////////////////////////////
046//// Waveform
047
048/**
049 This actor produces a periodic continuous-time signal defined by a
050 periodic sequence of samples and an interpolation method.
051 The <i>interpolation</i> parameter specifies the interpolation method,
052 which is either "linear",   which indicates linear interpolation, or "hermite",
053 which indicates  third order interpolation based on the Hermite curves in chapter
054 11 of "Computer Graphic, Principles and Practice", by Foley, van Dam, Feiner
055 and Hughes, 2nd ed. in C, 1996. Interpolation is calculated assuming that
056 the waveform is periodic.
057 <p>
058 At the beginning of each time interval of length given by <i>period</i>,
059 starting from the time at which initialize() is invoked,
060 this actor initiates a continuous output that passes through the values given by
061 <i>values</i> at the time offsets into the period given by <i>offsets</i>.
062 These parameters contain arrays, which are required to have the same length.
063 The <i>offsets</i> array contains doubles, which
064 must be nondecreasing and nonnegative,
065 or an exception will be thrown when it is set.
066 <p>
067 You can provide a finite <i>stopTime</i>. Upon reaching that stop time,
068 postfire() returns false, which requests that the director
069 not fire this actor again. The output will be absent after that time.
070 The clock can also be started and stopped repeatedly
071 during an execution. A token at the <i>start</i> input will start the clock
072 at the beginning of a period. A token
073 at the <i>stop</i> input will stop the clock, if it is still running.
074 If both <i>start</i> and <i>stop</i> are received simultaneously, then
075 the clock will be stopped. When the clock is stopped, the output is absent.
076 <p>
077 The <i>values</i> parameter by default
078 contains the array {1.0, -1.0}.  The default
079 <i>offsets</i> array is {0.0, 1.0}.
080 The default period is 2.0. This results in a triangle
081 wave (for linear interpolation) and a smooth sinusoid-like
082 waveform (for hermite interpolation).
083 <p>
084 The type of the output is double.
085 <p>
086 If two offsets are equal, or if one is equal to the period,
087 then two events will be produced at the same time, but with
088 different microsteps. This will cause strange effects with
089 hermite interpolation, and hence is not recommended. But
090 it can sometimes be useful with linear interpolation to get
091 discontinuous outputs.
092 <p>
093 If the <i>period</i> is changed at any time, either by
094 provided by an input or by changing the parameter, then the
095 new period will take effect immediately if the new period
096 is provided at the same time (including the
097 microstep) that the current cycle starts,
098 or after the current cycle completes otherwise.
099
100 @author Sarah Packman, Yuhong Xiong, Edward A. Lee
101 @version $Id$
102 @since Ptolemy II 10.0
103 @Pt.ProposedRating Yellow (yuhong)
104 @Pt.AcceptedRating Yellow (yuhong)
105 @see ptolemy.math.Interpolation
106 */
107public class Waveform extends DiscreteClock {
108    /** Construct an actor with the specified container and name.
109     *  @param container The container.
110     *  @param name The name of this actor.
111     *  @exception IllegalActionException If the entity cannot be contained
112     *   by the proposed container.
113     *  @exception NameDuplicationException If the container already has an
114     *   actor with this name.
115     */
116    public Waveform(CompositeEntity container, String name)
117            throws NameDuplicationException, IllegalActionException {
118        super(container, name);
119
120        interpolation = new StringParameter(this, "interpolation");
121        interpolation.setExpression("linear");
122        interpolation.addChoice("linear");
123        interpolation.addChoice("hermite");
124        attributeChanged(interpolation);
125
126        // Constrain the values to be doubles and change defaults.
127        values.setExpression("{1.0, -1.0}");
128        values.setTypeEquals(new ArrayType(BaseType.DOUBLE));
129
130        period.setExpression("2.0");
131
132        offsets.setExpression("{0.0, 1.0}");
133
134        // Trigger is not supported.
135        Parameter hide = new SingletonParameter(trigger, "_hide");
136        hide.setExpression("true");
137
138        _attachText("_iconDescription", "<svg>\n" + "<rect x=\"-20\" y=\"-20\" "
139                + "width=\"40\" height=\"40\" " + "style=\"fill:lightGrey\"/>\n"
140                + "<line x1=\"-20\" y1=\"-15\" x2=\"-10\" y2=\"15\"/>\n"
141                + "<line x1=\"-10\" y1=\"15\" x2=\"0\" y2=\"-15\"/>\n"
142                + "<line x1=\"0\" y1=\"-15\" x2=\"10\" y2=\"15\"/>\n"
143                + "<line x1=\"10\" y1=\"15\" x2=\"20\" y2=\"-15\"/>\n"
144                + "</svg>\n");
145    }
146
147    ///////////////////////////////////////////////////////////////////
148    ////                         public variables                  ////
149
150    /** The interpolation method, which must be "linear" or "hermite". */
151    public StringParameter interpolation;
152
153    ///////////////////////////////////////////////////////////////////
154    ////                         public methods                    ////
155
156    /** Check the validity of the parameter.
157     *  @param attribute The attribute that changed.
158     *  @exception IllegalActionException If the argument is the
159     *   <i>values</i> parameter and it does not contain an one dimensional
160     *   array; or the argument is the <i>times</i> parameter and it does
161     *   not contain an one dimensional array or is not increasing and
162     *   non-negative; or the argument is the <i>period</i> parameter and is
163     *   negative; or the argument is the <i>order</i> parameter and the order
164     *   is not supported by the Interpolation class.
165     */
166    @Override
167    public void attributeChanged(Attribute attribute)
168            throws IllegalActionException {
169        if (attribute == interpolation) {
170            _interpolation = _LINEAR;
171            String interpolationValue = interpolation.stringValue();
172            if (interpolationValue.equals("hermite")) {
173                _interpolation = _HERMITE;
174            }
175        } else if (attribute == period) {
176            double periodValue = ((DoubleToken) period.getToken())
177                    .doubleValue();
178            if (periodValue == Double.POSITIVE_INFINITY) {
179                throw new IllegalActionException(this,
180                        "Period is required to be finite.  " + "Period given: "
181                                + periodValue);
182            }
183            super.attributeChanged(attribute);
184        } else {
185            super.attributeChanged(attribute);
186        }
187    }
188
189    /** Override the base class to set the output microstep to zero.
190     *  @exception IllegalActionException If the parent class throws it,
191     *   or if the <i>values</i> parameter is not a row vector, or if the
192     *   fireAt() method of the director throws it, or if the director does not
193     *   agree to fire the actor at the specified time.
194     */
195    @Override
196    public synchronized void initialize() throws IllegalActionException {
197        super.initialize();
198        _nextOutputIndex = 0;
199    }
200
201    ///////////////////////////////////////////////////////////////////
202    ////                         protected methods                 ////
203
204    /** Return the Hermite curve interpolation.
205     *  @param index The interpolation point index.
206     *  @param startTime The time of the starting reference point.
207     *  @param startValue The value of the starting reference point.
208     *  @param tanStart The tangent of the starting reference point.
209     *  @param endTime The time of the ending reference point.
210     *  @param endValue The value of the ending reference point.
211     *  @param tanEnd The tangent of the ending reference point.
212     *  @return The Hermite curve interpolation.
213     */
214    protected double _hermite(double index, double startTime, double startValue,
215            double tanStart, double endTime, double endValue, double tanEnd) {
216        // forming the Hermite matrix M
217        double[][] M = new double[4][4];
218        double iStartSqr = startTime * startTime;
219        double iEndSqr = endTime * endTime;
220        M[0][0] = iStartSqr * startTime;
221        M[0][1] = iStartSqr;
222        M[0][2] = startTime;
223        M[0][3] = 1;
224
225        M[1][0] = iEndSqr * endTime;
226        M[1][1] = iEndSqr;
227        M[1][2] = endTime;
228        M[1][3] = 1;
229
230        M[2][0] = 3 * iStartSqr;
231        M[2][1] = 2 * startTime;
232        M[2][2] = 1;
233        M[2][3] = 0;
234
235        M[3][0] = 3 * iEndSqr;
236        M[3][1] = 2 * endTime;
237        M[3][2] = 1;
238        M[3][3] = 0;
239
240        double[][] MInverse = DoubleMatrixMath.inverse(M);
241
242        // forming the column vector of values and tangents
243        double[] Gh = new double[4];
244        Gh[0] = startValue;
245        Gh[1] = endValue;
246        Gh[2] = tanStart;
247        Gh[3] = tanEnd;
248
249        // compute the coefficients vector coef[a, b, c, d] or the 3rd order
250        // curve.
251        double[] coef = DoubleMatrixMath.multiply(Gh, MInverse);
252
253        // compute the interpolated value
254        double indexSqr = index * index;
255        return coef[0] * indexSqr * index + coef[1] * indexSqr + coef[2] * index
256                + coef[3];
257    }
258
259    /** Return the interpolation result for the current model time.
260     *  @return A double.
261     *  @exception IllegalActionException If the values parameter is malformed.
262     *  @exception IllegalStateException If the index and value arrays
263     *   do not have the same length, or the period is not 0 and not
264     *   greater than the largest index.
265     */
266    private Token _interpolate() throws IllegalActionException {
267        int numRefPoints = _offsets.length;
268
269        // index is now within [0, period-1]. If it is outside the range of
270        // the smallest and the largest index, values must be periodic.
271        // Handle a special case where the number of reference points is
272        // 1. The code for order 3 later won't work for this case.
273        if (numRefPoints == 1) {
274            return _getValue(0);
275        }
276
277        // Time into the current cycle.
278        Time currentTime = getDirector().getModelTime();
279        double time = currentTime.subtract(_cycleStartTime).getDoubleValue();
280
281        // indexIndexStart is the index to _offsets whose entry is the
282        // index to the left of the interpolation point.
283        int indexIndexStart = _phase - 1;
284
285        double periodValue = ((DoubleToken) period.getToken()).doubleValue();
286
287        // Need at least the two points surrounding
288        // the interpolation point.
289        double startTime;
290        double endTime;
291        double startValue;
292        double endValue;
293
294        if (indexIndexStart == -1) {
295            startTime = _offsets[numRefPoints - 1] - periodValue;
296            startValue = ((DoubleToken) _getValue(numRefPoints - 1))
297                    .doubleValue();
298        } else {
299            startTime = _offsets[indexIndexStart];
300            startValue = ((DoubleToken) _getValue(indexIndexStart))
301                    .doubleValue();
302        }
303
304        if (indexIndexStart == numRefPoints - 1) {
305            endTime = _offsets[0] + periodValue;
306            endValue = ((DoubleToken) _getValue(0)).doubleValue();
307        } else {
308            endTime = _offsets[indexIndexStart + 1];
309            endValue = ((DoubleToken) _getValue(indexIndexStart + 1))
310                    .doubleValue();
311        }
312
313        if (_interpolation == _LINEAR) {
314            return new DoubleToken(startValue + (time - startTime)
315                    * (endValue - startValue) / (endTime - startTime));
316        }
317
318        // order is 3. Need the points before Start and the point after End
319        // to compute the tangent at Start and End.
320        double timeBeforeStart;
321        double timeAfterEnd;
322        double valueBeforeStart;
323        double valueAfterEnd;
324
325        if (indexIndexStart == -1) {
326            timeBeforeStart = _offsets[numRefPoints - 2] - periodValue;
327            valueBeforeStart = ((DoubleToken) _getValue(numRefPoints - 2))
328                    .doubleValue();
329        } else if (indexIndexStart == 0) {
330            if (periodValue > 0) {
331                timeBeforeStart = _offsets[numRefPoints - 1] - periodValue;
332                valueBeforeStart = ((DoubleToken) _getValue(numRefPoints - 1))
333                        .doubleValue();
334            } else {
335                // Not periodic
336                timeBeforeStart = _offsets[0] - 1;
337                valueBeforeStart = 0.0;
338            }
339        } else {
340            timeBeforeStart = _offsets[indexIndexStart - 1];
341            valueBeforeStart = ((DoubleToken) _getValue(indexIndexStart - 1))
342                    .doubleValue();
343        }
344
345        if (indexIndexStart == numRefPoints - 1) {
346            timeAfterEnd = _offsets[1] + periodValue;
347            valueAfterEnd = ((DoubleToken) _getValue(1)).doubleValue();
348        } else if (indexIndexStart == numRefPoints - 2) {
349            if (periodValue > 0 && periodValue != Double.POSITIVE_INFINITY) {
350                timeAfterEnd = _offsets[0] + periodValue;
351                valueAfterEnd = ((DoubleToken) _getValue(0)).doubleValue();
352            } else {
353                // Not periodic
354                timeAfterEnd = _offsets[numRefPoints - 1] + 1;
355                // FIXME: Assuming 0.0 doesn't seem right.
356                valueAfterEnd = 0.0;
357            }
358        } else {
359            timeAfterEnd = _offsets[indexIndexStart + 2];
360            valueAfterEnd = ((DoubleToken) _getValue(indexIndexStart + 2))
361                    .doubleValue();
362        }
363
364        // Compute the tangent at Start and End.
365        double tanBefore2Start = (startValue - valueBeforeStart)
366                / (startTime - timeBeforeStart);
367        double tanStart2End = (endValue - startValue) / (endTime - startTime);
368        double tanEnd2After = (valueAfterEnd - endValue)
369                / (timeAfterEnd - endTime);
370
371        double tanStart = 0.5 * (tanBefore2Start + tanStart2End);
372        double tanEnd = 0.5 * (tanStart2End + tanEnd2After);
373
374        return new DoubleToken(_hermite(time, startTime, startValue, tanStart,
375                endTime, endValue, tanEnd));
376    }
377
378    /** Produce the output required at times between the specified times
379     *  using the specified interpolation method.
380     *  @exception IllegalActionException If sending the output fails.
381     */
382    @Override
383    protected void _produceIntermediateOutput() throws IllegalActionException {
384        if (!_enabled) {
385            output.sendClear(0);
386            return;
387        }
388        if (_debugging) {
389            _debug("Interpolating output.");
390        }
391        output.send(0, _interpolate());
392    }
393
394    /** Skip the current firing phase and request a refiring at the
395     *  time of the next one.
396     *  @exception IllegalActionException If the period cannot be evaluated.
397     */
398    @Override
399    protected void _skipToNextPhase() throws IllegalActionException {
400        _phase++;
401        if (_phase >= _offsets.length) {
402            double periodValue = ((DoubleToken) period.getToken())
403                    .doubleValue();
404            _phase = 0;
405            _cycleStartTime = _cycleStartTime.add(periodValue);
406        }
407        Time nextOutputTime = _cycleStartTime.add(_offsets[_phase]);
408        if (_nextOutputTime.equals(nextOutputTime)) {
409            _nextOutputIndex++;
410        } else {
411            _nextOutputTime = nextOutputTime;
412            _nextOutputIndex = 0;
413        }
414        _fireAt(_nextOutputTime);
415    }
416
417    ///////////////////////////////////////////////////////////////////
418    ////                         private variables                 ////
419
420    /** Indicator for third order interpolation. */
421    private static final int _HERMITE = 1;
422
423    /** The value of the interpolation parameter. */
424    private int _interpolation = _LINEAR;
425
426    /** Indicator for linear interpolation. */
427    private static final int _LINEAR = 0;
428}