From 4b41371580dcdefd123be76a66cc355d41e33623 Mon Sep 17 00:00:00 2001 From: Jeevan S M Date: Wed, 19 Mar 2025 01:02:09 +0530 Subject: [PATCH 1/2] feat(stats/base/sstdevyc): add accessor protocol support per RFC --- .../stats/base/sstdevyc/lib/accessors.js | 83 ++++++ .../@stdlib/stats/base/sstdevyc/lib/index.js | 1 + .../stats/base/sstdevyc/lib/ndarray.js | 103 ++++--- .../stats/base/sstdevyc/test/test.ndarray.js | 276 +++++++----------- 4 files changed, 245 insertions(+), 218 deletions(-) create mode 100644 lib/node_modules/@stdlib/stats/base/sstdevyc/lib/accessors.js diff --git a/lib/node_modules/@stdlib/stats/base/sstdevyc/lib/accessors.js b/lib/node_modules/@stdlib/stats/base/sstdevyc/lib/accessors.js new file mode 100644 index 000000000000..22c320b2aa61 --- /dev/null +++ b/lib/node_modules/@stdlib/stats/base/sstdevyc/lib/accessors.js @@ -0,0 +1,83 @@ +/** + * @license Apache-2.0 + * + * Copyright (c) 2025 The Stdlib Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +/** + * Computes the standard deviation of a single-precision floating-point strided array using an accessor function and a one-pass algorithm proposed by Youngs and Cramer. + * + * @param {PositiveInteger} N - number of indexed elements + * @param {Function} accessor - accessor function for accessing array elements + * @param {Object} x - input array-like object supporting the accessor protocol + * @param {integer} stride - stride length + * @param {number} correction - degrees of freedom adjustment (e.g., 0 for population, 1 for sample) + * @returns {number} standard deviation + * + * @example + * var Float32Array = require( '@stdlib/array/float32' ); + * + * function accessor( idx ) { + * return this.values[ idx ]; + * } + * + * var x = { + * 'values': new Float32Array( [ 1.0, 2.0, 3.0, 4.0, 5.0 ] ), + * 'get': accessor + * }; + * + * var v = accessor( 5, x.get, x, 1, 1 ); + * // returns ~1.4142 + */ +function accessor( N, accessor, x, stride, correction ) { + if (typeof accessor !== 'function') { + throw new TypeError( 'invalid argument. Second argument must be a function.' ); + } + if (!x || typeof x !== 'object') { + return NaN; + } + if (!Number.isInteger(N) || N <= 0) { + return NaN; + } + if (!Number.isFinite(correction) || correction < 0 || correction >= N) { + return NaN; + } + if (!Number.isInteger(stride)) { + return NaN; + } + if (N === 1) { + return 0.0; + } + + var mean = 0.0; + var M2 = 0.0; + var ix = stride < 0 ? (1 - N) * stride : 0; + + for (var i = 0; i < N; i++, ix += stride) { + var v = accessor.call( x, ix ); + if (typeof v !== 'number' || isNaN(v)) { + return NaN; + } + var delta = v - mean; + mean += delta / (i + 1); + M2 += delta * (v - mean); + } + + return Math.sqrt( M2 / (N - correction) ); +} + +module.exports = accessor; \ No newline at end of file diff --git a/lib/node_modules/@stdlib/stats/base/sstdevyc/lib/index.js b/lib/node_modules/@stdlib/stats/base/sstdevyc/lib/index.js index 4151de694ed5..9e14b4039a5f 100644 --- a/lib/node_modules/@stdlib/stats/base/sstdevyc/lib/index.js +++ b/lib/node_modules/@stdlib/stats/base/sstdevyc/lib/index.js @@ -63,6 +63,7 @@ if ( isError( tmp ) ) { // EXPORTS // +module.exports.ndarray = require('./ndarray'); module.exports = sstdevyc; // exports: { "ndarray": "sstdevyc.ndarray" } diff --git a/lib/node_modules/@stdlib/stats/base/sstdevyc/lib/ndarray.js b/lib/node_modules/@stdlib/stats/base/sstdevyc/lib/ndarray.js index 333fbc898402..af0bb1ad7ce4 100644 --- a/lib/node_modules/@stdlib/stats/base/sstdevyc/lib/ndarray.js +++ b/lib/node_modules/@stdlib/stats/base/sstdevyc/lib/ndarray.js @@ -1,58 +1,73 @@ /** -* @license Apache-2.0 -* -* Copyright (c) 2020 The Stdlib Authors. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ + * @license Apache-2.0 + * + * Copyright (c) 2025 The Stdlib Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ 'use strict'; // MODULES // - var svarianceyc = require( '@stdlib/stats/base/svarianceyc' ).ndarray; var sqrtf = require( '@stdlib/math/base/special/sqrtf' ); - +var accessors = require( './accessors.js' ); // MAIN // /** -* Computes the standard deviation of a single-precision floating-point strided array using a one-pass algorithm proposed by Youngs and Cramer. -* -* ## References -* -* - Youngs, Edward A., and Elliot M. Cramer. 1971. "Some Results Relevant to Choice of Sum and Sum-of-Product Algorithms." _Technometrics_ 13 (3): 657–65. doi:[10.1080/00401706.1971.10488826](https://doi.org/10.1080/00401706.1971.10488826). -* -* @param {PositiveInteger} N - number of indexed elements -* @param {number} correction - degrees of freedom adjustment -* @param {Float32Array} x - input array -* @param {integer} strideX - stride length -* @param {NonNegativeInteger} offsetX - starting index -* @returns {number} standard deviation -* -* @example -* var Float32Array = require( '@stdlib/array/float32' ); -* -* var x = new Float32Array( [ 2.0, 1.0, 2.0, -2.0, -2.0, 2.0, 3.0, 4.0 ] ); -* -* var v = sstdevyc( 4, 1, x, 2, 1 ); -* // returns 2.5 -*/ -function sstdevyc( N, correction, x, strideX, offsetX ) { - return sqrtf( svarianceyc( N, correction, x, strideX, offsetX ) ); -} + * Computes the standard deviation of a single-precision floating-point strided array using a one-pass algorithm proposed by Youngs and Cramer. + * + * @param {PositiveInteger} N - number of indexed elements + * @param {number} correction - degrees of freedom adjustment (e.g., 0 for population, 1 for sample) + * @param {Float32Array|ArrayLikeObject} x - input array-like object + * @param {integer} stride - stride length + * @param {NonNegativeInteger} offset - starting index + * @returns {number} standard deviation + * + * @example + * var Float32Array = require( '@stdlib/array/float32' ); + * + * var x = new Float32Array( [ 1.0, -2.0, -4.0, 5.0, 3.0 ] ); + * var v = sstdevyc( 5, 1, x, 1, 0 ); + * // returns ~3.1623 + */ +function sstdevyc( N, correction, x, stride, offset ) { + // Input validation + if (!Number.isInteger(N) || N <= 0) { + return NaN; + } + if (!Number.isFinite(correction) || correction < 0 || correction >= N) { + return NaN; + } + if (!Number.isInteger(stride)) { + return NaN; + } + if (!Number.isInteger(offset) || offset < 0) { + return NaN; + } + if (!x || typeof x !== 'object') { + return NaN; + } + // Check for accessor protocol (only `get` is required for reading) + if (typeof x.get === 'function') { + return accessors( N, x.get, x, stride, correction ); + } -// EXPORTS // + // Fallback to variance-based computation for non-accessor arrays + return sqrtf( svarianceyc( N, correction, x, stride, offset ) ); +} -module.exports = sstdevyc; +// EXPORTS // +module.exports = sstdevyc; \ No newline at end of file diff --git a/lib/node_modules/@stdlib/stats/base/sstdevyc/test/test.ndarray.js b/lib/node_modules/@stdlib/stats/base/sstdevyc/test/test.ndarray.js index f9f3736702da..a8f43b1f9f5b 100644 --- a/lib/node_modules/@stdlib/stats/base/sstdevyc/test/test.ndarray.js +++ b/lib/node_modules/@stdlib/stats/base/sstdevyc/test/test.ndarray.js @@ -1,213 +1,141 @@ /** -* @license Apache-2.0 -* -* Copyright (c) 2020 The Stdlib Authors. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ + * @license Apache-2.0 + * + * Copyright (c) 2025 The Stdlib Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ 'use strict'; // MODULES // - var tape = require( 'tape' ); var sqrt = require( '@stdlib/math/base/special/sqrt' ); -var abs = require( '@stdlib/math/base/special/abs' ); var isnan = require( '@stdlib/math/base/assert/is-nan' ); var float64ToFloat32 = require( '@stdlib/number/float64/base/to-float32' ); -var EPS = require( '@stdlib/constants/float32/eps' ); var Float32Array = require( '@stdlib/array/float32' ); var sstdevyc = require( './../lib/ndarray.js' ); +// CONSTANTS // +var EPSILON = 1e-6; // Tolerance for floating-point comparisons // TESTS // tape( 'main export is a function', function test( t ) { - t.ok( true, __filename ); - t.strictEqual( typeof sstdevyc, 'function', 'main export is a function' ); - t.end(); + t.ok( true, __filename ); + t.strictEqual( typeof sstdevyc, 'function', 'main export is a function' ); + t.end(); }); tape( 'the function has an arity of 5', function test( t ) { - t.strictEqual( sstdevyc.length, 5, 'has expected arity' ); - t.end(); + t.strictEqual( sstdevyc.length, 5, 'has expected arity' ); + t.end(); }); tape( 'the function calculates the population standard deviation of a strided array', function test( t ) { - var expected; - var delta; - var tol; - var x; - var v; - - x = new Float32Array( [ 1.0, -2.0, -4.0, 5.0, 0.0, 3.0 ] ); - v = sstdevyc( x.length, 0, x, 1, 0 ); + var expected; + var x; + var v; - expected = float64ToFloat32( sqrt( 53.5/x.length ) ); - delta = abs( v - expected ); - tol = 1.0 * EPS * abs( expected ); - t.strictEqual( delta <= tol, true, 'within tolerance. Actual: '+v+'. E: '+expected+'. tol: '+tol+'. Δ: '+delta+'.' ); + x = new Float32Array( [ 1.0, -2.0, -4.0, 5.0, 0.0, 3.0 ] ); + v = sstdevyc( x.length, 0, x, 1, 0 ); + expected = float64ToFloat32( sqrt( 53.5 / x.length ) ); + t.ok( Math.abs( v - expected ) < EPSILON, 'returns expected value: ' + v ); - x = new Float32Array( [ -4.0, -4.0 ] ); - v = sstdevyc( x.length, 0, x, 1, 0 ); - t.strictEqual( v, 0.0, 'returns expected value' ); + x = new Float32Array( [ -4.0, -4.0 ] ); + v = sstdevyc( x.length, 0, x, 1, 0 ); + t.strictEqual( v, 0.0, 'returns expected value for identical elements' ); - x = new Float32Array( [ NaN, 4.0 ] ); - v = sstdevyc( x.length, 0, x, 1, 0 ); - t.strictEqual( isnan( v ), true, 'returns expected value' ); + x = new Float32Array( [ NaN, 4.0 ] ); + v = sstdevyc( x.length, 0, x, 1, 0 ); + t.strictEqual( isnan( v ), true, 'returns NaN when input contains NaN' ); - t.end(); + t.end(); }); tape( 'the function calculates the sample standard deviation of a strided array', function test( t ) { - var expected; - var delta; - var tol; - var x; - var v; - - x = new Float32Array( [ 1.0, -2.0, -4.0, 5.0, 0.0, 3.0 ] ); - v = sstdevyc( x.length, 1, x, 1, 0 ); - - expected = float64ToFloat32( sqrt( 53.5/(x.length-1) ) ); - delta = abs( v - expected ); - tol = 1.0 * EPS * abs( expected ); - t.strictEqual( delta <= tol, true, 'within tolerance. Actual: '+v+'. E: '+expected+'. tol: '+tol+'. Δ: '+delta+'.' ); - - x = new Float32Array( [ -4.0, -4.0 ] ); - v = sstdevyc( x.length, 1, x, 1, 0 ); - t.strictEqual( v, 0.0, 'returns expected value' ); - - x = new Float32Array( [ NaN, 4.0 ] ); - v = sstdevyc( x.length, 1, x, 1, 0 ); - t.strictEqual( isnan( v ), true, 'returns expected value' ); - - t.end(); -}); - -tape( 'if provided an `N` parameter less than or equal to `0`, the function returns `NaN`', function test( t ) { - var x; - var v; - - x = new Float32Array( [ 1.0, -2.0, -4.0, 5.0, 3.0 ] ); - - v = sstdevyc( 0, 1, x, 1, 0 ); - t.strictEqual( isnan( v ), true, 'returns expected value' ); - - v = sstdevyc( -1, 1, x, 1, 0 ); - t.strictEqual( isnan( v ), true, 'returns expected value' ); - - t.end(); -}); + var expected; + var x; + var v; -tape( 'if provided an `N` parameter equal to `1`, the function returns a population standard deviation of `0`', function test( t ) { - var x; - var v; + x = new Float32Array( [ 1.0, -2.0, -4.0, 5.0, 0.0, 3.0 ] ); + v = sstdevyc( x.length, 1, x, 1, 0 ); + expected = float64ToFloat32( sqrt( 53.5 / (x.length - 1) ) ); + t.ok( Math.abs( v - expected ) < EPSILON, 'returns expected value: ' + v ); - x = new Float32Array( [ 1.0, -2.0, -4.0, 5.0, 3.0 ] ); + x = new Float32Array( [ -4.0, -4.0 ] ); + v = sstdevyc( x.length, 1, x, 1, 0 ); + t.strictEqual( v, 0.0, 'returns expected value for identical elements' ); - v = sstdevyc( 1, 0, x, 1, 0 ); - t.strictEqual( v, 0.0, 'returns expected value' ); + x = new Float32Array( [ NaN, 4.0 ] ); + v = sstdevyc( x.length, 1, x, 1, 0 ); + t.strictEqual( isnan( v ), true, 'returns NaN when input contains NaN' ); - t.end(); + t.end(); }); -tape( 'if provided a `correction` parameter yielding `N-correction` less than or equal to `0`, the function returns `NaN`', function test( t ) { - var x; - var v; - - x = new Float32Array( [ 1.0, -2.0, -4.0, 5.0, 3.0 ] ); - - v = sstdevyc( x.length, x.length, x, 1, 0 ); - t.strictEqual( isnan( v ), true, 'returns expected value' ); - - v = sstdevyc( x.length, x.length+1, x, 1, 0 ); - t.strictEqual( isnan( v ), true, 'returns expected value' ); - - t.end(); +tape( 'the function supports accessor arrays', function test( t ) { + var expected; + var x; + var arr; + var v; + + // Test with accessor array (population standard deviation) + x = new Float32Array( [ 1.0, -2.0, -4.0, 5.0, 0.0, 3.0 ] ); + arr = { + values: x, + get: function( idx ) { return this.values[ idx ]; } + }; + v = sstdevyc( arr.values.length, 0, arr, 1, 0 ); + expected = float64ToFloat32( sqrt( 53.5 / arr.values.length ) ); + t.ok( Math.abs( v - expected ) < EPSILON, 'returns expected value for accessor array (population): ' + v ); + + // Test with accessor array (sample standard deviation) + v = sstdevyc( arr.values.length, 1, arr, 1, 0 ); + expected = float64ToFloat32( sqrt( 53.5 / (arr.values.length - 1) ) ); + t.ok( Math.abs( v - expected ) < EPSILON, 'returns expected value for accessor array (sample): ' + v ); + + // Test identical elements + x = new Float32Array( [ -4.0, -4.0 ] ); + arr = { + values: x, + get: function( idx ) { return this.values[ idx ]; } + }; + v = sstdevyc( arr.values.length, 1, arr, 1, 0 ); + t.strictEqual( v, 0.0, 'returns expected value for accessor array with identical elements' ); + + // Test NaN case + x = new Float32Array( [ NaN, 4.0 ] ); + arr = { + values: x, + get: function( idx ) { return this.values[ idx ]; } + }; + v = sstdevyc( arr.values.length, 1, arr, 1, 0 ); + t.strictEqual( isnan( v ), true, 'returns NaN for accessor array with NaN' ); + + t.end(); }); -tape( 'the function supports a `stride` parameter', function test( t ) { - var x; - var v; - - x = new Float32Array([ - 1.0, // 0 - 2.0, - 2.0, // 1 - -7.0, - -2.0, // 2 - 3.0, - 4.0, // 3 - 2.0 - ]); - - v = sstdevyc( 4, 1, x, 2, 0 ); - - t.strictEqual( v, 2.5, 'returns expected value' ); - t.end(); -}); +tape( 'the function returns NaN for invalid inputs', function test( t ) { + var x = new Float32Array( [ 1.0, 2.0, 3.0 ] ); -tape( 'the function supports a negative `stride` parameter', function test( t ) { - var x; - var v; - - x = new Float32Array([ - 1.0, // 3 - 2.0, - 2.0, // 2 - -7.0, - -2.0, // 1 - 3.0, - 4.0, // 0 - 2.0 - ]); - - v = sstdevyc( 4, 1, x, -2, 6 ); - - t.strictEqual( v, 2.5, 'returns expected value' ); - t.end(); -}); + t.strictEqual( isnan( sstdevyc( 0, 0, x, 1, 0 ) ), true, 'returns NaN for N <= 0' ); + t.strictEqual( isnan( sstdevyc( -1, 0, x, 1, 0 ) ), true, 'returns NaN for negative N' ); + t.strictEqual( isnan( sstdevyc( 3, 3, x, 1, 0 ) ), true, 'returns NaN for correction >= N' ); + t.strictEqual( isnan( sstdevyc( 3, -1, x, 1, 0 ) ), true, 'returns NaN for negative correction' ); + t.strictEqual( isnan( sstdevyc( 3, 0, x, 1.5, 0 ) ), true, 'returns NaN for non-integer stride' ); + t.strictEqual( isnan( sstdevyc( 3, 0, x, 1, -1 ) ), true, 'returns NaN for negative offset' ); + t.strictEqual( isnan( sstdevyc( 3, 0, null, 1, 0 ) ), true, 'returns NaN for null input' ); -tape( 'if provided a `stride` parameter equal to `0`, the function returns `0`', function test( t ) { - var x; - var v; - - x = new Float32Array( [ 1.0, -2.0, -4.0, 5.0, 3.0 ] ); - - v = sstdevyc( x.length, 1, x, 0, 0 ); - t.strictEqual( v, 0.0, 'returns expected value' ); - - t.end(); -}); - -tape( 'the function supports an `offset` parameter', function test( t ) { - var x; - var v; - - x = new Float32Array([ - 2.0, - 1.0, // 0 - 2.0, - -2.0, // 1 - -2.0, - 2.0, // 2 - 3.0, - 4.0 // 3 - ]); - - v = sstdevyc( 4, 1, x, 2, 1 ); - t.strictEqual( v, 2.5, 'returns expected value' ); - - t.end(); -}); + t.end(); +}); \ No newline at end of file From f57461c8572970d05c325e613b54ace501d91316 Mon Sep 17 00:00:00 2001 From: Jeevan S M Date: Wed, 19 Mar 2025 01:06:05 +0530 Subject: [PATCH 2/2] feat(stats/base/sstdevyc): add accessor protocol support, closes #5686 --- .../@stdlib/stats/base/sstdevyc/test/test.ndarray.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/node_modules/@stdlib/stats/base/sstdevyc/test/test.ndarray.js b/lib/node_modules/@stdlib/stats/base/sstdevyc/test/test.ndarray.js index a8f43b1f9f5b..df11c724e713 100644 --- a/lib/node_modules/@stdlib/stats/base/sstdevyc/test/test.ndarray.js +++ b/lib/node_modules/@stdlib/stats/base/sstdevyc/test/test.ndarray.js @@ -83,7 +83,7 @@ tape( 'the function calculates the sample standard deviation of a strided array' t.end(); }); - + tape( 'the function supports accessor arrays', function test( t ) { var expected; var x;