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}