factory.js (7288B)
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 isBoolean = require( '@stdlib/assert/is-boolean' ).isPrimitive; 29 var hasOwnProp = require( '@stdlib/assert/has-own-property' ); 30 var isUint32Array = require( '@stdlib/assert/is-uint32array' ); 31 var mt19937 = require( './../../../base/mt19937' ).factory; 32 var constantFunction = require( '@stdlib/utils/constant-function' ); 33 var noop = require( '@stdlib/utils/noop' ); 34 var typedarray2json = require( '@stdlib/array/to-json' ); 35 var randn0 = require( './randn.js' ); 36 var getMin = require( './min.js' ); 37 var getMax = require( './max.js' ); 38 39 40 // MAIN // 41 42 /** 43 * Returns a pseudorandom number generator which implements the Box-Muller transform to generate standard normally distributed pseudorandom numbers. 44 * 45 * @param {Options} [options] - function options 46 * @param {PRNG} [options.prng] - pseudorandom number generator which generates uniformly distributed pseudorandom numbers 47 * @param {PRNGSeedMT19937} [options.seed] - pseudorandom number generator seed 48 * @param {PRNGStateMT19937} [options.state] - pseudorandom number generator state 49 * @param {boolean} [options.copy=true] - boolean indicating whether to copy a provided pseudorandom number generator state 50 * @throws {TypeError} must provide an object 51 * @throws {TypeError} must provide valid options 52 * @throws {Error} must provide a valid state 53 * @returns {PRNG} pseudorandom number generator 54 * 55 * @example 56 * var randn = factory(); 57 * 58 * var r = randn(); 59 * // returns <number> 60 * 61 * @example 62 * // Return a seeded PRNG: 63 * var randn = factory({ 64 * 'seed': 12345 65 * }); 66 * 67 * var r = randn(); 68 * // returns <number> 69 */ 70 function factory( options ) { 71 var randu; 72 var randn; 73 var rand; 74 var opts; 75 76 opts = { 77 'copy': true 78 }; 79 if ( arguments.length ) { 80 if ( !isObject( options ) ) { 81 throw new TypeError( 'invalid argument. Must provide an object. Value: `' + options + '`.' ); 82 } 83 if ( hasOwnProp( options, 'copy' ) ) { 84 opts.copy = options.copy; 85 if ( !isBoolean( options.copy ) ) { 86 throw new TypeError( 'invalid option. `copy` option must be a boolean. Option: `' + options.copy + '`.' ); 87 } 88 } 89 if ( hasOwnProp( options, 'prng' ) ) { 90 if ( !isFunction( options.prng ) ) { 91 throw new TypeError( 'invalid option. `prng` option must be a pseudorandom number generator function. Option: `' + options.prng + '`.' ); 92 } 93 randu = options.prng; 94 } 95 // If provided a PRNG, ignore the `state` option, as we don't support getting or setting PRNG state. 96 else if ( hasOwnProp( options, 'state' ) ) { 97 opts.state = options.state; 98 if ( !isUint32Array( options.state ) ) { 99 throw new TypeError( 'invalid option. `state` option must be a Uint32Array. Option: `' + options.state + '`.' ); 100 } 101 } 102 // If provided a PRNG, ignore the `seed` option, as a `seed`, by itself, is insufficient to guarantee reproducibility. If provided a state, ignore the `seed` option, as a PRNG state should contain seed information. 103 else if ( hasOwnProp( options, 'seed' ) ) { 104 opts.seed = options.seed; 105 if ( options.seed === void 0 ) { 106 throw new TypeError( 'invalid option. `seed` option must be either a positive integer less than or equal to the maximum unsigned 32-bit integer or an array-like object containing integer values less than or equal to the maximum unsigned 32-bit integer. Option: `' + options.seed + '`.' ); 107 } 108 } 109 } 110 if ( opts.state === void 0 ) { 111 if ( randu === void 0 ) { 112 rand = mt19937( opts ); 113 randu = rand.normalized; 114 } else { 115 opts.seed = null; 116 } 117 } else { 118 rand = mt19937( opts ); 119 randu = rand.normalized; 120 } 121 randn = randn0( randu ); 122 123 setReadOnly( randn, 'NAME', 'box-muller' ); 124 if ( opts.seed === null ) { 125 setReadOnly( randn, 'seed', null ); 126 setReadOnly( randn, 'seedLength', null ); 127 } else { 128 setReadOnlyAccessor( randn, 'seed', getSeed ); 129 setReadOnlyAccessor( randn, 'seedLength', getSeedLength ); 130 } 131 // 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. 132 if ( options && options.prng ) { 133 setReadWriteAccessor( randn, 'state', constantFunction( null ), noop ); 134 setReadOnly( randn, 'stateLength', null ); 135 setReadOnly( randn, 'byteLength', null ); 136 setReadOnly( randn, 'toJSON', constantFunction( null ) ); 137 } else { 138 setReadWriteAccessor( randn, 'state', getState, setState ); 139 setReadOnlyAccessor( randn, 'stateLength', getStateLength ); 140 setReadOnlyAccessor( randn, 'byteLength', getStateSize ); 141 setReadOnly( randn, 'toJSON', toJSON ); 142 } 143 setReadOnly( randn, 'PRNG', randu ); 144 145 if ( hasOwnProp( randu, 'MIN' ) ) { 146 setReadOnly( randn, 'MIN', getMin( randu.MIN ) ); 147 setReadOnly( randn, 'MAX', getMax( randu.MIN ) ); 148 } else { 149 setReadOnly( randn, 'MIN', null ); 150 setReadOnly( randn, 'MAX', null ); 151 } 152 153 return randn; 154 155 /** 156 * Returns the PRNG seed. 157 * 158 * @private 159 * @returns {PRNGSeedMT19937} seed 160 */ 161 function getSeed() { 162 return rand.seed; 163 } 164 165 /** 166 * Returns the PRNG seed length. 167 * 168 * @private 169 * @returns {PositiveInteger} seed length 170 */ 171 function getSeedLength() { 172 return rand.seedLength; 173 } 174 175 /** 176 * Returns the PRNG state length. 177 * 178 * @private 179 * @returns {PositiveInteger} state length 180 */ 181 function getStateLength() { 182 return rand.stateLength; 183 } 184 185 /** 186 * Returns the PRNG state size (in bytes). 187 * 188 * @private 189 * @returns {PositiveInteger} state size (in bytes) 190 */ 191 function getStateSize() { 192 return rand.byteLength; 193 } 194 195 /** 196 * Returns the current pseudorandom number generator state. 197 * 198 * @private 199 * @returns {PRNGStateMT19937} current state 200 */ 201 function getState() { 202 return rand.state; 203 } 204 205 /** 206 * Sets the pseudorandom number generator state. 207 * 208 * @private 209 * @param {PRNGStateMT19937} s - generator state 210 * @throws {Error} must provide a valid state 211 */ 212 function setState( s ) { 213 rand.state = s; 214 } 215 216 /** 217 * Serializes the pseudorandom number generator as a JSON object. 218 * 219 * ## Notes 220 * 221 * - `JSON.stringify()` implicitly calls this method when stringifying a PRNG. 222 * 223 * @private 224 * @returns {Object} JSON representation 225 */ 226 function toJSON() { 227 var out = {}; 228 out.type = 'PRNG'; 229 out.name = randn.NAME; 230 out.state = typedarray2json( rand.state ); 231 out.params = []; 232 return out; 233 } 234 } 235 236 237 // EXPORTS // 238 239 module.exports = factory;