001/* A pulse source.
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 ptolemy.data.ArrayToken;
031import ptolemy.data.BooleanToken;
032import ptolemy.data.IntToken;
033import ptolemy.data.Token;
034import ptolemy.data.expr.Parameter;
035import ptolemy.data.type.ArrayType;
036import ptolemy.data.type.BaseType;
037import ptolemy.kernel.CompositeEntity;
038import ptolemy.kernel.util.Attribute;
039import ptolemy.kernel.util.IllegalActionException;
040import ptolemy.kernel.util.InternalErrorException;
041import ptolemy.kernel.util.NameDuplicationException;
042import ptolemy.kernel.util.Workspace;
043
044///////////////////////////////////////////////////////////////////
045//// Pulse
046
047/**
048 Produce a pulse with a shape specified by the parameters.
049 The <i>values</i> parameter contains an ArrayToken, which specifies
050 the sequence of values to produce at the output.  The <i>indexes</i>
051 parameter contains an array of integers, which specifies when those values
052 should be produced.  The array in the <i>indexes</i> parameter
053 must have the same length as that in the
054 <i>values</i> parameter or an exception will be thrown by the fire() method.
055 Also, the <i>indexes</i> array must be increasing and non-negative,
056 or an exception will be thrown when it is set.
057 <p>
058 Eventually, this actor will support various kinds of interpolation.
059 For now, it outputs a zero (of the same type as the values) whenever
060 the iteration count does not match an index in <i>indexes</i>.
061 <p>
062 The default for the <i>values</i> parameter is
063 an integer vector of form {1, 0}.
064 The default indexes array is {0, 1}.
065 Thus, the default output sequence will be 1, 0, 0, ...
066 <p>
067 However, the Pulse actor has a <I>repeat</i> parameter. When set to
068 true, the defined sequence is repeated indefinitely. Otherwise, the
069 default sequence of zero values result.
070 <p>
071 The type of the output can be any token type. This type is inferred
072 from the element type of the <i>values</i> parameter.
073 <p>The Ptolemy Expression language has several constructs that are
074 useful for creating arrays for use as values or indexes:
075 <dl>
076 <dt><code>[0:1:100].toArray()</code>
077 <dd>Matlab style array construction that creates an array of 100 elements,
078 0 through 99.
079 <dt><code>repeat(100, {1}(0))</code>
080 <dd>Creat a sequence of one hundred 1's.
081 </dl>
082 <p>
083 NOTE: A reset input for this actor would be useful.  This would reset
084 the iterations count, to cause the pulse to emerge again.
085
086 @author Edward A. Lee
087 @version $Id$
088 @since Ptolemy II 0.2
089 @Pt.ProposedRating Yellow (eal)
090 @Pt.AcceptedRating Yellow (cxh)
091 */
092public class Pulse extends SequenceSource {
093    /** Construct an actor with the specified container and name.
094     *  @param container The container.
095     *  @param name The name of this actor.
096     *  @exception IllegalActionException If the entity cannot be contained
097     *   by the proposed container.
098     *  @exception NameDuplicationException If the container already has an
099     *   actor with this name.
100     */
101    public Pulse(CompositeEntity container, String name)
102            throws NameDuplicationException, IllegalActionException {
103        super(container, name);
104
105        indexes = new Parameter(this, "indexes");
106        indexes.setExpression("{0, 1}");
107        indexes.setTypeEquals(new ArrayType(BaseType.INT));
108
109        // Call this so that we don't have to copy its code here...
110        attributeChanged(indexes);
111
112        // set values parameter
113        values = new Parameter(this, "values");
114        values.setExpression("{1, 0}");
115
116        // Set the Repeat Flag.
117        repeat = new Parameter(this, "repeat", new BooleanToken(false));
118        repeat.setTypeEquals(BaseType.BOOLEAN);
119        attributeChanged(repeat);
120
121        // set type constraint
122        output.setTypeAtLeast(ArrayType.elementType(values));
123
124        // Call this so that we don't have to copy its code here...
125        attributeChanged(values);
126
127        // Show the firingCountLimit parameter last.
128        firingCountLimit.moveToLast();
129    }
130
131    ///////////////////////////////////////////////////////////////////
132    ////                     ports and parameters                  ////
133
134    /** The indexes at which the specified values will be produced.
135     *  This parameter is an array of integers, with default value {0, 1}.
136     */
137    public Parameter indexes;
138
139    /** The flag that indicates whether the pulse sequence needs to be
140     *  repeated. This is a boolean, and defaults to false.
141     */
142    public Parameter repeat;
143
144    /** The values that will be produced at the specified indexes.
145     *  This parameter is an array, with default value {1, 0}.
146     */
147    public Parameter values;
148
149    ///////////////////////////////////////////////////////////////////
150    ////                         public methods                    ////
151
152    /** If the attribute being changed is <i>indexes</i>, then check
153     *  that it is increasing and nonnegative.
154     *  @param attribute The attribute that changed.
155     *  @exception IllegalActionException If the indexes vector is not
156     *   increasing and nonnegative, or the indexes is not a row vector.
157     */
158    @Override
159    public void attributeChanged(Attribute attribute)
160            throws IllegalActionException {
161        if (attribute == indexes) {
162            ArrayToken indexesValue = (ArrayToken) indexes.getToken();
163            _indexes = new int[indexesValue.length()];
164
165            int previous = 0;
166
167            for (int i = 0; i < indexesValue.length(); i++) {
168                _indexes[i] = ((IntToken) indexesValue.getElement(i))
169                        .intValue();
170
171                // Check nondecreasing property.
172                if (_indexes[i] < previous) {
173                    throw new IllegalActionException(this,
174                            "Value of indexes is not nondecreasing "
175                                    + "and nonnegative.");
176                }
177
178                previous = _indexes[i];
179            }
180        } else if (attribute == values) {
181            try {
182                ArrayToken valuesArray = (ArrayToken) values.getToken();
183                Token prototype = valuesArray.getElement(0);
184                _zero = prototype.zero();
185            } catch (ArrayIndexOutOfBoundsException ex) {
186                throw new IllegalActionException(this,
187                        "Cannot set values to an empty array.");
188            } catch (ClassCastException ex) {
189                throw new IllegalActionException(this,
190                        "Cannot set values to something that is not an array: "
191                                + values.getToken());
192            }
193        } else if (attribute == repeat) {
194            _repeatFlag = ((BooleanToken) repeat.getToken()).booleanValue();
195        } else {
196            super.attributeChanged(attribute);
197        }
198    }
199
200    /** Clone the actor into the specified workspace. This overrides the
201     *  base class to handle type constraints.
202     *  @param workspace The workspace for the new object.
203     *  @return A new actor.
204     *  @exception CloneNotSupportedException If a derived class contains
205     *   an attribute that cannot be cloned.
206     */
207    @Override
208    public Object clone(Workspace workspace) throws CloneNotSupportedException {
209        Pulse newObject = (Pulse) super.clone(workspace);
210        try {
211            newObject.output
212                    .setTypeAtLeast(ArrayType.elementType(newObject.values));
213        } catch (IllegalActionException e) {
214            throw new InternalErrorException(e);
215        }
216
217        newObject._indexes = new int[_indexes.length];
218        System.arraycopy(_indexes, 0, newObject._indexes, 0, _indexes.length);
219
220        try {
221            ArrayToken valuesArray = (ArrayToken) newObject.values.getToken();
222            Token prototype = valuesArray.getElement(0);
223            newObject._zero = prototype.zero();
224        } catch (Exception ex) {
225            throw new InternalErrorException(ex);
226        }
227        return newObject;
228    }
229
230    /** Output a value if the count of iterations matches one of the entries
231     *  in the indexes array.
232     *  Otherwise output a zero token with the same type as the values in
233     *  the value array.
234     *  @exception IllegalActionException If the values and indexes parameters
235     *   do not have the same length, or if there is no director.
236     */
237    @Override
238    public void fire() throws IllegalActionException {
239        super.fire();
240
241        int currentIndex = 0;
242        ArrayToken val = (ArrayToken) values.getToken();
243
244        if (_indexColCount < _indexes.length) {
245            if (val.length() != _indexes.length) {
246                throw new IllegalActionException(this,
247                        "Parameters values and indexes have "
248                                + "different lengths.  Length of values = "
249                                + val.length() + ". Length of indexes = "
250                                + _indexes.length + ".");
251            }
252
253            currentIndex = _indexes[_indexColCount];
254
255            if (_iterationCount == currentIndex) {
256                // Got a match with an index.
257                output.send(0, val.getElement(_indexColCount));
258                _match = true;
259                return;
260            }
261        } else {
262            if (_repeatFlag) {
263                // Repeat the pulse sequence again.
264                _iterationCount = 0;
265                _indexColCount = 0;
266
267                currentIndex = _indexes[_indexColCount];
268
269                if (_iterationCount == currentIndex) {
270                    output.send(0, val.getElement(_indexColCount));
271                    _match = true;
272                }
273
274                return;
275            }
276        }
277
278        output.send(0, _zero);
279        _match = false;
280    }
281
282    /** Set the iteration count to zero.
283     *  @exception IllegalActionException If the parent class throws it.
284     */
285    @Override
286    public void initialize() throws IllegalActionException {
287        super.initialize();
288        _iterationCount = 0;
289        _indexColCount = 0;
290    }
291
292    /** Update the iteration counters until they exceed the values
293     *  in the indexes array.
294     *  @exception IllegalActionException If the expression of indexes
295     *   is not valid.
296     */
297    @Override
298    public boolean postfire() throws IllegalActionException {
299        // We stop incrementing after reaching the top of the indexes
300        // vector to avoid possibility of overflow.
301        if (_iterationCount <= _indexes[_indexes.length - 1]) {
302            ++_iterationCount;
303        }
304
305        if (_match) {
306            ++_indexColCount;
307        }
308
309        return super.postfire();
310    }
311
312    /** Start an iteration.
313     *  @exception IllegalActionException If the base class throws it.
314     */
315    @Override
316    public boolean prefire() throws IllegalActionException {
317        _match = false;
318        return super.prefire();
319    }
320
321    ///////////////////////////////////////////////////////////////////
322    ////                         private variables                 ////
323    // Count of the iterations.  This stops incrementing when
324    // we exceed the top of the indexes vector.
325    private int _iterationCount = 0;
326
327    // Index of the next output in the values array.
328    private int _indexColCount = 0;
329
330    // Cache of indexes array value.
331    private transient int[] _indexes;
332
333    // Zero token of the same type as in the values array.
334    private Token _zero;
335
336    // Indicator of whether the iterations count matches one of the indexes.
337    private boolean _match = false;
338
339    // Flag to indicate whether or not to repeat the sequence.
340    private boolean _repeatFlag;
341}