factory.js (10222B)
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 hasOwnProp = require( '@stdlib/assert/has-own-property' ); 27 var isObject = require( '@stdlib/assert/is-plain-object' ); 28 var isUint32Array = require( '@stdlib/assert/is-uint32array' ); 29 var isBoolean = require( '@stdlib/assert/is-boolean' ).isPrimitive; 30 var isFunction = require( '@stdlib/assert/is-function' ); 31 var constantFunction = require( '@stdlib/utils/constant-function' ); 32 var noop = require( '@stdlib/utils/noop' ); 33 var isnan = require( '@stdlib/math/base/assert/is-nan' ); 34 var poisson = require( './../../../base/poisson' ).factory; 35 var gamma = require( './../../../base/gamma' ).factory; 36 var gcopy = require( '@stdlib/blas/base/gcopy' ); 37 var Uint32Array = require( '@stdlib/array/uint32' ); 38 var copy = require( '@stdlib/utils/copy' ); 39 var typedarray2json = require( '@stdlib/array/to-json' ); 40 var validate = require( './validate.js' ); 41 42 43 // MAIN // 44 45 /** 46 * Returns a pseudorandom number generator for generating negative binomial distributed random numbers. 47 * 48 * @param {PositiveNumber} [r] - number of successes until experiment is stopped 49 * @param {number} [p] - success probability 50 * @param {Options} [options] - function options 51 * @param {PRNG} [options.prng] - pseudorandom number generator which generates uniformly distributed pseudorandom numbers 52 * @param {PRNGSeedMT19937} [options.seed] - pseudorandom number generator seed 53 * @param {PRNGStateMT19937} [options.state] - pseudorandom number generator state 54 * @param {boolean} [options.copy=true] - boolean indicating whether to copy a provided pseudorandom number generator state 55 * @throws {TypeError} `r` must be a positive number 56 * @throws {TypeError} `p` must be number 57 * @throws {RangeError} `p` must be a number on the interval `(0,1)` 58 * @throws {TypeError} options argument must be an object 59 * @throws {TypeError} must provide valid options 60 * @throws {Error} must provide a valid state 61 * @returns {PRNG} pseudorandom number generator 62 * 63 * @example 64 * var negativeBinomial = factory( 10, 0.5 ); 65 * 66 * var v = negativeBinomial(); 67 * // returns <number> 68 * 69 * @example 70 * var negativeBinomial = factory( 10, 0.8, { 71 * 'seed': 297 72 * }); 73 * 74 * var v = negativeBinomial(); 75 * // returns <number> 76 * 77 * @example 78 * var negativeBinomial = factory(); 79 * 80 * var v = negativeBinomial( 10, 0.5 ); 81 * // returns <number> 82 */ 83 function factory() { 84 var rgamma; 85 var STATE; 86 var rpois; 87 var opts; 88 var prng; 89 var rand; 90 var FLG; 91 var err; 92 var p; 93 var r; 94 95 FLG = true; 96 if ( arguments.length === 0 ) { 97 opts = { 98 'copy': false 99 }; 100 rpois = poisson( opts ); 101 } else if ( arguments.length === 1 ) { 102 opts = arguments[ 0 ]; 103 if ( !isObject( opts ) ) { 104 throw new TypeError( 'invalid argument. Options argument must be an object. Value: `' + opts + '`.' ); 105 } 106 if ( hasOwnProp( opts, 'copy' ) && !isBoolean( opts.copy ) ) { 107 throw new TypeError( 'invalid option. `copy` option must be a boolean. Option: `' + opts.copy + '`.' ); 108 } 109 if ( hasOwnProp( opts, 'prng' ) ) { 110 if ( !isFunction( opts.prng ) ) { 111 throw new TypeError( 'invalid option. `prng` option must be a pseudorandom number generator function. Option: `' + opts.prng + '`.' ); 112 } 113 rpois = poisson({ 114 'prng': opts.prng 115 }); 116 } else { 117 if ( hasOwnProp( opts, 'state' ) && !isUint32Array( opts.state ) ) { 118 throw new TypeError( 'invalid option. `state` option must be a Uint32Array. Option: `' + opts.state + '`.' ); 119 } 120 opts = copy( opts, 1 ); 121 if ( opts.copy === false ) { 122 FLG = false; 123 } else if ( opts.state ) { 124 opts.state = gcopy( opts.state.length, opts.state, 1, new Uint32Array( opts.state.length ), 1 ); // eslint-disable-line max-len 125 } 126 opts.copy = false; 127 rpois = poisson( opts ); 128 } 129 } else { 130 r = arguments[ 0 ]; 131 p = arguments[ 1 ]; 132 err = validate( r, p ); 133 if ( err ) { 134 throw err; 135 } 136 if ( arguments.length > 2 ) { 137 opts = arguments[ 2 ]; 138 if ( !isObject( opts ) ) { 139 throw new TypeError( 'invalid argument. Options argument must be an object. Value: `' + opts + '`.' ); 140 } 141 if ( hasOwnProp( opts, 'copy' ) && !isBoolean( opts.copy ) ) { 142 throw new TypeError( 'invalid option. `copy` option must be a boolean. Option: `' + opts.copy + '`.' ); 143 } 144 if ( hasOwnProp( opts, 'prng' ) ) { 145 if ( !isFunction( opts.prng ) ) { 146 throw new TypeError( 'invalid option. `prng` option must be a pseudorandom number generator function. Option: `' + opts.prng + '`.' ); 147 } 148 rpois = poisson({ 149 'prng': opts.prng 150 }); 151 } else { 152 if ( hasOwnProp( opts, 'state' ) && !isUint32Array( opts.state ) ) { 153 throw new TypeError( 'invalid option. `state` option must be a Uint32Array. Option: `' + opts.state + '`.' ); 154 } 155 opts = copy( opts, 1 ); 156 if ( opts.copy === false ) { 157 FLG = false; 158 } else if ( opts.state ) { 159 opts.state = gcopy( opts.state.length, opts.state, 1, new Uint32Array( opts.state.length ), 1 ); // eslint-disable-line max-len 160 } 161 opts.copy = false; 162 rpois = poisson( opts ); 163 } 164 } else { 165 opts = { 166 'copy': false 167 }; 168 rpois = poisson( opts ); 169 } 170 } 171 if ( opts && opts.prng ) { 172 if ( r === void 0 ) { 173 rgamma = gamma({ 174 'prng': opts.prng 175 }); 176 } else { 177 rgamma = gamma( r, p/(1-p), { 178 'prng': opts.prng 179 }); 180 } 181 } else { 182 if ( opts.state ) { 183 STATE = opts.state; 184 } else { 185 STATE = rpois.state; 186 rpois.state = STATE; // updates the underlying PRNG to point to a shared state 187 } 188 if ( r === void 0 ) { 189 rgamma = gamma({ 190 'state': STATE, 191 'copy': false 192 }); 193 } else { 194 rgamma = gamma( r, p/(1-p), { 195 'state': STATE, 196 'copy': false 197 }); 198 } 199 } 200 if ( r === void 0 ) { 201 prng = negativeBinomial2; 202 } else { 203 prng = negativeBinomial1; 204 } 205 rand = rpois.PRNG; 206 207 setReadOnly( prng, 'NAME', 'negative-binomial' ); 208 209 // 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. 210 if ( opts && opts.prng ) { 211 setReadOnly( prng, 'seed', null ); 212 setReadOnly( prng, 'seedLength', null ); 213 setReadWriteAccessor( prng, 'state', constantFunction( null ), noop ); 214 setReadOnly( prng, 'stateLength', null ); 215 setReadOnly( prng, 'byteLength', null ); 216 setReadOnly( prng, 'toJSON', constantFunction( null ) ); 217 } else { 218 setReadOnlyAccessor( prng, 'seed', getSeed ); 219 setReadOnlyAccessor( prng, 'seedLength', getSeedLength ); 220 setReadWriteAccessor( prng, 'state', getState, setState ); 221 setReadOnlyAccessor( prng, 'stateLength', getStateLength ); 222 setReadOnlyAccessor( prng, 'byteLength', getStateSize ); 223 setReadOnly( prng, 'toJSON', toJSON ); 224 } 225 setReadOnly( prng, 'PRNG', rand ); 226 return prng; 227 228 /** 229 * Returns the PRNG seed. 230 * 231 * @private 232 * @returns {PRNGSeedMT19937} seed 233 */ 234 function getSeed() { 235 return rand.seed; 236 } 237 238 /** 239 * Returns the PRNG seed length. 240 * 241 * @private 242 * @returns {PositiveInteger} seed length 243 */ 244 function getSeedLength() { 245 return rand.seedLength; 246 } 247 248 /** 249 * Returns the PRNG state length. 250 * 251 * @private 252 * @returns {PositiveInteger} state length 253 */ 254 function getStateLength() { 255 return rand.stateLength; 256 } 257 258 /** 259 * Returns the PRNG state size (in bytes). 260 * 261 * @private 262 * @returns {PositiveInteger} state size (in bytes) 263 */ 264 function getStateSize() { 265 return rand.byteLength; 266 } 267 268 /** 269 * Returns the current pseudorandom number generator state. 270 * 271 * @private 272 * @returns {PRNGStateMT19937} current state 273 */ 274 function getState() { 275 return rand.state; 276 } 277 278 /** 279 * Sets the pseudorandom number generator state. 280 * 281 * @private 282 * @param {PRNGStateMT19937} s - generator state 283 * @throws {TypeError} must provide a `Uint32Array` 284 * @throws {Error} must provide a valid state 285 */ 286 function setState( s ) { 287 if ( !isUint32Array( s ) ) { 288 throw new TypeError( 'invalid argument. Must provide a Uint32Array. Value: `' + s + '`.' ); 289 } 290 if ( FLG ) { 291 s = gcopy( s.length, s, 1, new Uint32Array( s.length ), 1 ); 292 } 293 rand.state = s; 294 } 295 296 /** 297 * Serializes the pseudorandom number generator as a JSON object. 298 * 299 * ## Notes 300 * 301 * - `JSON.stringify()` implicitly calls this method when stringifying a PRNG. 302 * 303 * @private 304 * @returns {Object} JSON representation 305 */ 306 function toJSON() { 307 var out = {}; 308 out.type = 'PRNG'; 309 out.name = prng.NAME; 310 out.state = typedarray2json( rand.state ); 311 if ( r === void 0 ) { 312 out.params = []; 313 } else { 314 out.params = [ r, p ]; 315 } 316 return out; 317 } 318 319 /** 320 * Returns a pseudorandom number drawn from a negative binomial distribution with bound parameters `r` and `p`. 321 * 322 * @private 323 * @returns {NonNegativeInteger} pseudorandom number 324 * 325 * @example 326 * var v = negativeBinomial1(); 327 * // returns <number> 328 */ 329 function negativeBinomial1() { 330 return rpois( rgamma() ); 331 } 332 333 /** 334 * Returns a pseudorandom number drawn from a negative binomial distribution with parameters `r` and `p`. 335 * 336 * @private 337 * @param {PositiveNumber} r - number of successes until experiment is stopped 338 * @param {number} p - success probability 339 * @returns {NonNegativeInteger} pseudorandom number 340 * 341 * @example 342 * var v = negativeBinomial2( 10, 0.5 ); 343 * // returns <number> 344 */ 345 function negativeBinomial2( r, p ) { 346 if ( 347 isnan( r ) || 348 isnan( p ) || 349 p <= 0.0 || 350 p >= 1.0 351 ) { 352 return NaN; 353 } 354 return rpois( rgamma( r, p/(1-p) ) ); 355 } 356 } 357 358 359 // EXPORTS // 360 361 module.exports = factory;