001/* A Poisson process clock 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.actor.Director;
031import ptolemy.actor.SuperdenseTimeDirector;
032import ptolemy.actor.TimedActor;
033import ptolemy.actor.util.Time;
034import ptolemy.data.ArrayToken;
035import ptolemy.data.BooleanToken;
036import ptolemy.data.DoubleToken;
037import ptolemy.data.Token;
038import ptolemy.data.expr.Parameter;
039import ptolemy.data.type.ArrayType;
040import ptolemy.data.type.BaseType;
041import ptolemy.kernel.CompositeEntity;
042import ptolemy.kernel.util.Attribute;
043import ptolemy.kernel.util.IllegalActionException;
044import ptolemy.kernel.util.InternalErrorException;
045import ptolemy.kernel.util.NameDuplicationException;
046import ptolemy.kernel.util.Workspace;
047
048///////////////////////////////////////////////////////////////////
049//// PoissonClock
050
051/**
052 This actor produces discrete events according to a Poisson process.
053 The time between events is given by independent and identically
054 distributed exponential random variables. The values produced
055 rotate sequentially through those given in the <i>values</i> parameter,
056 which is an array of anything and defaults to {1, 0}.
057 The type of the output can be any token type.  This type is inferred from
058 the element type of the <i>values</i> parameter.
059 The mean time between events is given by the <i>meanTime</i> parameter,
060 which defaults to 1.0.
061 <p>
062 In the initialize() method and postfire() methods,
063 the actor uses the fireAt() method of the director to request
064 the next firing.  The first firing is always at the start time, unless
065 the parameter <i>fireAtStart</i> is changed to <i>false</i>.
066 <p>
067 If the trigger input is connected, then any event on it will
068 cause the Poisson process to immediately produce the next
069 event, as if the time for that event had arrived.
070 <p>
071 If this actor is inactive at the time at which it would have
072 otherwise produced an output, then it will stop producing outputs.
073 This should not happen.
074
075 @author Edward A. Lee
076 @version $Id$
077 @since Ptolemy II 1.0
078 @Pt.ProposedRating Yellow (eal)
079 @Pt.AcceptedRating Yellow (yuhong)
080 */
081public class PoissonClock extends RandomSource implements TimedActor {
082    /** Construct an actor with the specified container and name.
083     *  @param container The container.
084     *  @param name The name of this actor.
085     *  @exception IllegalActionException If the entity cannot be contained
086     *   by the proposed container.
087     *  @exception NameDuplicationException If the container already has an
088     *   actor with this name.
089     */
090    public PoissonClock(CompositeEntity container, String name)
091            throws NameDuplicationException, IllegalActionException {
092        super(container, name);
093
094        meanTime = new Parameter(this, "meanTime");
095        meanTime.setExpression("1.0");
096        meanTime.setTypeEquals(BaseType.DOUBLE);
097
098        // Set the values parameter
099        values = new Parameter(this, "values");
100        values.setExpression("{1, 0}");
101        output.setTypeAtLeast(ArrayType.elementType(values));
102
103        // Call this so that we don't have to copy its code here...
104        attributeChanged(values);
105
106        fireAtStart = new Parameter(this, "fireAtStart");
107        fireAtStart.setExpression("true");
108        fireAtStart.setTypeEquals(BaseType.BOOLEAN);
109
110        // Note that this class copies much of TimedSource into here
111        // because it can't subclass both TimedSource and RandomSource.
112        stopTime = new Parameter(this, "stopTime");
113        stopTime.setExpression("Infinity");
114        stopTime.setTypeEquals(BaseType.DOUBLE);
115
116        _attachText("_iconDescription", "<svg>\n" + "<rect x=\"-20\" y=\"-20\" "
117                + "width=\"40\" height=\"40\" " + "style=\"fill:lightGrey\"/>\n"
118                + "<circle cx=\"0\" cy=\"0\" r=\"17\""
119                + "style=\"fill:white\"/>\n"
120                + "<line x1=\"0\" y1=\"-15\" x2=\"0\" y2=\"-13\"/>\n"
121                + "<line x1=\"0\" y1=\"14\" x2=\"0\" y2=\"16\"/>\n"
122                + "<line x1=\"-15\" y1=\"0\" x2=\"-13\" y2=\"0\"/>\n"
123                + "<line x1=\"14\" y1=\"0\" x2=\"16\" y2=\"0\"/>\n"
124                + "<line x1=\"0\" y1=\"-8\" x2=\"0\" y2=\"0\"/>\n"
125                + "<line x1=\"0\" y1=\"0\" x2=\"11.26\" y2=\"-6.5\"/>\n"
126                + "</svg>\n");
127    }
128
129    ///////////////////////////////////////////////////////////////////
130    ////                     ports and parameters                  ////
131
132    /** If true, then this actor will request a firing at the start time.
133     *  Otherwise, the first firing will be requested at the first random
134     *  time. This is a boolean-valued parameter that defaults to <i>true</i>.
135     */
136    public Parameter fireAtStart;
137
138    /** The mean time between events, where the output value transitions.
139     *  This parameter must contain a DoubleToken.
140     */
141    public Parameter meanTime;
142
143    /** The time at which postfire() should return false. This is a
144     *  double that defaults to Infinity, which means that postfire()
145     *  never returns false (or at least, doesn't do so due to stopTime
146     *  having been exceeded).
147     */
148    public Parameter stopTime;
149
150    /** The values that will be produced at the output.
151     *  This parameter can contain any ArrayToken, and it defaults to {1, 0}.
152     */
153    public Parameter values;
154
155    ///////////////////////////////////////////////////////////////////
156    ////                         public methods                    ////
157
158    /** If the argument is the meanTime parameter, check that it is
159     *  positive.
160     *  @param attribute The attribute that changed.
161     *  @exception IllegalActionException If the meanTime value is
162     *   not positive.
163     */
164    @Override
165    public void attributeChanged(Attribute attribute)
166            throws IllegalActionException {
167        if (attribute == meanTime) {
168            double mean = ((DoubleToken) meanTime.getToken()).doubleValue();
169
170            if (mean <= 0.0) {
171                throw new IllegalActionException(this,
172                        "meanTime is required to be positive.  meanTime given: "
173                                + mean);
174            }
175        } else if (attribute == values) {
176            ArrayToken val = (ArrayToken) values.getToken();
177            _length = val.length();
178        } else if (attribute == stopTime) {
179            double newStopTimeValue = ((DoubleToken) stopTime.getToken())
180                    .doubleValue();
181
182            if (_executing) {
183                Time newStopTime = new Time(getDirector(), newStopTimeValue);
184                Director director = getDirector();
185
186                if (director != null) {
187                    Time currentTime = director.getModelTime();
188
189                    if (newStopTime.compareTo(currentTime) > 0) {
190                        // NOTE: Do not throw an exception if the director ignores this
191                        // stop time request or returns some other value of time.
192                        // postfire() will return false on the next firing after time
193                        // equals the stop time or exceeds it.
194                        director.fireAt(this, newStopTime);
195                    } else {
196                        throw new IllegalActionException(this, "The stop time "
197                                + "is earlier than the current time.");
198                    }
199                }
200
201                _stopTime = newStopTime;
202            }
203        } else {
204            super.attributeChanged(attribute);
205        }
206    }
207
208    /** Clone the actor into the specified workspace. This calls the
209     *  base class and then sets the parameter public members to refer
210     *  to the parameters of the new actor.
211     *  @param workspace The workspace for the new object.
212     *  @return A new actor.
213     *  @exception CloneNotSupportedException If a derived class contains
214     *   an attribute that cannot be cloned.
215     */
216    @Override
217    public Object clone(Workspace workspace) throws CloneNotSupportedException {
218        PoissonClock newObject = (PoissonClock) super.clone(workspace);
219        try {
220            newObject.output
221                    .setTypeAtLeast(ArrayType.elementType(newObject.values));
222        } catch (IllegalActionException e) {
223            throw new InternalErrorException(e);
224        }
225        return newObject;
226    }
227
228    /** Output the current value.
229     *  @exception IllegalActionException If there is no director.
230     */
231    @Override
232    public void fire() throws IllegalActionException {
233        // If there is a trigger input, then it is time for an output.
234        boolean triggerInputPresent = false;
235        for (int i = 0; i < trigger.getWidth(); i++) {
236            if (trigger.isKnown() && trigger.hasToken(i)) {
237                triggerInputPresent = true;
238            }
239        }
240
241        Director director = getDirector();
242        Time currentTime = director.getModelTime();
243        // It is time to produce an output if the current time equals
244        // or exceeds the next firing time (it should never exceed).
245        boolean timeForOutput = currentTime.compareTo(_nextFiringTime) >= 0;
246
247        if (!timeForOutput && !triggerInputPresent) {
248            // It is too early.
249            return;
250        }
251        if (director instanceof SuperdenseTimeDirector) {
252            int currentMicrostep = ((SuperdenseTimeDirector) director)
253                    .getIndex();
254            if (currentMicrostep < 1 && !triggerInputPresent) {
255                // The time matches, but the microstep is too early.
256                return;
257            }
258        }
259        // If this is the first call to fire() in an iteration,
260        // the the superclass will generate a new random number
261        // to be used in postfire().
262        super.fire();
263        output.send(0, _getValue(_nextOutputIndex));
264        _outputProduced = true;
265    }
266
267    /** Get the stop time.
268     *  @return The stop time.
269     *  @deprecated As of Ptolemy II 4.1, replaced by
270     *  {@link ptolemy.actor.lib.PoissonClock#getModelStopTime}
271     */
272    @Deprecated
273    public double getStopTime() {
274        return getModelStopTime().getDoubleValue();
275    }
276
277    /** Get the stop time.
278     *  @return The stop time.
279     */
280    public Time getModelStopTime() {
281        return _stopTime;
282    }
283
284    /** Request the first firing either at the start time
285     *  or at a random time, depending on <i>fireAtStart</i>.
286     *  @exception IllegalActionException If the fireAt() method of the
287     *   director throws it, or if the director does not
288     *   agree to fire the actor at the specified time.
289     */
290    @Override
291    public void initialize() throws IllegalActionException {
292        // The superclass will ensure that the next call to fire() generates
293        // a new random number. That random number will be first used
294        // in the first postfire() call.
295        super.initialize();
296        Director director = getDirector();
297        if (director == null) {
298            throw new IllegalActionException(this, "No director!");
299        }
300
301        double stopTimeValue = ((DoubleToken) stopTime.getToken())
302                .doubleValue();
303        _stopTime = new Time(getDirector(), stopTimeValue);
304
305        Time currentTime = director.getModelTime();
306
307        if (!_stopTime.isInfinite() && _stopTime.compareTo(currentTime) > 0) {
308            // NOTE: Do not throw an exception if the director ignores this
309            // stop time request or returns some other value of time.
310            // postfire() will return false on the next firing after time
311            // equals the stop time or exceeds it.
312            director.fireAt(this, _stopTime);
313            _executing = true;
314        }
315
316        _nextOutputIndex = 0;
317        _nextFiringTime = currentTime;
318        _outputProduced = false;
319
320        if (((BooleanToken) fireAtStart.getToken()).booleanValue()) {
321            _fireAt(currentTime);
322        } else {
323            // Have to explicitly generate the first random number
324            // becuase the superclass doesn't do it until the first
325            // call to fire().
326            _generateRandomNumber();
327            _nextFiringTime = director.getModelTime().add(_current);
328            _fireAt(_nextFiringTime);
329        }
330    }
331
332    /** Generate an exponential random number and schedule the next firing.
333     *  @exception IllegalActionException If the director throws it when
334     *   scheduling the next firing, or if the director does not
335     *   agree to fire the actor at the specified time.
336     */
337    @Override
338    public boolean postfire() throws IllegalActionException {
339        boolean result = super.postfire();
340        Time currentTime = getDirector().getModelTime();
341        if (_outputProduced) {
342            // An output was produced in this iteration.
343            _outputProduced = false;
344            _nextOutputIndex++;
345            if (_nextOutputIndex >= _length) {
346                _nextOutputIndex = 0;
347            }
348            // The following is not needed because the first call
349            // to fire() in the superclass generated a new random
350            // number.
351            // _generateRandomNumber();
352            _nextFiringTime = currentTime.add(_current);
353            _fireAt(_nextFiringTime);
354        } else if (currentTime.compareTo(_nextFiringTime) >= 0) {
355            // Output was not produced, but time matches, which
356            // means the microstep was too early. Request a refiring.
357            _fireAt(currentTime);
358        }
359
360        if (currentTime.compareTo(_stopTime) >= 0) {
361            return false;
362        }
363        return result;
364    }
365
366    /** If the current time matches the expected time for the next
367     *  output, then return true. Also return true if the
368     *  trigger input is connected and has events.
369     *  Otherwise, return false.
370     *  @exception IllegalActionException If there is no director.
371     */
372    @Override
373    public boolean prefire() throws IllegalActionException {
374        // Do not call super.prefire() because that returns false if
375        // there are no trigger inputs.
376        if (_debugging) {
377            _debug("Called prefire()");
378        }
379
380        // If any trigger input has a token, then return true.
381        // NOTE: It might seem that using trigger.numberOfSources() is
382        // correct here, but it is not. It is possible for channels
383        // to be connected, for example, to other output ports or
384        // even back to this same trigger port, in which case higher
385        // numbered channels will not have their inputs read.
386        for (int i = 0; i < trigger.getWidth(); i++) {
387            if (trigger.isKnown() && trigger.hasToken(i)) {
388                // No need to continue. fire() will
389                // consume the tokens (in the superclass).
390                return true;
391            }
392        }
393
394        // Also return true if there are no trigger inputs
395        // but it is time for the next output.
396        Time currentTime = getDirector().getModelTime();
397        if (currentTime.compareTo(_nextFiringTime) == 0) {
398            return true;
399        }
400
401        // Otherwise, return false.
402        return false;
403    }
404
405    /** Override the base class to reset a flag that indicates that the
406     *  model is executing. This method is invoked exactly once per execution
407     *  of an application.  None of the other action methods should be
408     *  be invoked after it.
409     *  @exception IllegalActionException Not thrown in this base class.
410     */
411    @Override
412    public void wrapup() throws IllegalActionException {
413        super.wrapup();
414        _executing = false;
415    }
416
417    ///////////////////////////////////////////////////////////////////
418    ////                         protected methods                 ////
419
420    /** Generate a new random number.
421     *  @exception IllegalActionException If parameter values are incorrect.
422     */
423    @Override
424    protected void _generateRandomNumber() throws IllegalActionException {
425        double meanTimeValue = ((DoubleToken) meanTime.getToken())
426                .doubleValue();
427        double test = _random.nextDouble();
428        _current = -Math.log(1 - test) * meanTimeValue;
429    }
430
431    ///////////////////////////////////////////////////////////////////
432    ////                         private methods                   ////
433
434    /* Get the specified value, checking the form of the values parameter.
435     */
436    private Token _getValue(int index) throws IllegalActionException {
437        ArrayToken val = (ArrayToken) values.getToken();
438
439        if (val == null || index >= _length) {
440            throw new IllegalActionException(this,
441                    "Index out of range of the values parameter.");
442        }
443
444        return val.getElement(index);
445    }
446
447    ///////////////////////////////////////////////////////////////////
448    ////                         private variables                 ////
449
450    // Most recently generated exponential random number.
451    private double _current;
452
453    // Flag indicating that the model is running.
454    private boolean _executing = false;
455
456    // The following are all transient to silence a javadoc bug
457    // about the @serialize tag.
458    // The transient qualifier should probably be removed if this
459    // class is made serializable.
460    // The length of the values parameter vector.
461    private transient int _length;
462
463    // The next firing time requested of the director.
464    private transient Time _nextFiringTime;
465
466    // The index of the next output.
467    private transient int _nextOutputIndex;
468
469    // Indicator that an output was produced in this iteration.
470    private boolean _outputProduced;
471
472    // stop time.
473    private Time _stopTime;
474}