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