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