001/* An actor that computes a specified rounded value of the input.
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.conversions;
029
030import ptolemy.actor.lib.Transformer;
031import ptolemy.data.DoubleToken;
032import ptolemy.data.IntToken;
033import ptolemy.data.Token;
034import ptolemy.data.type.BaseType;
035import ptolemy.kernel.CompositeEntity;
036import ptolemy.kernel.util.Attribute;
037import ptolemy.kernel.util.IllegalActionException;
038import ptolemy.kernel.util.InvalidStateException;
039import ptolemy.kernel.util.NameDuplicationException;
040import ptolemy.kernel.util.StringAttribute;
041import ptolemy.kernel.util.Workspace;
042
043// NOTE: If you update the list of functions, then you will want
044// to update the list in actor/lib/math.xml.
045///////////////////////////////////////////////////////////////////
046//// Round
047
048/**
049 Produce an output token on each firing with a value that is
050 equal to the specified rounded value of the input.
051 The input type is DoubleToken. The output type is IntToken.
052 The functions are a subset of those in the java.lang.Math class.
053 They are:
054 <ul>
055 <li> <b>ceil</b>: Round towards positive infinity.
056 <li> <b>floor</b>: Round towards negative infinity.
057 <li> <b>round</b>: Round towards nearest integer.  This is the
058 default behavior.
059 <li> <b>truncate</b>: Round towards zero.
060 </ul>
061
062 If the input is NaN, then an exception is thrown.
063 The reason for this is that there is no way to represent a NaN
064 as an integer.  Thus, even though java.lang.Math.round(Double.NaN)
065 returns 0, ceil(Double.NaN), floor(Double.NaN) and truncate(DoubleNaN) all
066 return a Double.NaN.  However, this actor has an integer output,
067 so there is no way to represent the Double.NaN as an integer, so
068 we throw an exception.
069
070
071 @author C. Fong, Contributor: Christopher Brooks
072 @version $Id$
073 @since Ptolemy II 1.0
074 @Pt.ProposedRating Green (chf)
075 @Pt.AcceptedRating Green (janneck)
076 */
077public class Round extends Transformer {
078    /** Construct an actor with the given container and name.
079     *  @param container The container.
080     *  @param name The name of this actor.
081     *  @exception NameDuplicationException If the container already has an
082     *   actor with this name.
083     *  @exception IllegalActionException If the actor cannot be contained
084     *   by the proposed container.
085     */
086    public Round(CompositeEntity container, String name)
087            throws NameDuplicationException, IllegalActionException {
088        super(container, name);
089
090        // Parameters
091        function = new StringAttribute(this, "function");
092        function.setExpression("round");
093        _function = _ROUND;
094
095        // Ports
096        input.setTypeEquals(BaseType.DOUBLE);
097        output.setTypeEquals(BaseType.INT);
098
099        _attachText("_iconDescription",
100                "<svg>\n" + "<circle cx=\"0\" cy=\"0\" r=\"17\""
101                        + "style=\"fill:white\"/>\n" + "</svg>\n");
102    }
103
104    ///////////////////////////////////////////////////////////////////
105    ////                     ports and parameters                  ////
106
107    /** The rounding strategy to use.  This is a string-valued parameter
108     *  that defaults to "round".
109     */
110    public StringAttribute function;
111
112    ///////////////////////////////////////////////////////////////////
113    ////                         public methods                    ////
114
115    /** Override the base class to determine which function is being
116     *  specified.
117     *  @param attribute The attribute that changed.
118     *  @exception IllegalActionException If the function is not recognized.
119     */
120    @Override
121    public void attributeChanged(Attribute attribute)
122            throws IllegalActionException {
123        if (attribute == function) {
124            String functionName = function.getExpression();
125
126            if (functionName.equals("ceil")) {
127                _function = _CEIL;
128            } else if (functionName.equals("floor")) {
129                _function = _FLOOR;
130            } else if (functionName.equals("round")) {
131                _function = _ROUND;
132            } else if (functionName.equals("truncate")) {
133                _function = _TRUNCATE;
134            } else {
135                throw new IllegalActionException(this,
136                        "Unrecognized rounding function: " + functionName);
137            }
138        } else {
139            super.attributeChanged(attribute);
140        }
141    }
142
143    /** Clone the actor into the specified workspace.
144     *  @param workspace The workspace for the new object.
145     *  @return A new actor.
146     *  @exception CloneNotSupportedException If a derived class contains
147     *   an attribute that cannot be cloned.
148     */
149    @Override
150    public Object clone(Workspace workspace) throws CloneNotSupportedException {
151        Round newObject = (Round) super.clone(workspace);
152
153        // The non-primitive fields of the clone must refer to objects
154        // distinct from the objects of the same name in the class.
155        // If this is not done, then there may be problems with actor
156        // oriented classes.
157        newObject._resultArray = new IntToken[0];
158        return newObject;
159    }
160
161    /** This computes the specified rounded value of the input.
162     *  This consumes and produces at most one token for each firing.
163     *  If there is no input, then produce no output.
164     *  @exception IllegalActionException If there is no director,
165     *   or if the input is NaN.
166     */
167    @Override
168    public void fire() throws IllegalActionException {
169        super.fire();
170        double in = ((DoubleToken) input.get(0)).doubleValue();
171        if (Double.isNaN(in)) {
172            throw new IllegalActionException("Input is Double.NaN, "
173                    + "there is no way to represent a NaN as an integer.");
174        }
175        output.send(0, new IntToken(_doFunction(in)));
176    }
177
178    /** Invoke a specified number of iterations of this actor. Each
179     *  iteration computes the rounding function specified by the
180     *  <i>function</i> attribute on a single token. An invocation
181     *  of this method therefore applies the function to <i>count</i>
182     *  successive input tokens.
183     *  <p>
184     *  This method should be called instead of the usual prefire(),
185     *  fire(), postfire() methods when this actor is used in a
186     *  domain that supports vectorized actors.  This leads to more
187     *  efficient execution.
188     *  @param count The number of iterations to perform.
189     *  @return COMPLETED if the actor was successfully iterated the
190     *   specified number of times. Otherwise, return NOT_READY, and do
191     *   not consume any input tokens.
192     *  @exception IllegalActionException Not thrown in this base class.
193     */
194    @Override
195    public int iterate(int count) throws IllegalActionException {
196        // Check whether we need to reallocate the output token array.
197        if (count > _resultArray.length) {
198            _resultArray = new IntToken[count];
199        }
200
201        if (input.hasToken(0, count)) {
202            // NOTE: inArray.length may be > count, in which case
203            // only the first count tokens are valid.
204            Token[] inArray = input.get(0, count);
205
206            for (int i = 0; i < count; i++) {
207                double value = ((DoubleToken) inArray[i]).doubleValue();
208                if (Double.isNaN(value)) {
209                    throw new IllegalActionException("Input is Double.NaN, "
210                            + "there is no way to represent a NaN as an integer.");
211                }
212
213                _resultArray[i] = new IntToken(_doFunction(value));
214            }
215
216            output.send(0, _resultArray, count);
217            return COMPLETED;
218        } else {
219            return NOT_READY;
220        }
221    }
222
223    /** Return false if there is no available input token, and otherwise
224     *  return whatever the superclass returns (presumably true).
225     *  @exception IllegalActionException If there is no director.
226     */
227    @Override
228    public boolean prefire() throws IllegalActionException {
229        if (!input.hasToken(0)) {
230            return false;
231        }
232
233        return super.prefire();
234    }
235
236    ///////////////////////////////////////////////////////////////////
237    ////                         private methods                   ////
238
239    /** Calculate the function on the given argument.
240     *  @param in The input value.
241     *  @return The result of applying the function.
242     */
243    private int _doFunction(double in) {
244        int result;
245
246        switch (_function) {
247        case _CEIL:
248            result = (int) Math.ceil(in);
249            break;
250
251        case _FLOOR:
252            result = (int) Math.floor(in);
253            break;
254
255        case _ROUND:
256            result = (int) Math.round(in);
257            break;
258
259        case _TRUNCATE:
260
261            if (in > 0) {
262                result = (int) Math.floor(in);
263            } else {
264                result = (int) Math.ceil(in);
265            }
266
267            break;
268
269        default:
270            throw new InvalidStateException(
271                    "Invalid value for _function private variable. "
272                            + "Round actor (" + getFullName() + ")"
273                            + " on function type " + _function);
274        }
275
276        return result;
277    }
278
279    ///////////////////////////////////////////////////////////////////
280    ////                         private variables                 ////
281    private IntToken[] _resultArray = new IntToken[0];
282
283    // An indicator for the function to compute.
284    // This variable has values specified in the constants below
285    private int _function;
286
287    // Constants used for more efficient execution.
288    private static final int _CEIL = 0;
289
290    private static final int _FLOOR = 1;
291
292    private static final int _ROUND = 2;
293
294    private static final int _TRUNCATE = 3;
295}