time-to-botec

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

main.js (7295B)


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