001/* An actor that outputs the sum of the inputs so far.
002
003 Copyright (c) 2002-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.TypedIOPort;
031import ptolemy.data.BooleanToken;
032import ptolemy.data.ScalarToken;
033import ptolemy.data.Token;
034import ptolemy.data.expr.Parameter;
035import ptolemy.data.expr.SingletonParameter;
036import ptolemy.data.type.BaseType;
037import ptolemy.kernel.CompositeEntity;
038import ptolemy.kernel.util.IllegalActionException;
039import ptolemy.kernel.util.NameDuplicationException;
040import ptolemy.kernel.util.StringAttribute;
041import ptolemy.kernel.util.Workspace;
042
043///////////////////////////////////////////////////////////////////
044//// Accumulator
045
046/**
047 Output the initial value plus the sum of all the inputs since
048 the last time a true token was received at the reset port.
049 One output is produced each time the actor is fired. The
050 inputs and outputs can be any token type that supports addition.
051 The output type is constrained to be greater than or
052 equal to the input type and the type of the <i>init</i> parameter.
053 <p>
054 If the input and <i>init</i> data type are scalars, then you can
055 also set the <i>lowerBound</i> and <i>upperBound</i> parameters to
056 limit the range of the accumulated value.
057
058 @author Edward A. Lee
059 @version $Id$
060 @since Ptolemy II 2.0
061 @Pt.ProposedRating Green (eal)
062 @Pt.AcceptedRating Yellow (neuendor)
063 */
064public class Accumulator extends Transformer {
065    /** Construct an actor with the given container and name.
066     *  @param container The container.
067     *  @param name The name of this actor.
068     *  @exception IllegalActionException If the actor cannot be contained
069     *   by the proposed container.
070     *  @exception NameDuplicationException If the container already has an
071     *   actor with this name.
072     */
073    public Accumulator(CompositeEntity container, String name)
074            throws NameDuplicationException, IllegalActionException {
075        super(container, name);
076
077        input.setMultiport(true);
078
079        reset = new TypedIOPort(this, "reset", true, false);
080        reset.setTypeEquals(BaseType.BOOLEAN);
081        reset.setMultiport(true);
082        new StringAttribute(reset, "_cardinal").setExpression("SOUTH");
083        new SingletonParameter(reset, "_showName").setToken(BooleanToken.TRUE);
084
085        init = new Parameter(this, "init");
086        init.setExpression("0");
087
088        lowerBound = new Parameter(this, "lowerBound");
089        lowerBound.setTypeSameAs(init);
090
091        upperBound = new Parameter(this, "upperBound");
092        upperBound.setTypeSameAs(init);
093
094        // set the type constraints.
095        output.setTypeAtLeast(init);
096        output.setTypeAtLeast(input);
097    }
098
099    ///////////////////////////////////////////////////////////////////
100    ////                     ports and parameters                  ////
101
102    /** The lower bound. If this is set, then its type must be the
103     *  same as that of the <i>init</i> parameter, and the output
104     *  will be constrained to never drop below the lower bound.
105     *  By default, this is not set, so there is no lower bound.
106     */
107    public Parameter lowerBound;
108
109    /** The value produced by the actor on its first iteration.
110     *  The default value of this parameter is the integer 0.
111     */
112    public Parameter init;
113
114    /** If this port receives a True token on any channel, then the
115     *  accumulator state will be reset to the initial value.
116     *  This is a multiport and has type boolean.
117     */
118    public TypedIOPort reset;
119
120    /** The upper bound. If this is set, then its type must be the
121     *  same as that of the <i>init</i> parameter, and the output
122     *  will be constrained to never rise above the upper bound.
123     *  By default, this is not set, so there is no upper bound.
124     */
125    public Parameter upperBound;
126
127    ///////////////////////////////////////////////////////////////////
128    ////                         public methods                    ////
129
130    /** Clone the actor into the specified workspace. This calls the
131     *  base class and then sets up the type constraints.
132     *  @param workspace The workspace for the new object.
133     *  @return A new actor.
134     *  @exception CloneNotSupportedException If a derived class contains
135     *   an attribute that cannot be cloned.
136     */
137    @Override
138    public Object clone(Workspace workspace) throws CloneNotSupportedException {
139        Accumulator newObject = (Accumulator) super.clone(workspace);
140
141        // set the type constraints.
142        newObject.lowerBound.setTypeSameAs(newObject.init);
143        newObject.upperBound.setTypeSameAs(newObject.init);
144        newObject.output.setTypeAtLeast(newObject.init);
145        newObject.output.setTypeAtLeast(newObject.input);
146
147        return newObject;
148    }
149
150    /** Consume at most one token from each channel of the <i>input</i>
151     *  port, add it to the running sum, and produce the result at the
152     *  <i>output</i> port.  If there is no input token available,
153     *  the current value of the running sum is produced at the output.
154     *  If there is a true-valued token on the <i>reset</i> input,
155     *  then the running sum is reset to the initial value before
156     *  adding the input.
157     *  @exception IllegalActionException If addition is not
158     *   supported by the supplied tokens.
159     */
160    @Override
161    public void fire() throws IllegalActionException {
162        super.fire();
163        _latestSum = _sum;
164
165        // Check whether to reset.
166        for (int i = 0; i < reset.getWidth(); i++) {
167            if (reset.hasToken(i)) {
168                BooleanToken r = (BooleanToken) reset.get(i);
169
170                if (r.booleanValue()) {
171                    // Being reset at this firing.
172                    _latestSum = output.getType().convert(init.getToken());
173                }
174            }
175        }
176
177        for (int i = 0; i < input.getWidth(); i++) {
178            if (input.hasToken(i)) {
179                Token in = input.get(i);
180                _latestSum = _latestSum.add(in);
181            }
182        }
183        // Check the bounds.
184        Token lowerBoundValue = lowerBound.getToken();
185        if (lowerBoundValue != null) {
186            if (lowerBoundValue instanceof ScalarToken) {
187                if (((ScalarToken) lowerBoundValue)
188                        .isGreaterThan((ScalarToken) _latestSum)
189                        .booleanValue()) {
190                    _latestSum = lowerBoundValue;
191                }
192            } else {
193                throw new IllegalActionException(this,
194                        "lowerBound parameter only works with scalar values. Value given was: "
195                                + lowerBoundValue);
196            }
197        }
198        Token upperBoundValue = upperBound.getToken();
199        if (upperBoundValue != null) {
200            if (upperBoundValue instanceof ScalarToken) {
201                if (((ScalarToken) upperBoundValue)
202                        .isLessThan((ScalarToken) _latestSum).booleanValue()) {
203                    _latestSum = upperBoundValue;
204                }
205            } else {
206                throw new IllegalActionException(this,
207                        "upperBound parameter only works with scalar values. Value given was: "
208                                + upperBoundValue);
209            }
210        }
211
212        output.broadcast(_latestSum);
213    }
214
215    /** Reset the running sum to equal the value of <i>init</i>.
216     *  @exception IllegalActionException If the parent class throws it.
217     */
218    @Override
219    public void initialize() throws IllegalActionException {
220        super.initialize();
221        _latestSum = _sum = output.getType().convert(init.getToken());
222    }
223
224    /** Record the most recent input as part of the running average.
225     *  Do nothing if there is no input.
226     *  @exception IllegalActionException If the base class throws it.
227     */
228    @Override
229    public boolean postfire() throws IllegalActionException {
230        _sum = _latestSum;
231        return super.postfire();
232    }
233
234    ///////////////////////////////////////////////////////////////////
235    ////                         private members                   ////
236
237    /** The running sum. */
238    private Token _sum;
239
240    /** The latest sum, prior to a state commit. */
241    private Token _latestSum;
242}