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