001/* An actor that outputs the discrete derivative between successive inputs.
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.de.lib;
029
030import ptolemy.actor.TypedIOPort;
031import ptolemy.actor.util.Time;
032import ptolemy.actor.util.TimedEvent;
033import ptolemy.data.DoubleToken;
034import ptolemy.data.type.BaseType;
035import ptolemy.kernel.CompositeEntity;
036import ptolemy.kernel.util.IllegalActionException;
037import ptolemy.kernel.util.NameDuplicationException;
038import ptolemy.kernel.util.Workspace;
039
040///////////////////////////////////////////////////////////////////
041//// Derivative
042
043/**
044 Output the discrete derivative of the input, y[n] = (x[n] - x[n-1])/dt,
045 where <i>dt</i> is the time gap between input events. Output is not generated
046 until two inputs have been consumed.
047 <p>
048 The output of this actor is constrained to be a double, and input must be castable
049 to a double. If the input signal is not left-continuous, the derivative will be either
050 infinite or undefined and an exception is thrown.
051 <p>
052 In postfire(), if an event is present on the <i>reset</i> port, this
053 actor resets to its initial state, and will not output until two
054 subsequent inputs have been consumed.  This is useful if the input signal is
055 switched on and off, in which case the time gap between events becomes large
056 and would otherwise effect the value of the derivative for one sample.
057 <p>
058 @author Jeff C. Jensen
059 @version $Id: Derivative.java$
060 @since Ptolemy II 8.0
061 @see ptolemy.actor.lib.Differential
062 */
063public class Derivative extends DETransformer {
064    /** Construct an actor with the given container and name.
065     *  @param container The container.
066     *  @param name The name of this actor.
067     *  @exception IllegalActionException If the actor cannot be contained
068     *   by the proposed container.
069     *  @exception NameDuplicationException If the container already has an
070     *   actor with this name.
071     */
072    public Derivative(CompositeEntity container, String name)
073            throws NameDuplicationException, IllegalActionException {
074        super(container, name);
075        reset = new TypedIOPort(this, "reset", true, false);
076        reset.setMultiport(true);
077        input.setTypeAtMost(BaseType.DOUBLE);
078        output.setTypeEquals(BaseType.DOUBLE);
079    }
080
081    ///////////////////////////////////////////////////////////////////
082    ////                         public methods                    ////
083
084    /** Clone the actor into the specified workspace. This calls the
085     *  base class and then sets the ports.
086     *  @param workspace The workspace for the new object.
087     *  @return A new actor.
088     *  @exception CloneNotSupportedException If a derived class has
089     *   has an attribute that cannot be cloned.
090     */
091    @Override
092    public Object clone(Workspace workspace) throws CloneNotSupportedException {
093        Derivative newObject = (Derivative) super.clone(workspace);
094
095        newObject.input.setTypeAtMost(BaseType.DOUBLE);
096        newObject.output.setTypeEquals(BaseType.DOUBLE);
097
098        // This is not strictly needed (since it is always recreated
099        // in preinitialize) but it is safer.
100        newObject._lastInput = null;
101
102        return newObject;
103    }
104
105    /** Consume at most one token from the <i>input</i> port and output
106     *  its value minus the value of the input read in the previous
107     *  iteration, divided by the time gap between the two events.
108     *  If there has been no previous iteration, no output is sent.
109     *  If there is no input, then produce no output.
110     *  @exception IllegalActionException If subtraction or division is not
111     *   supported by the supplied tokens.
112     */
113    @Override
114    public void fire() throws IllegalActionException {
115        super.fire();
116        if (input.hasToken(0)) {
117            Time currentTime = getDirector().getModelTime();
118            DoubleToken currentToken = (DoubleToken) input.get(0);
119            _currentInput = new TimedEvent(currentTime, currentToken);
120
121            if (_lastInput != null) {
122                Time lastTime = _lastInput.timeStamp;
123                DoubleToken lastToken = (DoubleToken) _lastInput.contents;
124                DoubleToken timeGap = new DoubleToken(
125                        currentTime.subtract(lastTime).getDoubleValue());
126
127                //If the timeGap is zero, then we have received a simultaneous event. If the
128                // value of the input has not changed, then we can ignore this input, as a control
129                // signal was already generated. However if the value has changed, then the signal
130                // is discontinuous and an exception will be thrown.
131                if (timeGap.doubleValue() == 0
132                        && !currentToken.equals(lastToken)) {
133                    throw new IllegalActionException(
134                            "Derivative received discontinuous input.");
135                }
136
137                output.broadcast(
138                        currentToken.subtract(lastToken).divide(timeGap));
139            }
140        }
141    }
142
143    /** Reset to indicate that no input has yet been seen.
144     *  @exception IllegalActionException If the parent class throws it.
145     */
146    @Override
147    public void initialize() throws IllegalActionException {
148        super.initialize();
149        _lastInput = null;
150        _currentInput = null;
151    }
152
153    /** Record the most recent input as the latest input. If a reset
154     *  event has been received, process it here.
155     *  @exception IllegalActionException If the base class throws it.
156     */
157    @Override
158    public boolean postfire() throws IllegalActionException {
159        //If reset port is connected and has a token, reset state.
160        if (reset.getWidth() > 0) {
161            if (reset.hasToken(0)) {
162                //Consume reset token
163                reset.get(0);
164
165                //Reset the current input
166                _currentInput = null;
167            }
168        }
169        _lastInput = _currentInput;
170        return super.postfire();
171    }
172
173    ///////////////////////////////////////////////////////////////////
174    ////                     ports and parameters                  ////
175
176    /** The reset port, which has undeclared type. If this port
177     *  receives a token, this actor resets to its initial state,
178     *  and no output is generated until two inputs have been received.
179     */
180    public TypedIOPort reset;
181
182    ///////////////////////////////////////////////////////////////////
183    ////                         private members                   ////
184    private TimedEvent _currentInput;
185
186    private TimedEvent _lastInput;
187}