001/* An actor that outputs a quantized version of the input.
002
003 Copyright (c) 1998-2015 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.data.ArrayToken;
031import ptolemy.data.DoubleToken;
032import ptolemy.data.expr.Parameter;
033import ptolemy.data.type.ArrayType;
034import ptolemy.data.type.BaseType;
035import ptolemy.kernel.CompositeEntity;
036import ptolemy.kernel.util.Attribute;
037import ptolemy.kernel.util.IllegalActionException;
038import ptolemy.kernel.util.NameDuplicationException;
039import ptolemy.kernel.util.Workspace;
040
041///////////////////////////////////////////////////////////////////
042//// Quantizer
043
044/**
045 <p>Produce an output token on each firing with a value that is
046 a quantized version of the input.  The input and output types
047 are both double.
048 </p><p>
049 There are two ways to specify the quantization. If the <i>levels</i>
050 parameter is given, then the output will be the element of the array specified
051 by <i>levels</i> that is closest to the input. Otherwise, if <i>levels</i> is
052 empty, then the output will be quantized according to the <i>delta</i> parameter,
053 which specifies the spacing between quantization levels. Specifically, the
054 output will be
055 </p><p>
056 <i>delta</i> * Math.floor( <i>input</i>/<i>delta</i>) .
057 </p><p>
058 With the default value of <i>delta</i>, which is 1.0, the output is simply
059 the integer part of the input.
060 </p><p>
061 The <i>levels</i> parameter contains an array of doubles
062 specifying the quantization levels. The elements must be in
063 an increasing order, or an exception will be thrown.
064 The default value of <i>levels</i> is {-1.0, 1.0}.</p>
065 <p>
066 Suppose <i>u</i> is the input, and
067 <i>levels</i> = {<i>a</i>, <i>b</i>, <i>c</i>}, where
068 <i>a</i> &lt; <i>b</i> &lt; <i>c</i>, then the output of the actor will be:
069 </p>
070 <p>
071 <i>y</i> = <i>a</i>, for <i>u</i> &lt;= (<i>b</i>+<i>a</i>)/2;
072 <br><i>y</i> = <i>b</i>, for (<i>b</i>+<i>a</i>)/2 &lt;<i>u</i> &lt;= (<i>c</i>+<i>b</i>)/2;<br>
073 <br><i>y</i> = <i>c</i>, for <i>u</i> &gt; (<i>c</i>+<i>b</i>)/2;<br>
074 </p><p>
075 Thus, for the default <i>levels</i>, the output is (almost)
076 the signum function of the input, or +1.0 if the input is positive,
077 and -1.0 otherwise.  This is almost the signum function because it
078 outputs -1.0 if the input is zero.
079 </p><p>
080 This actor does not require that the quantization intervals be equal,
081 i.e. we allow that (<i>c</i>-<i>b</i>) != (<i>b</i>-<i>a</i>).</p>
082
083 @author Jie Liu
084 @version $Id$
085 @since Ptolemy II 0.3
086 @Pt.ProposedRating Yellow (liuj)
087 @Pt.AcceptedRating Yellow (yuhong)
088 */
089public class Quantizer extends Transformer {
090    /** Construct an actor with the given container and name.
091     *  @param container The container.
092     *  @param name The name of this actor.
093     *  @exception IllegalActionException If the actor cannot be contained
094     *   by the proposed container.
095     *  @exception NameDuplicationException If the container already has an
096     *   actor with this name.
097     */
098    public Quantizer(CompositeEntity container, String name)
099            throws NameDuplicationException, IllegalActionException {
100        super(container, name);
101        levels = new Parameter(this, "levels");
102        levels.setExpression("{-1.0, 1.0}");
103        levels.setTypeEquals(new ArrayType(BaseType.DOUBLE));
104
105        delta = new Parameter(this, "delta");
106        delta.setExpression("1.0");
107        delta.setTypeEquals(BaseType.DOUBLE);
108
109        // Call this so that we don't have to copy its code here...
110        attributeChanged(levels);
111
112        // Set the type constraints.
113        input.setTypeEquals(BaseType.DOUBLE);
114        output.setTypeEquals(BaseType.DOUBLE);
115    }
116
117    ///////////////////////////////////////////////////////////////////
118    ////                     ports and parameters                  ////
119
120    /** The spacing between quantization levels to use if the <i>levels</i>
121     *  parameter is not given. This is a double that defaults to 1.0.
122     */
123    public Parameter delta;
124
125    /** The quantization levels.
126     *  This parameter contains an array of doubles with default value
127     *  {-1.0, 1.0}.
128     */
129    public Parameter levels;
130
131    ///////////////////////////////////////////////////////////////////
132    ////                         public methods                    ////
133
134    /** Clone the actor into the specified workspace.
135     *  @param workspace The workspace for the new object.
136     *  @return A new actor.
137     *  @exception CloneNotSupportedException If a derived class contains
138     *   an attribute that cannot be cloned.
139     */
140    @Override
141    public Object clone(Workspace workspace) throws CloneNotSupportedException {
142        Quantizer newObject = (Quantizer) super.clone(workspace);
143
144        newObject._thresholds = new double[_thresholds.length];
145        System.arraycopy(_thresholds, 0, newObject._thresholds, 0,
146                _thresholds.length);
147
148        return newObject;
149    }
150
151    /** If the argument is the levels parameter, check that the array
152     *  is increasing and has the right dimension.  Recompute the
153     *  quantization thresholds.
154     *  @param attribute The attribute that changed.
155     *  @exception IllegalActionException If the levels array is not
156     *   increasing.
157     */
158    @Override
159    public void attributeChanged(Attribute attribute)
160            throws IllegalActionException {
161        if (attribute == levels) {
162            ArrayToken levelsValue = (ArrayToken) levels.getToken();
163            if (levelsValue == null || levelsValue.length() == 0) {
164                _thresholds = null;
165                return;
166            }
167            double[] _levels = new double[levelsValue.length()];
168            double previous = Double.NEGATIVE_INFINITY;
169
170            for (int i = 0; i < levelsValue.length(); i++) {
171                _levels[i] = ((DoubleToken) levelsValue.getElement(i))
172                        .doubleValue();
173
174                // Check nondecreasing property.
175                if (_levels[i] < previous) {
176                    throw new IllegalActionException(this,
177                            "Value of levels is not nondecreasing.");
178                }
179
180                previous = _levels[i];
181            }
182
183            // Compute the quantization thresholds.
184            _thresholds = new double[_levels.length - 1];
185
186            for (int j = 0; j < _levels.length - 1; j++) {
187                _thresholds[j] = (_levels[j + 1] + _levels[j]) / 2.0;
188            }
189        } else {
190            super.attributeChanged(attribute);
191        }
192    }
193
194    /** Output the quantization of the input.
195     *  If there is no input, then produce no output.
196     *  @exception IllegalActionException If there is no director.
197     */
198    @Override
199    public void fire() throws IllegalActionException {
200        super.fire();
201        if (input.hasToken(0)) {
202            double in = ((DoubleToken) input.get(0)).doubleValue();
203            if (_thresholds != null) {
204                int index = _getQuantizationIndex(in);
205                output.send(0,
206                        ((ArrayToken) levels.getToken()).getElement(index));
207            } else {
208                // Using delta parameter.
209                double deltaValue = ((DoubleToken) delta.getToken())
210                        .doubleValue();
211                double result = deltaValue * Math.floor(in / deltaValue);
212                output.send(0, new DoubleToken(result));
213            }
214        }
215    }
216
217    ///////////////////////////////////////////////////////////////////
218    ////                         private methods                   ////
219
220    /* Compute the quantization index for the given input value.
221     * @parameter in The input value
222     * @return The quantization index.
223     */
224    private int _getQuantizationIndex(double in) {
225        int index = _thresholds.length;
226
227        for (int i = 0; i < _thresholds.length; i++) {
228            if (in <= _thresholds[i]) {
229                index = i;
230                break;
231            }
232        }
233
234        return index;
235    }
236
237    ///////////////////////////////////////////////////////////////////
238    ////                         private variables                 ////
239    // The thresholds for quantization.
240    private double[] _thresholds;
241}