factory.js (8126B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2018 The Stdlib Authors. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 'use strict'; 20 21 // MODULES // 22 23 var setReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' ); 24 var setReadOnlyAccessor = require( '@stdlib/utils/define-nonenumerable-read-only-accessor' ); 25 var setReadWriteAccessor = require( '@stdlib/utils/define-nonenumerable-read-write-accessor' ); 26 var isObject = require( '@stdlib/assert/is-plain-object' ); 27 var isFunction = require( '@stdlib/assert/is-function' ); 28 var hasOwnProp = require( '@stdlib/assert/has-own-property' ); 29 var constantFunction = require( '@stdlib/utils/constant-function' ); 30 var noop = require( '@stdlib/utils/noop' ); 31 var randu = require( './../../../base/mt19937' ).factory; 32 var isNonNegativeInteger = require( '@stdlib/math/base/assert/is-nonnegative-integer' ); 33 var PINF = require( '@stdlib/constants/float64/pinf' ); 34 var typedarray2json = require( '@stdlib/array/to-json' ); 35 var validate = require( './validate.js' ); 36 var hypergeometric0 = require( './hypergeometric.js' ); 37 38 39 // MAIN // 40 41 /** 42 * Returns a pseudorandom number generator for generating hypergeometric distributed random numbers. 43 * 44 * @param {NonNegativeInteger} [N] - population size 45 * @param {NonNegativeInteger} [K] - subpopulation size 46 * @param {NonNegativeInteger} [n] - number of draws 47 * @param {Options} [options] - function options 48 * @param {PRNG} [options.prng] - pseudorandom number generator which generates uniformly distributed pseudorandom numbers 49 * @param {PRNGSeedMT19937} [options.seed] - pseudorandom number generator seed 50 * @param {PRNGStateMT19937} [options.state] - pseudorandom number generator state 51 * @param {boolean} [options.copy=true] - boolean indicating whether to copy a provided pseudorandom number generator state 52 * @throws {TypeError} `N` must be a nonnegative integer 53 * @throws {TypeError} `K` must be a nonnegative integer 54 * @throws {TypeError} `n` must be a nonnegative integer 55 * @throws {RangeError} `n` must be less than or equal to `N` 56 * @throws {TypeError} options argument must be an object 57 * @throws {TypeError} must provide valid options 58 * @throws {Error} must provide a valid state 59 * @returns {PRNG} pseudorandom number generator 60 * 61 * @example 62 * var hypergeometric = factory( 5, 3, 2 ); 63 * var v = hypergeometric(); 64 * // returns <number> 65 * 66 * @example 67 * var hypergeometric = factory( 10, 10, 10, { 68 * 'seed': 297 69 * }); 70 * var v = hypergeometric(); 71 * // returns <number> 72 * 73 * @example 74 * var hypergeometric = factory(); 75 * var v = hypergeometric( 5, 3, 2 ); 76 * // returns <number> 77 */ 78 function factory() { 79 var opts; 80 var rand; 81 var prng; 82 var err; 83 var N; 84 var K; 85 var n; 86 87 if ( arguments.length === 0 ) { 88 rand = randu(); 89 } else if ( arguments.length === 1 ) { 90 opts = arguments[ 0 ]; 91 if ( !isObject( opts ) ) { 92 throw new TypeError( 'invalid argument. Options argument must be an object. Value: `' + opts + '`.' ); 93 } 94 if ( hasOwnProp( opts, 'prng' ) ) { 95 if ( !isFunction( opts.prng ) ) { 96 throw new TypeError( 'invalid option. `prng` option must be a pseudorandom number generator function. Option: `' + opts.prng + '`.' ); 97 } 98 rand = opts.prng; 99 } else { 100 rand = randu( opts ); 101 } 102 } else { 103 N = arguments[ 0 ]; 104 K = arguments[ 1 ]; 105 n = arguments[ 2 ]; 106 err = validate( N, K, n ); 107 if ( err ) { 108 throw err; 109 } 110 if ( arguments.length > 3 ) { 111 opts = arguments[ 3 ]; 112 if ( !isObject( opts ) ) { 113 throw new TypeError( 'invalid argument. Options argument must be an object. Value: `' + opts + '`.' ); 114 } 115 if ( hasOwnProp( opts, 'prng' ) ) { 116 if ( !isFunction( opts.prng ) ) { 117 throw new TypeError( 'invalid option. `prng` option must be a pseudorandom number generator function. Option: `' + opts.prng + '`.' ); 118 } 119 rand = opts.prng; 120 } else { 121 rand = randu( opts ); 122 } 123 } else { 124 rand = randu(); 125 } 126 } 127 if ( N === void 0 ) { 128 prng = hypergeometric2; 129 } else { 130 prng = hypergeometric1; 131 } 132 setReadOnly( prng, 'NAME', 'hypergeometric' ); 133 134 // If we are provided an "external" PRNG, we don't support getting or setting PRNG state, as we'd need to check for compatible state value types, etc, entailing considerable complexity. 135 if ( opts && opts.prng ) { 136 setReadOnly( prng, 'seed', null ); 137 setReadOnly( prng, 'seedLength', null ); 138 setReadWriteAccessor( prng, 'state', constantFunction( null ), noop ); 139 setReadOnly( prng, 'stateLength', null ); 140 setReadOnly( prng, 'byteLength', null ); 141 setReadOnly( prng, 'toJSON', constantFunction( null ) ); 142 setReadOnly( prng, 'PRNG', rand ); 143 } else { 144 setReadOnlyAccessor( prng, 'seed', getSeed ); 145 setReadOnlyAccessor( prng, 'seedLength', getSeedLength ); 146 setReadWriteAccessor( prng, 'state', getState, setState ); 147 setReadOnlyAccessor( prng, 'stateLength', getStateLength ); 148 setReadOnlyAccessor( prng, 'byteLength', getStateSize ); 149 setReadOnly( prng, 'toJSON', toJSON ); 150 setReadOnly( prng, 'PRNG', rand ); 151 rand = rand.normalized; 152 } 153 return prng; 154 155 /** 156 * Returns the PRNG seed. 157 * 158 * @private 159 * @returns {PRNGSeedMT19937} seed 160 */ 161 function getSeed() { 162 return rand.seed; 163 } 164 165 /** 166 * Returns the PRNG seed length. 167 * 168 * @private 169 * @returns {PositiveInteger} seed length 170 */ 171 function getSeedLength() { 172 return rand.seedLength; 173 } 174 175 /** 176 * Returns the PRNG state length. 177 * 178 * @private 179 * @returns {PositiveInteger} state length 180 */ 181 function getStateLength() { 182 return rand.stateLength; 183 } 184 185 /** 186 * Returns the PRNG state size (in bytes). 187 * 188 * @private 189 * @returns {PositiveInteger} state size (in bytes) 190 */ 191 function getStateSize() { 192 return rand.byteLength; 193 } 194 195 /** 196 * Returns the current pseudorandom number generator state. 197 * 198 * @private 199 * @returns {PRNGStateMT19937} current state 200 */ 201 function getState() { 202 return rand.state; 203 } 204 205 /** 206 * Sets the pseudorandom number generator state. 207 * 208 * @private 209 * @param {PRNGStateMT19937} s - generator state 210 * @throws {Error} must provide a valid state 211 */ 212 function setState( s ) { 213 rand.state = s; 214 } 215 216 /** 217 * Serializes the pseudorandom number generator as a JSON object. 218 * 219 * ## Notes 220 * 221 * - `JSON.stringify()` implicitly calls this method when stringifying a PRNG. 222 * 223 * @private 224 * @returns {Object} JSON representation 225 */ 226 function toJSON() { 227 var out = {}; 228 out.type = 'PRNG'; 229 out.name = prng.NAME; 230 out.state = typedarray2json( rand.state ); 231 if ( N === void 0 ) { 232 out.params = []; 233 } else { 234 out.params = [ N, K, n ]; 235 } 236 return out; 237 } 238 239 /** 240 * Returns a random number drawn from a hypergeometric distribution with bound parameters. 241 * 242 * @private 243 * @returns {NonNegativeInteger} pseudorandom number 244 * 245 * @example 246 * var v = hypergeometric1(); 247 * // returns <number> 248 */ 249 function hypergeometric1() { 250 return hypergeometric0( rand, N, K, n ); 251 } 252 253 /** 254 * Returns a pseudorandom number drawn from a hypergeometric distribution. 255 * 256 * @private 257 * @param {NonNegativeInteger} N - population size 258 * @param {NonNegativeInteger} K - subpopulation size 259 * @param {NonNegativeInteger} n - number of draws 260 * @returns {NonNegativeInteger} pseudorandom number 261 * 262 * @example 263 * var v = hypergeometric2( 5, 3, 2 ); 264 * // returns <number> 265 * 266 * @example 267 * var v = hypergeometric2( NaN, NaN, NaN ); 268 * // returns NaN 269 * 270 * @example 271 * var v = hypergeometric2( 5.21, 3.14, 2.76 ); 272 * // returns NaN 273 */ 274 function hypergeometric2( N, K, n ) { 275 if ( 276 N === PINF || 277 K === PINF || 278 !isNonNegativeInteger( N ) || 279 !isNonNegativeInteger( K ) || 280 !isNonNegativeInteger( n ) || 281 n > N 282 ) { 283 return NaN; 284 } 285 return hypergeometric0( rand, N, K, n ); 286 } 287 } 288 289 290 // EXPORTS // 291 292 module.exports = factory;