001/* An actor that delays the input by the specified amount.
002
003 Copyright (c) 2009-2015 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.Transformer;
031import ptolemy.actor.util.CalendarQueue;
032import ptolemy.actor.util.Time;
033import ptolemy.actor.util.TimedEvent;
034import ptolemy.data.AbsentToken;
035import ptolemy.data.DoubleToken;
036import ptolemy.data.Token;
037import ptolemy.data.expr.Parameter;
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.kernel.util.Workspace;
044
045///////////////////////////////////////////////////////////////////
046//// ContinuousTimeDelay
047
048/**
049 Delay the input by a specified amount of time.
050
051 <p>This actor is designed
052 to be used in timed domains such as DE and Continuous. It can also be used
053 in other domains, such as SR and SDF, but this will only be useful if the
054 delay value is a multiple of the period of those directors. The amount
055 of the delay is required to be non-negative and has a default value 1.0.
056 The input and output types are unconstrained, except that the output type
057 must be the same as that of the input.
058 <p>
059 This actor keeps a local FIFO queue of all input events that may be requested
060 by the director; an event expires and is removed from this queue if its
061 timestamp is older than the current time less the delay. The behavior of this
062 actor on each firing is to read a token from the input port (if present) and
063 generates an output that is either equal to or an approximation of the delayed
064 input signal. Output is absent if and only if no initial value is given and
065 the actor is fired before input is received, or before the transient delay
066 period has passed (i.e. model time is less than delay time).
067 <p>
068 Output is generated by the fire() method, and inputs are processed in postFire().
069 <p>
070 Occasionally, this actor is useful with the
071 delay parameter set to 0.0.  The time stamp of the output will
072 equal that of the input, but there is a "microstep" delay.
073 The continuous domain in Ptolemy II has a "super dense" model
074 of time, meaning that a signal from one actor to another can
075 contain multiple events with the same time stamp. These events
076 are "simultaneous," but nonetheless
077 have a well-defined sequential ordering determined by the order
078 in which they are produced.
079 If \textit{delay} is 0.0, then the actor does not generate output
080 in the current time microstep, but rather on a refiring at the
081 the same physical time but incremented timestep.
082 <p>
083 A consequence of this strategy is that this actor is
084 able to produce an output (or assert that there is no output) before the
085 input with the same time is known.   Hence, it can be used to break
086 causality loops in feedback systems. The Continuous director will leverage this when
087 determining the fixed point behavior. It is sometimes useful to think
088 of this zero-valued delay as an infinitesimal delay.
089
090 @author Edward A. Lee, Jeff C. Jensen
091 @version $Id$
092 @since Ptolemy II 8.0
093 @Pt.ProposedRating Yellow (eal)
094 @Pt.AcceptedRating Red (eal)
095 */
096public class ContinuousTimeDelay extends Transformer {
097    // FIXME: delay cannot change during a run (more
098    // precisely... ignored until next initialize()).
099
100    // FIXME: implement solver step size control to capture periods of
101    // fine granularity; without this, though input events were stored
102    // with the same resolution as the solver deemed necessary, these
103    // delayed input events may be sampled sparsely if no other actor
104    // requires increased resolution at output time. If this actor
105    // uses fireAt() to force the solver to sample with the same
106    // resolution at which the input was generated, then subsequent
107    // inputs to the delay actor will arrive with this frequency
108    // regardless of whether or not this resolution is necessary.
109
110    /** Construct an actor with the specified container and name.
111     *  Constrain that the output type to be the same as the input type.
112     *  @param container The composite entity to contain this one.
113     *  @param name The name of this actor.
114     *  @exception IllegalActionException If the entity cannot be contained
115     *   by the proposed container.
116     *  @exception NameDuplicationException If the container already has an
117     *   actor with this name.
118     */
119    public ContinuousTimeDelay(CompositeEntity container, String name)
120            throws NameDuplicationException, IllegalActionException {
121        super(container, name);
122
123        delay = new Parameter(this, "delay");
124        delay.setTypeEquals(BaseType.DOUBLE);
125        delay.setExpression("1.0");
126        _delay = 1.0;
127
128        output.setTypeSameAs(input);
129
130        initialOutput = new Parameter(this, "initialOutput");
131        initialOutput.setExpression(null);
132
133        //FIXME - this causes an exception if initialDelay = NULL
134        //output.setTypeAtLeast(initialOutput);
135    }
136
137    ///////////////////////////////////////////////////////////////////
138    ////                       ports and parameters                ////
139
140    /** The amount of delay. The default for this parameter is 1.0. This
141     * parameter must contain a DoubleToken with a non-negative value, or an
142     * exception will be thrown when it is set.
143     */
144    public Parameter delay;
145
146    /** Initial output of the delay actor. The default for this parameter
147     *  is null, indicating no output will be generated until after input
148     *  has been received.
149     */
150    public Parameter initialOutput;
151
152    ///////////////////////////////////////////////////////////////////
153    ////                         public methods                    ////
154
155    /** If the attribute is <i>delay</i>, then ensure that the value
156     *  is non-negative.
157     *  <p>NOTE: the newDelay may be 0.0, which may change the causality
158     *  property of the model. We leave the model designers to decide
159     *  whether the zero delay is really what they want.
160     *  @param attribute The attribute that changed.
161     *  @exception IllegalActionException If the delay is negative.
162     */
163    @Override
164    public void attributeChanged(Attribute attribute)
165            throws IllegalActionException {
166        if (attribute == delay) {
167            double newDelay = ((DoubleToken) delay.getToken()).doubleValue();
168
169            if (newDelay < 0.0) {
170                throw new IllegalActionException(this,
171                        "Cannot have negative delay: " + newDelay);
172            } else {
173                _delay = newDelay;
174            }
175        } else {
176            super.attributeChanged(attribute);
177        }
178    }
179
180    /** Clone the actor into the specified workspace. Set a type
181     *  constraint that the output type is the same as the that of input.
182     *  @param workspace The workspace for the new object.
183     *  @return A new actor.
184     *  @exception CloneNotSupportedException If a derived class has
185     *   has an attribute that cannot be cloned.
186     */
187    @Override
188    public Object clone(Workspace workspace) throws CloneNotSupportedException {
189        ContinuousTimeDelay newObject = (ContinuousTimeDelay) super.clone(
190                workspace);
191        newObject.output.setTypeSameAs(newObject.input);
192        return newObject;
193    }
194
195    /** Declare that the <i>output</i>
196     *  does not depend on the <i>input</i> in a firing.
197     *  @exception IllegalActionException If the causality interface
198     *  cannot be computed.
199     *  @see #getCausalityInterface()
200     */
201    @Override
202    public void declareDelayDependency() throws IllegalActionException {
203        _declareDelayDependency(input, output, _delay);
204    }
205
206    /*
207     * Consume input (if available) and produce output for this actor, either
208     * by using the initial value (during transient behavior), or finding or
209     * interpolating the delayed signal from input buffers.
210     *
211     * The goal is to determine the input signal at the current time less delay.
212     * We refer to this point as the center point. If the center point occurs
213     * during the transient period of this actor and an initial value is
214     * present, then output the initial value. Otherwise, if the input buffer
215     * contains the center point, we simply output the recorded token and
216     * discard it from the input buffer.
217     *
218     * If the center point was not recorded in the input buffer, then find the
219     * most recent event that occurred before the center point, and label it the
220     * left point. Similarly, we search for the nearest point of our input
221     * buffer that occurred after the center point, and label it the right
222     * point.
223     *
224     * By discarding expired tokens (tokens which will no longer be requested by
225     * the director), the center point falls between the most recently discarded
226     * event and the first element of the input buffer. Hence the left point is
227     * the most recently discarded event, and the right point is the first
228     * element of the input buffer.
229     *
230     * If the time of the center point is equal to the current time, then the
231     * input is delayed by a microstep. The right point retains the input value
232     * and is output on a refire at the same physical time. The left point is
233     * ignored.
234     *
235     * @exception IllegalActionException
236     */
237    @Override
238    public void fire() throws IllegalActionException {
239        super.fire();
240
241        Time currentTime = getDirector().getModelTime();
242        Time centerTime = currentTime.subtract(_delay);
243        TimedEvent leftEvent = null;
244        TimedEvent rightEvent = null;
245        _currentOutput = null;
246
247        // Consume input; if input is absent, do not add an event to the output queue,
248        // as it will force the scheduler to fire this actor to produce an absent output.
249        // Otherwise, the solver step size is prevented from increasing as delayed
250        // absent events are present everywhere in the signal. This would result in
251        // monotonically decreasing solver step size that quickly converges to the minimum
252        // allowed step size, effectively bypassing the solver logic and slowing simulation.
253        if (input.isKnown(0) && input.hasToken(0)) {
254            Token inputToken = input.get(0);
255            if (!inputToken.equals(AbsentToken.ABSENT)) {
256                _inputBuffer.put(new TimedEvent(currentTime, inputToken));
257            } else {
258                inputToken = null;
259            }
260        }
261
262        // Discard expired input events that will never be considered
263        // by the solver. These are events that have timestamps before
264        // the current time less delay.
265        while (_inputBuffer.size() > 0) {
266            Time earliestEventTime = ((TimedEvent) _inputBuffer
267                    .get()).timeStamp;
268
269            //Expired event
270            if (earliestEventTime.compareTo(centerTime) < 0) {
271                _discarded = (TimedEvent) _inputBuffer.take();
272            }
273            //Earliest event is valid, so stop searching
274            else {
275                break;
276            }
277        }
278
279        //Record the left event; this is the most recently discarded input token.
280        // Note that if we have not seen input, but we have an initial value, the
281        // value was put to the input queue in the initialize() method, so the left
282        // point will be the initial value at time 0.
283        leftEvent = _discarded;
284
285        //Record the right event; because expired events are discarded from the
286        // input queue, the right event is always the first element of the queue
287        if (_inputBuffer.size() > 0) {
288            rightEvent = (TimedEvent) _inputBuffer.get();
289        }
290
291        // If the input signal was recorded at the center point, output it here,
292        // and remove the event from the input queue to prevent refiring
293        if (rightEvent != null && rightEvent.timeStamp.equals(centerTime)) {
294            _currentOutput = (Token) rightEvent.contents;
295            _discarded = (TimedEvent) _inputBuffer.take();
296        }
297        // If the current time is less than the delay time, output the initial value
298        else if (currentTime.compareTo(new Time(getDirector(), _delay)) < 0) {
299            _currentOutput = initialOutput.getToken();
300        }
301        // If the current time is equal to the center time (delay=0), but the event was
302        // not on the input queue, then we have not read the input. Output nothing now,
303        // and postFire() will read the input and request a refiring at the current time;
304        // this will force the director to increase its microstep.
305        else if (currentTime.equals(centerTime)) {
306            //Do nothing
307        }
308        // If we have a left point, we construct the center point here
309        else if (leftEvent != null) {
310            //If we have a right point, then we interpolate the center point
311            if (rightEvent != null) {
312                _currentOutput = linearInterpolate(leftEvent, rightEvent);
313            }
314            // If we have a left point but no right point, we assume the value has not changed.
315            else {
316                //FIXME: Is this the best solution?
317                _currentOutput = (Token) leftEvent.contents;
318            }
319        }
320        // Otherwise, we did not record the event at the center time, have not seen input,
321        // and do not have a left point (e.g. no initial value). We cannot generate output.
322
323        // Produce output
324        if (_currentOutput != null && !output.isKnown(0)) {
325            output.send(0, _currentOutput);
326            // In the case where delay is zero (currentTime = centerTime) we have refired in
327            // order to output a token with an increased microstep. After this token is sent,
328            // it needs to be removed from the input buffer.
329            if (currentTime.equals(centerTime)) {
330                _discarded = (TimedEvent) _inputBuffer.take();
331            }
332        }
333
334        //FIXME: What happens if two events in input buffer
335        // have the same physical timestamp? We need to interpolate
336        // earlier points by using the matching timestamp,
337        // and then output both events at some point.
338        // How does this affect the left point in interpolation?
339        //
340        // To maintain continuity principles, the current implementation
341        // may be correct; the events in the input buffer are ordered
342        // by microstep index, so that in interpolation, the event with
343        // the earliest time index is always used. This will preserve
344        // left continuity of an input signal.
345    }
346
347    /** Initialize the states of this actor. Place initial output
348     *  token on the input buffer.
349     *  @exception IllegalActionException If a derived class throws it.
350     */
351    @Override
352    public void initialize() throws IllegalActionException {
353        Token initialToken = initialOutput.getToken();
354        super.initialize();
355        _currentOutput = null;
356        _inputBuffer = new CalendarQueue(new TimedEvent.TimeComparator());
357        _discarded = null;
358        _nextFireAt = new Time(getDirector(), 0);
359
360        //Place the initial token in the input buffer
361        if (initialToken != null) {
362            Time modelStartTime = getDirector().getModelStartTime();
363            _inputBuffer.put(new TimedEvent(modelStartTime, initialToken));
364        }
365    }
366
367    /** Schedule the next output event.
368     *  @exception IllegalActionException If thrown by fireAt() or
369     *  by the superclass.
370     */
371    @Override
372    public boolean postfire() throws IllegalActionException {
373        // Schedule the next output event. This event may fire at the same
374        // physical time in the case of zero delay or simultaneous events.
375        if (_inputBuffer.size() > 0) {
376            TimedEvent nextEvent = (TimedEvent) _inputBuffer.get();
377            Time nextOutputTime = nextEvent.timeStamp.add(_delay);
378            Time currentTime = getDirector().getModelTime();
379
380            // If the next output time is now, then there are additional tokens to output at the
381            // current physical time, so a refire is requested. If the next output time is in the
382            // future, first ensure we have not already scheduled this actor to fire.
383            if (nextOutputTime.equals(currentTime)
384                    || !nextOutputTime.equals(_nextFireAt)) {
385                getDirector().fireAt(this, nextOutputTime);
386                _nextFireAt = nextOutputTime;
387            }
388        }
389
390        return super.postfire();
391    }
392
393    /** Override the base class to declare that the actor is nonstrict
394     *  if it has an initial value token.
395     */
396    @Override
397    public boolean isStrict() {
398        //        //FIXME: Does strictness depend on presence of an initial value?
399        //      return false;
400        try {
401            Token t = initialOutput.getToken();
402            return t == null;
403        } catch (IllegalActionException e) {
404            return true;
405        }
406    }
407
408    ///////////////////////////////////////////////////////////////////
409    ////                         protected methods                 ////
410
411    /**
412     * Linear interpolate between previous and current input.
413     *
414     * <p>To interpolate, we determine the slope between the left and right
415     * interpolation points, and multiply this by time gap between the left
416     * point and the current time. This estimates the amount by which the input
417     * signal has changed between the left and center points. We add this change
418     * to the value of the left point to estimate the value of the center point.
419     *
420     * @param leftEvent The left event.
421     * @param rightEvent the right event.
422     * @return The linear interpolation.
423     * @exception IllegalActionException If thrown by arithmetic operations
424     * on the events
425     */
426    protected Token linearInterpolate(TimedEvent leftEvent,
427            TimedEvent rightEvent) throws IllegalActionException {
428        Time centerTime = getDirector().getModelTime().subtract(_delay);
429
430        //time gap (run) between left and right events
431        Token slope = new DoubleToken(rightEvent.timeStamp
432                .subtract(leftEvent.timeStamp).getDoubleValue());
433
434        //slope = rise / run
435        slope = ((Token) rightEvent.contents)
436                .subtract((Token) leftEvent.contents).divide(slope);
437
438        //leftEvent + estimated rise from leftEvent to centerEvent
439        return ((Token) leftEvent.contents).add(slope.multiply(new DoubleToken(
440                centerTime.subtract(leftEvent.timeStamp).getDoubleValue())));
441    }
442
443    ///////////////////////////////////////////////////////////////////
444    ////                         protected variables               ////
445
446    /** Current output. */
447    protected Token _currentOutput;
448
449    /** The amount of delay. */
450    protected double _delay;
451
452    /** A local event queue to store input tokens, sorted by input time. */
453    protected CalendarQueue _inputBuffer;
454
455    /** Holds the most recently discarded event from the input buffer. */
456    protected TimedEvent _discarded;
457
458    /** Records the next scheduled fireAt() call, so that we do not request more than
459     *  one fireAt() call for a given input event.
460     */
461    protected Time _nextFireAt;
462}