factory.js (7077B)
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 isPositive = require( '@stdlib/assert/is-positive-number' ).isPrimitive; 27 var isObject = require( '@stdlib/assert/is-plain-object' ); 28 var isFunction = require( '@stdlib/assert/is-function' ); 29 var hasOwnProp = require( '@stdlib/assert/has-own-property' ); 30 var constantFunction = require( '@stdlib/utils/constant-function' ); 31 var noop = require( '@stdlib/utils/noop' ); 32 var isnan = require( '@stdlib/math/base/assert/is-nan' ); 33 var gamma = require( './../../../base/gamma' ).factory; 34 var typedarray2json = require( '@stdlib/array/to-json' ); 35 36 37 // MAIN // 38 39 /** 40 * Returns a pseudorandom number generator for generating chi-square distributed random numbers. 41 * 42 * @param {PositiveNumber} [k] - degrees of freedom 43 * @param {Options} [options] - function options 44 * @param {PRNG} [options.prng] - pseudorandom number generator which generates uniformly distributed pseudorandom numbers 45 * @param {PRNGSeedMT19937} [options.seed] - pseudorandom number generator seed 46 * @param {PRNGStateMT19937} [options.state] - pseudorandom number generator state 47 * @param {boolean} [options.copy=true] - boolean indicating whether to copy a provided pseudorandom number generator state 48 * @throws {TypeError} `k` must be a positive number 49 * @throws {TypeError} options argument must be an object 50 * @throws {TypeError} must provide valid options 51 * @throws {Error} must provide a valid state 52 * @returns {PRNG} pseudorandom number generator 53 * 54 * @example 55 * var chisquare = factory( 2 ); 56 * var v = chisquare(); 57 * // returns <number> 58 * 59 * @example 60 * var chisquare = factory( 2, { 61 * 'seed': 297 62 * }); 63 * var v = chisquare(); 64 * // returns <number> 65 */ 66 function factory() { 67 var rgamma; 68 var rand; 69 var opts; 70 var prng; 71 var k; 72 if ( arguments.length === 0 ) { 73 rgamma = gamma(); 74 } else if ( 75 arguments.length === 1 && 76 isObject( arguments[ 0 ] ) 77 ) { 78 opts = arguments[ 0 ]; 79 if ( hasOwnProp( opts, 'prng' ) ) { 80 if ( !isFunction( opts.prng ) ) { 81 throw new TypeError( 'invalid option. `prng` option must be a pseudorandom number generator function. Option: `' + opts.prng + '`.' ); 82 } 83 rgamma = gamma({ 84 'prng': opts.prng 85 }); 86 } else { 87 rgamma = gamma( opts ); 88 } 89 } else { 90 k = arguments[ 0 ]; 91 if ( !isPositive( k ) ) { 92 throw new TypeError( 'invalid argument. First argument must be a positive number. Value: `' + k + '`.' ); 93 } 94 if ( arguments.length > 1 ) { 95 opts = arguments[ 1 ]; 96 if ( !isObject( opts ) ) { 97 throw new TypeError( 'invalid argument. Options argument must be an object. Value: `' + opts + '`.' ); 98 } 99 if ( hasOwnProp( opts, 'prng' ) ) { 100 if ( !isFunction( opts.prng ) ) { 101 throw new TypeError( 'invalid option. `prng` option must be a pseudorandom number generator function. Option: `' + opts.prng + '`.' ); 102 } 103 rgamma = gamma( k/2.0, 0.5, { 104 'prng': opts.prng 105 }); 106 } else { 107 rgamma = gamma( k/2.0, 0.5, opts ); 108 } 109 } else { 110 rgamma = gamma( k/2.0, 0.5 ); 111 } 112 } 113 if ( k === void 0 ) { 114 prng = chisquare2; 115 } else { 116 prng = chisquare1; 117 } 118 rand = rgamma.PRNG; 119 120 setReadOnly( prng, 'NAME', 'chisquare' ); 121 122 // 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. 123 if ( opts && opts.prng ) { 124 setReadOnly( prng, 'seed', null ); 125 setReadOnly( prng, 'seedLength', null ); 126 setReadWriteAccessor( prng, 'state', constantFunction( null ), noop ); 127 setReadOnly( prng, 'stateLength', null ); 128 setReadOnly( prng, 'byteLength', null ); 129 setReadOnly( prng, 'toJSON', constantFunction( null ) ); 130 } else { 131 setReadOnlyAccessor( prng, 'seed', getSeed ); 132 setReadOnlyAccessor( prng, 'seedLength', getSeedLength ); 133 setReadWriteAccessor( prng, 'state', getState, setState ); 134 setReadOnlyAccessor( prng, 'stateLength', getStateLength ); 135 setReadOnlyAccessor( prng, 'byteLength', getStateSize ); 136 setReadOnly( prng, 'toJSON', toJSON ); 137 } 138 setReadOnly( prng, 'PRNG', rand ); 139 return prng; 140 141 /** 142 * Returns the PRNG seed. 143 * 144 * @private 145 * @returns {PRNGSeedMT19937} seed 146 */ 147 function getSeed() { 148 return rand.seed; 149 } 150 151 /** 152 * Returns the PRNG seed length. 153 * 154 * @private 155 * @returns {PositiveInteger} seed length 156 */ 157 function getSeedLength() { 158 return rand.seedLength; 159 } 160 161 /** 162 * Returns the PRNG state length. 163 * 164 * @private 165 * @returns {PositiveInteger} state length 166 */ 167 function getStateLength() { 168 return rand.stateLength; 169 } 170 171 /** 172 * Returns the PRNG state size (in bytes). 173 * 174 * @private 175 * @returns {PositiveInteger} state size (in bytes) 176 */ 177 function getStateSize() { 178 return rand.byteLength; 179 } 180 181 /** 182 * Returns the current pseudorandom number generator state. 183 * 184 * @private 185 * @returns {PRNGStateMT19937} current state 186 */ 187 function getState() { 188 return rand.state; 189 } 190 191 /** 192 * Sets the pseudorandom number generator state. 193 * 194 * @private 195 * @param {PRNGStateMT19937} s - generator state 196 * @throws {Error} must provide a valid state 197 */ 198 function setState( s ) { 199 rand.state = s; 200 } 201 202 /** 203 * Serializes the pseudorandom number generator as a JSON object. 204 * 205 * ## Notes 206 * 207 * - `JSON.stringify()` implicitly calls this method when stringifying a PRNG. 208 * 209 * @private 210 * @returns {Object} JSON representation 211 */ 212 function toJSON() { 213 var out = {}; 214 out.type = 'PRNG'; 215 out.name = prng.NAME; 216 out.state = typedarray2json( rand.state ); 217 if ( k === void 0 ) { 218 out.params = []; 219 } else { 220 out.params = [ k ]; 221 } 222 return out; 223 } 224 225 /** 226 * Returns a random number drawn from a chi-square distribution with bound `k`. 227 * 228 * @private 229 * @returns {number} pseudorandom number 230 * 231 * @example 232 * var v = chisquare1(); 233 * // returns <number> 234 */ 235 function chisquare1() { 236 return rgamma(); 237 } 238 239 /** 240 * Returns a random number drawn from a chi-square distribution with degrees of freedom `k`. 241 * 242 * @private 243 * @param {PositiveNumber} k - degrees of freedom 244 * @returns {number} pseudorandom number 245 * 246 * @example 247 * var v = chisquare2( 2 ); 248 * // returns <number> 249 */ 250 function chisquare2( k ) { 251 if ( 252 isnan( k ) || 253 k <= 0 254 ) { 255 return NaN; 256 } 257 return rgamma( k/2.0, 0.5 ); 258 } 259 } 260 261 262 // EXPORTS // 263 264 module.exports = factory;