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}