001/* An actor that outputs the discrete integration over successive inputs.
002
003 Copyright (c) 2009-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.Token;
035import ptolemy.data.expr.Parameter;
036import ptolemy.kernel.CompositeEntity;
037import ptolemy.kernel.util.IllegalActionException;
038import ptolemy.kernel.util.NameDuplicationException;
039import ptolemy.kernel.util.Workspace;
040
041///////////////////////////////////////////////////////////////////
042//// Integrator
043
044/** Output the discrete integral of the input. Inputs are multiplied by the time
045 *  gap from the previous input and accumulated. Output is not generated
046 *   until two inputs have been consumed.
047 *  <p>
048 *  The output type of this actor is forced to be double.
049 *  <p>
050 *  In postfire(), if an event is present on the <i>reset</i> port, this
051 *  actor resets to its initial state, and will not output until two
052 *  subsequent inputs have been consumed.  This is useful if the input signal is
053 *  switched on and off, in which case the time gap between events becomes large
054 *  and would otherwise effect the value of the integral.
055 *  <p>
056 *  The integrator performs linear interpolation between input events,
057 *  where the output of the integrator follows the equation
058 *  y[n] = y[n-1] + (x[n-1] + x[n])*dt/2 where <i>dt</i> is the time
059 *  differential between events. This equates to the trapezoidal method of
060 *  approximating a Riemann integral.
061
062 @author Jeff C. Jensen
063 @version $Id$
064 @since Ptolemy II 8.0
065 @see ptolemy.actor.lib.Accumulator
066 */
067public class Integrator extends DETransformer {
068    /** Construct an actor with the given container and name.
069     *  @param container The container.
070     *  @param name The name of this actor.
071     *  @exception IllegalActionException If the actor cannot be contained
072     *   by the proposed container.
073     *  @exception NameDuplicationException If the container already has an
074     *   actor with this name.
075     */
076    public Integrator(CompositeEntity container, String name)
077            throws NameDuplicationException, IllegalActionException {
078        super(container, name);
079        reset = new TypedIOPort(this, "reset", true, false);
080        reset.setMultiport(true);
081        output.setTypeAtLeast(input);
082        output.setWidthEquals(input, false);
083        initialValue = new Parameter(this, "initialValue");
084        initialValue.setExpression("0.0");
085    }
086
087    ///////////////////////////////////////////////////////////////////
088    ////                         public methods                    ////
089
090    /** Clone the actor into the specified workspace. This calls the
091     *  base class and then sets the ports.
092     *  @param workspace The workspace for the new object.
093     *  @return A new actor.
094     *  @exception CloneNotSupportedException If a derived class has
095     *   has an attribute that cannot be cloned.
096     */
097    @Override
098    public Object clone(Workspace workspace) throws CloneNotSupportedException {
099        Integrator newObject = (Integrator) super.clone(workspace);
100
101        newObject.output.setTypeAtLeast(newObject.input);
102        newObject.output.setWidthEquals(newObject.input, false);
103
104        // This is not strictly needed (since it is always recreated
105        // in preinitialize) but it is safer.
106        newObject._lastInput = null;
107        newObject._currentInput = null;
108        newObject._accumulated = null;
109
110        return newObject;
111    }
112
113    /** Consume at most one token from the <i>input</i> port and output
114     *  the average of it and the previous input (linear interpolation),
115     *  multiplied by the time gap between the two events.
116     *  If there has been no previous iteration, no output is sent unless
117     *  an initial token has been set.
118     *  If there is no input, then produce no output.
119     *  @exception IllegalActionException If subtraction or division is not
120     *   supported by the supplied tokens.
121     */
122    @Override
123    public void fire() throws IllegalActionException {
124        super.fire();
125        if (input.hasToken(0)) {
126            Time currentTime = getDirector().getModelTime();
127            Token currentToken = input.get(0);
128            _currentInput = new TimedEvent(currentTime, currentToken);
129
130            if (_lastInput != null) {
131                Token lastToken = (Token) _lastInput.contents;
132                Time lastTime = _lastInput.timeStamp;
133                Token timeGap = new DoubleToken(
134                        currentTime.subtract(lastTime).getDoubleValue());
135                Token integrand = new DoubleToken(0.0);
136
137                //Calculate the interpolated value, multiply by dt
138                integrand = currentToken.add(lastToken).multiply(timeGap)
139                        .divide(new DoubleToken(2));
140
141                //Accumulate the integrand
142                if (_accumulated != null) {
143                    _accumulated = _accumulated.add(integrand);
144                } else {
145                    _accumulated = integrand;
146                }
147            }
148        }
149
150        //If we have accumulated a value, output it here; otherwise,
151        //   we did not have an initial value and have not yet received
152        //   two inputs.
153        if (_accumulated != null) {
154            output.broadcast(_accumulated);
155        }
156    }
157
158    /** Reset to indicate that no input has yet been seen.
159     *  @exception IllegalActionException If the parent class throws it.
160     */
161    @Override
162    public void initialize() throws IllegalActionException {
163        super.initialize();
164        _lastInput = null;
165
166        resetAccumulation();
167    }
168
169    /** Record the most recent input as the latest input. If a reset
170     *  event has been received, process it here.
171     *  @exception IllegalActionException If the base class throws it.
172     */
173    @Override
174    public boolean postfire() throws IllegalActionException {
175        //If reset port is connected and has a token, reset state.
176        if (reset.getWidth() > 0) {
177            if (reset.hasToken(0)) {
178                //Consume reset token
179                reset.get(0);
180
181                //Reset the current input
182                _currentInput = null;
183
184                //Reset accumulation
185                resetAccumulation();
186            }
187        }
188        _lastInput = _currentInput;
189        return super.postfire();
190    }
191
192    ///////////////////////////////////////////////////////////////////
193    ////                         protected methods                 ////
194
195    /** Reset value of the accumulator to either an initial value or null.
196     * @exception IllegalActionException If the base class throws it
197     */
198    protected void resetAccumulation() throws IllegalActionException {
199        Token initialToken = initialValue.getToken();
200
201        if (initialToken != null) {
202            _accumulated = initialToken;
203        } else {
204            _accumulated = null;
205        }
206    }
207
208    ///////////////////////////////////////////////////////////////////
209    ////                     ports and parameters                  ////
210
211    /** The reset port, which has undeclared type. If this port
212     *  receives a token, this actor resets to its initial state,
213     *  and no output is generated until two inputs have been received.
214     */
215    public TypedIOPort reset;
216
217    /** The value produced by the actor on its first iteration.
218     *  The default value of this parameter is the double 0.0.
219     */
220    public Parameter initialValue;
221
222    ///////////////////////////////////////////////////////////////////
223    ////                         private members                   ////
224    private TimedEvent _currentInput;
225
226    private TimedEvent _lastInput;
227
228    private Token _accumulated;
229}