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