time-to-botec

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

main.js (12961B)


      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 var Float64Array = require( '@stdlib/array/float64' );
     27 
     28 
     29 // MAIN //
     30 
     31 /**
     32 * Returns an accumulator function which incrementally computes a moving unbiased sample covariance.
     33 *
     34 * ## Method
     35 *
     36 * -   Let \\(W\\) be a window of \\(N\\) elements over which we want to compute an unbiased sample covariance.
     37 *
     38 * -   We begin by defining the covariance \\( \operatorname{cov}_n(x,y) \\) for a window \\(n\\) as follows
     39 *
     40 *     ```tex
     41 *     \operatorname{cov}_n(x,y) &= \frac{C_n}{n}
     42 *     ```
     43 *
     44 *     where \\(C_n\\) is the co-moment, which is defined as
     45 *
     46 *     ```tex
     47 *     C_n = \sum_{i=1}^{N} ( x_i - \bar{x}_n ) ( y_i - \bar{y}_n )
     48 *     ```
     49 *
     50 *     and where \\(\bar{x}_n\\) and \\(\bar{y}_n\\) are the sample means for \\(x\\) and \\(y\\), respectively, and \\(i=1\\) specifies the first element in a window.
     51 *
     52 * -   The sample mean is computed using the canonical formula
     53 *
     54 *     ```tex
     55 *     \bar{x}_n = \frac{1}{N} \sum_{i=1}^{N} x_i
     56 *     ```
     57 *
     58 *     which, taking into account a previous window, can be expressed
     59 *
     60 *     ```tex
     61 *     \begin{align*}
     62 *     \bar{x}_n &= \frac{1}{N} \biggl( \sum_{i=0}^{N-1} x_i - x_0 + x_N \biggr) \\
     63 *               &= \bar{x}_{n-1} + \frac{x_N - x_0}{N}
     64 *     \end{align*}
     65 *     ```
     66 *
     67 *     where \\(x_0\\) is the first value in the previous window.
     68 *
     69 * -   We can substitute into the co-moment equation
     70 *
     71 *     ```tex
     72 *     \begin{align*}
     73 *     C_n &= \sum_{i=1}^{N} ( x_i - \bar{x}_n ) ( y_i - \bar{y}_n ) \\
     74 *         &= \sum_{i=1}^{N} \biggl( x_i - \bar{x}_{n-1} - \frac{x_N - x_0}{N} \biggr) \biggl( y_i - \bar{y}_{n-1} - \frac{y_N - y_0}{N} \biggr) \\
     75 *         &= \sum_{i=1}^{N} \biggl( \Delta x_{i,n-1} - \frac{x_N - x_0}{N} \biggr) \biggl( \Delta y_{i,n-1} - \frac{y_N - y_0}{N} \biggr)
     76 *     \end{align*}
     77 *     ```
     78 *
     79 *     where
     80 *
     81 *     ```tex
     82 *     \Delta x_{i,k} = x_i - \bar{x}_{k}
     83 *     ```
     84 *
     85 * -   We can subsequently expand terms and apply a summation identity
     86 *
     87 *     ```tex
     88 *     \begin{align*}
     89 *     C_n &= \sum_{i=1}^{N} \Delta x_{i,n-1} \Delta y_{i,n-1} - \sum_{i=1}^{N} \Delta x_{i,n-1} \biggl( \frac{y_N - y_0}{N} \biggr) - \sum_{i=1}^{N} \Delta y_{i,n-1} \biggl( \frac{x_N - x_0}{N} \biggr) + \sum_{i=1}^{N} \biggl( \frac{x_N - x_0}{N} \biggr) \biggl( \frac{y_N - y_0}{N} \biggr) \\
     90 *         &= \sum_{i=1}^{N} \Delta x_{i,n-1} \Delta y_{i,n-1} - \biggl( \frac{y_N - y_0}{N} \biggr) \sum_{i=1}^{N} \Delta x_{i,n-1} - \biggl( \frac{x_N - x_0}{N} \biggr) \sum_{i=1}^{N} \Delta y_{i,n-1} + \frac{(x_N - x_0)(y_N - y_0)}{N}
     91 *     \end{align*}
     92 *     ```
     93 *
     94 * -   Let us first consider the second term which we can reorganize as follows
     95 *
     96 *     ```tex
     97 *     \begin{align*}
     98 *     \biggl( \frac{y_N - y_0}{N} \biggr) \sum_{i=1}^{N} \Delta x_{i,n-1} &= \biggl( \frac{y_N - y_0}{N} \biggr) \sum_{i=1}{N} ( x_i - \bar{x}_{n-1}) \\
     99 *         &= \biggl( \frac{y_N - y_0}{N} \biggr) \sum_{i=1}^{N} x_i - \biggl( \frac{y_N - y_0}{N} \biggr) \sum_{i=1}^{N} \bar{x}_{n-1} \\
    100 *         &= (y_N - y_0) \bar{x}_{n} - (y_N - y_0)\bar{x}_{n-1} \\
    101 *         &= (y_N - y_0) (\bar{x}_{n} - \bar{x}_{n-1}) \\
    102 *         &= \frac{(x_N - x_0)(y_N - y_0)}{N}
    103 *     \end{align*}
    104 *     ```
    105 *
    106 * -   The third term can be reorganized in a manner similar to the second term such that
    107 *
    108 *     ```tex
    109 *     \biggl( \frac{x_N - x_0}{N} \biggr) \sum_{i=1}^{N} \Delta y_{i,n-1} = \frac{(x_N - x_0)(y_N - y_0)}{N}
    110 *     ```
    111 *
    112 * -   Substituting back into the equation for the co-moment
    113 *
    114 *     ```tex
    115 *     \begin{align*}
    116 *     C_n &= \sum_{i=1}^{N} \Delta x_{i,n-1} \Delta y_{i,n-1} - \frac{(x_N - x_0)(y_N - y_0)}{N} - \frac{(x_N - x_0)(y_N - y_0)}{N} + \frac{(x_N - x_0)(y_N - y_0)}{N} \\
    117 *         &= \sum_{i=1}^{N} \Delta x_{i,n-1} \Delta y_{i,n-1} - \frac{(x_N - x_0)(y_N - y_0)}{N}
    118 *     \end{align*}
    119 *     ```
    120 *
    121 * -   Let us now consider the first term which we can modify as follows
    122 *
    123 *     ```tex
    124 *     \begin{align*}
    125 *     \sum_{i=1}^{N} \Delta x_{i,n-1} \Delta y_{i,n-1} &= \sum_{i=1}^{N} (x_i - \bar{x}_{n-1})(y_i - \bar{y}_{n-1}) \\
    126 *         &= \sum_{i=1}^{N-1} (x_i - \bar{x}_{n-1})(y_i - \bar{y}_{n-1}) + (x_N - \bar{x}_{n-1})(y_N - \bar{y}_{n-1}) \\
    127 *         &= \sum_{i=1}^{N-1} (x_i - \bar{x}_{n-1})(y_i - \bar{y}_{n-1}) + (x_N - \bar{x}_{n-1})(y_N - \bar{y}_{n-1}) + (x_0 - \bar{x}_{n-1})(y_0 - \bar{y}_{n-1}) - (x_0 - \bar{x}_{n-1})(y_0 - \bar{y}_{n-1}) \\
    128 *         &= \sum_{i=0}^{N-1} (x_i - \bar{x}_{n-1})(y_i - \bar{y}_{n-1}) + (x_N - \bar{x}_{n-1})(y_N - \bar{y}_{n-1}) - (x_0 - \bar{x}_{n-1})(y_0 - \bar{y}_{n-1})
    129 *     \end{align*}
    130 *     ```
    131 *
    132 *     where we recognize that the first term equals the co-moment for the previous window
    133 *
    134 *     ```tex
    135 *     C_{n-1} = \sum_{i=0}^{N-1} (x_i - \bar{x}_{n-1})(y_i - \bar{y}_{n-1})
    136 *     ```
    137 *
    138 *     In which case,
    139 *
    140 *     ```tex
    141 *     \begin{align*}
    142 *     \sum_{i=1}^{N} \Delta x_{i,n-1} \Delta y_{i,n-1} &= C_{n-1} + (x_N - \bar{x}_{n-1})(y_N - \bar{y}_{n-1}) - (x_0 - \bar{x}_{n-1})(y_0 - \bar{y}_{n-1}) \\
    143 *         &= C_{n-1} + \Delta x_{N,n-1} \Delta y_{N,n-1} - \Delta x_{0,n-1} \Delta y_{0,n-1}
    144 *     \end{align*}
    145 *     ```
    146 *
    147 * -   Substituting into the equation for the co-moment
    148 *
    149 *     ```tex
    150 *     C_n = C_{n-1} + \Delta x_{N,n-1} \Delta y_{N,n-1} - \Delta x_{0,n-1} \Delta y_{0,n-1} - \frac{(x_N - x_0)(y_N - y_0)}{N}
    151 *     ```
    152 *
    153 * -   We can make one further modification to the last term
    154 *
    155 *     ```tex
    156 *     \begin{align*}
    157 *     \frac{(x_N - x_0)(y_N - y_0)}{N} &= \frac{(x_N - \bar{x}_{n-1} - x_0 + \bar{x}_{n-1})(y_N - \bar{y}_{n-1} - y_0 + \bar{y}_{n-1})}{N} \\
    158 *         &= \frac{(\Delta x_{N,n-1} - \Delta x_{0,n-1})(\Delta y_{N,n-1} - \Delta y_{0,n-1})}{N}
    159 *     \end{align*}
    160 *     ```
    161 *
    162 *     which, upon substitution into the equation for the co-moment, yields
    163 *
    164 *     ```tex
    165 *     C_n = C_{n-1} + \Delta x_{N,n-1} \Delta y_{N,n-1} - \Delta x_{0,n-1} \Delta y_{0,n-1} - \frac{(\Delta x_{N,n-1} - \Delta x_{0,n-1})(\Delta y_{N,n-1} - \Delta y_{0,n-1})}{N}
    166 *     ```
    167 *
    168 *
    169 * @param {PositiveInteger} W - window size
    170 * @param {number} [meanx] - mean value
    171 * @param {number} [meany] - mean value
    172 * @throws {TypeError} first argument must be a positive integer
    173 * @throws {TypeError} second argument must be a number primitive
    174 * @throws {TypeError} third argument must be a number primitive
    175 * @returns {Function} accumulator function
    176 *
    177 * @example
    178 * var accumulator = incrmcovariance( 3 );
    179 *
    180 * var v = accumulator();
    181 * // returns null
    182 *
    183 * v = accumulator( 2.0, 1.0 );
    184 * // returns 0.0
    185 *
    186 * v = accumulator( -5.0, 3.14 );
    187 * // returns ~-7.49
    188 *
    189 * v = accumulator( 3.0, -1.0 );
    190 * // returns -8.35
    191 *
    192 * v = accumulator( 5.0, -9.5 );
    193 * // returns -29.42
    194 *
    195 * v = accumulator();
    196 * // returns -29.42
    197 *
    198 * @example
    199 * var accumulator = incrmcovariance( 3, -2.0, 10.0 );
    200 */
    201 function incrmcovariance( W, meanx, meany ) {
    202 	var buf;
    203 	var dx0;
    204 	var dxN;
    205 	var dy0;
    206 	var dyN;
    207 	var mx;
    208 	var my;
    209 	var wi;
    210 	var C;
    211 	var N;
    212 	var n;
    213 	var i;
    214 	if ( !isPositiveInteger( W ) ) {
    215 		throw new TypeError( 'invalid argument. First argument must be a positive integer. Value: `' + W + '`.' );
    216 	}
    217 	buf = new Float64Array( 2*W ); // strided array
    218 	n = W - 1;
    219 	C = 0.0;
    220 	wi = -1;
    221 	N = 0;
    222 	if ( arguments.length > 1 ) {
    223 		if ( !isNumber( meanx ) ) {
    224 			throw new TypeError( 'invalid argument. Second argument must be a number primitive. Value: `' + meanx + '`.' );
    225 		}
    226 		if ( !isNumber( meany ) ) {
    227 			throw new TypeError( 'invalid argument. Third argument must be a number primitive. Value: `' + meany + '`.' );
    228 		}
    229 		mx = meanx;
    230 		my = meany;
    231 		return accumulator2;
    232 	}
    233 	mx = 0.0;
    234 	my = 0.0;
    235 	return accumulator1;
    236 
    237 	/**
    238 	* If provided a value, the accumulator function returns an updated unbiased sample covariance. If not provided a value, the accumulator function returns the current unbiased sample covariance.
    239 	*
    240 	* @private
    241 	* @param {number} [x] - input value
    242 	* @param {number} [y] - input value
    243 	* @returns {(number|null)} unbiased sample covariance or null
    244 	*/
    245 	function accumulator1( x, y ) {
    246 		var v1;
    247 		var v2;
    248 		var k;
    249 		var j;
    250 		if ( arguments.length === 0 ) {
    251 			if ( N === 0 ) {
    252 				return null;
    253 			}
    254 			if ( N === 1 ) {
    255 				return 0.0;
    256 			}
    257 			if ( N < W ) {
    258 				return C / (N-1);
    259 			}
    260 			return C / n;
    261 		}
    262 		// Update the window and strided array indices for managing the circular buffer:
    263 		wi = (wi+1) % W;
    264 		i = 2 * wi;
    265 
    266 		// Case: an incoming value is NaN, the sliding co-moment is automatically NaN...
    267 		if ( isnan( x ) || isnan( y ) ) {
    268 			N = W; // explicitly set to avoid `N < W` branch
    269 			C = NaN;
    270 		}
    271 		// Case: initial window...
    272 		else if ( N < W ) {
    273 			buf[ i ] = x; // update buffer
    274 			buf[ i+1 ] = y;
    275 
    276 			N += 1;
    277 			dxN = x - mx;
    278 			mx += dxN / N;
    279 			my += ( y-my ) / N;
    280 			C += dxN * ( y-my ); // Note: repeated `y-my` is intentional, as `my` is updated when used here
    281 			if ( N === 1 ) {
    282 				return 0.0;
    283 			}
    284 			return C / (N-1);
    285 		}
    286 		// Case: N = W = 1
    287 		else if ( N === 1 ) {
    288 			return 0.0;
    289 		}
    290 		// Case: an outgoing value is NaN, and, thus, we need to compute the accumulated values...
    291 		else if ( isnan( buf[ i ] ) || isnan( buf[ i+1 ] ) ) {
    292 			N = 1;
    293 			mx = x;
    294 			my = y;
    295 			C = 0.0;
    296 			for ( k = 0; k < W; k++ ) {
    297 				j = 2 * k; // convert to a strided array index
    298 				if ( j !== i ) {
    299 					v1 = buf[ j ];
    300 					v2 = buf[ j+1 ];
    301 					if ( isnan( v1 ) || isnan( v2 ) ) {
    302 						N = W; // explicitly set to avoid `N < W` branch
    303 						C = NaN;
    304 						break; // co-moment is automatically NaN, so no need to continue
    305 					}
    306 					N += 1;
    307 					dxN = v1 - mx;
    308 					mx += dxN / N;
    309 					my += ( v2-my ) / N;
    310 					C += dxN * ( v2-my ); // Note: repeated `y-my` is intentional, as `my` is updated when used here
    311 				}
    312 			}
    313 		}
    314 		// Case: neither the current co-moment nor the incoming values are NaN, so we need to update the accumulated values...
    315 		else if ( isnan( C ) === false ) {
    316 			dx0 = buf[ i ] - mx;
    317 			dy0 = buf[ i+1 ] - my;
    318 			dxN = x - mx;
    319 			dyN = y - my;
    320 			C += (dxN*dyN) - (dx0*dy0) - ( (dxN-dx0)*(dyN-dy0)/W );
    321 			mx += ( dxN-dx0 ) / W;
    322 			my += ( dyN-dy0 ) / W;
    323 		}
    324 		// Case: the current co-moment is NaN, so nothing to do until the buffer no longer contains NaN values...
    325 		buf[ i ] = x;
    326 		buf[ i+1 ] = y;
    327 
    328 		return C / n;
    329 	}
    330 
    331 	/**
    332 	* If provided a value, the accumulator function returns an updated unbiased sample covariance. If not provided a value, the accumulator function returns the current unbiased sample covariance.
    333 	*
    334 	* @private
    335 	* @param {number} [x] - input value
    336 	* @param {number} [y] - input value
    337 	* @returns {(number|null)} unbiased sample covariance or null
    338 	*/
    339 	function accumulator2( x, y ) {
    340 		var k;
    341 		var j;
    342 		if ( arguments.length === 0 ) {
    343 			if ( N === 0 ) {
    344 				return null;
    345 			}
    346 			if ( N < W ) {
    347 				return C / N;
    348 			}
    349 			return C / W;
    350 		}
    351 		// Update the window and strided array indices for managing the circular buffer:
    352 		wi = (wi+1) % W;
    353 		i = 2 * wi;
    354 
    355 		// Case: an incoming value is NaN, the sliding co-moment is automatically NaN...
    356 		if ( isnan( x ) || isnan( y ) ) {
    357 			N = W; // explicitly set to avoid `N < W` branch
    358 			C = NaN;
    359 		}
    360 		// Case: initial window...
    361 		else if ( N < W ) {
    362 			buf[ i ] = x; // update buffer
    363 			buf[ i+1 ] = y;
    364 
    365 			N += 1;
    366 			C += ( x-mx ) * ( y-my );
    367 			return C / N;
    368 		}
    369 		// Case: an outgoing value is NaN, and, thus, we need to compute the accumulated values...
    370 		else if ( isnan( buf[ i ] ) || isnan( buf[ i+1 ] ) ) {
    371 			C = 0.0;
    372 			for ( k = 0; k < W; k++ ) {
    373 				j = 2 * k; // convert to a strided array index
    374 				if ( j !== i ) {
    375 					if ( isnan( buf[ j ] ) || isnan( buf[ j+1 ] ) ) {
    376 						N = W; // explicitly set to avoid `N < W` branch
    377 						C = NaN;
    378 						break; // co-moment is automatically NaN, so no need to continue
    379 					}
    380 					C += ( buf[j]-mx ) * ( buf[j+1]-my );
    381 				}
    382 			}
    383 		}
    384 		// Case: neither the current co-moment nor the incoming values are NaN, so we need to update the accumulated values...
    385 		else if ( isnan( C ) === false ) {
    386 			// Use textbook formula along with simplification arising from difference of sums:
    387 			C += ( (x-mx)*(y-my) ) - ( (buf[i]-mx)*(buf[i+1]-my) );
    388 		}
    389 		// Case: the current co-moment is NaN, so nothing to do until the buffer no longer contains NaN values...
    390 		buf[ i ] = x;
    391 		buf[ i+1 ] = y;
    392 
    393 		return C / W;
    394 	}
    395 }
    396 
    397 
    398 // EXPORTS //
    399 
    400 module.exports = incrmcovariance;