001/* An actor that produces tokens with a given probability mass function.
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;
029
030import ptolemy.data.ArrayToken;
031import ptolemy.data.DoubleToken;
032import ptolemy.data.Token;
033import ptolemy.data.expr.Parameter;
034import ptolemy.data.type.ArrayType;
035import ptolemy.data.type.BaseType;
036import ptolemy.kernel.CompositeEntity;
037import ptolemy.kernel.util.Attribute;
038import ptolemy.kernel.util.IllegalActionException;
039import ptolemy.kernel.util.InternalErrorException;
040import ptolemy.kernel.util.NameDuplicationException;
041import ptolemy.kernel.util.Workspace;
042import ptolemy.math.SignalProcessing;
043
044///////////////////////////////////////////////////////////////////
045//// DiscreteRandomSource
046
047/**
048 <p>
049 An actor that produces tokens with a given probability mass function.
050 </p><p>
051 The probability mass function is a parameter, <i>pmf</i>, of this
052 actor. The <i>pmf</i> must be an array that contains entries that
053 are all between 0.0 and 1.1, and sum to 1.0. By default, <i>pmf</i> is
054 initialized to {0.5, 0.5}.
055 </p><p>
056 Output values are selected at random from the <i>values</i> parameter,
057 which contains an ArrayToken. This array must have the same length as
058 <i>pmf</i>.  Thus the <i>i</i>-th token in <i>values</i> has probability
059 <i>pmf</i>[<i>i</i>]. The output port has the same type as the elements of
060 the <i>values</i> array.  The default <i>values</i> are {0, 1}, which are
061 integers.</p>
062
063 @author Jeff Tsay, Yuhong Xiong
064 @version $Id$
065 @since Ptolemy II 1.0
066 @Pt.ProposedRating Yellow (eal)
067 @Pt.AcceptedRating Yellow (ssachs)
068 */
069public class DiscreteRandomSource extends RandomSource {
070    /** Construct an actor with the given container and name.
071     *  @param container The container.
072     *  @param name The name of this actor.
073     *  @exception IllegalActionException If the actor cannot be contained
074     *   by the proposed container.
075     *  @exception NameDuplicationException If the container already has an
076     *   actor with this name.
077     */
078    public DiscreteRandomSource(CompositeEntity container, String name)
079            throws NameDuplicationException, IllegalActionException {
080        super(container, name);
081        pmf = new Parameter(this, "pmf");
082        pmf.setExpression("{0.5, 0.5}");
083        pmf.setTypeEquals(new ArrayType(BaseType.DOUBLE));
084
085        // set the values parameter
086        values = new Parameter(this, "values");
087        values.setExpression("{0, 1}");
088
089        // set type constraint
090        output.setTypeAtLeast(ArrayType.elementType(values));
091    }
092
093    ///////////////////////////////////////////////////////////////////
094    ////                     ports and parameters                  ////
095
096    /** The probability mass function.
097     *  This parameter contains an array of doubles, with default value
098     *  {0.5, 0.5}.
099     */
100    public Parameter pmf;
101
102    /** The values to be sent to the output.
103     *  This parameter contains an ArrayToken, initially with value
104     *  {0, 1} (an int array).
105     */
106    public Parameter values;
107
108    ///////////////////////////////////////////////////////////////////
109    ////                         public methods                    ////
110
111    /** If the specified attribute is <i>pmf</i>, then check that its
112     *  entries are all between zero and one, and that they add to one,
113     *  and that its dimension is correct.
114     *  @param attribute The attribute that changed.
115     *  @exception IllegalActionException If the requirements are
116     *   violated.
117     */
118    @Override
119    public void attributeChanged(Attribute attribute)
120            throws IllegalActionException {
121        if (attribute == pmf) {
122            ArrayToken pmfValue = (ArrayToken) pmf.getToken();
123            _pmf = new double[pmfValue.length()];
124
125            double sum = 0.0;
126
127            for (int i = 0; i < _pmf.length; i++) {
128                _pmf[i] = ((DoubleToken) pmfValue.getElement(i)).doubleValue();
129                sum += _pmf[i];
130            }
131
132            // Allow for roundoff error.
133            if (!SignalProcessing.close(sum, 1.0)) {
134                throw new IllegalActionException(this,
135                        "Parameter values are required to sum to one.");
136            }
137        } else {
138            super.attributeChanged(attribute);
139        }
140    }
141
142    /** Clone the actor into the specified workspace. This calls the
143     *  base class and then sets the parameter public members to refer
144     *  to the parameters of the new actor.
145     *  @param workspace The workspace for the new object.
146     *  @return A new actor.
147     *  @exception CloneNotSupportedException If a derived class contains
148     *   an attribute that cannot be cloned.
149     */
150    @Override
151    public Object clone(Workspace workspace) throws CloneNotSupportedException {
152        DiscreteRandomSource newObject = (DiscreteRandomSource) super.clone(
153                workspace);
154        try {
155            newObject.output
156                    .setTypeAtLeast(ArrayType.elementType(newObject.values));
157        } catch (IllegalActionException e) {
158            // Should have been caught before.
159            throw new InternalErrorException(e);
160        }
161
162        // Copy the array _pmf
163        newObject._pmf = null;
164        try {
165            ArrayToken pmfValue = (ArrayToken) pmf.getToken();
166            newObject._pmf = new double[pmfValue.length()];
167        } catch (IllegalActionException ex) {
168            CloneNotSupportedException exception = new CloneNotSupportedException();
169            exception.initCause(ex);
170            throw exception;
171        }
172
173        if (_pmf != null) {
174            System.arraycopy(_pmf, 0, newObject._pmf, 0, _pmf.length);
175        }
176
177        return newObject;
178    }
179
180    /** Output the token selected in the prefire() method.
181     *  @exception IllegalActionException If there is no director.
182     */
183    @Override
184    public void fire() throws IllegalActionException {
185        super.fire();
186        output.send(0, _current);
187    }
188
189    ///////////////////////////////////////////////////////////////////
190    ////                         protected methods                 ////
191
192    /** Choose one of the tokens in <i>values</i> randomly, using
193     *  the <i>pmf</i> parameter to select one.  The chosen token
194     *  will be sent to the output in the fire() method.
195     *  @exception IllegalActionException If parameter values are incorrect.
196     */
197    @Override
198    protected void _generateRandomNumber() throws IllegalActionException {
199        // Generate a double between 0 and 1, uniformly distributed.
200        double randomValue = _random.nextDouble();
201        ArrayToken valuesToken = (ArrayToken) values.getToken();
202
203        if (_pmf.length != valuesToken.length()) {
204            throw new IllegalActionException(this,
205                    "Parameters values and pmf are required to be arrays "
206                            + "with the same length.");
207        }
208
209        double cdf = 0.0;
210
211        for (int i = 0; i < _pmf.length; i++) {
212            cdf += _pmf[i];
213
214            if (randomValue <= cdf) {
215                _current = valuesToken.getElement(i);
216                return;
217            }
218        }
219
220        // We shouldn't get here, but if we do, we output the last value.
221        _current = valuesToken.getElement(_pmf.length - 1);
222    }
223
224    ///////////////////////////////////////////////////////////////////
225    ////                         private variables                 ////
226
227    /** Random value calculated in prefire(). */
228    private Token _current;
229
230    /** Cache of probability mass function. */
231    private transient double[] _pmf;
232}