001/* Compute a histogram of input data.
002
003 @Copyright (c) 2003-2014 The Regents of the University of California.
004 All rights reserved.
005
006 Permission is hereby granted, without written agreement and without
007 license or royalty fees, to use, copy, modify, and distribute this
008 software and its documentation for any purpose, provided that the
009 above copyright notice and the following two paragraphs appear in all
010 copies of this software.
011
012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
016 SUCH DAMAGE.
017
018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
023 ENHANCEMENTS, OR MODIFICATIONS.
024
025 PT_COPYRIGHT_VERSION 2
026 COPYRIGHTENDKEY
027
028 */
029package ptolemy.actor.lib;
030
031import ptolemy.actor.TypedAtomicActor;
032import ptolemy.actor.TypedIOPort;
033import ptolemy.actor.parameters.PortParameter;
034import ptolemy.data.ArrayToken;
035import ptolemy.data.DoubleToken;
036import ptolemy.data.IntToken;
037import ptolemy.data.Token;
038import ptolemy.data.expr.Parameter;
039import ptolemy.data.type.ArrayType;
040import ptolemy.data.type.BaseType;
041import ptolemy.kernel.CompositeEntity;
042import ptolemy.kernel.util.Attribute;
043import ptolemy.kernel.util.IllegalActionException;
044import ptolemy.kernel.util.NameDuplicationException;
045import ptolemy.kernel.util.Settable;
046import ptolemy.kernel.util.Workspace;
047
048///////////////////////////////////////////////////////////////////
049//// ComputeHistogram
050
051/**
052 Compute a histogram.
053 <p>
054 The output array consists of a set of vertical bars, each representing
055 a histogram bin.  The height of the bar is the count of the number
056 of inputs that have been observed that fall within that bin.
057 The <i>n</i>-th bin represents values in the range
058 (<i>x</i> - <i>w</i>/2 + <i>o</i>, <i>x</i> + <i>w</i>/2 + <i>o</i>),
059 where <i>w</i> is the value of the <i>binWidth</i> parameter,
060 and <i>o</i> is the value of the <i>binOffset</i> parameter.
061 So for example, if <i>o = w/2</i>,
062 then each bin represents values from <i>nw</i> to
063 (<i>n</i> + 1)<i>w</i> for some integer <i>n</i>.
064 The default offset is 0.5, half the default bin width, which is 1.0.
065 <p>
066 This actor has a <i>legend</i> parameter,
067 which gives a comma-separated list of labels to attach to
068 each dataset.  Normally, the number of elements in this list
069 should equal the number of input channels, although this
070 is not enforced.
071
072 @see ptolemy.plot.Histogram
073
074 @author Steve Neuendorffer
075 @version $Id$
076 @since Ptolemy II 4.0
077 @Pt.ProposedRating Red (eal)
078 @Pt.AcceptedRating Red (cxh)
079 */
080public class ComputeHistogram extends TypedAtomicActor {
081    /** Construct an actor with the given container and name.
082     *  @param container The container.
083     *  @param name The name of this actor.
084     *  @exception IllegalActionException If the actor cannot be contained
085     *   by the proposed container.
086     *  @exception NameDuplicationException If the container already has an
087     *   actor with this name.
088     */
089    public ComputeHistogram(CompositeEntity container, String name)
090            throws IllegalActionException, NameDuplicationException {
091        super(container, name);
092
093        input = new TypedIOPort(this, "input", true, false);
094        input.setTypeEquals(BaseType.DOUBLE);
095
096        output = new TypedIOPort(this, "output", false, true);
097        output.setTypeEquals(new ArrayType(BaseType.INT));
098
099        minimumValue = new Parameter(this, "minimumValue");
100        minimumValue.setExpression("0.0");
101        minimumValue.setTypeEquals(BaseType.DOUBLE);
102
103        maximumValue = new Parameter(this, "maximumValue");
104        maximumValue.setExpression("1.0");
105        maximumValue.setTypeEquals(BaseType.DOUBLE);
106
107        numberOfBins = new Parameter(this, "numberOfBins");
108        numberOfBins.setExpression("10");
109        numberOfBins.setTypeEquals(BaseType.INT);
110
111        inputCount = new PortParameter(this, "inputCount");
112        inputCount.setExpression("10");
113        inputCount.setTypeEquals(BaseType.INT);
114
115        input_tokenConsumptionRate = new Parameter(input,
116                "tokenConsumptionRate");
117        input_tokenConsumptionRate.setExpression("inputCount");
118        input_tokenConsumptionRate.setTypeEquals(BaseType.INT);
119        input_tokenConsumptionRate.setVisibility(Settable.NOT_EDITABLE);
120        input_tokenConsumptionRate.setPersistent(false);
121    }
122
123    ///////////////////////////////////////////////////////////////////
124    ////                     ports and parameters                  ////
125
126    /** The lowest value that will be recorded in the histogram.
127     *  This parameter has type double, with default value 0.0.
128     */
129    public Parameter minimumValue;
130
131    /** The highest value that will be recorded in the histogram.
132     *  This parameter has type double, with default value 1.0.
133     */
134    public Parameter maximumValue;
135
136    /** The number of bins.
137     *  This parameter has type int, with default value 10.
138     */
139    public Parameter numberOfBins;
140
141    /** The number of tokens to compute the histogram for.
142     */
143    public PortParameter inputCount;
144
145    /** The parameter that determines the consumption rate of the input.
146     */
147    public Parameter input_tokenConsumptionRate;
148
149    /** The input port of type double. */
150    public TypedIOPort input;
151
152    /** The input port of type array of integer. */
153    public TypedIOPort output;
154
155    ///////////////////////////////////////////////////////////////////
156    ////                         public methods                    ////
157
158    /** If the parameter is <i>binWidth</i> or <i>binOffset</i>, then
159     *  configure the histogram with the specified bin width or offset.
160     *  @param attribute The attribute that changed.
161     *  @exception IllegalActionException If the bin width is not positive.
162     */
163    @Override
164    public void attributeChanged(Attribute attribute)
165            throws IllegalActionException {
166        if (attribute == minimumValue || attribute == maximumValue
167                || attribute == numberOfBins) {
168            _minimumValue = ((DoubleToken) minimumValue.getToken())
169                    .doubleValue();
170            _maximumValue = ((DoubleToken) maximumValue.getToken())
171                    .doubleValue();
172            _numberOfBins = ((IntToken) numberOfBins.getToken()).intValue();
173
174            double width = (_maximumValue - _minimumValue) / _numberOfBins;
175
176            if (width <= 0.0) {
177                throw new IllegalActionException(this,
178                        "Invalid bin width (must be positive): " + width);
179            }
180
181            _binWidth = width;
182            _bins = new int[_numberOfBins];
183        } else {
184            super.attributeChanged(attribute);
185        }
186    }
187
188    /** Clone the actor into the specified workspace.
189     *  @param workspace The workspace for the new object.
190     *  @return A new actor.
191     *  @exception CloneNotSupportedException If a derived class contains
192     *   an attribute that cannot be cloned.
193     */
194    @Override
195    public Object clone(Workspace workspace) throws CloneNotSupportedException {
196        ComputeHistogram newObject = (ComputeHistogram) super.clone(workspace);
197
198        newObject._bins = new int[_numberOfBins];
199        if (_bins != null) {
200            System.arraycopy(_bins, 0, newObject._bins, 0, _bins.length);
201        }
202        return newObject;
203    }
204
205    /** Read at most one input token from each input channel
206     *  and update the histogram.
207     *  @exception IllegalActionException If there is no director.
208     */
209    @Override
210    public void fire() throws IllegalActionException {
211        super.fire();
212        _bins = new int[_numberOfBins];
213        inputCount.update();
214        int count = ((IntToken) inputCount.getToken()).intValue();
215
216        for (int i = 0; i < count; i++) {
217            if (input.hasToken(0)) {
218                DoubleToken curToken = (DoubleToken) input.get(0);
219                double curValue = curToken.doubleValue();
220
221                _addPoint(curValue);
222            }
223        }
224
225        // Send the output array.
226        Token[] values = new Token[_bins.length];
227
228        for (int i = 0; i < _bins.length; i++) {
229            values[i] = new IntToken(_bins[i]);
230        }
231
232        output.send(0, new ArrayToken(BaseType.INT, values));
233    }
234
235    /** Return false if the input does not have enough tokens to fire.
236     *  Otherwise, return true.
237     *  @return False if the number of input tokens available is not at least
238     *   equal to the <i>decimation</i> parameter multiplied by the
239     *   <i>blockSize</i> parameter.
240     *  @exception IllegalActionException If the superclass throws it.
241     */
242    @Override
243    public boolean prefire() throws IllegalActionException {
244        int count = ((IntToken) inputCount.getToken()).intValue();
245        return input.hasToken(0, count) && super.prefire();
246    }
247
248    ///////////////////////////////////////////////////////////////////
249    ////                         private methods                   ////
250    private void _addPoint(double value) {
251        // Calculate the bin number.
252        int bin = (int) Math
253                .round((value - (_minimumValue + _binWidth * 0.5)) / _binWidth);
254
255        if (bin >= 0 && bin < _numberOfBins) {
256            _bins[bin]++;
257        }
258    }
259
260    ///////////////////////////////////////////////////////////////////
261    ////                         private fields                    ////
262    private int[] _bins;
263
264    private double _minimumValue;
265
266    private double _maximumValue;
267
268    private double _binWidth;
269
270    private int _numberOfBins;
271}