time-to-botec

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

main.js (8629B)


      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 isPositiveInteger = require( '@stdlib/assert/is-positive-integer' ).isPrimitive;
     24 var isNumber = require( '@stdlib/assert/is-number' ).isPrimitive;
     25 var isnan = require( '@stdlib/math/base/assert/is-nan' );
     26 
     27 
     28 // MAIN //
     29 
     30 /**
     31 * Returns an accumulator function which incrementally computes a moving variance-to-mean ratio (VMR).
     32 *
     33 * ## Method
     34 *
     35 * -   Let \\(W\\) be a window of \\(N\\) elements over which we want to compute a variance-to-mean ratio (VMR).
     36 *
     37 * -   The difference between the unbiased sample variance in a window \\(W_i\\) and the unbiased sample variance in a window \\(W_{i+1})\\) is given by
     38 *
     39 *     ```tex
     40 *     \Delta s^2 = s_{i+1}^2 - s_{i}^2
     41 *     ```
     42 *
     43 * -   If we multiply both sides by \\(N-1\\),
     44 *
     45 *     ```tex
     46 *     (N-1)(\Delta s^2) = (N-1)s_{i+1}^2 - (N-1)s_{i}^2
     47 *     ```
     48 *
     49 * -   If we substitute the definition of the unbiased sample variance having the form
     50 *
     51 *     ```tex
     52 *     \begin{align*}
     53 *     s^2 &= \frac{1}{N-1} \biggl( \sum_{i=1}^{N} (x_i - \bar{x})^2 \biggr) \\
     54 *         &= \frac{1}{N-1} \biggl( \sum_{i=1}^{N} (x_i^2 - 2\bar{x}x_i + \bar{x}^2) \biggr) \\
     55 *         &= \frac{1}{N-1} \biggl( \sum_{i=1}^{N} x_i^2 - 2\bar{x} \sum_{i=1}^{N} x_i + \sum_{i=1}^{N} \bar{x}^2) \biggr) \\
     56 *         &= \frac{1}{N-1} \biggl( \sum_{i=1}^{N} x_i^2 - \frac{2N\bar{x}\sum_{i=1}^{N} x_i}{N} + N\bar{x}^2 \biggr) \\
     57 *         &= \frac{1}{N-1} \biggl( \sum_{i=1}^{N} x_i^2 - 2N\bar{x}^2 + N\bar{x}^2 \biggr) \\
     58 *         &= \frac{1}{N-1} \biggl( \sum_{i=1}^{N} x_i^2 - N\bar{x}^2 \biggr)
     59 *     \end{align*}
     60 *     ```
     61 *
     62 *     we return
     63 *
     64 *     ```tex
     65 *     (N-1)(\Delta s^2) = \biggl(\sum_{k=1}^N x_k^2 - N\bar{x}_{i+1}^2 \biggr) - \biggl(\sum_{k=0}^{N-1} x_k^2 - N\bar{x}_{i}^2 \biggr)
     66 *     ```
     67 *
     68 * -   This can be further simplified by recognizing that subtracting the sums reduces to \\(x_N^2 - x_0^2\\); in which case,
     69 *
     70 *     ```tex
     71 *     \begin{align*}
     72 *     (N-1)(\Delta s^2) &= x_N^2 - x_0^2 - N\bar{x}_{i+1}^2 + N\bar{x}_{i}^2 \\
     73 *     &= x_N^2 - x_0^2 - N(\bar{x}_{i+1}^2 - \bar{x}_{i}^2) \\
     74 *     &= x_N^2 - x_0^2 - N(\bar{x}_{i+1} - \bar{x}_{i})(\bar{x}_{i+1} + \bar{x}_{i})
     75 *     \end{align*}
     76 *     ```
     77 *
     78 * -   Recognizing that the difference of means can be expressed
     79 *
     80 *     ```tex
     81 *     \bar{x}_{i+1} - \bar{x}_i = \frac{1}{N} \biggl( \sum_{k=1}^N x_k - \sum_{k=0}^{N-1} x_k \biggr) = \frac{x_N - x_0}{N}
     82 *     ```
     83 *
     84 *     and substituting into the equation above
     85 *
     86 *     ```tex
     87 *     (N-1)(\Delta s^2) = x_N^2 - x_0^2 - (x_N - x_0)(\bar{x}_{i+1} + \bar{x}_{i})
     88 *     ```
     89 *
     90 * -   Rearranging terms gives us the update equation for the unbiased sample variance
     91 *
     92 *     ```tex
     93 *     \begin{align*}
     94 *     (N-1)(\Delta s^2) &= (x_N - x_0)(x_N + x_0) - (x_N - x_0)(\bar{x}_{i+1} + \bar{x}_{i})
     95 *     &= (x_N - x_0)(x_N + x_0 - \bar{x}_{i+1} - \bar{x}_{i}) \\
     96 *     &= (x_N - x_0)(x_N - \bar{x}_{i+1} + x_0 - \bar{x}_{i})
     97 *     \end{align*}
     98 *     ```
     99 *
    100 * @param {PositiveInteger} W - window size
    101 * @param {number} [mean] - mean value
    102 * @throws {TypeError} first argument must be a positive integer
    103 * @throws {TypeError} second argument must be a number primitive
    104 * @returns {Function} accumulator function
    105 *
    106 * @example
    107 * var accumulator = incrmvmr( 3 );
    108 *
    109 * var F = accumulator();
    110 * // returns null
    111 *
    112 * F = accumulator( 2.0 );
    113 * // returns 0.0
    114 *
    115 * F = accumulator( 1.0 );
    116 * // returns ~0.33
    117 *
    118 * F = accumulator( 3.0 );
    119 * // returns 0.5
    120 *
    121 * F = accumulator( 7.0 );
    122 * // returns ~2.55
    123 *
    124 * F = accumulator();
    125 * // returns ~2.55
    126 *
    127 * @example
    128 * var accumulator = incrmvmr( 3, 2.0 );
    129 */
    130 function incrmvmr( W, mean ) {
    131 	var delta;
    132 	var buf;
    133 	var tmp;
    134 	var M2;
    135 	var mu;
    136 	var d1;
    137 	var d2;
    138 	var N;
    139 	var n;
    140 	var i;
    141 	if ( !isPositiveInteger( W ) ) {
    142 		throw new TypeError( 'invalid argument. Must provide a positive integer. Value: `' + W + '`.' );
    143 	}
    144 	buf = new Array( W );
    145 	n = W - 1;
    146 	M2 = 0.0;
    147 	i = -1;
    148 	N = 0;
    149 	if ( arguments.length > 1 ) {
    150 		if ( !isNumber( mean ) ) {
    151 			throw new TypeError( 'invalid argument. Must provide a number primitive. Value: `' + mean + '`.' );
    152 		}
    153 		mu = mean;
    154 		return accumulator2;
    155 	}
    156 	mu = 0.0;
    157 	return accumulator1;
    158 
    159 	/**
    160 	* If provided a value, the accumulator function returns an updated accumulated value. If not provided a value, the accumulator function returns the current accumulated value.
    161 	*
    162 	* @private
    163 	* @param {number} [x] - input value
    164 	* @returns {(number|null)} accumulated value or null
    165 	*/
    166 	function accumulator1( x ) {
    167 		var k;
    168 		var v;
    169 		if ( arguments.length === 0 ) {
    170 			if ( N === 0 ) {
    171 				return null;
    172 			}
    173 			if ( N === 1 ) {
    174 				return ( isnan( M2 ) ) ? NaN : 0.0/mu;
    175 			}
    176 			if ( N < W ) {
    177 				return ( M2/(N-1) ) / mu;
    178 			}
    179 			return ( M2/n ) / mu;
    180 		}
    181 		// Update the index for managing the circular buffer:
    182 		i = (i+1) % W;
    183 
    184 		// Case: incoming value is NaN, the sliding second moment is automatically NaN...
    185 		if ( isnan( x ) ) {
    186 			N = W; // explicitly set to avoid `N < W` branch
    187 			mu = NaN;
    188 			M2 = NaN;
    189 		}
    190 		// Case: initial window...
    191 		else if ( N < W ) {
    192 			buf[ i ] = x; // update buffer
    193 			N += 1;
    194 			delta = x - mu;
    195 			mu += delta / N;
    196 			M2 += delta * (x - mu);
    197 			if ( N === 1 ) {
    198 				return 0.0 / mu;
    199 			}
    200 			return ( M2/(N-1) ) / mu;
    201 		}
    202 		// Case: N = W = 1
    203 		else if ( N === 1 ) {
    204 			mu = x;
    205 			M2 = 0.0;
    206 			return M2 / mu;
    207 		}
    208 		// Case: outgoing value is NaN, and, thus, we need to compute the accumulated values...
    209 		else if ( isnan( buf[ i ] ) ) {
    210 			N = 1;
    211 			mu = x;
    212 			M2 = 0.0;
    213 			for ( k = 0; k < W; k++ ) {
    214 				if ( k !== i ) {
    215 					v = buf[ k ];
    216 					if ( isnan( v ) ) {
    217 						N = W; // explicitly set to avoid `N < W` branch
    218 						mu = NaN;
    219 						M2 = NaN;
    220 						break; // second moment is automatically NaN, so no need to continue
    221 					}
    222 					N += 1;
    223 					delta = v - mu;
    224 					mu += delta / N;
    225 					M2 += delta * (v - mu);
    226 				}
    227 			}
    228 		}
    229 		// Case: neither the current second moment nor the incoming value are NaN, so we need to update the accumulated values...
    230 		else if ( isnan( M2 ) === false ) {
    231 			tmp = buf[ i ];
    232 			delta = x - tmp;
    233 			d1 = tmp - mu;
    234 			mu += delta / W;
    235 			d2 = x - mu;
    236 			M2 += delta * (d1 + d2);
    237 		}
    238 		// Case: the current second moment is NaN, so nothing to do until the buffer no longer contains NaN values...
    239 		buf[ i ] = x;
    240 
    241 		return ( M2/n ) / mu;
    242 	}
    243 
    244 	/**
    245 	* If provided a value, the accumulator function returns an updated accumulated value. If not provided a value, the accumulator function returns the current accumulated value.
    246 	*
    247 	* @private
    248 	* @param {number} [x] - input value
    249 	* @returns {(number|null)} accumulated value or null
    250 	*/
    251 	function accumulator2( x ) {
    252 		var k;
    253 		if ( arguments.length === 0 ) {
    254 			if ( N === 0 ) {
    255 				return null;
    256 			}
    257 			if ( N < W ) {
    258 				return ( M2/N ) / mu;
    259 			}
    260 			return ( M2/W ) / mu;
    261 		}
    262 		// Update the index for managing the circular buffer:
    263 		i = (i+1) % W;
    264 
    265 		// Case: incoming value is NaN, the sliding second moment is automatically NaN...
    266 		if ( isnan( x ) ) {
    267 			N = W; // explicitly set to avoid `N < W` branch
    268 			M2 = NaN;
    269 		}
    270 		// Case: initial window...
    271 		else if ( N < W ) {
    272 			buf[ i ] = x; // update buffer
    273 			N += 1;
    274 			delta = x - mu;
    275 			M2 += delta * delta;
    276 			return ( M2/N ) / mu;
    277 		}
    278 		// Case: outgoing value is NaN, and, thus, we need to compute the accumulated values...
    279 		else if ( isnan( buf[ i ] ) ) {
    280 			M2 = 0.0;
    281 			for ( k = 0; k < W; k++ ) {
    282 				if ( k !== i ) {
    283 					if ( isnan( buf[ k ] ) ) {
    284 						N = W; // explicitly set to avoid `N < W` branch
    285 						M2 = NaN;
    286 						break; // second moment is automatically NaN, so no need to continue
    287 					}
    288 					delta = buf[ k ] - mu;
    289 					M2 += delta * delta;
    290 				}
    291 			}
    292 		}
    293 		// Case: neither the current second moment nor the incoming value are NaN, so we need to update the accumulated values...
    294 		else if ( isnan( M2 ) === false ) {
    295 			tmp = buf[ i ];
    296 			M2 += ( x-tmp ) * ( x-mu + tmp-mu );
    297 		}
    298 		// Case: the current second moment is NaN, so nothing to do until the buffer no longer contains NaN values...
    299 		buf[ i ] = x;
    300 
    301 		return ( M2/W ) / mu;
    302 	}
    303 }
    304 
    305 
    306 // EXPORTS //
    307 
    308 module.exports = incrmvmr;