factory.js (7257B)
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 isFunction = require( '@stdlib/assert/is-function' ); 27 var isObject = require( '@stdlib/assert/is-plain-object' ); 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 floor = require( '@stdlib/math/base/special/floor' ); 35 var UINT32_MAX = require( '@stdlib/constants/uint32/max' ); 36 var typedarray2json = require( '@stdlib/array/to-json' ); 37 var improvedZiggurat = require( './improved_ziggurat.js' ); 38 39 40 // MAIN // 41 42 /** 43 * Returns a pseudorandom number generator which implements the improved Ziggurat method to generate 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 randi; 73 var randn; 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 randi = mt19937( opts ); 113 randu = randi.normalized; 114 } else { 115 randi = mt19937({ 116 'seed': floor( 1.0 + ( UINT32_MAX*randu() ) ), // allows seeding via an externally seeded PRNG 117 'copy': opts.copy 118 }); 119 opts.seed = null; 120 } 121 } else { 122 randi = mt19937( opts ); 123 randu = randi.normalized; 124 } 125 randn = improvedZiggurat( randu, randi ); 126 127 setReadOnly( randn, 'NAME', 'improved-ziggurat' ); 128 if ( opts.seed === null ) { 129 setReadOnly( randn, 'seed', null ); 130 setReadOnly( randn, 'seedLength', null ); 131 } else { 132 setReadOnlyAccessor( randn, 'seed', getSeed ); 133 setReadOnlyAccessor( randn, 'seedLength', getSeedLength ); 134 } 135 // 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. 136 if ( options && options.prng ) { 137 setReadWriteAccessor( randn, 'state', constantFunction( null ), noop ); 138 setReadOnly( randn, 'stateLength', null ); 139 setReadOnly( randn, 'byteLength', null ); 140 setReadOnly( randn, 'toJSON', constantFunction( null ) ); 141 } else { 142 setReadWriteAccessor( randn, 'state', getState, setState ); 143 setReadOnlyAccessor( randn, 'stateLength', getStateLength ); 144 setReadOnlyAccessor( randn, 'byteLength', getStateSize ); 145 setReadOnly( randn, 'toJSON', toJSON ); 146 } 147 setReadOnly( randn, 'PRNG', randu ); 148 149 return randn; 150 151 /** 152 * Returns the PRNG seed. 153 * 154 * @private 155 * @returns {PRNGSeedMT19937} seed 156 */ 157 function getSeed() { 158 return randi.seed; 159 } 160 161 /** 162 * Returns the PRNG seed length. 163 * 164 * @private 165 * @returns {PositiveInteger} seed length 166 */ 167 function getSeedLength() { 168 return randi.seedLength; 169 } 170 171 /** 172 * Returns the PRNG state length. 173 * 174 * @private 175 * @returns {PositiveInteger} state length 176 */ 177 function getStateLength() { 178 return randi.stateLength; 179 } 180 181 /** 182 * Returns the PRNG state size (in bytes). 183 * 184 * @private 185 * @returns {PositiveInteger} state size (in bytes) 186 */ 187 function getStateSize() { 188 return randi.byteLength; 189 } 190 191 /** 192 * Returns the current PRNG state. 193 * 194 * @private 195 * @returns {PRNGStateMT19937} current state 196 */ 197 function getState() { 198 return randi.state; 199 } 200 201 /** 202 * Sets the PRNG state. 203 * 204 * @private 205 * @param {PRNGStateMT19937} s - generator state 206 * @throws {Error} must provide a valid state 207 */ 208 function setState( s ) { 209 randi.state = s; 210 } 211 212 /** 213 * Serializes the pseudorandom number generator as a JSON object. 214 * 215 * ## Notes 216 * 217 * - `JSON.stringify()` implicitly calls this method when stringifying a PRNG. 218 * 219 * @private 220 * @returns {Object} JSON representation 221 */ 222 function toJSON() { 223 var out = {}; 224 out.type = 'PRNG'; 225 out.name = randn.NAME; 226 out.state = typedarray2json( randi.state ); 227 out.params = []; 228 return out; 229 } 230 } 231 232 233 // EXPORTS // 234 235 module.exports = factory;