001/* An actor that outputs a random sequence with a Binomial distribution.
002
003 Copyright (c) 2006-2016 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.colt;
029
030import cern.jet.random.Binomial;
031import ptolemy.actor.TypedIOPort;
032import ptolemy.actor.parameters.PortParameter;
033import ptolemy.data.BooleanToken;
034import ptolemy.data.IntToken;
035import ptolemy.data.LongToken;
036import ptolemy.data.expr.SingletonParameter;
037import ptolemy.data.type.BaseType;
038import ptolemy.kernel.CompositeEntity;
039import ptolemy.kernel.util.IllegalActionException;
040import ptolemy.kernel.util.NameDuplicationException;
041
042///////////////////////////////////////////////////////////////////
043//// Binomial Selector
044
045/**
046
047 Assign trials from several populations using a conditional Binomial
048 selection process.  For example, if a vector of <code>P</code>
049 populations are presented (as <code>P</code> input channels) and
050 <code>N</code> trials is specified, then this algorithm will
051 distribute the <code>N</code> trials based on the proportions
052 represented in the <code>P</code> populations.  This is done by
053 performing a progressively conditional Binomial selection in which
054 <code>n</code> and <code>p</code> change after each trial assignment
055 step.  The Binomial trials (<code>n</code>) is decremented after each
056 assignment step to represent the remaining trials, and the new
057 Binomial probability (<code>p</code>) is calculated based on the
058 populations that remain eligible for selection.
059
060 <p> A new set of trial assignments is produced for each iteration and
061 will not change until the next iteration.  The values that are
062 generated are independent and the expected values of the assignments
063 will have expected values that are representative of the population
064 proportions.
065
066 @see ptolemy.actor.lib.colt.ColtBinomial
067 @see cern.jet.random.Binomial
068 @author Raymond A. Cardillo, Matthew J. Robbins, Contributors: Jason Smith and Brian Hudson
069 @version $Id$
070 @since Ptolemy II 6.0
071 @Pt.ProposedRating Red (cxh)
072 @Pt.AcceptedRating Red (cxh)
073 */
074public class ColtBinomialSelector extends ColtRandomSource {
075
076    /** Construct an actor with the given container and name.
077     *  @param container The container.
078     *  @param name The name of this actor.
079     *  @exception IllegalActionException If the actor cannot be contained
080     *   by the proposed container.
081     *  @exception NameDuplicationException If the container already has an
082     *   actor with this name.
083     */
084    public ColtBinomialSelector(CompositeEntity container, String name)
085            throws NameDuplicationException, IllegalActionException {
086        super(container, name);
087
088        trials = new PortParameter(this, "trials", new IntToken(1));
089        trials.setTypeEquals(BaseType.INT);
090        new SingletonParameter(trials.getPort(), "_showName")
091                .setToken(BooleanToken.TRUE);
092
093        populations = new TypedIOPort(this, "populations", true, false);
094        populations.setMultiport(true);
095        populations.setTypeEquals(BaseType.LONG);
096        new SingletonParameter(populations, "_showName")
097                .setToken(BooleanToken.TRUE);
098
099        output.setMultiport(true);
100        output.setTypeEquals(BaseType.INT);
101    }
102
103    ///////////////////////////////////////////////////////////////////
104    ////                     ports and parameters                  ////
105
106    /** The total number of trials to assign.  This PortParameter is
107     *  of type Int and has an initial default value of 1.
108     */
109    public PortParameter trials;
110
111    /** The populations to select from.  This multiport is of type Long.
112     */
113    public TypedIOPort populations;
114
115    ///////////////////////////////////////////////////////////////////
116    ////                         public methods                    ////
117
118    /** Send the trial distributions to the output.
119     *  This set of trial distributions is only changed in the
120     *  prefire() method, so it will remain constant throughout an
121     *  iteration.
122     *  @exception IllegalActionException If there is no director.
123     */
124    @Override
125    public void fire() throws IllegalActionException {
126        trials.update();
127        super.fire();
128
129        for (int i = 0; i < _current.length; i++) {
130            output.send(i, new IntToken(_current[i]));
131        }
132    }
133
134    ///////////////////////////////////////////////////////////////////
135    ////                         protected methods                 ////
136
137    /** Create a new random number generator.  This method is called
138     *  after _randomNumberGenerator is changed.
139     */
140    @Override
141    protected void _createdNewRandomNumberGenerator() {
142        _generator = new Binomial(1, 0.5, _randomNumberGenerator);
143    }
144
145    /** Generate a new random number.
146     *  @exception IllegalActionException If parameter values are incorrect.
147     */
148    @Override
149    protected void _generateRandomNumber() throws IllegalActionException {
150        // Pull out the source values, make sure they're valid, and
151        // calculate the total.
152        long[] sourceValues = new long[populations.getWidth()];
153        long sourcePool = 0;
154        for (int i = 0; i < sourceValues.length; i++) {
155            sourceValues[i] = ((LongToken) populations.get(i)).longValue();
156            if (sourceValues[i] < 0) {
157                throw new IllegalActionException(this,
158                        "sourceValue[" + i + "] is negative.");
159            }
160
161            sourcePool += sourceValues[i];
162        }
163
164        // Process the binomial selections.
165        int trialsRemaining = ((IntToken) trials.getToken()).intValue();
166        // Initialize the _current array.
167        _current = new int[sourceValues.length];
168
169        // Constrain trialsRemaining to be less than or equal to the sourcePool.
170        if (trialsRemaining > sourcePool) {
171            trialsRemaining = (int) sourcePool;
172        }
173        // While there are trials remaining...
174        // Loop through the array multiple times.  Formerly, if we passed
175        // in trials of 10, 10, 10, the selection would only occur 3 times.
176        // The selection code is not guaranteed to select the correct number
177        // of trial in only one pass.  If the number of trials was 29, then the
178        // only acceptable results from 10, 10, 10 would be combinations of
179        // one 9 and two 10.  Formerly, the old code exceeded the possible
180        // selections in one or two populations given, results like 7, 8, 14
181        // or 7, 11, 11 were common.
182        // See test/auto/ColtBinomialSelectorManyTrials.xml
183        while (trialsRemaining > 0) {
184            for (int i = 0; i < _current.length; i++) {
185                // Do a selection for a population.
186                int selected = 0;
187                if (trialsRemaining > 0 && sourceValues[i] > 0) {
188                    double p = (double) sourceValues[i] / (double) sourcePool;
189                    if (p < 1.0) {
190                        // Make sure that selections don't exceed
191                        // possible populations.  Doing a selection of
192                        // 15 people with some probability, but given
193                        // a population of 10, it's possible to select
194                        // too many from that population.  This fixes
195                        // it from "select up to x people with
196                        // probability y, and select them from this
197                        // population" to "select up to x people from
198                        // this population with probability y". The
199                        // while loop takes care of ensuring the
200                        // correct number of selections should the
201                        // first pass (with high probability) fail to
202                        // select the required number of selections
203                        selected = _generator.nextInt((int) Math
204                                .min(trialsRemaining, sourceValues[i]), p);
205                    } else {
206                        selected = trialsRemaining;
207                    }
208                }
209
210                // Add to the selection record (_current).
211                _current[i] += selected;
212
213                // Reduce the amount that can be selected from this population in the future.
214                sourceValues[i] -= selected;
215
216                // Reduce the trials remaining by the successful trials.
217                trialsRemaining -= selected;
218
219                // Reduce the remaining source pool.
220                sourcePool -= selected;
221            }
222        }
223    }
224
225    ///////////////////////////////////////////////////////////////////
226    ////                         private variables                 ////
227
228    /** The tokens to emit during the current iteration. */
229    private int _current[];
230
231    /** The random number generator. */
232    private Binomial _generator;
233}