001/* An actor that detects level crossings of its trigger input 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.TypedAtomicActor;
031import ptolemy.actor.TypedIOPort;
032import ptolemy.actor.continuous.ContinuousStepSizeController;
033import ptolemy.data.DoubleToken;
034import ptolemy.data.expr.Parameter;
035import ptolemy.data.expr.StringParameter;
036import ptolemy.data.type.BaseType;
037import ptolemy.domains.continuous.kernel.ContinuousDirector;
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//// LevelCrossingDetector
046
047/**
048 An event detector that converts continuous signals to discrete events when
049 the input <i>trigger</i> signal crosses a threshold specified by the <i>level</i>
050 parameter. The <i>direction</i> parameter
051 can constrain the actor to detect only rising or falling transitions.
052 It has three possible values, "rising", "falling", and "both", where
053 "both" is the default. This actor will produce an output whether the
054 input is continuous or not. That is, if a discontinuity crosses the
055 threshold in the right direction, it produces an output at the time
056 of the discontinuity.  If the input is continuous,
057 then the output is generated when the input is
058 within <i>errorTolerance</i> of the level.
059 The value of the output is given by the <i>value</i> parameter,
060 which by default has the value of the <i>level</i> parameter.
061  <p>
062 This actor has a one microstep delay before it will produce an
063 output. That is, when a level crossing is detected, the actor
064 requests a refiring in the next microstep at the current time,
065 and only in that refiring produces the output.
066 This ensures that the output satisfies the piecewise
067 continuity constraint. It is always absent at microstep 0.
068<p>
069 This actor will not produce an event at the time of the first firing
070 unless there is a level crossing discontinuity at that time.
071
072 @author Edward A. Lee, Haiyang Zheng
073 @version $Id$
074 @since Ptolemy II 6.0
075 @Pt.ProposedRating Yellow (hyzheng)
076 @Pt.AcceptedRating Red (hyzheng)
077 */
078public class LevelCrossingDetector extends TypedAtomicActor
079        implements ContinuousStepSizeController {
080    /** Construct an actor in the specified container with the specified
081     *  name.  The name must be unique within the container or an exception
082     *  is thrown. The container argument must not be null, or a
083     *  NullPointerException will be thrown.
084     *
085     *  @param container The subsystem that this actor is lived in
086     *  @param name The actor's name
087     *  @exception IllegalActionException If the entity cannot be contained
088     *   by the proposed container.
089     *  @exception NameDuplicationException If name coincides with
090     *   an entity already in the container.
091     */
092    public LevelCrossingDetector(CompositeEntity container, String name)
093            throws IllegalActionException, NameDuplicationException {
094        super(container, name);
095
096        output = new TypedIOPort(this, "output", false, true);
097
098        trigger = new TypedIOPort(this, "trigger", true, false);
099        trigger.setMultiport(false);
100        trigger.setTypeEquals(BaseType.DOUBLE);
101
102        level = new Parameter(this, "level", new DoubleToken(0.0));
103        level.setTypeEquals(BaseType.DOUBLE);
104
105        value = new Parameter(this, "value");
106        value.setExpression("level");
107
108        // By default, this director detects both directions of level crossings.
109        direction = new StringParameter(this, "direction");
110        direction.setExpression("both");
111        _detectRisingCrossing = true;
112        _detectFallingCrossing = true;
113
114        direction.addChoice("both");
115        direction.addChoice("falling");
116        direction.addChoice("rising");
117
118        output.setTypeAtLeast(value);
119
120        _errorTolerance = 1e-4;
121        errorTolerance = new Parameter(this, "errorTolerance",
122                new DoubleToken(_errorTolerance));
123        errorTolerance.setTypeEquals(BaseType.DOUBLE);
124    }
125
126    ///////////////////////////////////////////////////////////////////
127    ////                         public variables                  ////
128
129    /** A parameter that can be used to limit the detected level crossings
130     *  to rising or falling. There are three choices: "falling", "rising", and
131     *  "both". The default value is "both".
132     */
133    public StringParameter direction;
134
135    /** The error tolerance specifying how close the value of a continuous
136     *  input needs to be to the specified level to produce the output event.
137     *  Note that this indirectly affects the accuracy of the time of the
138     *  output since the output can be produced at any time after the
139     *  level crossing occurs while it is still within the specified
140     *  error tolerance of the level. This is a double with default 1e-4.
141     */
142    public Parameter errorTolerance;
143
144    /** The parameter that specifies the level threshold. By default, it
145     *  contains a double with value 0.0. Note, a change of this
146     *  parameter at run time will not be applied until the next
147     *  iteration.
148     */
149    public Parameter level;
150
151    /** The output value to produce when a level-crossing is detected.
152     *  This can be any data type. It defaults to the same value
153     *  as the <i>level</i> parameter.
154     */
155    public Parameter value;
156
157    /** The output port. The type is at least the type of the
158     *  <i>value</i> parameter.
159     */
160    public TypedIOPort output;
161
162    /** The trigger port. This is an input port with type double.
163     */
164    public TypedIOPort trigger;
165
166    ///////////////////////////////////////////////////////////////////
167    ////                         public methods                    ////
168
169    /** Update the attribute if it has been changed. If the attribute
170     *  is <i>errorTolerance</i> or <i>level</i>, then update the local cache.
171     *  @param attribute The attribute that has changed.
172     *  @exception IllegalActionException If the attribute change failed.
173     */
174    @Override
175    public void attributeChanged(Attribute attribute)
176            throws IllegalActionException {
177        if (attribute == errorTolerance) {
178            double tolerance = ((DoubleToken) errorTolerance.getToken())
179                    .doubleValue();
180
181            if (tolerance <= 0.0) {
182                throw new IllegalActionException(this,
183                        "Error tolerance must be greater than 0.");
184            }
185
186            _errorTolerance = tolerance;
187        } else if (attribute == direction) {
188            String crossingDirections = direction.stringValue();
189
190            if (crossingDirections.equalsIgnoreCase("falling")) {
191                _detectFallingCrossing = true;
192                _detectRisingCrossing = false;
193            } else if (crossingDirections.equalsIgnoreCase("rising")) {
194                _detectFallingCrossing = false;
195                _detectRisingCrossing = true;
196            } else if (crossingDirections.equalsIgnoreCase("both")) {
197                _detectFallingCrossing = true;
198                _detectRisingCrossing = true;
199            } else {
200                throw new IllegalActionException(
201                        "Unknown direction: " + crossingDirections);
202            }
203        } else if (attribute == level) {
204            _level = ((DoubleToken) level.getToken()).doubleValue();
205        } else {
206            super.attributeChanged(attribute);
207        }
208    }
209
210    /** Clone the actor into the specified workspace.
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        LevelCrossingDetector newObject = (LevelCrossingDetector) super.clone(
219                workspace);
220
221        // Set the type constraints.
222        newObject.output.setTypeAtLeast(newObject.value);
223        return newObject;
224    }
225
226    /** Declare that the output does not depend on the input in a firing.
227     *  @exception IllegalActionException If the causality interface
228     *  cannot be computed.
229     *  @see #getCausalityInterface()
230     */
231    @Override
232    public void declareDelayDependency() throws IllegalActionException {
233        _declareDelayDependency(trigger, output, 0.0);
234    }
235
236    /** Detect whether the current input compared to the input
237     *  on the last iteration indicates that a level crossing in the
238     *  appropriate direction has occurred, if the time is within
239     *  <i>errorTolerance</i> of the time at which the crossing occurs.
240     *  If there is such a level crossing, then postfire will request
241     *  a refiring at the current time, and the next invocation of fire()
242     *  will produce the output event.
243     *  @exception IllegalActionException If it cannot get a token from the trigger
244     *   port or cannot send a token through the output port.
245     */
246    @Override
247    public void fire() throws IllegalActionException {
248        ContinuousDirector dir = (ContinuousDirector) getDirector();
249        double currentStepSize = dir.getCurrentStepSize();
250        int microstep = dir.getIndex();
251        _postponedOutputProduced = false;
252
253        if (_debugging) {
254            _debug("Called fire() at time " + dir.getModelTime()
255                    + " with microstep " + microstep + " and step size "
256                    + currentStepSize);
257        }
258
259        // If there is a postponed output, then produce it.
260        // Need to use <= rather than == here because a modal
261        // model may have been suspended when the microstep matched.
262        if (_postponed > 0 && _postponed <= microstep) {
263            if (_debugging) {
264                _debug("-- Produce postponed output.");
265            }
266            output.send(0, value.getToken());
267            _postponedOutputProduced = true;
268        } else {
269            // There is no postponed output, so send clear.
270            if (_debugging) {
271                _debug("-- Output is absent.");
272            }
273            output.sendClear(0);
274        }
275
276        // If the trigger input is available, record it.
277        if (trigger.getWidth() > 0 && trigger.isKnown(0)
278                && trigger.hasToken(0)) {
279            _thisTrigger = ((DoubleToken) trigger.get(0)).doubleValue();
280            if (_debugging) {
281                _debug("-- Consumed a trigger input: " + _thisTrigger);
282                _debug("-- Last trigger is: " + _lastTrigger);
283            }
284
285            // If first firing, do not look for a level crossing.
286            if (_lastTrigger == Double.NEGATIVE_INFINITY) {
287                return;
288            }
289
290            boolean inputIsIncreasing = _thisTrigger > _lastTrigger;
291            boolean inputIsDecreasing = _thisTrigger < _lastTrigger;
292
293            // If a crossing has occurred, and either the current step
294            // size is zero or the current input is within error tolerance
295            // of the level, then request a refiring.
296            // Check whether _lastTrigger and _thisTrigger are on opposite sides
297            // of the level.
298            // NOTE: The code below should not set _eventMissed = false because
299            // an event may be missed during any stage of speculative execution.
300            // This should be set to false only in postfire.
301            if ((_lastTrigger - _level) * (_thisTrigger - _level) < 0.0
302                    || _thisTrigger == _level) {
303                // Crossing has occurred. Check whether the direction is right.
304                // Note that we do not produce an output is the input is neither
305                // increasing nor decreasing. Presumably, we already produced
306                // an output in that case.
307                if (_detectFallingCrossing && inputIsDecreasing
308                        || _detectRisingCrossing && inputIsIncreasing) {
309                    // If the step size is not 0.0, and the
310                    // current input is not close enough, then we
311                    // have missed an event and the step size will need
312                    // to be adjusted.
313                    if (currentStepSize != 0.0 && Math
314                            .abs(_thisTrigger - _level) >= _errorTolerance) {
315                        // Step size is nonzero and the current input is not
316                        // close enough. We have missed an event.
317                        if (_debugging) {
318                            _debug("-- Missed an event. Step size will be adjusted.");
319                        }
320                        _eventMissed = true;
321                    } else {
322                        // Request a refiring.
323                        _postponed = microstep + 1;
324                    }
325                }
326            }
327        }
328    }
329
330    /** Initialize the execution.
331     *  @exception IllegalActionException If thrown by the super class.
332     */
333    @Override
334    public void initialize() throws IllegalActionException {
335        super.initialize();
336        _eventMissed = false;
337        _level = ((DoubleToken) level.getToken()).doubleValue();
338        _lastTrigger = Double.NEGATIVE_INFINITY;
339        _thisTrigger = _lastTrigger;
340        _postponed = 0;
341        _postponedOutputProduced = false;
342    }
343
344    /** Return false if with the current step size we miss a level crossing.
345     *  @return False if the step size needs to be refined.
346     */
347    @Override
348    public boolean isStepSizeAccurate() {
349        if (_debugging) {
350            _debug("Step size is accurate: " + !_eventMissed);
351        }
352        return !_eventMissed;
353    }
354
355    /** Return false. This actor can produce some outputs even the
356     *  inputs are unknown. This actor is usable for breaking feedback
357     *  loops.
358     *  @return False.
359     */
360    @Override
361    public boolean isStrict() {
362        return false;
363    }
364
365    /** Prepare for the next iteration, by making the current trigger
366     *  token to be the history trigger token.
367     *  @return True always.
368     *  @exception IllegalActionException If thrown by the super class.
369     */
370    @Override
371    public boolean postfire() throws IllegalActionException {
372        if (_debugging) {
373            _debug("Called postfire().");
374        }
375
376        _lastTrigger = _thisTrigger;
377        _eventMissed = false;
378        if (_postponed > 0) {
379            if (_debugging) {
380                _debug("Requesting refiring at the current time.");
381            }
382            getDirector().fireAtCurrentTime(this);
383        }
384        if (_postponedOutputProduced) {
385            _postponedOutputProduced = false;
386            // There might be yet another postponed output requested.
387            // If the current microstep matches _postponed, then there
388            // there is not, and we can reset _postponed.
389            ContinuousDirector dir = (ContinuousDirector) getDirector();
390            int microstep = dir.getIndex();
391            if (microstep >= _postponed) {
392                _postponed = 0;
393            }
394        }
395        return super.postfire();
396    }
397
398    /** Make sure the actor runs with a ContinuousDirector.
399     *  @exception IllegalActionException If the director is not
400     *  a ContinuousDirector or the parent class throws it.
401     */
402    @Override
403    public void preinitialize() throws IllegalActionException {
404        if (!(getDirector() instanceof ContinuousDirector)) {
405            throw new IllegalActionException("LevelCrossingDetector can only"
406                    + " be used inside Continuous domain.");
407        }
408        super.preinitialize();
409    }
410
411    /** Return the refined step size if there is a missed event,
412     *  otherwise return the current step size.
413     *  @return The refined step size.
414     */
415    @Override
416    public double refinedStepSize() {
417        ContinuousDirector dir = (ContinuousDirector) getDirector();
418        double refinedStep = dir.getCurrentStepSize();
419
420        if (_eventMissed) {
421            // The refined step size is a linear interpolation.
422            // NOTE: we always to get a little overshoot to make sure the
423            // level crossing happens. The little overshoot chosen here
424            // is half of the error tolerance.
425            refinedStep = (Math.abs(_lastTrigger - _level)
426                    + _errorTolerance / 2) * dir.getCurrentStepSize()
427                    / Math.abs(_thisTrigger - _lastTrigger);
428
429            if (_debugging) {
430                _debug(getFullName() + "-- Event Missed: refine step to "
431                        + refinedStep);
432            }
433            // Reset this because the iteration will be repeated with a new step size.
434            // The new iteration may not miss the event.
435            _eventMissed = false;
436        }
437        return refinedStep;
438    }
439
440    /** Return the maximum Double value. This actor does not suggest
441     *  or constrain the step size for the next iteration.
442     *  @return java.Double.MAX_VALUE.
443     */
444    @Override
445    public double suggestedStepSize() {
446        return java.lang.Double.MAX_VALUE;
447    }
448
449    ///////////////////////////////////////////////////////////////////
450    ////                         protected variables               ////
451
452    /** The level threshold this actor detects. */
453    protected double _level;
454
455    ///////////////////////////////////////////////////////////////////
456    ////                         private variables                 ////
457
458    // Flag indicating whether this actor detects the level crossing
459    // when the input value is rising.
460    private boolean _detectRisingCrossing;
461
462    // Flag indicating whether this actor detects the level crossing
463    // when the input value is falling.
464    private boolean _detectFallingCrossing;
465
466    // Cache of the value of errorTolerance.
467    private double _errorTolerance;
468
469    // Flag indicating a missed event.
470    private boolean _eventMissed = false;
471
472    // Last trigger input.
473    private double _lastTrigger;
474
475    // Indicator that the output is postponed to the specified microstep.
476    private int _postponed;
477
478    // Indicator that the postponed output was produced.
479    private boolean _postponedOutputProduced;
480
481    // This trigger input.
482    private double _thisTrigger;
483}