time-to-botec

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

main.js (7751B)


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