001/* Generate discrete events at prespecified time instants. 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.actor.lib; 029 030import ptolemy.actor.Director; 031import ptolemy.actor.SuperdenseTimeDirector; 032import ptolemy.actor.TypedIOPort; 033import ptolemy.actor.parameters.PortParameter; 034import ptolemy.actor.util.Time; 035import ptolemy.data.ArrayToken; 036import ptolemy.data.BooleanToken; 037import ptolemy.data.DoubleToken; 038import ptolemy.data.Token; 039import ptolemy.data.expr.Parameter; 040import ptolemy.data.expr.SingletonParameter; 041import ptolemy.data.type.ArrayType; 042import ptolemy.data.type.BaseType; 043import ptolemy.kernel.CompositeEntity; 044import ptolemy.kernel.util.Attribute; 045import ptolemy.kernel.util.IllegalActionException; 046import ptolemy.kernel.util.NameDuplicationException; 047import ptolemy.kernel.util.StringAttribute; 048import ptolemy.kernel.util.Workspace; 049 050/////////////////////////////////////////////////////////////////// 051//// DiscreteClock 052 053/** 054 This actor produces a periodic signal, a sequence of events at 055 regularly spaced intervals. 056 At the beginning of each time interval of length given by <i>period</i>, 057 starting from the time at which initialize() is invoked, 058 this actor initiates a sequence of output events with values given by 059 <i>values</i> and offset into the period given by <i>offsets</i>. 060 These parameters contain arrays, which are required to have the same length. 061 The <i>offsets</i> array contains doubles, which 062 must be nondecreasing and nonnegative, 063 or an exception will be thrown when it is set. 064 If any entry is greater than the <i>period</i> 065 then the corresponding output will never be produced. 066 To get a finite sequence of events that is not periodic, 067 just set <i>period</i> to Infinity. 068 Alternatively, you can provide 069 a finite <i>stopTime</i>. Upon reaching that stop time, 070 postfire() returns false, which requests that the director 071 not fire this actor again. 072 The clock can also be started and stopped repeatedly 073 during an execution. A token at the <i>start</i> input will start the clock 074 at the beginning of a period. A token 075 at the <i>stop</i> input will stop the clock, if it is still running. 076 If both <i>start</i> and <i>stop</i> are received simultaneously, then 077 the clock will be stopped. 078 <p> 079 The <i>values</i> parameter by default 080 contains the array {1}. The default 081 <i>offsets</i> array is {0.0}. 082 The default period is 1.0. 083 <p> 084 The type of the output can be any token type. This type is inferred 085 from the element type of the <i>values</i> parameter. 086 <p> 087 For example, if <i>values</i> = {1, 2, 3}, 088 <i>offsets</i> = {0.1, 0.2, 0.3}, 089 <i>period</i> = 1.0, 090 and the actor is initialized at time 0.0, then 091 it will produce outputs with value 1 at all times 092 <i>n</i> + 0.1, outputs with value 2 at all times 093 <i>n</i> + 0.2, and outputs with value 3 at all times 094 <i>n</i> + 0.3, for all non-negative integers <i>n</i>. 095 <p> 096 If the actor is not fired by the enclosing director at the time 097 of the next expected output, then it will stop producing outputs. 098 This should not occur. If it does, it is a bug in the director. 099 <p> 100 If the director that this is used with supports superdense time 101 (like DE, Continuous), then the outputs are normally produced at microstep 102 index 1. The reason for producing outputs at index 1 103 is to maintain continuity in continuous-time models. 104 Specifically, if the signal is absent prior to an output time, 105 then it should be absent at index 0 of the time at which it will 106 produce the next output. There are two exceptions. If 107 two or more offsets have the same value, then each output 108 at the same time is produced at superdense time index one greater 109 than the previous output. Also, if an expected output has not been 110 produced by the expected index, then it will be produced at the 111 next available index. E.g., the very first output may be produced 112 at a superdense index greater than zero if the director's index is 113 greater than zero when this actor is initialized. This can happen, 114 for example, if this clock is in a refinement in a modal model, 115 and the modal model enters that mode with a reset transition. 116 <p> 117 If the <i>period</i> is changed at any time, either by 118 provided by an input or by changing the parameter, then the 119 new period will take effect immediately if the new period 120 is provided at the same time (including the 121 microstep) that the current cycle starts, 122 or after the current cycle completes otherwise. 123 <p> 124 If the <i>trigger</i> input is connected, then an output will only 125 be produced if a trigger input has been received since the last output 126 or if the trigger input coincides with the time when an output should 127 be produced. If a trigger input has not been received, then the 128 output will be skipped, moving on to the the next phase. 129 The only exception is the first output, which is produced 130 whether a trigger is provided or not. This is because the 131 trigger input is typically useful 132 in a feedback situation, where the output of the clock 133 eventually results in a trigger input. If the time-stamp 134 of that trigger input is less than the time between clock 135 events, then the clock will behave as if there were no 136 trigger input. Otherwise, it will "skip beats." 137 <p> 138 This actor is a timed source; the untimed version is Pulse. 139 140 @author Edward A. Lee 141 @version $Id$ 142 @since Ptolemy II 8.0 143 @Pt.ProposedRating Yellow (eal) 144 @Pt.AcceptedRating Red (hyzheng) 145 */ 146public class DiscreteClock extends TimedSource { 147 148 /** Construct an actor in the specified container with the specified 149 * name. The name must be unique within the container or an exception 150 * is thrown. The container argument must not be null, or a 151 * NullPointerException will be thrown. 152 * @param container The container. 153 * @param name The actor's name 154 * @exception IllegalActionException If the entity cannot be contained 155 * by the proposed container. 156 * @exception NameDuplicationException If name coincides with 157 * an entity already in the container. 158 */ 159 public DiscreteClock(CompositeEntity container, String name) 160 throws IllegalActionException, NameDuplicationException { 161 super(container, name); 162 163 period = new PortParameter(this, "period"); 164 period.setExpression("1.0"); 165 period.setTypeEquals(BaseType.DOUBLE); 166 new SingletonParameter(period.getPort(), "_showName") 167 .setToken(BooleanToken.TRUE); 168 169 offsets = new Parameter(this, "offsets"); 170 offsets.setExpression("{0.0}"); 171 offsets.setTypeEquals(new ArrayType(BaseType.DOUBLE)); 172 173 // Call this so that we don't have to copy its code here... 174 _updateOffsets(offsets); 175 176 // Set the values parameter. 177 values = new Parameter(this, "values"); 178 values.setExpression("{1}"); 179 180 // Set type constraint on the output. 181 output.setTypeAtLeast(ArrayType.elementType(values)); 182 183 // Don't call attributeChanged here, see _updateOffsets for 184 // details. 185 //attributeChanged(values); 186 187 start = new TypedIOPort(this, "start"); 188 start.setInput(true); 189 new StringAttribute(start, "_cardinal").setExpression("SOUTH"); 190 new Parameter(start, "_showName").setExpression("true"); 191 192 stop = new TypedIOPort(this, "stop"); 193 stop.setInput(true); 194 new StringAttribute(stop, "_cardinal").setExpression("SOUTH"); 195 new Parameter(stop, "_showName").setExpression("true"); 196 } 197 198 /////////////////////////////////////////////////////////////////// 199 //// ports and parameters //// 200 201 /** The offsets at which the specified values will be produced. 202 * This parameter must contain an array of doubles, and it defaults 203 * to {0.0}. 204 */ 205 public Parameter offsets; 206 207 /** The period of the output waveform. 208 * This is a double that defaults to 1.0. 209 */ 210 public PortParameter period; 211 212 /** A port that, if connected, is used to specify when the clock 213 * starts. This port accepts any type. The arrival of an event 214 * is what starts the clock. Upon arrival of such an event, 215 * the clock starts as if just initialized. The clock will not 216 * start until such an event is provided, unless the port is 217 * left unconnected, in which case the actor starts immediately. 218 * Note that when the clock starts, the period will be set to 219 * its initial value. If an input period arrives before a 220 * start input, then that arrived value will be ignored. 221 */ 222 public TypedIOPort start; 223 224 /** A port that, if connected, is used to specify when the clock 225 * stops. This port accepts any type. The arrival of an event 226 * is what stops the clock. 227 */ 228 public TypedIOPort stop; 229 230 /** The values that will be produced at the specified offsets. 231 * This parameter must contain an ArrayToken, and it defaults to 232 * {1} 233 */ 234 public Parameter values; 235 236 /////////////////////////////////////////////////////////////////// 237 //// public methods //// 238 239 /** If the argument is the <i>offsets</i> parameter, check that the 240 * array is nondecreasing and has the right dimension; if the 241 * argument is <i>period</i>, check that it is positive. Other 242 * sanity checks with <i>period</i> and <i>values</i> are done in 243 * the fire() method. 244 * @param attribute The attribute that changed. 245 * @exception IllegalActionException If the offsets array is not 246 * nondecreasing and nonnegative. 247 */ 248 @Override 249 public void attributeChanged(Attribute attribute) 250 throws IllegalActionException { 251 if (attribute == offsets) { 252 _updateOffsets(attribute); 253 } else if (attribute == period) { 254 _updatePeriod(attribute); 255 } else { 256 super.attributeChanged(attribute); 257 } 258 } 259 260 /** Clone the actor into the specified workspace. 261 * @param workspace The workspace for the new object. 262 * @return A new actor. 263 * @exception CloneNotSupportedException If a derived class contains 264 * an attribute that cannot be cloned. 265 */ 266 @Override 267 public Object clone(Workspace workspace) throws CloneNotSupportedException { 268 DiscreteClock newObject = (DiscreteClock) super.clone(workspace); 269 try { 270 ArrayToken offsetsValue = (ArrayToken) offsets.getToken(); 271 newObject._offsets = new double[offsetsValue.length()]; 272 System.arraycopy(_offsets, 0, newObject._offsets, 0, 273 _offsets.length); 274 newObject.output 275 .setTypeAtLeast(ArrayType.elementType(newObject.values)); 276 } catch (IllegalActionException ex) { 277 // CloneNotSupportedException does not have a constructor 278 // that takes a cause argument, so we use initCause 279 CloneNotSupportedException throwable = new CloneNotSupportedException(); 280 throwable.initCause(ex); 281 throw throwable; 282 } 283 return newObject; 284 } 285 286 /** Output the current value of the clock if the clock is currently 287 * enabled and, if the trigger input is connected, a trigger has been 288 * received. This method is expected to be called only at the right 289 * time to produce the next output, since otherwise prefire() will 290 * return false. 291 * @exception IllegalActionException If 292 * the value in the offsets parameter is encountered that is greater 293 * than the period, or if there is no director. 294 */ 295 @Override 296 public void fire() throws IllegalActionException { 297 super.fire(); 298 // Check the start input, to see whether everything needs to 299 // be reinitialized. 300 if (start.numberOfSources() > 0) { 301 if (start.hasToken(0)) { 302 if (_debugging) { 303 _debug("Received a start input."); 304 } 305 start.get(0); 306 // Restart everything. 307 initialize(); 308 _enabled = true; 309 } 310 } 311 // Check stop 312 if (stop.numberOfSources() > 0) { 313 if (stop.hasToken(0)) { 314 if (_debugging) { 315 _debug("Received a stop input."); 316 } 317 stop.get(0); 318 _enabled = false; 319 } 320 } 321 322 // Update the period from the port parameter, if appropriate. 323 period.update(); 324 325 // // Check for a trigger input. 326 // // Have to consume all trigger inputs. 327 // if (trigger.numberOfSources() > 0) { 328 // // Have to consume all trigger inputs. 329 // for (int i = 0; i < trigger.getWidth(); i++) { 330 // if (trigger.isKnown(i) && trigger.hasToken(i)) { 331 // trigger.get(i); 332 // _triggered = true; 333 // if (_debugging) { 334 // _debug("Received a trigger input. Enabling an output."); 335 // } 336 // } 337 // } 338 // } 339 340 // See whether it is time to produce an output. 341 Director director = getDirector(); 342 Time currentTime = director.getModelTime(); 343 int currentIndex = 0; 344 if (director instanceof SuperdenseTimeDirector) { 345 currentIndex = ((SuperdenseTimeDirector) director).getIndex(); 346 } 347 if (_debugging) { 348 _debug("Called fire() at time (" + currentTime + ", " + currentIndex 349 + ")"); 350 } 351 if (!_enabled) { 352 if (_debugging) { 353 _debug("Not sending output because start input has not arrived."); 354 } 355 output.sendClear(0); 356 return; 357 } 358 359 int comparison = _nextOutputTime.compareTo(currentTime); 360 if (comparison > 0) { 361 // If it is too early to produce an output. 362 // This is safe because we have made a fireAt() call for 363 // the next output time. 364 _produceIntermediateOutput(); 365 return; 366 } else if (comparison == 0) { 367 // It is the right time to produce an output. Check 368 // the index. 369 if (director instanceof SuperdenseTimeDirector) { 370 if (_nextOutputIndex > currentIndex) { 371 // We have not yet reached the requisite index. 372 // Request another firing at the current time. 373 _fireAt(currentTime); 374 _produceIntermediateOutput(); 375 return; 376 } 377 } 378 // At this point, the time matches the next output, and 379 // the index either matches or exceeds the index for the next output, 380 // or the director does not support superdense time. 381 if (!_triggered) { 382 if (_debugging) { 383 _debug("No trigger yet. Skipping phase."); 384 } 385 // Pretend we produced an output so that posfire() will 386 // skip to the next phase. 387 _outputProduced = true; 388 output.sendClear(0); 389 return; 390 } 391 // Ready to fire. 392 if (_enabled) { 393 if (_debugging) { 394 _debug("Sending output data: " + _getValue(_phase)); 395 } 396 output.send(0, _getValue(_phase)); 397 _outputProduced = true; 398 } 399 return; 400 } 401 // If we get here, then current time has passed our 402 // expected next firing time. This should not occur. 403 throw new IllegalActionException(this, getDirector(), 404 "Director failed to fire this actor at the requested time " 405 + _nextOutputTime + " Current time is " + currentTime 406 + ". Perhaps the director is incompatible with DiscreteClock?"); 407 } 408 409 /** Override the base class to initialize the index. 410 * @exception IllegalActionException If the parent class throws it, 411 * or if the <i>values</i> parameter is not a row vector, or if the 412 * fireAt() method of the director throws it, or if the director does not 413 * agree to fire the actor at the specified time. 414 */ 415 @Override 416 public synchronized void initialize() throws IllegalActionException { 417 super.initialize(); 418 419 // Start cycles at the current time. 420 // This is important in modal models that reinitialize the actor. 421 Time currentTime = getDirector().getModelTime(); 422 _cycleStartTime = currentTime; 423 _cycleCount = 0; 424 _phase = 0; 425 _nextOutputTime = _cycleStartTime.add(_offsets[_phase]); 426 _nextOutputIndex = 1; 427 _enabled = true; 428 _outputProduced = false; 429 430 // Enable without a trigger input on the first firing. 431 _triggered = true; 432 433 if (_debugging) { 434 _debug("In initialize, requesting firing at time " 435 + _nextOutputTime); 436 _debug("Requesting a refiring at " + _nextOutputTime 437 + ", with index " + _nextOutputIndex); 438 } 439 _fireAt(_nextOutputTime); 440 441 // If the start port is connected, then start disabled. 442 if (start.isOutsideConnected()) { 443 _enabled = false; 444 } 445 } 446 447 /** Update the time and index of the next expected output. 448 * @return False if the specified number of cycles has been reached, 449 * and true otherwise. 450 * @exception IllegalActionException If the director throws it when 451 * scheduling the next firing, or if an offset value exceeds the period. 452 */ 453 @Override 454 public boolean postfire() throws IllegalActionException { 455 boolean result = super.postfire(); 456 if (_outputProduced) { 457 _skipToNextPhase(); 458 _outputProduced = false; 459 if (_debugging) { 460 _debug("Postfiring. Requesting refiring at (" + _nextOutputTime 461 + ", " + _nextOutputIndex + ")"); 462 } 463 if (trigger.numberOfSources() > 0) { 464 _triggered = false; 465 if (_debugging) { 466 _debug("Trigger input is connected. Wait for the next trigger before producing an output."); 467 } 468 } 469 } else if (_debugging) { 470 _debug("Postfiring, but not requesting a firing since we've already requested it."); 471 } 472 return result; 473 } 474 475 /** Return true if current time has not exceeded the 476 * stopTime. 477 * Check that the length of the <i>values</i> and 478 * <i>offsets</i> parameters are the same and return true 479 * if it is time to produce an output. 480 * @return True if current time is less than or equal to the 481 * stop time. 482 * @exception IllegalActionException If the <i>values</i> and 483 * <i>offsets</i> parameters do not have the same length. 484 */ 485 @Override 486 public boolean prefire() throws IllegalActionException { 487 // FIXME: This comment is not correct: 488 // Cannot call super.prefire() because it consumes trigger 489 // inputs. 490 491 // super.prefire() longer consumes trigger inputs. 492 // However, if we call super.prefire() then some of 493 // the DiscreteClock tests fail. 494 495 // Check the length of the values and offsets arrays. 496 // This is done here because it cannot be done in 497 // attributeChanged(), since the two parameters are set 498 // separately, and checking in initialize() is not really 499 // sufficient, since the values of these parameters can 500 // change at run time. 501 ArrayToken val = (ArrayToken) values.getToken(); 502 if (_offsets.length != val.length()) { 503 throw new IllegalActionException(this, 504 "Values and offsets vectors do not have the same length."); 505 } 506 507 // Start of portion of prefire() from TimedSource 508 Time currentTime; 509 boolean localTime = ((BooleanToken) stopTimeIsLocal.getToken()) 510 .booleanValue(); 511 if (localTime) { 512 currentTime = getDirector().getModelTime(); 513 } else { 514 currentTime = getDirector().getGlobalTime(); 515 } 516 if (currentTime.compareTo(getModelStopTime()) > 0) { 517 if (_debugging) { 518 _debug("Called prefire, which returns false because time exceeds stopTime."); 519 } 520 return false; 521 } 522 // End of portion of prefire() from TimedSource 523 524 if (_debugging) { 525 _debug("Called prefire, which returns true."); 526 } 527 528 return true; 529 } 530 531 /////////////////////////////////////////////////////////////////// 532 //// protected methods //// 533 534 /** Get the specified output value, checking the form of the values 535 * parameter. 536 * @param index The index of the output values. 537 * @return A token that contains the output value. 538 * @exception IllegalActionException If the index is out of the range of 539 * the values parameter. 540 */ 541 protected Token _getValue(int index) throws IllegalActionException { 542 ArrayToken val = (ArrayToken) values.getToken(); 543 if (val == null || val.length() <= index) { 544 throw new IllegalActionException(this, 545 "Index out of range of the values parameter."); 546 } 547 return val.getElement(index); 548 } 549 550 /** Produce the output required at times between the specified times. 551 * This base class makes the output absent, but subclasses may 552 * interpolate the values. 553 * @exception IllegalActionException If sending the output fails. 554 */ 555 protected void _produceIntermediateOutput() throws IllegalActionException { 556 if (_debugging) { 557 _debug("Too early to produce output."); 558 } 559 output.sendClear(0); 560 } 561 562 /** Skip the current firing phase and request a refiring at the 563 * time of the next one. 564 * @exception IllegalActionException If the period cannot be evaluated, or 565 * if an offset is encountered that is greater than the period. 566 */ 567 protected void _skipToNextPhase() throws IllegalActionException { 568 _phase++; 569 if (_phase >= _offsets.length) { 570 double periodValue = ((DoubleToken) period.getToken()) 571 .doubleValue(); 572 _phase = 0; 573 _cycleStartTime = _cycleStartTime.add(periodValue); 574 } 575 double periodValue = ((DoubleToken) period.getToken()).doubleValue(); 576 if (_offsets[_phase] > periodValue) { 577 throw new IllegalActionException(this, 578 "Offset of " + _offsets[_phase] 579 + " is greater than the period " + periodValue); 580 } 581 Time nextOutputTime = _cycleStartTime.add(_offsets[_phase]); 582 if (_nextOutputTime.equals(nextOutputTime)) { 583 _nextOutputIndex++; 584 } else { 585 _nextOutputTime = nextOutputTime; 586 _nextOutputIndex = 1; 587 } 588 _fireAt(_nextOutputTime); 589 } 590 591 /////////////////////////////////////////////////////////////////// 592 //// protected variables //// 593 594 /** The count of cycles executed so far, or 0 before the start. */ 595 protected transient int _cycleCount; 596 597 /** The most recent cycle start time. */ 598 protected transient Time _cycleStartTime; 599 600 /** Indicator of whether the specified number of cycles have 601 * been completed. Also used in derived classes to turn on 602 * and off the clock. 603 */ 604 protected transient boolean _enabled; 605 606 /** Indicator of whether the first output has been produced. */ 607 protected transient boolean _firstOutputProduced = false; 608 609 /** The time for the next output. */ 610 protected transient Time _nextOutputTime; 611 612 /** The index of when the output should be emitted. */ 613 protected transient int _nextOutputIndex; 614 615 /** Cache of offsets array value. */ 616 protected transient double[] _offsets; 617 618 /** The phase of the next output. */ 619 protected transient int _phase; 620 621 /** Update the offsets attribute. 622 * @param attribute The attribute that changed. 623 * @exception IllegalActionException If the offsets array is not 624 * nondecreasing and nonnegative. 625 */ 626 private void _updateOffsets(Attribute attribute) 627 throws IllegalActionException { 628 // This method is necessary because FindBugs reports 629 // "Unitialized read of field in method called from constructor" 630 // if this constructor calls attributeChanged() and this 631 // class has a subclass. The workaround is to define 632 // a separate method. 633 ArrayToken offsetsValue = (ArrayToken) offsets.getToken(); 634 _offsets = new double[offsetsValue.length()]; 635 636 double previous = 0.0; 637 for (int i = 0; i < offsetsValue.length(); i++) { 638 _offsets[i] = ((DoubleToken) offsetsValue.getElement(i)) 639 .doubleValue(); 640 // Check nondecreasing property. 641 if (_offsets[i] < previous) { 642 throw new IllegalActionException(this, 643 "Value of offsets is not nondecreasing " 644 + "and nonnegative."); 645 } 646 previous = _offsets[i]; 647 } 648 } 649 650 /** Update the period attribute. 651 * @param attribute The attribute that changed. 652 * @exception IllegalActionException If the period is not 653 * positive. 654 */ 655 private void _updatePeriod(Attribute attribute) 656 throws IllegalActionException { 657 // This method is necessary because FindBugs reports 658 // "Unitialized read of filed in method called from constructor" 659 // if this constructor calls attributeChanged() and this 660 // class has a subclass. The workaround is to define 661 // a separate method. 662 double periodValue = ((DoubleToken) period.getToken()).doubleValue(); 663 if (_debugging) { 664 _debug("Setting period to " + periodValue); 665 } 666 if (periodValue <= 0.0) { 667 throw new IllegalActionException(this, 668 "Period is required to be positive. " + "Period given: " 669 + periodValue); 670 } 671 } 672 673 /////////////////////////////////////////////////////////////////// 674 //// private variables //// 675 676 /** Indicator that an output was produced and hence we should 677 * skip to the next phase in postfire. 678 */ 679 private boolean _outputProduced; 680}