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