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}