001/* Generate discrete events at prespecified time instants.
002
003 Copyright (c) 2006-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.TypedAtomicActor;
031import ptolemy.actor.TypedIOPort;
032import ptolemy.actor.sched.FixedPointDirector;
033import ptolemy.actor.util.Time;
034import ptolemy.data.ArrayToken;
035import ptolemy.data.DoubleToken;
036import ptolemy.data.StringToken;
037import ptolemy.data.expr.Parameter;
038import ptolemy.data.type.ArrayType;
039import ptolemy.data.type.BaseType;
040import ptolemy.kernel.CompositeEntity;
041import ptolemy.kernel.util.Attribute;
042import ptolemy.kernel.util.IllegalActionException;
043import ptolemy.kernel.util.InternalErrorException;
044import ptolemy.kernel.util.NameDuplicationException;
045import ptolemy.kernel.util.Workspace;
046
047///////////////////////////////////////////////////////////////////
048//// EventSource
049
050/**
051 This actor outputs a set of events at a discrete set of time points.
052 It can be used to generate impulses in Continuous models. Events are
053 produced at superdense time index 1 or greater in order to ensure
054 that the output is piecewise continuous.
055 <p>
056 This actor only generates predictable events and that is why it does not
057 implement the ContinuousStepSizeControlActor interface. This actor requests a
058 refiring in its initialize() method to produce events. During its postfire()
059 method, it requests further firings to produce more events if necessary.
060
061 @author Haiyang Zheng
062 @version $Id$
063 @since Ptolemy II 6.0
064 @Pt.ProposedRating Yellow (hyzheng)
065 @Pt.AcceptedRating Yellow (hyzheng)
066 @deprecated Use {@link ptolemy.actor.lib.DiscreteClock with period set to Infinity}
067 */
068@Deprecated
069public class EventSource extends TypedAtomicActor {
070    /** Construct an actor in the specified container with the specified
071     *  name.  The name must be unique within the container or an exception
072     *  is thrown. The container argument must not be null, or a
073     *  NullPointerException will be thrown.
074     *
075     *  @param container The container.
076     *  @param name The actor's name
077     *  @exception IllegalActionException If the entity cannot be contained
078     *   by the proposed container.
079     *  @exception NameDuplicationException If name coincides with
080     *   an entity already in the container.
081     */
082    public EventSource(CompositeEntity container, String name)
083            throws IllegalActionException, NameDuplicationException {
084        super(container, name);
085
086        // Create port and parameters.
087        output = new TypedIOPort(this, "output", false, true);
088        new Parameter(output, "signalType", new StringToken("DISCRETE"));
089
090        period = new Parameter(this, "period");
091        period.setExpression("2.0");
092        period.setTypeEquals(BaseType.DOUBLE);
093
094        offsets = new Parameter(this, "offsets");
095        offsets.setExpression("{0.0, 1.0}");
096        offsets.setTypeEquals(new ArrayType(BaseType.DOUBLE));
097
098        // Call this so that we don't have to copy its code here...
099        attributeChanged(offsets);
100
101        // set the values parameter
102        values = new Parameter(this, "values");
103        values.setExpression("{1, 0}");
104
105        // set type constraints
106        output.setTypeAtLeast(ArrayType.elementType(values));
107
108        // Call this so that we don't have to copy its code here...
109        attributeChanged(values);
110    }
111
112    ///////////////////////////////////////////////////////////////////
113    ////                     ports and parameters                  ////
114
115    /** The output port.  The type of this port is determined by from
116     *  the <i>values</i> parameter.
117     */
118    public TypedIOPort output = null;
119
120    /** The offsets at which the specified values will be produced.
121     *  This parameter must contain an array of doubles, and it defaults
122     *  to {0.0, 1.0}.
123     */
124    public Parameter offsets;
125
126    /** The period of the output events.
127     *  This parameter must contain a DoubleToken, and defaults to 2.0.
128     */
129    public Parameter period;
130
131    /** The values that will be produced at the specified offsets.
132     *  This parameter must contain an ArrayToken, and defaults to {1, 0}.
133     */
134    public Parameter values;
135
136    ///////////////////////////////////////////////////////////////////
137    ////                         public methods                    ////
138
139    /** If the argument is the <i>offsets</i> parameter, check that the
140     *  array is nondecreasing and has the right dimension; if the
141     *  argument is <i>period</i>, check that it is positive. Other
142     *  sanity checks with <i>period</i> and <i>values</i> are done in
143     *  the fire() method.
144     *  @param attribute The attribute that changed.
145     *  @exception IllegalActionException If the offsets array is not
146     *   nondecreasing and nonnegative, or it is not a row vector.
147     */
148    @Override
149    public void attributeChanged(Attribute attribute)
150            throws IllegalActionException {
151        if (attribute == offsets) {
152            ArrayToken offsetsValue = (ArrayToken) offsets.getToken();
153            _offsets = new double[offsetsValue.length()];
154
155            double previous = 0.0;
156
157            for (int i = 0; i < offsetsValue.length(); i++) {
158                _offsets[i] = ((DoubleToken) offsetsValue.getElement(i))
159                        .doubleValue();
160
161                // Check nondecreasing property.
162                if (_offsets[i] < previous) {
163                    throw new IllegalActionException(this,
164                            "Value of offsets is not nondecreasing "
165                                    + "and nonnegative.");
166                }
167
168                previous = _offsets[i];
169            }
170        } else if (attribute == period) {
171            double periodValue = ((DoubleToken) period.getToken())
172                    .doubleValue();
173
174            if (periodValue <= 0.0) {
175                throw new IllegalActionException(this,
176                        "Period is required to be positive.  "
177                                + "Period given: " + periodValue);
178            }
179        } else {
180            super.attributeChanged(attribute);
181        }
182    }
183
184    /** Clone the actor into the specified workspace. This calls the
185     *  base class and then sets the parameter public members to refer
186     *  to the parameters of the new actor.
187     *  @param workspace The workspace for the new object.
188     *  @return A new actor.
189     *  @exception CloneNotSupportedException If a derived class contains
190     *   an attribute that cannot be cloned.
191     */
192    @Override
193    public Object clone(Workspace workspace) throws CloneNotSupportedException {
194        EventSource newObject = (EventSource) super.clone(workspace);
195
196        newObject._offsets = new double[_offsets.length];
197        System.arraycopy(_offsets, 0, newObject._offsets, 0, _offsets.length);
198
199        try {
200            newObject.output
201                    .setTypeAtLeast(ArrayType.elementType(newObject.values));
202        } catch (IllegalActionException e) {
203            throw new InternalErrorException(e);
204        }
205
206        return newObject;
207    }
208
209    /** Emit the discrete event that happens at the current time. If there
210     *  is no such events, do nothing.
211     *  @exception IllegalActionException If the event cannot be sent.
212     */
213    @Override
214    public void fire() throws IllegalActionException {
215        if (_readyToFire == _TIME_RIGHT) {
216            super.fire();
217            output.send(0, ((ArrayToken) values.getToken()).getElement(_phase));
218        }
219    }
220
221    /** Schedule the first firing and initialize local variables.
222     *  @exception IllegalActionException If the parent class throws it,
223     *   or if the <i>values</i> parameter is not a row vector, or if the
224     *   fireAt() method of the director throws it, or if the director does not
225     *   agree to fire the actor at the specified time.
226     */
227    @Override
228    public synchronized void initialize() throws IllegalActionException {
229        super.initialize();
230        _cycleStartTime = getDirector().getModelTime();
231        _nextOutputIndex = 1;
232        _phase = 0;
233
234        // Schedule the first firing.
235        _nextOutputTime = _cycleStartTime.add(_offsets[0]);
236        _fireAt(_nextOutputTime);
237    }
238
239    /** Update the state of the actor and schedule the next firing,
240     *  if the director is in the discrete phase.
241     *  @exception IllegalActionException If the director throws it when
242     *   scheduling the next firing, or if the length of the values and
243     *   offsets parameters don't match, or if the director does not
244     *   agree to fire the actor at the specified time.
245     */
246    @Override
247    public boolean postfire() throws IllegalActionException {
248        if (_readyToFire == _TIME_NOT_RIGHT) {
249            return true;
250        } else if (_readyToFire == _TIME_RIGHT_INDEX_EARLY) {
251            // Request a refiring at the current time.
252            _fireAt(getDirector().getModelTime());
253            return true;
254        }
255        // Advance to the next phase.
256        double periodValue = ((DoubleToken) period.getToken()).doubleValue();
257
258        // Increment to the next phase.
259        _phase++;
260
261        if (_phase >= _offsets.length) {
262            _phase = 0;
263            _cycleStartTime = _cycleStartTime.add(periodValue);
264        }
265
266        if (_offsets[_phase] >= periodValue) {
267            throw new IllegalActionException(this,
268                    "Offset number " + _phase + " with value "
269                            + _offsets[_phase] + " must be less than the "
270                            + "period, which is " + periodValue);
271        }
272
273        _nextOutputTime = _cycleStartTime.add(_offsets[_phase]);
274        _fireAt(_nextOutputTime);
275        return true;
276    }
277
278    /** Return true if this actor is scheduled to fire at the current time.
279     *  @return True if this actor is scheduled to fire at the current time.
280     *  @exception IllegalActionException If thrown by the super class.
281     */
282    @Override
283    public boolean prefire() throws IllegalActionException {
284        FixedPointDirector director = (FixedPointDirector) getDirector();
285        if (director.getModelTime().compareTo(_nextOutputTime) == 0) {
286            if (_nextOutputIndex <= director.getIndex()) {
287                _readyToFire = _TIME_RIGHT;
288            } else {
289                _readyToFire = _TIME_RIGHT_INDEX_EARLY;
290            }
291        } else {
292            _readyToFire = _TIME_NOT_RIGHT;
293        }
294        return super.prefire();
295    }
296
297    ///////////////////////////////////////////////////////////////////
298    ////                         private variables                 ////
299    // The following are all transient because they need not be cloned.
300    // Either the clone method or the initialize() method sets them.
301
302    /** The most recent cycle start time. */
303    private transient Time _cycleStartTime;
304
305    /** Cache of offsets array value. */
306    private transient double[] _offsets;
307
308    /** The phase of the next output. */
309    private transient int _phase;
310
311    /** The index of when the output should be emitted. */
312    private transient int _nextOutputIndex;
313
314    /** The next time point when the output should be emitted. */
315    private transient Time _nextOutputTime;
316
317    /** The flag indicating whether the time is right to produce output. */
318    private transient int _readyToFire;
319    private static final int _TIME_RIGHT = 0;
320    private static final int _TIME_RIGHT_INDEX_EARLY = 1;
321    private static final int _TIME_NOT_RIGHT = 2;
322}