001/* An actor that delays the input by the amount specified through another port.
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.de.lib;
029
030import ptolemy.actor.lib.Transformer;
031import ptolemy.actor.parameters.PortParameter;
032import ptolemy.actor.util.CalendarQueue;
033import ptolemy.actor.util.Time;
034import ptolemy.actor.util.TimedEvent;
035import ptolemy.data.DoubleToken;
036import ptolemy.data.Token;
037import ptolemy.data.type.BaseType;
038import ptolemy.kernel.CompositeEntity;
039import ptolemy.kernel.util.Attribute;
040import ptolemy.kernel.util.IllegalActionException;
041import ptolemy.kernel.util.NameDuplicationException;
042import ptolemy.kernel.util.Workspace;
043
044///////////////////////////////////////////////////////////////////
045//// VariableDelay
046
047/**
048 This actor delays its inputs by a variable delay.
049 It works in a similar way as the TimedDelay actor except that the
050 amount of time delayed is specified by an incoming token through
051 the delay port (a parameter port). If the delay is zero, then this
052 actor only increments the microstep, not the model time.
053 If a negative delay is specified, then an exception is thrown.
054
055 @deprecated Use ptolemy.actor.lib.TimeDelay.
056 @see ptolemy.domains.de.lib.TimedDelay
057 @author Jie Liu, Haiyang Zheng
058 @version $Id$
059 @since Ptolemy II 1.0
060 @Pt.ProposedRating Green (hyzheng)
061 @Pt.AcceptedRating Yellow (hyzheng)
062 */
063@Deprecated
064public class VariableDelay extends Transformer {
065
066    // NOTE: This actor has alot copies from TimeDelay, but because it has
067    // a PortParameter named delay and TimeDelay has just a parameter,
068    // subclassing does not work well.
069
070    /** Construct an actor with the specified container and name.
071     *  @param container The composite entity to contain this one.
072     *  @param name The name of this actor.
073     *  @exception IllegalActionException If the entity cannot be contained
074     *   by the proposed container.
075     *  @exception NameDuplicationException If the container already has an
076     *   actor with this name.
077     */
078    public VariableDelay(CompositeEntity container, String name)
079            throws NameDuplicationException, IllegalActionException {
080        super(container, name);
081
082        delay = new PortParameter(this, "delay");
083        delay.setExpression("1.0");
084        delay.setTypeEquals(BaseType.DOUBLE);
085
086        output.setTypeSameAs(input);
087
088        _delay = 1.0;
089    }
090
091    ///////////////////////////////////////////////////////////////////
092    ////                       ports and parameters                ////
093
094    // FIXME: VariableDelay.delay overrides TimedDelay.delay.
095    /** The amount specifying delay. Its default value is 1.0.
096     */
097    public PortParameter delay;
098
099    ///////////////////////////////////////////////////////////////////
100    ////                         public methods                    ////
101
102    /** If the attribute is <i>delay</i>, then ensure that the value
103     *  is non-negative.
104     *  <p>NOTE: the newDelay may be 0.0, which may change the causality
105     *  property of the model. We leave the model designers to decide
106     *  whether the zero delay is really what they want.
107     *  @param attribute The attribute that changed.
108     *  @exception IllegalActionException If the delay is negative.
109     */
110    @Override
111    public void attributeChanged(Attribute attribute)
112            throws IllegalActionException {
113        if (attribute == delay) {
114            double newDelay = ((DoubleToken) delay.getToken()).doubleValue();
115
116            if (newDelay < 0.0) {
117                throw new IllegalActionException(this,
118                        "Cannot have negative delay: " + newDelay);
119            } else {
120                _delay = newDelay;
121            }
122        } else {
123            super.attributeChanged(attribute);
124        }
125    }
126
127    /** Clone the actor into the specified workspace. Set a type
128     *  constraint that the output type is the same as the that of input.
129     *  @param workspace The workspace for the new object.
130     *  @return A new actor.
131     *  @exception CloneNotSupportedException If a derived class has
132     *   has an attribute that cannot be cloned.
133     */
134    @Override
135    public Object clone(Workspace workspace) throws CloneNotSupportedException {
136        VariableDelay newObject = (VariableDelay) super.clone(workspace);
137        newObject.output.setTypeSameAs(newObject.input);
138        return newObject;
139    }
140
141    /** Declare that the <i>output</i>
142     *  does not depend on the <i>input</i> and <i>delay</i> in a firing.
143     *  @exception IllegalActionException If the causality interface
144     *  cannot be computed.
145     *  @see #getCausalityInterface()
146     */
147    @Override
148    public void declareDelayDependency() throws IllegalActionException {
149        // Declare that output does not immediately depend on the delay input
150        // and the input port,
151        // though there is no lower bound on the time delay.
152        _declareDelayDependency(delay.getPort(), output, 0.0);
153        _declareDelayDependency(input, output, 0.0);
154    }
155
156    /** Update the delay parameter from the delay port and ensure the delay
157     *  is not negative. Call the fire method of super class to consume
158     *  inputs and generate outputs.
159     *  @exception IllegalActionException If the super class throws it,
160     *  or a negative delay is received.
161     */
162    @Override
163    public void fire() throws IllegalActionException {
164        super.fire();
165
166        delay.update();
167        _delay = ((DoubleToken) delay.getToken()).doubleValue();
168
169        if (_delay < 0) {
170            throw new IllegalActionException(this,
171                    "Cannot have negative delay: " + _delay);
172        }
173
174        // produce output
175        // NOTE: The amount of delay may be zero.
176        // In this case, if there is already some token scheduled to
177        // be produced at the current time before the current input
178        // arrives, that token is produced. While the current input
179        // is delayed to the next available firing at the current time.
180        //
181        // If we observe events in the queue that have expired,
182        // discard them here.
183        Time currentTime = getDirector().getModelTime();
184        _currentOutput = null;
185
186        if (_delayedOutputTokens.size() == 0) {
187            output.send(0, null);
188            return;
189        }
190
191        while (_delayedOutputTokens.size() > 0) {
192            TimedEvent earliestEvent = (TimedEvent) _delayedOutputTokens.get();
193            Time eventTime = earliestEvent.timeStamp;
194
195            int comparison = eventTime.compareTo(currentTime);
196            if (comparison == 0) {
197                _currentOutput = (Token) earliestEvent.contents;
198                output.send(0, _currentOutput);
199                break;
200            } else if (comparison > 0) {
201                // It is not yet time to produce an output.
202                output.send(0, null);
203                break;
204            }
205            // If we get here, then we have passed the time of the delayed
206            // output. We simply discard it and check the next one in the queue.
207            _delayedOutputTokens.take();
208        }
209    }
210
211    /** Initialize the states of this actor.
212     *  @exception IllegalActionException If a derived class throws it.
213     */
214    @Override
215    public void initialize() throws IllegalActionException {
216        super.initialize();
217        _currentOutput = null;
218        _delayedOutputTokens = new CalendarQueue(
219                new TimedEvent.TimeComparator());
220    }
221
222    /** Return false indicating that this actor can be fired even if
223     *  the inputs are unknown.
224     *  @return False.
225     */
226    @Override
227    public boolean isStrict() {
228        return false;
229    }
230
231    /** Process the current input if it has not been processed. Schedule
232     *  a firing to produce the earliest output token.
233     *  @exception IllegalActionException If scheduling to refire cannot
234     *  be performed or the superclass throws it.
235     */
236    @Override
237    public boolean postfire() throws IllegalActionException {
238        Time currentTime = getDirector().getModelTime();
239        Time delayToTime = currentTime.add(_delay);
240
241        // Remove the token that is sent at the current time.
242        if (_delayedOutputTokens.size() > 0) {
243            if (_currentOutput != null) {
244                _delayedOutputTokens.take();
245            }
246        }
247
248        // Handle the refiring of the multiple tokens
249        // that are scheduled to be produced at the same time.
250        if (_delayedOutputTokens.size() > 0) {
251            TimedEvent earliestEvent = (TimedEvent) _delayedOutputTokens.get();
252            Time eventTime = earliestEvent.timeStamp;
253
254            if (eventTime.equals(currentTime)) {
255                _fireAt(currentTime);
256            }
257        }
258
259        // consume input
260        if (input.hasToken(0)) {
261            _delayedOutputTokens.put(new TimedEvent(delayToTime, input.get(0)));
262            _fireAt(delayToTime);
263        }
264
265        return super.postfire();
266    }
267
268    ///////////////////////////////////////////////////////////////////
269    ////                         protected variables               ////
270
271    /** Current output. */
272    protected Token _currentOutput;
273
274    /** The amount of delay. */
275    protected double _delay;
276
277    /** A local event queue to store the delayed output tokens. */
278    protected CalendarQueue _delayedOutputTokens;
279
280}