001/* An actor that delays the input by the specified amount. 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 java.util.LinkedList; 031import java.util.ListIterator; 032 033import ptolemy.actor.Director; 034import ptolemy.actor.SuperdenseTimeDirector; 035import ptolemy.actor.parameters.PortParameter; 036import ptolemy.actor.util.Time; 037import ptolemy.data.DoubleToken; 038import ptolemy.data.Token; 039import ptolemy.data.expr.Parameter; 040import ptolemy.data.type.BaseType; 041import ptolemy.kernel.CompositeEntity; 042import ptolemy.kernel.util.Attribute; 043import ptolemy.kernel.util.IllegalActionException; 044import ptolemy.kernel.util.NameDuplicationException; 045import ptolemy.kernel.util.StringAttribute; 046import ptolemy.kernel.util.Workspace; 047 048/////////////////////////////////////////////////////////////////// 049//// TimeDelay 050 051/** 052 This actor delays the input by a specified amount of time given by 053 the <i>delay</i> port or parameter, which defaults to 1.0. It is designed 054 to be used in timed domains, particularly DE. It can also be used 055 in other domains, such as SR and SDF, but this will only be useful if the 056 delay value is a multiple of the period of those directors. The value 057 of <i>delay</i> is required to be nonnegative. In addition, if the 058 <i>delay</i> port is connected (and hence the delay will be variable 059 at run time), then the values provided at the port are required to be 060 greater than or equal <i>minimumDelay</i>, 061 which defaults to the initial value of <i>delay</i>. 062 If the <i>delay</i> is to be changed dynamically during execution, consider 063 setting <i>minimumDelay</i> to 0.0. 064 The input and output types are unconstrained, except that the output type 065 must be the same as that of the input. 066 <p> 067 Note that in Ptides the SuperdenseDependency is used for computing 068 offsets and deadlines. The dependency between the input and the output 069 of this actor is the <i>minimumDelay</i>. A <i>minimumDelay</i> of 070 values greater than 0.0 allows for more efficient execution of Ptides models. If 071 this actor is used as a fixed delay actor, i.e. the delay value is not 072 changed during the execution, the <i>minimumDelay</i> should be set to 073 the actual delay, which is the default. 074 <p> 075 For directors that implement {@link SuperdenseTimeDirector}, such as 076 DE, the output microstep of an event will match the input microstep, 077 unless the time delay is 0.0, in which case, the output microstep will 078 be one greater than the input microstep. 079 A time delay of 0.0 is sometimes useful to break 080 causality loops in feedback systems. It is sometimes useful to think 081 of this zero-valued delay as an infinitesimal delay. 082 <p> 083 This actor keeps a local FIFO queue to store all received but not produced 084 inputs. The behavior of this actor on each firing is to 085 output any previously received token that is scheduled to be produced 086 at the current time (and microstep). 087 If there is no previously received token scheduled 088 to be produced, then the output will be absent. 089 <p> 090 Inputs are read only during the postfire() method. 091 If an input is present, then this actor schedules itself to fire again 092 to produce the just received token on the corresponding output channel after 093 the appropriate time delay. Note that if the value of delay is 0.0, the 094 actor schedules itself to fire at the current model time, resulting in 095 an output with an incremented microstep. 096 <p> 097 This actor can also be used in the Continuous 098 domain, but it is only useful to delay purely discrete signals. 099 As a consequence, for directors that implement {@link SuperdenseTimeDirector}, 100 this actor insists that input events have microstep 1 or greater. 101 It will throw an exception if it receives an input with microstep 0, 102 which in the Continuous domain, implies a continuous signal. 103 There are two reasons for rejecting continuous inputs. 104 First, because of the way variable-step-size ODE solvers work, the TimeDelay 105 actor has the side effect of forcing the solver to use very small step 106 sizes, which slows down a simulation. 107 Second, and more important, some odd artifacts will 108 appear if a variable step-size solver is being used. In particular, the 109 output will be absent on any firing where there was no input at exactly 110 time <i>t</i> - <i>d</i>, where <i>t</i> is the time of the firing 111 and <i>d</i> is the value of the delay parameter. Thus, a continuous 112 signal input will have gaps on the output, and will fail to be 113bR piecewise continuous. 114 115 @author Edward A. Lee 116 @version $Id$ 117 @since Ptolemy II 8.0 118 @Pt.ProposedRating Yellow (eal) 119 @Pt.AcceptedRating Red (eal) 120 */ 121public class TimeDelay extends Transformer { 122 /** Construct an actor with the specified container and name. 123 * Constrain that the output type to be the same as the input type. 124 * @param container The composite entity to contain this one. 125 * @param name The name of this actor. 126 * @exception IllegalActionException If the entity cannot be contained 127 * by the proposed container. 128 * @exception NameDuplicationException If the container already has an 129 * actor with this name. 130 */ 131 public TimeDelay(CompositeEntity container, String name) 132 throws NameDuplicationException, IllegalActionException { 133 super(container, name); 134 135 delay = new PortParameter(this, "delay"); 136 delay.setTypeEquals(BaseType.DOUBLE); 137 delay.setExpression("1.0"); 138 _delay = 1.0; 139 140 minimumDelay = new Parameter(this, "minimumDelay"); 141 minimumDelay.setTypeEquals(BaseType.DOUBLE); 142 minimumDelay.setExpression("delay"); 143 144 // Put the delay input on the bottom of the actor. 145 StringAttribute controlCardinal = new StringAttribute(delay.getPort(), 146 "_cardinal"); 147 controlCardinal.setExpression("SOUTH"); 148 149 output.setTypeSameAs(input); 150 } 151 152 /////////////////////////////////////////////////////////////////// 153 //// ports and parameters //// 154 155 /** The amount of delay. The default for this parameter is 1.0. 156 * This parameter must contain a DoubleToken 157 * with a non-negative value, or an exception will be thrown when 158 * it is set. 159 */ 160 public PortParameter delay; 161 162 /** Minimum delay to impose if the <i>delay</i> 163 * port is connected. This is a double that defaults to the value of the delay. 164 */ 165 public Parameter minimumDelay; 166 167 /////////////////////////////////////////////////////////////////// 168 //// public methods //// 169 170 /** If the attribute is <i>delay</i>, then ensure that the value 171 * is non-negative. 172 * <p>NOTE: the newDelay may be 0.0, which may change the causality 173 * property of the model. We leave the model designers to decide 174 * whether the zero delay is really what they want. 175 * @param attribute The attribute that changed. 176 * @exception IllegalActionException If the delay is negative. 177 */ 178 @Override 179 public void attributeChanged(Attribute attribute) 180 throws IllegalActionException { 181 // NOTE: We used to check whether minimumDelay < delay here. 182 // This causes a circular dependency. If the value 183 // of minimumDelay is "delay", then when minimumDelay is 184 // evaluated, this could cause delay to be evaluated (if 185 // it needs evaluation), which will cause this attributeChanged() 186 // method to be called, which will then try (again) to evaluate 187 // minimumDelay. This is a circular dependency. 188 if (attribute == delay) { 189 _delay = ((DoubleToken) delay.getToken()).doubleValue(); 190 if (_delay < 0.0) { 191 throw new IllegalActionException(this, 192 "Cannot have negative delay: " + _delay); 193 } 194 } 195 if (attribute == minimumDelay) { 196 _minimumDelay = ((DoubleToken) minimumDelay.getToken()) 197 .doubleValue(); 198 } else { 199 super.attributeChanged(attribute); 200 } 201 } 202 203 /** Clone the actor into the specified workspace. Set a type 204 * constraint that the output type is the same as the that of input. 205 * @param workspace The workspace for the new object. 206 * @return A new actor. 207 * @exception CloneNotSupportedException If a derived class has 208 * has an attribute that cannot be cloned. 209 */ 210 @Override 211 public Object clone(Workspace workspace) throws CloneNotSupportedException { 212 TimeDelay newObject = (TimeDelay) super.clone(workspace); 213 newObject.output.setTypeSameAs(newObject.input); 214 newObject._pendingOutputs = null; 215 return newObject; 216 } 217 218 /** Declare that the output does not depend on the input in a firing. 219 * @exception IllegalActionException If the causality interface 220 * cannot be computed. 221 * @see #getCausalityInterface() 222 */ 223 @Override 224 public void declareDelayDependency() throws IllegalActionException { 225 _declareDelayDependency(delay.getPort(), output, _minimumDelay); 226 _declareDelayDependency(input, output, _minimumDelay); 227 } 228 229 /** Send out a token that is scheduled 230 * to be produced at the current time, if any. 231 * @exception IllegalActionException If there is no director, or the 232 * input can not be read, or the output can not be sent. 233 */ 234 @Override 235 public void fire() throws IllegalActionException { 236 super.fire(); 237 if (_isTime()) { 238 // Time to produce the output. 239 PendingEvent event = _pendingOutputs.getLast(); 240 output.send(0, event.token); 241 if (_debugging) { 242 _debug("Sending output. Value = " + event.token + ", time = " 243 + event.timeStamp + ", microstep = " + event.microstep); 244 } 245 } else { 246 // Nothing to send. Assert the output to be absent. 247 output.send(0, null); 248 if (_debugging) { 249 _debug("Nothing to send. Asserting absent output at time " 250 + getDirector().getModelTime()); 251 } 252 } 253 } 254 255 /** Initialize the states of this actor. 256 * @exception IllegalActionException If a derived class throws it. 257 */ 258 @Override 259 public void initialize() throws IllegalActionException { 260 super.initialize(); 261 if (_pendingOutputs != null) { 262 _pendingOutputs.clear(); 263 } else { 264 _pendingOutputs = new LinkedList<PendingEvent>(); 265 } 266 } 267 268 /** Return false indicating that this actor can be fired even if 269 * the inputs are unknown. 270 * @return False. 271 */ 272 @Override 273 public boolean isStrict() { 274 return false; 275 } 276 277 /** Read the input, if there is one, and request refiring. 278 * @exception IllegalActionException If scheduling to refire cannot 279 * be performed or the superclass throws it. 280 */ 281 @Override 282 public boolean postfire() throws IllegalActionException { 283 delay.update(); 284 if (_minimumDelay > _delay) { 285 throw new IllegalActionException(this, 286 "Cannot have minimumDelay > delay" 287 + ". Consider setting minimumDelay to 0.0."); 288 } 289 290 // No point in using the isTime() method here, since we need 291 // all the intermediate values. 292 Director director = getDirector(); 293 Time currentTime = director.getModelTime(); 294 int microstep = 1; 295 if (director instanceof SuperdenseTimeDirector) { 296 microstep = ((SuperdenseTimeDirector) director).getIndex(); 297 } 298 299 if (_pendingOutputs.size() > 0) { 300 PendingEvent event = _pendingOutputs.getLast(); 301 int comparison = currentTime.compareTo(event.timeStamp); 302 if (comparison == 0 && microstep >= event.microstep) { 303 // Remove the oldest event in the event queue, since 304 // this will have been produced in fire(). 305 _pendingOutputs.removeLast(); 306 } 307 } 308 309 // Check whether the next oldest event has the same time. 310 if (_pendingOutputs.size() > 0) { 311 // The current time stamp of the next event 312 // may match, but not the microstep. 313 // In this case, we have to request a refiring. 314 PendingEvent nextEvent = _pendingOutputs.getLast(); 315 if (currentTime.equals(nextEvent.timeStamp)) { 316 _fireAt(currentTime); 317 } 318 if (_debugging) { 319 _debug("Deferring output to a later microstep. Value = " 320 + nextEvent.token + ", time = " + nextEvent.timeStamp 321 + ", microstep = " + nextEvent.microstep 322 + ". Current microstep is " + microstep); 323 } 324 } 325 326 if (input.hasToken(0)) { 327 Token token = input.get(0); 328 PendingEvent newEvent = new PendingEvent(); 329 newEvent.token = token; 330 newEvent.timeStamp = currentTime.add(_delay); 331 newEvent.microstep = microstep; 332 if (_delay == 0.0) { 333 newEvent.microstep++; 334 } 335 _fireAt(newEvent.timeStamp); 336 _addEvent(newEvent); 337 if (_debugging) { 338 _debug("Queueing event for later output. Value = " 339 + newEvent.token + ", time = " + newEvent.timeStamp 340 + ", microstep = " + newEvent.microstep); 341 } 342 } 343 return super.postfire(); 344 } 345 346 /////////////////////////////////////////////////////////////////// 347 //// protected methods //// 348 349 /** Insert a new event into the queue of pending events. 350 * This method ensures that events in the queue are in time-stamp 351 * and microstep order, and that when time stamps and microsteps match, 352 * that the order is FIFO. The latest time stamp and largest microstep 353 * are at the beginning of the list. 354 * @param newEvent The new event to be inserted into the queue 355 * of pending events. 356 */ 357 protected void _addEvent(PendingEvent newEvent) { 358 if (_pendingOutputs.size() == 0) { 359 // List is empty. This is easy. 360 _pendingOutputs.add(newEvent); 361 return; 362 } 363 // Optimize for the common case, which is that insertions 364 // go at the beginning. 365 PendingEvent newestEvent = _pendingOutputs.getFirst(); 366 int comparison = newEvent.timeStamp.compareTo(newestEvent.timeStamp); 367 if (comparison > 0) { 368 // New event has higher time stamp than all in the queue. 369 _pendingOutputs.addFirst(newEvent); 370 } else if (comparison == 0 371 && newEvent.microstep >= newestEvent.microstep) { 372 // New event has the same time stamp as the newest 373 // in the queue, but microstep is greater or equal. 374 _pendingOutputs.addFirst(newEvent); 375 } else { 376 // Event has to be inserted into the queue. 377 // Here we do a linear search, which is a poor choice if 378 // the delay is highly variable. But that case is rare. 379 ListIterator<PendingEvent> iterator = _pendingOutputs 380 .listIterator(); 381 while (iterator.hasNext()) { 382 PendingEvent nextNewestEvent = iterator.next(); 383 comparison = newEvent.timeStamp 384 .compareTo(nextNewestEvent.timeStamp); 385 if (comparison > 0 || comparison == 0 386 && newEvent.microstep >= newestEvent.microstep) { 387 // New event is later than or equal to current one. 388 // First replace the current element, then add the current element back in. 389 iterator.set(newEvent); 390 iterator.add(nextNewestEvent); 391 return; 392 } 393 } 394 // Got to the end of the list without finding an event 395 // that is older than the new event. Put at the end. 396 _pendingOutputs.addLast(newEvent); 397 } 398 } 399 400 /** Return true if it is time to produce an output. 401 * @return Return true if it is time to produce an output. 402 * @exception IllegalActionException If current time exceeds the time of 403 * of the next pending event. 404 */ 405 protected boolean _isTime() throws IllegalActionException { 406 if (_pendingOutputs.size() == 0) { 407 // No pending events. 408 return false; 409 } 410 Director director = getDirector(); 411 Time currentTime = director.getModelTime(); 412 int microstep = 1; 413 if (director instanceof SuperdenseTimeDirector) { 414 microstep = ((SuperdenseTimeDirector) director).getIndex(); 415 } 416 417 PendingEvent event = _pendingOutputs.getLast(); 418 int comparison = currentTime.compareTo(event.timeStamp); 419 if (comparison > 0) { 420 // Current time exceeds the event time. This should not happen. 421 throw new IllegalActionException(this, 422 "Failed to output event with time stamp " + event.timeStamp 423 + " and value " + event.token 424 + ". Perhaps the director is incompatible with TimeDelay?"); 425 } 426 // If the time is right and the microstep matches or exceeds 427 // the desired microstep, then it is time. 428 return comparison == 0 && microstep >= event.microstep; 429 } 430 431 /////////////////////////////////////////////////////////////////// 432 //// protected variables //// 433 434 /** The amount of delay. */ 435 protected double _delay; 436 437 /** The amount of minimumDelay. */ 438 protected double _minimumDelay = 0.0; 439 440 /** A local queue to store the delayed output tokens. */ 441 protected LinkedList<PendingEvent> _pendingOutputs; 442 443 /////////////////////////////////////////////////////////////////// 444 //// inner classes //// 445 446 /** Data structure to store pending events. */ 447 public static class PendingEvent { 448 // FindBugs indicates that this should be a static class. 449 /** The time stamp for the pending event. */ 450 public Time timeStamp; 451 /** The token associated with the event. */ 452 public Token token; 453 /** The microstep associated with the pending event. */ 454 public int microstep; 455 } 456}