001/* Generate discrete events by periodically sampling a CT signal.
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.continuous.lib;
029
030import ptolemy.actor.Director;
031import ptolemy.actor.SuperdenseTimeDirector;
032import ptolemy.actor.lib.Sampler;
033import ptolemy.actor.lib.Transformer;
034import ptolemy.actor.util.CausalityInterface;
035import ptolemy.actor.util.Time;
036import ptolemy.data.DoubleToken;
037import ptolemy.data.IntToken;
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.Workspace;
046
047///////////////////////////////////////////////////////////////////
048//// PeriodicSampler
049
050/**
051 This actor generates discrete events by periodically sampling the input signal.
052 The sampling rate is given by parameter <i>samplePeriod</i>, which has default value
053 0.1.  Specifically, if the actor is initialized at time <i>t</i> and the sample
054 period is <i>T</i>, then the output will have the value of the input
055 at times <i>t</i> + <i>nT</i>, for all natural numbers <i>n</i>.
056 By default, this sampler will send to the output the initial value of
057 the input (the input value at microstep 0), but will send it one
058 microstep later (at microstep 1).
059 This ensures that the output at microstep 0 is always absent, thus
060 ensuring continuity from the left. That is, the input is absent prior
061 to the sample time, so continuity requires that it be absent at
062 microstep 0 at the sample time.
063 <p>
064 To get this sampler to record values other than the initial value,
065 set the <i>microstep</i> parameter to a value greater than 0.
066 Setting it to 1, for example, will record the input value after
067 a discontinuity rather than before the discontinuity. Note
068 that {@link Sampler} normally records its inputs at microstep 1
069 (because it is triggered by a discrete signal, which has events
070 at microstep 1), and therefore if you want this PeriodicSampler
071 to behave the same as Sampler, you should set <i>microstep</i>
072 to 1.
073 <p>
074 This actor has multiport inputs and outputs. Signals in
075 each input channel are sampled and produced to corresponding output
076 channel.
077 <p>
078 Note that this actor does not tolerate changing input or output
079 connections during execution.
080
081 @author Edward A. Lee
082 @version $Id$
083 @since Ptolemy II 8.0
084 @Pt.ProposedRating Yellow (eal)
085 @Pt.AcceptedRating Red (cxh)
086 */
087public class PeriodicSampler extends Transformer {
088    /** Construct an actor in the specified container with the specified
089     *  name.  The name must be unique within the container or an exception
090     *  is thrown. The container argument must not be null, or a
091     *  NullPointerException will be thrown.
092     *  The actor can be either dynamic, or not.  It must be set at the
093     *  construction time and can't be changed thereafter.
094     *  A dynamic actor will produce a token at its initialization phase.
095     *  @param container The container of this actor.
096     *  @param name The actor's name
097     *  @exception IllegalActionException If the entity cannot be contained
098     *   by the proposed container.
099     *  @exception NameDuplicationException If name coincides with
100     *   an entity already in the container.
101     */
102    public PeriodicSampler(CompositeEntity container, String name)
103            throws IllegalActionException, NameDuplicationException {
104        super(container, name);
105        input.setMultiport(true);
106        output.setMultiport(true);
107        output.setWidthEquals(input, true);
108
109        samplePeriod = new Parameter(this, "samplePeriod");
110        samplePeriod.setExpression("0.1");
111        samplePeriod.setTypeEquals(BaseType.DOUBLE);
112
113        microstep = new Parameter(this, "microstep");
114        microstep.setExpression("0");
115        microstep.setTypeEquals(BaseType.INT);
116
117        _attachText("_iconDescription", "<svg>\n" + "<rect x=\"-30\" y=\"-20\" "
118                + "width=\"60\" height=\"40\" " + "style=\"fill:white\"/>\n"
119                + "<polyline points=\"-30,0 -20,0 -10,0 10,-7\"/>\n"
120                + "<polyline points=\"10,0 30,0\"/>\n" + "</svg>\n");
121    }
122
123    ///////////////////////////////////////////////////////////////////
124    ////                         public variables                  ////
125
126    /** The microstep at which to read the input. This is an
127     *  whose default value is 0.
128     */
129    public Parameter microstep;
130
131    /** The parameter for the sampling period. This is a double
132     *  whose default value is 0.1.
133     */
134    public Parameter samplePeriod;
135
136    ///////////////////////////////////////////////////////////////////
137    ////                         public methods                    ////
138
139    /** If the attribute is microstep, adjust the causality interface.
140     *  @exception IllegalActionException If the superclass throws it.
141     */
142    @Override
143    public void attributeChanged(Attribute attribute)
144            throws IllegalActionException {
145        if (attribute == microstep) {
146            int microstepValue = ((IntToken) microstep.getToken()).intValue();
147            if (microstepValue != _microstep) {
148                CausalityInterface causalityInterface = getCausalityInterface();
149                if (microstepValue == 0) {
150                    // Output depends on the input after a microstep delay.
151                    causalityInterface.declareDelayDependency(input, output,
152                            0.0, 1);
153                } else {
154                    // Output depends immediately on the input.
155                    causalityInterface.declareDelayDependency(input, output,
156                            0.0, 0);
157                }
158                _microstep = microstepValue;
159                Director director = getDirector();
160                if (director != null) {
161                    director.invalidateSchedule();
162                }
163            }
164        } else {
165            super.attributeChanged(attribute);
166        }
167    }
168
169    /** Clone this actor into the specified workspace. The new actor is
170     *  <i>not</i> added to the directory of that workspace (you must do this
171     *  yourself if you want it there).
172     *  The result is a new actor with the same ports as the original, but
173     *  no connections and no container.  A container must be set before
174     *  much can be done with this actor.
175     *
176     *  @param workspace The workspace for the cloned object.
177     *  @exception CloneNotSupportedException If cloned ports cannot have
178     *   as their container the cloned entity (this should not occur), or
179     *   if one of the attributes cannot be cloned.
180     *  @return A new ComponentEntity.
181     */
182    @Override
183    public Object clone(Workspace workspace) throws CloneNotSupportedException {
184        PeriodicSampler newObject = (PeriodicSampler) super.clone(workspace);
185        newObject.output.setWidthEquals(newObject.input, true);
186        return newObject;
187    }
188
189    /** Generate an output if the current time is one of the sampling
190     *  times and the microstep matches.
191     *  In addition, if the microstep parameter has value 0,
192     *  produce the output only if the current microstep is 1.
193     *  The value of the event is the value of the input signal at the
194     *  current time at the microstep specified by the microstep parameter.
195     *  @exception IllegalActionException If the transfer of tokens failed.
196     */
197    @Override
198    public void fire() throws IllegalActionException {
199        super.fire();
200        SuperdenseTimeDirector director = (SuperdenseTimeDirector) getDirector();
201        Time currentTime = getDirector().getModelTime();
202        int microstep = director.getIndex();
203        if (_debugging) {
204            _debug("Current time is " + currentTime + " with microstep "
205                    + microstep);
206        }
207        int inputWidth = input.getWidth();
208        int outputWidth = output.getWidth();
209        if (currentTime.compareTo(_nextSamplingTime) == 0) {
210            // Time is right to produce an output. Check the microstep.
211            if (_microstep == 0 && microstep == 1) {
212                // In delay mode. Input should have been read in microstep 0.
213                for (int i = 0; i < outputWidth; i++) {
214                    if (_pendingOutputs[i] != null) {
215                        // There is a deferred output for this input channel.
216                        output.send(i, _pendingOutputs[i]);
217                        if (_debugging) {
218                            _debug("Sending output value " + _pendingOutputs[i]
219                                    + " on channel " + i);
220                        }
221                    } else {
222                        output.sendClear(i);
223                    }
224                }
225            } else if (_microstep != 0 && _microstep == microstep) {
226                // Microstep matches.
227                // Read the input and produce an output.
228                for (int i = 0; i < inputWidth; i++) {
229                    if (input.isKnown(i) && input.hasToken(i)) {
230                        Token token = input.get(i);
231                        if (i < outputWidth) {
232                            output.send(i, token);
233                            if (_debugging) {
234                                _debug("Read input and sent to output: " + token
235                                        + " on channel " + i);
236                            }
237                        }
238                    }
239                }
240                // If the output is wider than the input, send clear.
241                for (int i = inputWidth; i < outputWidth; i++) {
242                    output.sendClear(i);
243                }
244            } else {
245                // Microstep does not match.
246                for (int i = 0; i < outputWidth; i++) {
247                    output.sendClear(i);
248                }
249            }
250        } else {
251            // Not time to send an output.
252            for (int i = 0; i < outputWidth; i++) {
253                output.sendClear(i);
254            }
255        }
256    }
257
258    /** Set the next sampling time for each
259     *  input as the start time (i.e. the current time).
260     *  We do not register the start time as a breakpoint, since the
261     *  director will fire at the start time any way.
262     *  @exception IllegalActionException If thrown by the supper class.
263     */
264    @Override
265    public void initialize() throws IllegalActionException {
266        super.initialize();
267        int width = output.getWidth();
268        if (_pendingOutputs == null || _pendingOutputs.length != width) {
269            _pendingOutputs = new Token[width];
270        }
271        Time currentTime = getDirector().getModelTime();
272        _nextSamplingTime = currentTime;
273        for (int i = 0; i < width; i++) {
274            _pendingOutputs[i] = null;
275        }
276        getDirector().fireAt(this, _nextSamplingTime, _microstep);
277    }
278
279    /** Return false if the microstep value
280     *  is zero. In that case, this actor can produce some outputs even the
281     *  inputs are unknown. This actor is usable for breaking feedback
282     *  loops. It does not read inputs in the fire() method.
283     *  @return False.
284     */
285    @Override
286    public boolean isStrict() {
287        if (_microstep == 0) {
288            return false;
289        } else {
290            return true;
291        }
292    }
293
294    /** If the current microstep is zero, sample the inputs and request
295     *  a refiring at the current time. If it is one, then request a refiring
296     *  at the next sample time.
297     *  @return True.
298     *  @exception IllegalActionException If the superclass throws it.
299     */
300    @Override
301    public boolean postfire() throws IllegalActionException {
302        Director director = getDirector();
303        Time currentTime = director.getModelTime();
304        int microstep = ((SuperdenseTimeDirector) director).getIndex();
305        int inputWidth = input.getWidth();
306        if (currentTime.compareTo(_nextSamplingTime) == 0) {
307            // Current time matches. Check microstep.
308            if (_microstep == 0) {
309                // In delay mode. Read the input if the microstep is 0.
310                if (microstep == 0) {
311                    int outputWidth = output.getWidth();
312                    for (int i = 0; i < outputWidth; i++) {
313                        if (i < inputWidth && input.hasToken(i)) {
314                            _pendingOutputs[i] = input.get(i);
315                        } else {
316                            _pendingOutputs[i] = null;
317                        }
318                        if (_debugging) {
319                            _debug("Read input: " + _pendingOutputs[i]
320                                    + " on channel " + i);
321                        }
322                    }
323                    for (int i = outputWidth; i < inputWidth; i++) {
324                        // Read and discard the input.
325                        input.get(i);
326                    }
327                    director.fireAt(this, currentTime, 1);
328                } else {
329                    if (microstep == 1) {
330                        double samplePeriodValue = ((DoubleToken) samplePeriod
331                                .getToken()).doubleValue();
332                        _nextSamplingTime = currentTime.add(samplePeriodValue);
333                        director.fireAt(this, _nextSamplingTime, 0);
334                    }
335                    // Consume the inputs.
336                    for (int i = 0; i < inputWidth; i++) {
337                        if (input.hasToken(i)) {
338                            input.get(i);
339                        }
340                    }
341                }
342            } else {
343                // Not in delay mode. If the microstep matches,
344                // request refiring.
345                if (_microstep == microstep) {
346                    double samplePeriodValue = ((DoubleToken) samplePeriod
347                            .getToken()).doubleValue();
348                    _nextSamplingTime = currentTime.add(samplePeriodValue);
349                    director.fireAt(this, _nextSamplingTime, _microstep);
350                } else {
351                    // Consume the inputs.
352                    for (int i = 0; i < inputWidth; i++) {
353                        if (input.hasToken(i)) {
354                            input.get(i);
355                        }
356                    }
357                }
358            }
359        } else {
360            // Time does not match.
361            // Consume the inputs.
362            for (int i = 0; i < inputWidth; i++) {
363                if (input.hasToken(i)) {
364                    input.get(i);
365                }
366            }
367        }
368        return super.postfire();
369    }
370
371    /** Make sure the actor runs inside a domain that understands
372     *  superdense time.
373     *  @exception IllegalActionException If the director is not
374     *  a SuperdenseTimeDirector or the parent class throws it.
375     */
376    @Override
377    public void preinitialize() throws IllegalActionException {
378        if (!(getDirector() instanceof SuperdenseTimeDirector)) {
379            throw new IllegalActionException("PeriodicSampler can only"
380                    + " be used inside a superdense time domain.");
381        }
382        super.preinitialize();
383    }
384
385    ///////////////////////////////////////////////////////////////////
386    ////                         private variables                 ////
387
388    /** The next sampling time. */
389    private Time _nextSamplingTime;
390
391    /** Record of pending output tokens (those that have been
392     *  delayed because they appeared at the input when the
393     *  the microstep was zero).
394     */
395    private Token[] _pendingOutputs;
396
397    /** Value of the microstep. */
398    private int _microstep = 1;
399}