main.js (7325B)
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 constantFunction = require( '@stdlib/utils/constant-function' ); 27 var noop = require( '@stdlib/utils/noop' ); 28 var copy = require( '@stdlib/utils/copy' ); 29 var isObject = require( '@stdlib/assert/is-plain-object' ); 30 var isNonNegativeInteger = require( '@stdlib/assert/is-nonnegative-integer' ).isPrimitive; 31 var hasOwnProp = require( '@stdlib/assert/has-own-property' ); 32 var MAX_VALUE = require( '@stdlib/constants/float64/max' ); 33 var rhypergeom = require( './../../../base/hypergeometric' ).factory; 34 var iteratorSymbol = require( '@stdlib/symbol/iterator' ); 35 36 37 // MAIN // 38 39 /** 40 * Returns an iterator for generating pseudorandom numbers drawn from a hypergeometric distribution. 41 * 42 * @param {NonNegativeInteger} N - population size 43 * @param {NonNegativeInteger} K - subpopulation size 44 * @param {NonNegativeInteger} n - number of draws 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 * @param {NonNegativeInteger} [options.iter] - number of iterations 51 * @throws {TypeError} first argument must be a nonnegative integer 52 * @throws {TypeError} second argument must be a nonnegative integer 53 * @throws {TypeError} third argument must be a nonnegative integer 54 * @throws {RangeError} `n` must be less than or equal to `N` 55 * @throws {RangeError} `K` must be less than or equal to `N` 56 * @throws {TypeError} options argument must be an object 57 * @throws {TypeError} must provide valid options 58 * @throws {Error} must provide a valid state 59 * @returns {Iterator} iterator 60 * 61 * @example 62 * var iter = iterator( 10, 5, 3 ); 63 * 64 * var r = iter.next().value; 65 * // returns <number> 66 * 67 * r = iter.next().value; 68 * // returns <number> 69 * 70 * r = iter.next().value; 71 * // returns <number> 72 * 73 * // ... 74 */ 75 function iterator( N, K, n, options ) { 76 var opts; 77 var iter; 78 var rand; 79 var FLG; 80 var i; 81 if ( !isNonNegativeInteger( N ) ) { 82 throw new TypeError( 'invalid argument. First argument must be a nonnegative integer. Value: `'+N+'`.' ); 83 } 84 if ( !isNonNegativeInteger( K ) ) { 85 throw new TypeError( 'invalid argument. Second argument must be a nonnegative integer. Value: `'+K+'`.' ); 86 } 87 if ( !isNonNegativeInteger( n ) ) { 88 throw new TypeError( 'invalid argument. Third argument must be a nonnegative integer. Value: `'+n+'`.' ); 89 } 90 if ( n > N ) { 91 throw new RangeError( 'invalid argument. `n` must be less than or equal to `N`. Value: `' + n + '`.' ); 92 } 93 if ( K > N ) { 94 throw new RangeError( 'invalid argument. `K` must be less than or equal to `N`. Value: `' + K + '`.' ); 95 } 96 if ( arguments.length > 3 ) { 97 if ( !isObject( options ) ) { 98 throw new TypeError( 'invalid argument. Options argument must be an object. Value: `'+options+'`.' ); 99 } 100 opts = copy( options, 1 ); 101 if ( hasOwnProp( opts, 'iter' ) ) { 102 if ( !isNonNegativeInteger( opts.iter ) ) { 103 throw new TypeError( 'invalid option. `iter` option must be a nonnegative integer. Option: `'+opts.iter+'`.' ); 104 } 105 } else { 106 opts.iter = MAX_VALUE; 107 } 108 rand = rhypergeom( N, K, n, opts ); 109 if ( opts.prng === void 0 && opts.copy !== false ) { 110 opts.state = rand.state; // cache a copy of the PRNG state 111 } 112 } else { 113 rand = rhypergeom( N, K, n ); 114 opts = { 115 'iter': MAX_VALUE, 116 'state': rand.state // cache a copy of the PRNG state 117 }; 118 } 119 i = 0; 120 121 // Create an iterator protocol-compliant object: 122 iter = {}; 123 setReadOnly( iter, 'next', next ); 124 setReadOnly( iter, 'return', end ); 125 126 if ( opts && opts.prng ) { 127 setReadOnly( iter, 'seed', null ); 128 setReadOnly( iter, 'seedLength', null ); 129 setReadWriteAccessor( iter, 'state', constantFunction( null ), noop ); 130 setReadOnly( iter, 'stateLength', null ); 131 setReadOnly( iter, 'byteLength', null ); 132 } else { 133 setReadOnlyAccessor( iter, 'seed', getSeed ); 134 setReadOnlyAccessor( iter, 'seedLength', getSeedLength ); 135 setReadWriteAccessor( iter, 'state', getState, setState ); 136 setReadOnlyAccessor( iter, 'stateLength', getStateLength ); 137 setReadOnlyAccessor( iter, 'byteLength', getStateSize ); 138 } 139 setReadOnly( iter, 'PRNG', rand.PRNG ); 140 141 // If an environment supports `Symbol.iterator`, make the iterator iterable: 142 if ( iteratorSymbol ) { 143 setReadOnly( iter, iteratorSymbol, factory ); 144 } 145 return iter; 146 147 /** 148 * Returns an iterator protocol-compliant object containing the next iterated value. 149 * 150 * @private 151 * @returns {Object} iterator protocol-compliant object 152 */ 153 function next() { 154 i += 1; 155 if ( FLG || i > opts.iter ) { 156 return { 157 'done': true 158 }; 159 } 160 return { 161 'value': rand(), 162 'done': false 163 }; 164 } 165 166 /** 167 * Finishes an iterator. 168 * 169 * @private 170 * @param {*} [value] - value to return 171 * @returns {Object} iterator protocol-compliant object 172 */ 173 function end( value ) { 174 FLG = true; 175 if ( arguments.length ) { 176 return { 177 'value': value, 178 'done': true 179 }; 180 } 181 return { 182 'done': true 183 }; 184 } 185 186 /** 187 * Returns a new iterator. 188 * 189 * @private 190 * @returns {Iterator} iterator 191 */ 192 function factory() { 193 return iterator( N, K, n, opts ); 194 } 195 196 /** 197 * Returns the PRNG seed. 198 * 199 * @private 200 * @returns {PRNGSeedMT19937} seed 201 */ 202 function getSeed() { 203 return rand.PRNG.seed; 204 } 205 206 /** 207 * Returns the PRNG seed length. 208 * 209 * @private 210 * @returns {PositiveInteger} seed length 211 */ 212 function getSeedLength() { 213 return rand.PRNG.seedLength; 214 } 215 216 /** 217 * Returns the PRNG state length. 218 * 219 * @private 220 * @returns {PositiveInteger} state length 221 */ 222 function getStateLength() { 223 return rand.PRNG.stateLength; 224 } 225 226 /** 227 * Returns the PRNG state size (in bytes). 228 * 229 * @private 230 * @returns {PositiveInteger} state size (in bytes) 231 */ 232 function getStateSize() { 233 return rand.PRNG.byteLength; 234 } 235 236 /** 237 * Returns the current pseudorandom number generator state. 238 * 239 * @private 240 * @returns {PRNGStateMT19937} current state 241 */ 242 function getState() { 243 return rand.PRNG.state; 244 } 245 246 /** 247 * Sets the pseudorandom number generator state. 248 * 249 * @private 250 * @param {PRNGStateMT19937} s - generator state 251 * @throws {Error} must provide a valid state 252 */ 253 function setState( s ) { 254 rand.PRNG.state = s; 255 } 256 } 257 258 259 // EXPORTS // 260 261 module.exports = iterator;