time-to-botec

Benchmark sampling in different programming languages
Log | Files | Refs | README

factory.js (14850B)


      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 /* eslint-disable max-len */
     20 
     21 'use strict';
     22 
     23 // MODULES //
     24 
     25 var setReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' );
     26 var setReadOnlyAccessor = require( '@stdlib/utils/define-nonenumerable-read-only-accessor' );
     27 var setReadWriteAccessor = require( '@stdlib/utils/define-nonenumerable-read-write-accessor' );
     28 var hasOwnProp = require( '@stdlib/assert/has-own-property' );
     29 var isObject = require( '@stdlib/assert/is-plain-object' );
     30 var isBoolean = require( '@stdlib/assert/is-boolean' ).isPrimitive;
     31 var isCollection = require( '@stdlib/assert/is-collection' );
     32 var isPositiveInteger = require( '@stdlib/assert/is-positive-integer' ).isPrimitive;
     33 var isInt32Array = require( '@stdlib/assert/is-int32array' );
     34 var INT32_MAX = require( '@stdlib/constants/int32/max' );
     35 var Int32Array = require( '@stdlib/array/int32' );
     36 var gcopy = require( '@stdlib/blas/base/gcopy' );
     37 var typedarray2json = require( '@stdlib/array/to-json' );
     38 var randint32 = require( './rand_int32.js' );
     39 
     40 
     41 // VARIABLES //
     42 
     43 var NORMALIZATION_CONSTANT = (INT32_MAX - 1)|0; // asm type annotation
     44 var MAX_SEED = (INT32_MAX - 1)|0; // asm type annotation
     45 var A = 16807|0; // asm type annotation
     46 
     47 // Define the state array schema version:
     48 var STATE_ARRAY_VERSION = 1; // NOTE: anytime the state array schema changes, this value should be incremented!!!
     49 
     50 // Define the number of sections in the state array:
     51 var NUM_STATE_SECTIONS = 2; // state, seed
     52 
     53 // Define the index offset of the "state" section in the state array:
     54 var STATE_SECTION_OFFSET = 2; // | version | num_sections | state_length | ...state | seed_length | ...seed |
     55 
     56 // Define the index offset of the seed section in the state array:
     57 var SEED_SECTION_OFFSET = 4; // | version | num_sections | state_length | ...state | seed_length | ...seed |
     58 
     59 // Define the length of the "fixed" length portion of the state array:
     60 var STATE_FIXED_LENGTH = 5; // 1 (version) + 1 (num_sections) + 1 (state_length) + 1 (state) + 1 (seed_length)
     61 
     62 
     63 // FUNCTIONS //
     64 
     65 /**
     66 * Verifies state array integrity.
     67 *
     68 * @private
     69 * @param {Int32Array} state - state array
     70 * @param {boolean} FLG - flag indicating whether the state array was provided as an option (true) or an argument (false)
     71 * @returns {(Error|null)} an error or `null`
     72 */
     73 function verifyState( state, FLG ) {
     74 	var s1;
     75 	if ( FLG ) {
     76 		s1 = 'option';
     77 	} else {
     78 		s1 = 'argument';
     79 	}
     80 	// The state array must have a minimum length...
     81 	if ( state.length < STATE_FIXED_LENGTH+1 ) {
     82 		return new RangeError( 'invalid '+s1+'. `state` array has insufficient length.' );
     83 	}
     84 	// The first element of the state array must equal the supported state array schema version...
     85 	if ( state[ 0 ] !== STATE_ARRAY_VERSION ) {
     86 		return new RangeError( 'invalid '+s1+'. `state` array has an incompatible schema version. Expected: '+STATE_ARRAY_VERSION+'. Actual: '+state[ 0 ]+'.' );
     87 	}
     88 	// The second element of the state array must contain the number of sections...
     89 	if ( state[ 1 ] !== NUM_STATE_SECTIONS ) {
     90 		return new RangeError( 'invalid '+s1+'. `state` array has an incompatible number of sections. Expected: '+NUM_STATE_SECTIONS+'. Actual: '+state[ 1 ]+'.' );
     91 	}
     92 	// The length of the "state" section must equal `1`...
     93 	if ( state[ STATE_SECTION_OFFSET ] !== 1 ) {
     94 		return new RangeError( 'invalid '+s1+'. `state` array has an incompatible state length. Expected: '+(1).toString()+'. Actual: '+state[ STATE_SECTION_OFFSET ]+'.' );
     95 	}
     96 	// The length of the "seed" section much match the empirical length...
     97 	if ( state[ SEED_SECTION_OFFSET ] !== state.length-STATE_FIXED_LENGTH ) {
     98 		return new RangeError( 'invalid '+s1+'. `state` array length is incompatible with seed section length. Expected: '+(state.length-STATE_FIXED_LENGTH)+'. Actual: '+state[ SEED_SECTION_OFFSET ]+'.' );
     99 	}
    100 	return null;
    101 }
    102 
    103 
    104 // MAIN //
    105 
    106 /**
    107 * Returns a linear congruential pseudorandom number generator (LCG) based on Park and Miller.
    108 *
    109 * @param {Options} [options] - options
    110 * @param {PRNGSeedMINSTD} [options.seed] - pseudorandom number generator seed
    111 * @param {PRNGStateMINSTD} [options.state] - pseudorandom number generator state
    112 * @param {boolean} [options.copy=true] - boolean indicating whether to copy a provided pseudorandom number generator state
    113 * @throws {TypeError} options argument must be an object
    114 * @throws {TypeError} a seed must be either a positive integer less than the maximum signed 32-bit integer or an array-like object containing integers less than the maximum signed 32-bit integer
    115 * @throws {RangeError} a numeric seed must be a positive integer less than the maximum signed 32-bit integer
    116 * @throws {TypeError} state must be an `Int32Array`
    117 * @throws {Error} must provide a valid state
    118 * @throws {TypeError} `copy` option must be a boolean
    119 * @returns {PRNG} LCG PRNG
    120 *
    121 * @example
    122 * var minstd = factory();
    123 *
    124 * var v = minstd();
    125 * // returns <number>
    126 *
    127 * @example
    128 * // Return a seeded LCG:
    129 * var minstd = factory({
    130 *     'seed': 1234
    131 * });
    132 *
    133 * var v = minstd();
    134 * // returns 20739838
    135 */
    136 function factory( options ) {
    137 	var STATE;
    138 	var state;
    139 	var opts;
    140 	var seed;
    141 	var slen;
    142 	var err;
    143 
    144 	opts = {};
    145 	if ( arguments.length ) {
    146 		if ( !isObject( options ) ) {
    147 			throw new TypeError( 'invalid argument. Options argument must be an object. Value: `' + options + '`.' );
    148 		}
    149 		if ( hasOwnProp( options, 'copy' ) ) {
    150 			opts.copy = options.copy;
    151 			if ( !isBoolean( options.copy ) ) {
    152 				throw new TypeError( 'invalid option. `copy` option must be a boolean. Option: `' + options.copy + '`.' );
    153 			}
    154 		}
    155 		if ( hasOwnProp( options, 'state' ) ) {
    156 			state = options.state;
    157 			opts.state = true;
    158 			if ( !isInt32Array( state ) ) {
    159 				throw new TypeError( 'invalid option. `state` option must be an Int32Array. Option: `' + state + '`.' );
    160 			}
    161 			err = verifyState( state, true );
    162 			if ( err ) {
    163 				throw err;
    164 			}
    165 			if ( opts.copy === false ) {
    166 				STATE = state;
    167 			} else {
    168 				STATE = new Int32Array( state.length );
    169 				gcopy( state.length, state, 1, STATE, 1 );
    170 			}
    171 			// Create a state "view":
    172 			state = new Int32Array( STATE.buffer, STATE.byteOffset+((STATE_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), 1 );
    173 
    174 			// Create a seed "view":
    175 			seed = new Int32Array( STATE.buffer, STATE.byteOffset+((SEED_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), state[ SEED_SECTION_OFFSET ] );
    176 		}
    177 		// If provided a PRNG state, we ignore the `seed` option...
    178 		if ( seed === void 0 ) {
    179 			if ( hasOwnProp( options, 'seed' ) ) {
    180 				seed = options.seed;
    181 				opts.seed = true;
    182 				if ( isPositiveInteger( seed ) ) {
    183 					if ( seed > MAX_SEED ) {
    184 						throw new RangeError( 'invalid option. `seed` option must be a positive integer less than the maximum signed 32-bit integer. Option: `' + seed + '`.' );
    185 					}
    186 					seed |= 0; // asm type annotation
    187 				} else if ( isCollection( seed ) && seed.length > 0 ) {
    188 					slen = seed.length;
    189 					STATE = new Int32Array( STATE_FIXED_LENGTH+slen );
    190 
    191 					// Initialize sections:
    192 					STATE[ 0 ] = STATE_ARRAY_VERSION;
    193 					STATE[ 1 ] = NUM_STATE_SECTIONS;
    194 					STATE[ STATE_SECTION_OFFSET ] = 1;
    195 					STATE[ SEED_SECTION_OFFSET ] = slen;
    196 
    197 					// Copy the provided seed array to prevent external mutation, as mutation would lead to an inability to reproduce PRNG values according to the PRNG's stated seed:
    198 					gcopy.ndarray( slen, seed, 1, 0, STATE, 1, SEED_SECTION_OFFSET+1 );
    199 
    200 					// Create a state "view":
    201 					state = new Int32Array( STATE.buffer, STATE.byteOffset+((STATE_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), 1 );
    202 
    203 					// Create a seed "view":
    204 					seed = new Int32Array( STATE.buffer, STATE.byteOffset+((SEED_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), slen );
    205 
    206 					// Initialize the internal PRNG state:
    207 					state[ 0 ] = seed[ 0 ];
    208 				} else {
    209 					throw new TypeError( 'invalid option. `seed` option must be either a positive integer less than the maximum signed 32-bit integer or an array-like object containing integer values less than the maximum signed 32-bit integer. Option: `' + seed + '`.' );
    210 				}
    211 			} else {
    212 				seed = randint32()|0; // asm type annotation
    213 			}
    214 		}
    215 	} else {
    216 		seed = randint32()|0; // asm type annotation
    217 	}
    218 	if ( state === void 0 ) {
    219 		STATE = new Int32Array( STATE_FIXED_LENGTH+1 );
    220 
    221 		// Initialize sections:
    222 		STATE[ 0 ] = STATE_ARRAY_VERSION;
    223 		STATE[ 1 ] = NUM_STATE_SECTIONS;
    224 		STATE[ STATE_SECTION_OFFSET ] = 1;
    225 		STATE[ SEED_SECTION_OFFSET ] = 1;
    226 		STATE[ SEED_SECTION_OFFSET+1 ] = seed;
    227 
    228 		// Create a state "view":
    229 		state = new Int32Array( STATE.buffer, STATE.byteOffset+((STATE_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), 1 );
    230 
    231 		// Create a seed "view":
    232 		seed = new Int32Array( STATE.buffer, STATE.byteOffset+((SEED_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), 1 );
    233 
    234 		// Initialize the internal PRNG state:
    235 		state[ 0 ] = seed[ 0 ];
    236 	}
    237 	setReadOnly( minstd, 'NAME', 'minstd' );
    238 	setReadOnlyAccessor( minstd, 'seed', getSeed );
    239 	setReadOnlyAccessor( minstd, 'seedLength', getSeedLength );
    240 	setReadWriteAccessor( minstd, 'state', getState, setState );
    241 	setReadOnlyAccessor( minstd, 'stateLength', getStateLength );
    242 	setReadOnlyAccessor( minstd, 'byteLength', getStateSize );
    243 	setReadOnly( minstd, 'toJSON', toJSON );
    244 	setReadOnly( minstd, 'MIN', 1 );
    245 	setReadOnly( minstd, 'MAX', INT32_MAX-1 );
    246 	setReadOnly( minstd, 'normalized', normalized );
    247 
    248 	setReadOnly( normalized, 'NAME', minstd.NAME );
    249 	setReadOnlyAccessor( normalized, 'seed', getSeed );
    250 	setReadOnlyAccessor( normalized, 'seedLength', getSeedLength );
    251 	setReadWriteAccessor( normalized, 'state', getState, setState );
    252 	setReadOnlyAccessor( normalized, 'stateLength', getStateLength );
    253 	setReadOnlyAccessor( normalized, 'byteLength', getStateSize );
    254 	setReadOnly( normalized, 'toJSON', toJSON );
    255 	setReadOnly( normalized, 'MIN', (minstd.MIN-1.0) / NORMALIZATION_CONSTANT );
    256 	setReadOnly( normalized, 'MAX', (minstd.MAX-1.0) / NORMALIZATION_CONSTANT );
    257 
    258 	return minstd;
    259 
    260 	/**
    261 	* Returns the PRNG seed.
    262 	*
    263 	* @private
    264 	* @returns {PRNGSeedMINSTD} seed
    265 	*/
    266 	function getSeed() {
    267 		var len = STATE[ SEED_SECTION_OFFSET ];
    268 		return gcopy( len, seed, 1, new Int32Array( len ), 1 );
    269 	}
    270 
    271 	/**
    272 	* Returns the PRNG seed length.
    273 	*
    274 	* @private
    275 	* @returns {PositiveInteger} seed length
    276 	*/
    277 	function getSeedLength() {
    278 		return STATE[ SEED_SECTION_OFFSET ];
    279 	}
    280 
    281 	/**
    282 	* Returns the PRNG state length.
    283 	*
    284 	* @private
    285 	* @returns {PositiveInteger} state length
    286 	*/
    287 	function getStateLength() {
    288 		return STATE.length;
    289 	}
    290 
    291 	/**
    292 	* Returns the PRNG state size (in bytes).
    293 	*
    294 	* @private
    295 	* @returns {PositiveInteger} state size (in bytes)
    296 	*/
    297 	function getStateSize() {
    298 		return STATE.byteLength;
    299 	}
    300 
    301 	/**
    302 	* Returns the current PRNG state.
    303 	*
    304 	* ## Notes
    305 	*
    306 	* -   The PRNG state array is comprised of a preamble followed by `2` sections:
    307 	*
    308 	*     0.  preamble (version + number of sections)
    309 	*     1.  internal PRNG state
    310 	*     2.  PRNG seed
    311 	*
    312 	* -   The first element of the PRNG state array preamble is the state array schema version.
    313 	*
    314 	* -   The second element of the PRNG state array preamble is the number of state array sections (i.e., `2`).
    315 	*
    316 	* -   The first element of each section following the preamble specifies the section length. The remaining section elements comprise the section contents.
    317 	*
    318 	* @private
    319 	* @returns {PRNGStateMINSTD} current state
    320 	*/
    321 	function getState() {
    322 		var len = STATE.length;
    323 		return gcopy( len, STATE, 1, new Int32Array( len ), 1 );
    324 	}
    325 
    326 	/**
    327 	* Sets the PRNG state.
    328 	*
    329 	* ## Notes
    330 	*
    331 	* -   If PRNG state is "shared" (meaning a state array was provided during PRNG creation and **not** copied) and one sets the generator state to a state array having a different length, the PRNG does **not** update the existing shared state and, instead, points to the newly provided state array. In order to synchronize PRNG output according to the new shared state array, the state array for **each** relevant PRNG must be **explicitly** set.
    332 	* -   If PRNG state is "shared" and one sets the generator state to a state array of the same length, the PRNG state is updated (along with the state of all other PRNGs sharing the PRNG's state array).
    333 	*
    334 	* @private
    335 	* @param {PRNGStateMINSTD} s - generator state
    336 	* @throws {TypeError} must provide an `Int32Array`
    337 	* @throws {Error} must provide a valid state
    338 	*/
    339 	function setState( s ) {
    340 		var err;
    341 		if ( !isInt32Array( s ) ) {
    342 			throw new TypeError( 'invalid argument. Must provide an Int32Array. Value: `' + s + '`.' );
    343 		}
    344 		err = verifyState( s, false );
    345 		if ( err ) {
    346 			throw err;
    347 		}
    348 		if ( opts.copy === false ) {
    349 			if ( opts.state && s.length === STATE.length ) {
    350 				gcopy( s.length, s, 1, STATE, 1 ); // update current shared state
    351 			} else {
    352 				STATE = s; // point to new shared state
    353 				opts.state = true; // setting this flag allows updating a shared state even if a state array was not provided at PRNG creation
    354 			}
    355 		} else {
    356 			// Check if we can reuse allocated memory...
    357 			if ( s.length !== STATE.length ) {
    358 				STATE = new Int32Array( s.length ); // reallocate
    359 			}
    360 			gcopy( s.length, s, 1, STATE, 1 );
    361 		}
    362 		// Create a new state "view":
    363 		state = new Int32Array( STATE.buffer, STATE.byteOffset+((STATE_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), 1 );
    364 
    365 		// Create a new seed "view":
    366 		seed = new Int32Array( STATE.buffer, STATE.byteOffset+((SEED_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), STATE[ SEED_SECTION_OFFSET ] );
    367 	}
    368 
    369 	/**
    370 	* Serializes the pseudorandom number generator as a JSON object.
    371 	*
    372 	* ## Notes
    373 	*
    374 	* -   `JSON.stringify()` implicitly calls this method when stringifying a PRNG.
    375 	*
    376 	* @private
    377 	* @returns {Object} JSON representation
    378 	*/
    379 	function toJSON() {
    380 		var out = {};
    381 		out.type = 'PRNG';
    382 		out.name = minstd.NAME;
    383 		out.state = typedarray2json( STATE );
    384 		out.params = [];
    385 		return out;
    386 	}
    387 
    388 	/**
    389 	* Generates a pseudorandom integer on the interval \\( [1,2^{31}-1) \\).
    390 	*
    391 	* @private
    392 	* @returns {integer32} pseudorandom integer
    393 	*/
    394 	function minstd() {
    395 		var s = state[ 0 ]|0; // asm type annotation
    396 		s = ( (A*s)%INT32_MAX )|0; // asm type annotation
    397 		state[ 0 ] = s;
    398 		return s|0; // asm type annotation
    399 	}
    400 
    401 	/**
    402 	* Generates a pseudorandom number on the interval \\( [0,1) \\).
    403 	*
    404 	* @private
    405 	* @returns {number} pseudorandom number
    406 	*/
    407 	function normalized() {
    408 		return (minstd()-1) / NORMALIZATION_CONSTANT;
    409 	}
    410 }
    411 
    412 
    413 // EXPORTS //
    414 
    415 module.exports = factory;