diff --git a/.github/.keepalive b/.github/.keepalive deleted file mode 100644 index 6fc954e..0000000 --- a/.github/.keepalive +++ /dev/null @@ -1 +0,0 @@ -2024-08-03T20:18:36.587Z diff --git a/CHANGELOG.md b/CHANGELOG.md index ccbbf78..4282ed0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,50 @@ > Package changelog. +
+ +## Unreleased (2024-08-17) + +
+ +### Features + +- [`0eaf8b6`](https://github.com/stdlib-js/stdlib/commit/0eaf8b6263017bfe14c1b8769dfd885a19d1778e) - add support for operating on stacks of vectors + +
+ + + +
+ +### Commits + +
+ +- [`0eaf8b6`](https://github.com/stdlib-js/stdlib/commit/0eaf8b6263017bfe14c1b8769dfd885a19d1778e) - **feat:** add support for operating on stacks of vectors _(by Athan Reines)_ + +
+ +
+ + + +
+ +### Contributors + +A total of 1 person contributed to this release. Thank you to this contributor: + +- Athan Reines + +
+ + + +
+ + +
## 0.2.2 (2024-07-28) @@ -84,8 +128,7 @@ A total of 1 person contributed to this release. Thank you to this contributor: ### BREAKING CHANGES -- [`cca37d0`](https://github.com/stdlib-js/stdlib/commit/cca37d051d8c0209970fc681353fdb4e4d257a8a): update minimum TypeScript version -- [`cca37d0`](https://github.com/stdlib-js/stdlib/commit/cca37d051d8c0209970fc681353fdb4e4d257a8a): update minimum TypeScript version to 4.1 +- [`cca37d0`](https://github.com/stdlib-js/stdlib/commit/cca37d051d8c0209970fc681353fdb4e4d257a8a): update minimum TypeScript version to 4.1 - To migrate, users should upgrade their TypeScript version to at least version 4.1. diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 26a1c46..57d1184 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -46,6 +46,7 @@ Marcus Fantham Matt Cochrane Mihir Pandit <129577900+MSP20086@users.noreply.github.com> Milan Raj +Mohammad Kaif <98884589+Kaif987@users.noreply.github.com> Momtchil Momtchev Muhammad Haris Naresh Jagadeesan @@ -70,6 +71,7 @@ Roman Stetsyk <25715951+romanstetsyk@users.noreply.github.com> Rutam <138517416+performant23@users.noreply.github.com> Ryan Seal Sai Srikar Dumpeti <80447788+the-r3aper7@users.noreply.github.com> +SarthakPaandey <145528240+SarthakPaandey@users.noreply.github.com> Seyyed Parsa Neshaei Shashank Shekhar Singh Shivam <11shivam00@gmail.com> diff --git a/README.md b/README.md index 60b0b3b..a939f90 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ To view installation and usage instructions specific to each branch build, be su var ddot = require( '@stdlib/blas-ddot' ); ``` -#### ddot( x, y ) +#### ddot( x, y\[, dim] ) Calculates the dot product of two double-precision floating-point vectors `x` and `y`. @@ -96,25 +96,38 @@ var x = array( new Float64Array( [ 4.0, 2.0, -3.0, 5.0, -1.0 ] ) ); var y = array( new Float64Array( [ 2.0, 6.0, -1.0, -4.0, 8.0 ] ) ); var z = ddot( x, y ); +// returns + +var v = z.get(); // returns -5.0 ``` The function has the following parameters: -- **x**: a 1-dimensional [`ndarray`][@stdlib/ndarray/array] whose underlying data type is `float64`. -- **y**: a 1-dimensional [`ndarray`][@stdlib/ndarray/array] whose underlying data type is `float64`. +- **x**: a non-zero-dimensional [`ndarray`][@stdlib/ndarray/ctor] whose underlying data type is `float64`. Must be [broadcast-compatible][@stdlib/ndarray/base/broadcast-shapes] with `y`. +- **y**: a non-zero-dimensional [`ndarray`][@stdlib/ndarray/ctor] whose underlying data type is `float64`. Must be [broadcast-compatible][@stdlib/ndarray/base/broadcast-shapes] with `x`. +- **dim**: dimension for which to compute the dot product. Must be a negative integer. Negative indices are resolved relative to the last array dimension, with the last dimension corresponding to `-1`. Default: `-1`. -If provided empty vectors, the function returns `0.0`. +If provided at least one input [`ndarray`][@stdlib/ndarray/ctor] having more than one dimension, the input [`ndarrays`][@stdlib/ndarray/ctor] are [broadcasted][@stdlib/ndarray/base/broadcast-shapes] to a common shape. For multi-dimensional input [`ndarrays`][@stdlib/ndarray/ctor], the function performs batched computation, such that the function computes the dot product for each pair of vectors in `x` and `y` according to the specified dimension index. ```javascript var Float64Array = require( '@stdlib/array-float64' ); var array = require( '@stdlib/ndarray-array' ); -var x = array( new Float64Array() ); -var y = array( new Float64Array() ); +var opts = { + 'shape': [ 2, 3 ] +}; +var x = array( new Float64Array( [ 4.0, 2.0, -3.0, 5.0, -1.0, 3.0 ] ), opts ); +var y = array( new Float64Array( [ 2.0, 6.0, -1.0, -4.0, 8.0, 2.0 ] ), opts ); var z = ddot( x, y ); -// returns 0.0 +// returns + +var v1 = z.get( 0 ); +// returns 23.0 + +var v2 = z.get( 1 ); +// returns -22.0 ```
@@ -125,6 +138,11 @@ var z = ddot( x, y ); ## Notes +- The size of the contracted dimension must be the same for both input [`ndarrays`][@stdlib/ndarray/ctor]. +- The function resolves the dimension index for which to compute the dot product **before** broadcasting. +- Negative indices are resolved relative to the last [`ndarray`][@stdlib/ndarray/ctor] dimension, with the last dimension corresponding to `-1`. +- The output [`ndarray`][@stdlib/ndarray/ctor] has the same data type as the input [`ndarrays`][@stdlib/ndarray/ctor] and has a shape which is determined by broadcasting and excludes the contracted dimension. +- If provided empty vectors, the dot product is `0`. - `ddot()` provides a higher-level interface to the [BLAS][blas] level 1 function [`ddot`][@stdlib/blas/base/ddot]. @@ -138,27 +156,27 @@ var z = ddot( x, y ); ```javascript -var discreteUniform = require( '@stdlib/random-base-discrete-uniform' ); -var Float64Array = require( '@stdlib/array-float64' ); +var discreteUniform = require( '@stdlib/random-array-discrete-uniform' ); +var ndarray2array = require( '@stdlib/ndarray-to-array' ); var array = require( '@stdlib/ndarray-array' ); var ddot = require( '@stdlib/blas-ddot' ); -var x = array( new Float64Array( 10 ) ); -var y = array( new Float64Array( 10 ) ); +var opts = { + 'dtype': 'float64' +}; -var rand1 = discreteUniform.factory( 0, 100 ); -var rand2 = discreteUniform.factory( 0, 10 ); +var x = array( discreteUniform( 10, 0, 100, opts ), { + 'shape': [ 5, 2 ] +}); +console.log( ndarray2array( x ) ); -var i; -for ( i = 0; i < x.length; i++ ) { - x.set( i, rand1() ); - y.set( i, rand2() ); -} -console.log( x.toString() ); -console.log( y.toString() ); +var y = array( discreteUniform( 10, 0, 10, opts ), { + 'shape': x.shape +}); +console.log( ndarray2array( y ) ); -var z = ddot( x, y ); -console.log( z ); +var z = ddot( x, y, -1 ); +console.log( ndarray2array( z ) ); ``` @@ -257,7 +275,9 @@ Copyright © 2016-2024. The Stdlib [Authors][stdlib-authors]. [blas]: http://www.netlib.org/blas -[@stdlib/ndarray/array]: https://github.com/stdlib-js/ndarray-array +[@stdlib/ndarray/ctor]: https://github.com/stdlib-js/ndarray-ctor + +[@stdlib/ndarray/base/broadcast-shapes]: https://github.com/stdlib-js/ndarray-base-broadcast-shapes diff --git a/benchmark/benchmark.js b/benchmark/benchmark.js index a14b80d..204e147 100644 --- a/benchmark/benchmark.js +++ b/benchmark/benchmark.js @@ -21,15 +21,21 @@ // MODULES // var bench = require( '@stdlib/bench-harness' ); -var randu = require( '@stdlib/random-base-randu' ); var isnan = require( '@stdlib/math-base-assert-is-nan' ); var pow = require( '@stdlib/math-base-special-pow' ); -var Float64Array = require( '@stdlib/array-float64' ); +var uniform = require( '@stdlib/random-array-uniform' ); var array = require( '@stdlib/ndarray-array' ); var pkg = require( './../package.json' ).name; var ddot = require( './../lib/main.js' ); +// VARIABLES // + +var opts = { + 'dtype': 'float64' +}; + + // FUNCTIONS // /** @@ -40,21 +46,16 @@ var ddot = require( './../lib/main.js' ); * @returns {Function} benchmark function */ function createBenchmark( len ) { - var x; - var y; - var i; - - x = new Float64Array( len ); - y = new Float64Array( len ); - for ( i = 0; i < len; i++ ) { - x[ i ] = ( randu()*10.0 ) - 20.0; - y[ i ] = ( randu()*10.0 ) - 20.0; - } - x = array( x ); - y = array( y ); - + var x = array( uniform( len, -100.0, 100.0, opts ) ); + var y = array( uniform( len, -100.0, 100.0, opts ) ); return benchmark; + /** + * Benchmark function. + * + * @private + * @param {Benchmark} b - benchmark instance + */ function benchmark( b ) { var d; var i; @@ -62,12 +63,12 @@ function createBenchmark( len ) { b.tic(); for ( i = 0; i < b.iterations; i++ ) { d = ddot( x, y ); - if ( isnan( d ) ) { + if ( isnan( d.get() ) ) { b.fail( 'should not return NaN' ); } } b.toc(); - if ( isnan( d ) ) { + if ( isnan( d.get() ) ) { b.fail( 'should not return NaN' ); } b.pass( 'benchmark finished' ); @@ -96,7 +97,7 @@ function main() { for ( i = min; i <= max; i++ ) { len = pow( 10, i ); f = createBenchmark( len ); - bench( pkg+':len='+len, f ); + bench( pkg+'::vectors:len='+len, f ); } } diff --git a/benchmark/benchmark.stacks.js b/benchmark/benchmark.stacks.js new file mode 100644 index 0000000..b5bdcbd --- /dev/null +++ b/benchmark/benchmark.stacks.js @@ -0,0 +1,122 @@ +/** +* @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. +*/ + +'use strict'; + +// MODULES // + +var bench = require( '@stdlib/bench-harness' ); +var isnan = require( '@stdlib/math-base-assert-is-nan' ); +var pow = require( '@stdlib/math-base-special-pow' ); +var uniform = require( '@stdlib/random-array-uniform' ); +var numel = require( '@stdlib/ndarray-base-numel' ); +var array = require( '@stdlib/ndarray-array' ); +var pkg = require( './../package.json' ).name; +var ddot = require( './../lib/main.js' ); + + +// VARIABLES // + +var OPTS = { + 'dtype': 'float64' +}; + + +// FUNCTIONS // + +/** +* Creates a benchmark function. +* +* @private +* @param {PositiveIntegerArray} shape - array shape +* @returns {Function} benchmark function +*/ +function createBenchmark( shape ) { + var x; + var y; + var N; + var o; + + N = numel( shape ); + o = { + 'shape': shape + }; + x = array( uniform( N, -100.0, 100.0, OPTS ), o ); + y = array( uniform( N, -100.0, 100.0, OPTS ), o ); + + return benchmark; + + /** + * Benchmark function. + * + * @private + * @param {Benchmark} b - benchmark instance + */ + function benchmark( b ) { + var d; + var i; + + b.tic(); + for ( i = 0; i < b.iterations; i++ ) { + d = ddot( x, y ); + if ( isnan( d.iget( 0 ) ) ) { + b.fail( 'should not return NaN' ); + } + } + b.toc(); + if ( isnan( d.iget( 0 ) ) ) { + b.fail( 'should not return NaN' ); + } + b.pass( 'benchmark finished' ); + b.end(); + } +} + + +// MAIN // + +/** +* Main execution sequence. +* +* @private +*/ +function main() { + var shape; + var min; + var max; + var N; + var f; + var i; + + min = 1; // 10^min + max = 6; // 10^max + + for ( i = min; i <= max; i++ ) { + N = pow( 10, i ); + + shape = [ 2, N/2 ]; + f = createBenchmark( shape ); + bench( pkg+'::stacks:size='+N+',ndims='+shape.length+',shape=('+shape.join( ',' )+')', f ); + + shape = [ N/2, 2 ]; + f = createBenchmark( shape ); + bench( pkg+'::stacks:size='+N+',ndims='+shape.length+',shape=('+shape.join( ',' )+')', f ); + } +} + +main(); diff --git a/dist/index.js b/dist/index.js index 45ab85a..dd8a7f8 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,5 +1,5 @@ -"use strict";var o=function(r,e){return function(){return e||r((e={exports:{}}).exports,e),e.exports}};var i=o(function(g,t){ -var a=require('@stdlib/assert-is-float64vector-like/dist'),n=require('@stdlib/error-tools-fmtprodmsg/dist'),s=require('@stdlib/blas-base-ddot/dist').ndarray;function u(r,e){if(!a(r))throw new TypeError(n('0CHD8',r));if(!a(e))throw new TypeError(n('0CHD9',e));if(r.length!==e.length)throw new RangeError(n('0CH3S',r.length,e.length));return s(r.length,r.data,r.strides[0],r.offset,e.data,e.strides[0],e.offset)}t.exports=u -});var d=i();module.exports=d; +"use strict";var S=function(s,a){return function(){return a||s((a={exports:{}}).exports,a),a.exports}};var T=S(function(N,E){ +var w=require('@stdlib/assert-is-float64ndarray-like/dist'),V=require('@stdlib/assert-is-negative-integer/dist').isPrimitive,k=require('@stdlib/math-base-special-fast-min/dist'),F=require('@stdlib/array-base-without/dist'),I=require('@stdlib/ndarray-base-numel/dist'),j=require('@stdlib/ndarray-base-normalize-index/dist'),z=require('@stdlib/ndarray-base-maybe-broadcast-arrays/dist'),b=require('@stdlib/ndarray-base-ndarraylike2ndarray/dist'),q=require('@stdlib/ndarray-iter-stacks/dist'),D=require('@stdlib/ndarray-empty/dist'),x=require('@stdlib/blas-base-ddot/dist').ndarray,i=require('@stdlib/error-tools-fmtprodmsg/dist');function R(s,a){var r,o,n,v,y,c,u,f,e,t,m,l,p,d,g,h;if(!w(s))throw new TypeError(i("invalid argument. First argument must be an ndarray containing double-precision floating-point numbers. Value: `%s`.",s));if(!w(a))throw new TypeError(i("invalid argument. Second argument must be an ndarray containing double-precision floating-point numbers. Value: `%s`.",a));if(e=b(s),t=b(a),o=e.shape,n=t.shape,o.length<1)throw new TypeError(i("invalid argument. First argument must have at least one dimension."));if(n.length<1)throw new TypeError(i("invalid argument. Second argument must have at least one dimension."));if(arguments.length>2){if(r=arguments[2],!V(r))throw new TypeError(i("invalid argument. Third argument must be a negative integer. Value: `%s`.",r))}else r=-1;if(p=k(o.length,n.length)-1,r=j(r,p),r===-1)throw new RangeError(i("invalid argument. Third argument must be a value on the interval: [%d,%d]. Value: `%d`.",-p,-1,arguments[2]));if(d=o[r],n[r]!==d)throw new RangeError(i("invalid argument. The size of the contracted dimension must be the same for both input ndarrays. Dim(%s,%d) = %d. Dim(%s,%d) = %d.","x",r,d,"y",r,n[r]));try{f=z([e,t])}catch(B){throw new Error(i("invalid arguments. Input ndarrays must be broadcast compatible. Shape(%s) = (%s). Shape(%s) = (%s).","x",o.join(","),"y",n.join(",")))}if(e=f[0],t=f[1],v=F(e.shape,r),u=D(v,{dtype:e.dtype,order:e.order}),v.length===0)return g=x(d,e.data,e.strides[0],e.offset,t.data,t.strides[0],t.offset),u.iset(g),u;for(y=q(e,[r]),c=q(t,[r]),h=0;h\n*\n* var v = z.get();\n* // returns -5.0\n*/\nfunction ddot( x, y ) {\n\tvar dim;\n\tvar xsh;\n\tvar ysh;\n\tvar osh;\n\tvar xit;\n\tvar yit;\n\tvar out;\n\tvar tmp;\n\tvar xc;\n\tvar yc;\n\tvar vx;\n\tvar vy;\n\tvar dm;\n\tvar S;\n\tvar v;\n\tvar i;\n\n\tif ( !isFloat64ndarrayLike( x ) ) {\n\t\tthrow new TypeError( format( 'invalid argument. First argument must be an ndarray containing double-precision floating-point numbers. Value: `%s`.', x ) );\n\t}\n\tif ( !isFloat64ndarrayLike( y ) ) {\n\t\tthrow new TypeError( format( 'invalid argument. Second argument must be an ndarray containing double-precision floating-point numbers. Value: `%s`.', y ) );\n\t}\n\t// Convert the input arrays to \"base\" ndarrays:\n\txc = ndarraylike2ndarray( x );\n\tyc = ndarraylike2ndarray( y );\n\n\t// Resolve the input array shapes:\n\txsh = xc.shape;\n\tysh = yc.shape;\n\n\t// Validate that we've been provided non-zero-dimensional arrays...\n\tif ( xsh.length < 1 ) {\n\t\tthrow new TypeError( format( 'invalid argument. First argument must have at least one dimension.' ) );\n\t}\n\tif ( ysh.length < 1 ) {\n\t\tthrow new TypeError( format( 'invalid argument. Second argument must have at least one dimension.' ) );\n\t}\n\t// Validate that the dimension argument is a negative integer...\n\tif ( arguments.length > 2 ) {\n\t\tdim = arguments[ 2 ];\n\t\tif ( !isNegativeInteger( dim ) ) {\n\t\t\tthrow new TypeError( format( 'invalid argument. Third argument must be a negative integer. Value: `%s`.', dim ) );\n\t\t}\n\t} else {\n\t\tdim = -1;\n\t}\n\t// Validate that a provided dimension index is within bounds **before** broadcasting...\n\tdm = min( xsh.length, ysh.length ) - 1;\n\tdim = normalizeIndex( dim, dm );\n\tif ( dim === -1 ) {\n\t\tthrow new RangeError( format( 'invalid argument. Third argument must be a value on the interval: [%d,%d]. Value: `%d`.', -dm, -1, arguments[ 2 ] ) );\n\t}\n\t// Validate that the contracted dimension size is the same for both input arrays...\n\tS = xsh[ dim ];\n\tif ( ysh[ dim ] !== S ) {\n\t\tthrow new RangeError( format( 'invalid argument. The size of the contracted dimension must be the same for both input ndarrays. Dim(%s,%d) = %d. Dim(%s,%d) = %d.', 'x', dim, S, 'y', dim, ysh[ dim ] ) );\n\t}\n\t// Broadcast the input arrays to a common shape....\n\ttry {\n\t\ttmp = maybeBroadcastArrays( [ xc, yc ] );\n\t} catch ( err ) { // eslint-disable-line no-unused-vars\n\t\tthrow new Error( format( 'invalid arguments. Input ndarrays must be broadcast compatible. Shape(%s) = (%s). Shape(%s) = (%s).', 'x', xsh.join( ',' ), 'y', ysh.join( ',' ) ) );\n\t}\n\txc = tmp[ 0 ];\n\tyc = tmp[ 1 ];\n\n\t// Resolve the output array shape by excluding the contracted dimension:\n\tosh = without( xc.shape, dim );\n\n\t// Allocate an empty output array:\n\tout = empty( osh, {\n\t\t'dtype': xc.dtype,\n\t\t'order': xc.order\n\t});\n\n\t// If we are only provided one-dimensional input arrays, we can skip iterating over stacks...\n\tif ( osh.length === 0 ) {\n\t\tv = base( S, xc.data, xc.strides[0], xc.offset, yc.data, yc.strides[0], yc.offset ); // eslint-disable-line max-len\n\t\tout.iset( v );\n\t\treturn out;\n\t}\n\t// Create iterators for iterating over stacks of vectors:\n\txit = nditerStacks( xc, [ dim ] );\n\tyit = nditerStacks( yc, [ dim ] );\n\n\t// Compute the dot product for each pair of vectors...\n\tfor ( i = 0; i < numel( osh ); i++ ) {\n\t\tvx = xit.next().value;\n\t\tvy = yit.next().value;\n\t\tv = base( S, vx.data, vx.strides[0], vx.offset, vy.data, vy.strides[0], vy.offset ); // eslint-disable-line max-len\n\t\tout.iset( i, v );\n\t}\n\treturn out;\n}\n\n\n// EXPORTS //\n\nmodule.exports = ddot;\n", "/**\n* @license Apache-2.0\n*\n* Copyright (c) 2020 The Stdlib Authors.\n*\n* Licensed under the Apache License, Version 2.0 (the \"License\");\n* you may not use this file except in compliance with the License.\n* You may obtain a copy of the License at\n*\n* http://www.apache.org/licenses/LICENSE-2.0\n*\n* Unless required by applicable law or agreed to in writing, software\n* distributed under the License is distributed on an \"AS IS\" BASIS,\n* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n* See the License for the specific language governing permissions and\n* limitations under the License.\n*/\n\n'use strict';\n\n/**\n* BLAS level 1 routine to compute the dot product of two double-precision floating-point vectors.\n*\n* @module @stdlib/blas-ddot\n*\n* @example\n* var Float64Array = require( '@stdlib/array-float64' );\n* var array = require( '@stdlib/ndarray-array' );\n* var ddot = require( '@stdlib/blas-ddot' );\n*\n* var x = array( new Float64Array( [ 4.0, 2.0, -3.0, 5.0, -1.0 ] ) );\n* var y = array( new Float64Array( [ 2.0, 6.0, -1.0, -4.0, 8.0 ] ) );\n*\n* var z = ddot( x, y );\n* // returns \n*\n* var v = z.get();\n* // returns -5.0\n*/\n\n// MODULES //\n\nvar main = require( './main.js' );\n\n\n// EXPORTS //\n\nmodule.exports = main;\n"], + "mappings": "uGAAA,IAAAA,EAAAC,EAAA,SAAAC,EAAAC,EAAA,cAsBA,IAAIC,EAAuB,QAAS,uCAAwC,EACxEC,EAAoB,QAAS,oCAAqC,EAAE,YACpEC,EAAM,QAAS,oCAAqC,EACpDC,EAAU,QAAS,4BAA6B,EAChDC,EAAQ,QAAS,4BAA6B,EAC9CC,EAAiB,QAAS,sCAAuC,EACjEC,EAAuB,QAAS,6CAA8C,EAC9EC,EAAsB,QAAS,0CAA2C,EAC1EC,EAAe,QAAS,6BAA8B,EACtDC,EAAQ,QAAS,uBAAwB,EACzCC,EAAO,QAAS,wBAAyB,EAAE,QAC3CC,EAAS,QAAS,uBAAwB,EAkC9C,SAASC,EAAMC,EAAGC,EAAI,CACrB,IAAIC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EAEJ,GAAK,CAAC9B,EAAsBa,CAAE,EAC7B,MAAM,IAAI,UAAWF,EAAQ,uHAAwHE,CAAE,CAAE,EAE1J,GAAK,CAACb,EAAsBc,CAAE,EAC7B,MAAM,IAAI,UAAWH,EAAQ,wHAAyHG,CAAE,CAAE,EAW3J,GARAS,EAAKhB,EAAqBM,CAAE,EAC5BW,EAAKjB,EAAqBO,CAAE,EAG5BE,EAAMO,EAAG,MACTN,EAAMO,EAAG,MAGJR,EAAI,OAAS,EACjB,MAAM,IAAI,UAAWL,EAAQ,oEAAqE,CAAE,EAErG,GAAKM,EAAI,OAAS,EACjB,MAAM,IAAI,UAAWN,EAAQ,qEAAsE,CAAE,EAGtG,GAAK,UAAU,OAAS,GAEvB,GADAI,EAAM,UAAW,CAAE,EACd,CAACd,EAAmBc,CAAI,EAC5B,MAAM,IAAI,UAAWJ,EAAQ,4EAA6EI,CAAI,CAAE,OAGjHA,EAAM,GAKP,GAFAY,EAAKzB,EAAKc,EAAI,OAAQC,EAAI,MAAO,EAAI,EACrCF,EAAMV,EAAgBU,EAAKY,CAAG,EACzBZ,IAAQ,GACZ,MAAM,IAAI,WAAYJ,EAAQ,0FAA2F,CAACgB,EAAI,GAAI,UAAW,CAAE,CAAE,CAAE,EAIpJ,GADAC,EAAIZ,EAAKD,CAAI,EACRE,EAAKF,CAAI,IAAMa,EACnB,MAAM,IAAI,WAAYjB,EAAQ,qIAAsI,IAAKI,EAAKa,EAAG,IAAKb,EAAKE,EAAKF,CAAI,CAAE,CAAE,EAGzM,GAAI,CACHO,EAAMhB,EAAsB,CAAEiB,EAAIC,CAAG,CAAE,CACxC,OAAUO,EAAM,CACf,MAAM,IAAI,MAAOpB,EAAQ,sGAAuG,IAAKK,EAAI,KAAM,GAAI,EAAG,IAAKC,EAAI,KAAM,GAAI,CAAE,CAAE,CAC9K,CAcA,GAbAM,EAAKD,EAAK,CAAE,EACZE,EAAKF,EAAK,CAAE,EAGZJ,EAAMf,EAASoB,EAAG,MAAOR,CAAI,EAG7BM,EAAMZ,EAAOS,EAAK,CACjB,MAASK,EAAG,MACZ,MAASA,EAAG,KACb,CAAC,EAGIL,EAAI,SAAW,EACnB,OAAAW,EAAInB,EAAMkB,EAAGL,EAAG,KAAMA,EAAG,QAAQ,CAAC,EAAGA,EAAG,OAAQC,EAAG,KAAMA,EAAG,QAAQ,CAAC,EAAGA,EAAG,MAAO,EAClFH,EAAI,KAAMQ,CAAE,EACLR,EAOR,IAJAF,EAAMX,EAAce,EAAI,CAAER,CAAI,CAAE,EAChCK,EAAMZ,EAAcgB,EAAI,CAAET,CAAI,CAAE,EAG1Be,EAAI,EAAGA,EAAI1B,EAAOc,CAAI,EAAGY,IAC9BL,EAAKN,EAAI,KAAK,EAAE,MAChBO,EAAKN,EAAI,KAAK,EAAE,MAChBS,EAAInB,EAAMkB,EAAGH,EAAG,KAAMA,EAAG,QAAQ,CAAC,EAAGA,EAAG,OAAQC,EAAG,KAAMA,EAAG,QAAQ,CAAC,EAAGA,EAAG,MAAO,EAClFL,EAAI,KAAMS,EAAGD,CAAE,EAEhB,OAAOR,CACR,CAKAtB,EAAO,QAAUa,IC7HjB,IAAIoB,EAAO,IAKX,OAAO,QAAUA", + "names": ["require_main", "__commonJSMin", "exports", "module", "isFloat64ndarrayLike", "isNegativeInteger", "min", "without", "numel", "normalizeIndex", "maybeBroadcastArrays", "ndarraylike2ndarray", "nditerStacks", "empty", "base", "format", "ddot", "x", "y", "dim", "xsh", "ysh", "osh", "xit", "yit", "out", "tmp", "xc", "yc", "vx", "vy", "dm", "S", "v", "i", "err", "main"] } diff --git a/docs/repl.txt b/docs/repl.txt index cfd0d40..98a26ac 100644 --- a/docs/repl.txt +++ b/docs/repl.txt @@ -1,27 +1,52 @@ -{{alias}}( x, y ) +{{alias}}( x, y[, dim] ) Computes the dot product of two double-precision floating-point vectors. - If provided empty vectors, the function returns `0.0`. + If provided at least one input array having more than one dimension, the + input arrays are broadcasted to a common shape. + + For multi-dimensional input arrays, the function performs batched + computation, such that the function computes the dot product for each pair + of vectors in `x` and `y` according to the specified dimension index. + + The size of the contracted dimension must be the same for both input arrays. + + The function resolves the dimension index for which to compute the dot + product *before* broadcasting. + + If provided empty vectors, the dot product is `0`. Parameters ---------- x: ndarray - First input array whose underlying data type is 'float64'. + First input array. Must have a 'float64' data type. Must have at least + one dimension and be broadcast-compatible with the second input array. y: ndarray - Second input array whose underlying data type is 'float64'. + Second input array. Must have a 'float64' data type. Must have at least + one dimension and be broadcast-compatible with the first input array. + + dim: integer (optional) + Dimension index for which to compute the dot product. Must be a negative + integer. Negative indices are resolved relative to the last array + dimension, with the last dimension corresponding to `-1`. Default: -1. Returns ------- - dot: number - The dot product. + out: ndarray + The dot product. The output array has the same data type as the input + arrays and has a shape which is determined by broadcasting and excludes + the contracted dimension. Examples -------- - > var x = {{alias:@stdlib/ndarray/array}}( new {{alias:@stdlib/array/float64}}( [ 4.0, 2.0, -3.0, 5.0, -1.0 ] ) ); - > var y = {{alias:@stdlib/ndarray/array}}( new {{alias:@stdlib/array/float64}}( [ 2.0, 6.0, -1.0, -4.0, 8.0 ] ) ); - > {{alias}}( x, y ) + > var xbuf = new {{alias:@stdlib/array/float64}}( [ 4.0, 2.0, -3.0, 5.0, -1.0 ] ); + > var x = {{alias:@stdlib/ndarray/array}}( xbuf ); + > var ybuf = new {{alias:@stdlib/array/float64}}( [ 2.0, 6.0, -1.0, -4.0, 8.0 ] ); + > var y = {{alias:@stdlib/ndarray/array}}( ybuf ); + > var z = {{alias}}( x, y ) + + > z.get() -5.0 See Also diff --git a/docs/types/index.d.ts b/docs/types/index.d.ts index c2f2ffc..d92b8a4 100644 --- a/docs/types/index.d.ts +++ b/docs/types/index.d.ts @@ -25,11 +25,23 @@ import { float64ndarray } from '@stdlib/types/ndarray'; /** * Computes the dot product of two double-precision floating-point vectors. * +* ## Notes +* +* - If provided at least one input array having more than one dimension, the input arrays are broadcasted to a common shape. +* - For multi-dimensional input arrays, the function performs batched computation, such that the function computes the dot product for each pair of vectors in `x` and `y` according to the specified dimension index. +* - The size of the contracted dimension must be the same for both input arrays. +* - The function resolves the dimension index for which to compute the dot product **before** broadcasting. +* - If provided empty vectors, the dot product is `0`. +* - Negative indices are resolved relative to the last array dimension, with the last dimension corresponding to `-1`. +* - The output array has the same data type as the input arrays and has a shape which is determined by broadcasting and excludes the contracted dimension. +* * @param x - first input array * @param y - second input array -* @throws first argument must be a 1-dimensional `ndarray` containing double-precision floating-point numbers -* @throws second argument must be a 1-dimensional `ndarray` containing double-precision floating-point numbers -* @throws input arrays must be the same length +* @param dim - dimension for which to compute the dot product (default: -1) +* @throws first argument must be a non-zero-dimensional ndarray containing double-precision floating-point numbers +* @throws second argument must be a non-zero-dimensional ndarray containing double-precision floating-point numbers +* @throws input arrays must be broadcast-compatible +* @throws the size of the contracted dimension must be the same for both input arrays * @returns dot product * * @example @@ -40,9 +52,12 @@ import { float64ndarray } from '@stdlib/types/ndarray'; * var y = array( new Float64Array( [ 2.0, 6.0, -1.0, -4.0, 8.0 ] ) ); * * var z = ddot( x, y ); +* returns +* +* var v = z.get(); * // returns -5.0 */ -declare function ddot( x: float64ndarray, y: float64ndarray ): number; +declare function ddot( x: float64ndarray, y: float64ndarray, dim?: number ): float64ndarray; // EXPORTS // diff --git a/docs/types/test.ts b/docs/types/test.ts index 81984ba..b5874fe 100644 --- a/docs/types/test.ts +++ b/docs/types/test.ts @@ -24,7 +24,7 @@ import ddot = require( './index' ); // The function returns a number... { - ddot( zeros( [ 10 ] ), zeros( [ 10 ] ) ); // $ExpectType number + ddot( zeros( [ 10 ] ), zeros( [ 10 ] ) ); // $ExpectType float64ndarray } // The compiler throws an error if the function is provided a first argument which is not an ndarray... @@ -40,6 +40,16 @@ import ddot = require( './index' ); ddot( {}, y ); // $ExpectError ddot( [], y ); // $ExpectError ddot( ( x: number ): number => x, y ); // $ExpectError + + ddot( 10, y, -1 ); // $ExpectError + ddot( '10', y, -1 ); // $ExpectError + ddot( true, y, -1 ); // $ExpectError + ddot( false, y, -1 ); // $ExpectError + ddot( null, y, -1 ); // $ExpectError + ddot( undefined, y, -1 ); // $ExpectError + ddot( {}, y, -1 ); // $ExpectError + ddot( [], y, -1 ); // $ExpectError + ddot( ( x: number ): number => x, y, -1 ); // $ExpectError } // The compiler throws an error if the function is provided a second argument which is not an ndarray... @@ -55,6 +65,30 @@ import ddot = require( './index' ); ddot( x, {} ); // $ExpectError ddot( x, [] ); // $ExpectError ddot( x, ( x: number ): number => x ); // $ExpectError + + ddot( x, 10, -1 ); // $ExpectError + ddot( x, '10', -1 ); // $ExpectError + ddot( x, true, -1 ); // $ExpectError + ddot( x, false, -1 ); // $ExpectError + ddot( x, null, -1 ); // $ExpectError + ddot( x, undefined, -1 ); // $ExpectError + ddot( x, {}, -1 ); // $ExpectError + ddot( x, [], -1 ); // $ExpectError + ddot( x, ( x: number ): number => x, -1 ); // $ExpectError +} + +// The compiler throws an error if the function is provided a third argument which is not a number... +{ + const x = zeros( [ 10 ] ); + const y = zeros( [ 10 ] ); + + ddot( x, y, '10' ); // $ExpectError + ddot( x, y, true ); // $ExpectError + ddot( x, y, false ); // $ExpectError + ddot( x, y, null ); // $ExpectError + ddot( x, y, {} ); // $ExpectError + ddot( x, y, [] ); // $ExpectError + ddot( x, y, ( x: number ): number => x ); // $ExpectError } // The compiler throws an error if the function is provided an unsupported number of arguments... @@ -64,5 +98,5 @@ import ddot = require( './index' ); ddot(); // $ExpectError ddot( x ); // $ExpectError - ddot( x, y, {} ); // $ExpectError + ddot( x, y, -1, {} ); // $ExpectError } diff --git a/examples/index.js b/examples/index.js index 706ff78..17c1a16 100644 --- a/examples/index.js +++ b/examples/index.js @@ -18,24 +18,24 @@ 'use strict'; -var discreteUniform = require( '@stdlib/random-base-discrete-uniform' ); -var Float64Array = require( '@stdlib/array-float64' ); +var discreteUniform = require( '@stdlib/random-array-discrete-uniform' ); +var ndarray2array = require( '@stdlib/ndarray-to-array' ); var array = require( '@stdlib/ndarray-array' ); var ddot = require( './../lib' ); -var x = array( new Float64Array( 10 ) ); -var y = array( new Float64Array( 10 ) ); +var opts = { + 'dtype': 'float64' +}; -var rand1 = discreteUniform.factory( 0, 100 ); -var rand2 = discreteUniform.factory( 0, 10 ); +var x = array( discreteUniform( 10, 0, 100, opts ), { + 'shape': [ 5, 2 ] +}); +console.log( ndarray2array( x ) ); -var i; -for ( i = 0; i < x.length; i++ ) { - x.set( i, rand1() ); - y.set( i, rand2() ); -} -console.log( x.toString() ); -console.log( y.toString() ); +var y = array( discreteUniform( 10, 0, 10, opts ), { + 'shape': x.shape +}); +console.log( ndarray2array( y ) ); -var z = ddot( x, y ); -console.log( z ); +var z = ddot( x, y, -1 ); +console.log( ndarray2array( z ) ); diff --git a/lib/index.js b/lib/index.js index ae9a502..ed9dfa8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -32,6 +32,9 @@ * var y = array( new Float64Array( [ 2.0, 6.0, -1.0, -4.0, 8.0 ] ) ); * * var z = ddot( x, y ); +* // returns +* +* var v = z.get(); * // returns -5.0 */ diff --git a/lib/main.js b/lib/main.js index 44f715d..3d03cd8 100644 --- a/lib/main.js +++ b/lib/main.js @@ -20,9 +20,18 @@ // MODULES // -var isFloat64VectorLike = require( '@stdlib/assert-is-float64vector-like' ); +var isFloat64ndarrayLike = require( '@stdlib/assert-is-float64ndarray-like' ); +var isNegativeInteger = require( '@stdlib/assert-is-negative-integer' ).isPrimitive; +var min = require( '@stdlib/math-base-special-fast-min' ); +var without = require( '@stdlib/array-base-without' ); +var numel = require( '@stdlib/ndarray-base-numel' ); +var normalizeIndex = require( '@stdlib/ndarray-base-normalize-index' ); +var maybeBroadcastArrays = require( '@stdlib/ndarray-base-maybe-broadcast-arrays' ); +var ndarraylike2ndarray = require( '@stdlib/ndarray-base-ndarraylike2ndarray' ); +var nditerStacks = require( '@stdlib/ndarray-iter-stacks' ); +var empty = require( '@stdlib/ndarray-empty' ); +var base = require( '@stdlib/blas-base-ddot' ).ndarray; var format = require( '@stdlib/string-format' ); -var dot = require( '@stdlib/blas-base-ddot' ).ndarray; // MAIN // @@ -30,12 +39,18 @@ var dot = require( '@stdlib/blas-base-ddot' ).ndarray; /** * Computes the dot product of two double-precision floating-point vectors. * -* @param {VectorLike} x - first input array -* @param {VectorLike} y - second input array -* @throws {TypeError} first argument must be a 1-dimensional ndarray containing double-precision floating-point numbers -* @throws {TypeError} second argument must be a 1-dimensional ndarray containing double-precision floating-point numbers -* @throws {RangeError} input arrays must be the same length -* @returns {number} dot product +* @param {ndarrayLike} x - first input array +* @param {ndarrayLike} y - second input array +* @param {NegativeInteger} dim - dimension for which to compute the dot product +* @throws {TypeError} first argument must be a ndarray containing double-precision floating-point numbers +* @throws {TypeError} first argument must have at least one dimension +* @throws {TypeError} second argument must be a ndarray containing double-precision floating-point numbers +* @throws {TypeError} second argument must have at least one dimension +* @throws {TypeError} third argument must be a negative integer +* @throws {Error} input arrays must be broadcast compatible +* @throws {RangeError} the size of the contracted dimension must be the same for both input arrays +* @throws {RangeError} third argument is out-of-bounds +* @returns {ndarray} ndarray containing the dot product * * @example * var Float64Array = require( '@stdlib/array-float64' ); @@ -45,19 +60,106 @@ var dot = require( '@stdlib/blas-base-ddot' ).ndarray; * var y = array( new Float64Array( [ 2.0, 6.0, -1.0, -4.0, 8.0 ] ) ); * * var z = ddot( x, y ); +* // returns +* +* var v = z.get(); * // returns -5.0 */ function ddot( x, y ) { - if ( !isFloat64VectorLike( x ) ) { - throw new TypeError( format( 'invalid argument. First argument must be a one-dimensional ndarray containing double-precision floating-point numbers (i.e., an ndarray whose underlying data buffer is a Float64Array). Value: `%s`.', x ) ); + var dim; + var xsh; + var ysh; + var osh; + var xit; + var yit; + var out; + var tmp; + var xc; + var yc; + var vx; + var vy; + var dm; + var S; + var v; + var i; + + if ( !isFloat64ndarrayLike( x ) ) { + throw new TypeError( format( 'invalid argument. First argument must be an ndarray containing double-precision floating-point numbers. Value: `%s`.', x ) ); + } + if ( !isFloat64ndarrayLike( y ) ) { + throw new TypeError( format( 'invalid argument. Second argument must be an ndarray containing double-precision floating-point numbers. Value: `%s`.', y ) ); + } + // Convert the input arrays to "base" ndarrays: + xc = ndarraylike2ndarray( x ); + yc = ndarraylike2ndarray( y ); + + // Resolve the input array shapes: + xsh = xc.shape; + ysh = yc.shape; + + // Validate that we've been provided non-zero-dimensional arrays... + if ( xsh.length < 1 ) { + throw new TypeError( format( 'invalid argument. First argument must have at least one dimension.' ) ); + } + if ( ysh.length < 1 ) { + throw new TypeError( format( 'invalid argument. Second argument must have at least one dimension.' ) ); + } + // Validate that the dimension argument is a negative integer... + if ( arguments.length > 2 ) { + dim = arguments[ 2 ]; + if ( !isNegativeInteger( dim ) ) { + throw new TypeError( format( 'invalid argument. Third argument must be a negative integer. Value: `%s`.', dim ) ); + } + } else { + dim = -1; } - if ( !isFloat64VectorLike( y ) ) { - throw new TypeError( format( 'invalid argument. Second argument must be a one-dimensional ndarray containing double-precision floating-point numbers (i.e., an ndarray whose underlying data buffer is a Float64Array). Value: `%s`.', y ) ); + // Validate that a provided dimension index is within bounds **before** broadcasting... + dm = min( xsh.length, ysh.length ) - 1; + dim = normalizeIndex( dim, dm ); + if ( dim === -1 ) { + throw new RangeError( format( 'invalid argument. Third argument must be a value on the interval: [%d,%d]. Value: `%d`.', -dm, -1, arguments[ 2 ] ) ); } - if ( x.length !== y.length ) { - throw new RangeError( format( 'invalid argument. Arrays must be the same length. First argument length: `%u`. Second argument length: `%u`.', x.length, y.length ) ); + // Validate that the contracted dimension size is the same for both input arrays... + S = xsh[ dim ]; + if ( ysh[ dim ] !== S ) { + throw new RangeError( format( 'invalid argument. The size of the contracted dimension must be the same for both input ndarrays. Dim(%s,%d) = %d. Dim(%s,%d) = %d.', 'x', dim, S, 'y', dim, ysh[ dim ] ) ); + } + // Broadcast the input arrays to a common shape.... + try { + tmp = maybeBroadcastArrays( [ xc, yc ] ); + } catch ( err ) { // eslint-disable-line no-unused-vars + throw new Error( format( 'invalid arguments. Input ndarrays must be broadcast compatible. Shape(%s) = (%s). Shape(%s) = (%s).', 'x', xsh.join( ',' ), 'y', ysh.join( ',' ) ) ); + } + xc = tmp[ 0 ]; + yc = tmp[ 1 ]; + + // Resolve the output array shape by excluding the contracted dimension: + osh = without( xc.shape, dim ); + + // Allocate an empty output array: + out = empty( osh, { + 'dtype': xc.dtype, + 'order': xc.order + }); + + // If we are only provided one-dimensional input arrays, we can skip iterating over stacks... + if ( osh.length === 0 ) { + v = base( S, xc.data, xc.strides[0], xc.offset, yc.data, yc.strides[0], yc.offset ); // eslint-disable-line max-len + out.iset( v ); + return out; + } + // Create iterators for iterating over stacks of vectors: + xit = nditerStacks( xc, [ dim ] ); + yit = nditerStacks( yc, [ dim ] ); + + // Compute the dot product for each pair of vectors... + for ( i = 0; i < numel( osh ); i++ ) { + vx = xit.next().value; + vy = yit.next().value; + v = base( S, vx.data, vx.strides[0], vx.offset, vy.data, vy.strides[0], vy.offset ); // eslint-disable-line max-len + out.iset( i, v ); } - return dot( x.length, x.data, x.strides[ 0 ], x.offset, y.data, y.strides[ 0 ], y.offset ); // eslint-disable-line max-len + return out; } diff --git a/package.json b/package.json index fbb877c..351b973 100644 --- a/package.json +++ b/package.json @@ -38,8 +38,17 @@ "url": "https://github.com/stdlib-js/stdlib/issues" }, "dependencies": { - "@stdlib/assert-is-float64vector-like": "^0.2.2", + "@stdlib/array-base-without": "github:stdlib-js/array-base-without#main", + "@stdlib/assert-is-float64ndarray-like": "^0.2.2", + "@stdlib/assert-is-negative-integer": "^0.2.2", "@stdlib/blas-base-ddot": "^0.3.0", + "@stdlib/math-base-special-fast-min": "^0.3.0", + "@stdlib/ndarray-base-maybe-broadcast-arrays": "^0.2.1", + "@stdlib/ndarray-base-ndarraylike2ndarray": "github:stdlib-js/ndarray-base-ndarraylike2ndarray#main", + "@stdlib/ndarray-base-normalize-index": "^0.2.2", + "@stdlib/ndarray-base-numel": "^0.2.2", + "@stdlib/ndarray-empty": "^0.3.0", + "@stdlib/ndarray-iter-stacks": "github:stdlib-js/ndarray-iter-stacks#main", "@stdlib/string-format": "^0.2.2", "@stdlib/types": "^0.3.2", "@stdlib/error-tools-fmtprodmsg": "^0.2.2" @@ -51,8 +60,12 @@ "@stdlib/math-base-special-pow": "^0.3.0", "@stdlib/ndarray-array": "^0.2.1", "@stdlib/ndarray-ctor": "^0.2.2", - "@stdlib/random-base-discrete-uniform": "^0.2.1", - "@stdlib/random-base-randu": "^0.2.1", + "@stdlib/ndarray-ndims": "^0.2.2", + "@stdlib/ndarray-shape": "^0.2.2", + "@stdlib/ndarray-to-array": "^0.2.1", + "@stdlib/ndarray-zeros": "^0.3.0", + "@stdlib/random-array-discrete-uniform": "^0.2.1", + "@stdlib/random-array-uniform": "^0.2.1", "tape": "git+https://github.com/kgryte/tape.git#fix/globby", "istanbul": "^0.4.1", "tap-min": "git+https://github.com/Planeshifter/tap-min.git", diff --git a/test/test.js b/test/test.js index 513ebf7..f94457a 100644 --- a/test/test.js +++ b/test/test.js @@ -24,7 +24,10 @@ var tape = require( 'tape' ); var Float64Array = require( '@stdlib/array-float64' ); var Float32Array = require( '@stdlib/array-float32' ); var array = require( '@stdlib/ndarray-array' ); +var zeros = require( '@stdlib/ndarray-zeros' ); var ndarray = require( '@stdlib/ndarray-ctor' ); +var ndims = require( '@stdlib/ndarray-ndims' ); +var shape = require( '@stdlib/ndarray-shape' ); var ddot = require( './../lib' ); @@ -41,7 +44,7 @@ tape( 'the function has an arity of 2', function test( t ) { t.end(); }); -tape( 'the function throws an error if provided a first argument which is not a 1-dimensional ndarray containing double-precision floating-point numbers', function test( t ) { +tape( 'the function throws an error if provided a first argument which is not a non-zero-dimensional ndarray containing double-precision floating-point numbers', function test( t ) { var values; var i; @@ -55,6 +58,9 @@ tape( 'the function throws an error if provided a first argument which is not a {}, [], function noop() {}, + zeros( [], { + 'dtype': 'float64' + }), array( new Float32Array( 10 ) ) ]; @@ -72,7 +78,7 @@ tape( 'the function throws an error if provided a first argument which is not a } }); -tape( 'the function throws an error if provided a second argument which is not a 1-dimensional ndarray containing double-precision floating-point numbers', function test( t ) { +tape( 'the function throws an error if provided a first argument which is not a non-zero-dimensional ndarray containing double-precision floating-point numbers (dimension)', function test( t ) { var values; var i; @@ -86,6 +92,43 @@ tape( 'the function throws an error if provided a second argument which is not a {}, [], function noop() {}, + zeros( [], { + 'dtype': 'float64' + }), + array( new Float32Array( 10 ) ) + ]; + + for ( i = 0; i < values.length; i++ ) { + t.throws( badValue( values[ i ] ), TypeError, 'throws an error when provided ' + values[ i ] ); + } + t.end(); + + function badValue( value ) { + var y = array( new Float64Array( 10 ) ); + + return function badValue() { + ddot( value, y, -1 ); + }; + } +}); + +tape( 'the function throws an error if provided a second argument which is not a non-zero-dimensional ndarray containing double-precision floating-point numbers', function test( t ) { + var values; + var i; + + values = [ + 5, + '5', + true, + false, + null, + void 0, + {}, + [], + function noop() {}, + zeros( [], { + 'dtype': 'float64' + }), array( new Float32Array( 10 ) ) ]; @@ -103,7 +146,167 @@ tape( 'the function throws an error if provided a second argument which is not a } }); -tape( 'the function throws an error if provided unequal length vectors', function test( t ) { +tape( 'the function throws an error if provided a second argument which is not a non-zero-dimensional ndarray containing double-precision floating-point numbers (dimension)', function test( t ) { + var values; + var i; + + values = [ + 5, + '5', + true, + false, + null, + void 0, + {}, + [], + function noop() {}, + zeros( [], { + 'dtype': 'float64' + }), + array( new Float32Array( 10 ) ) + ]; + + for ( i = 0; i < values.length; i++ ) { + t.throws( badValue( values[ i ] ), TypeError, 'throws an error when provided ' + values[ i ] ); + } + t.end(); + + function badValue( value ) { + var x = array( new Float64Array( 10 ) ); + + return function badValue() { + ddot( x, value, -1 ); + }; + } +}); + +tape( 'the function throws an error if provided a third argument which is not a negative integer (vectors)', function test( t ) { + var values; + var i; + + values = [ + '5', + 0, + 5, + NaN, + -3.14, + true, + false, + null, + void 0, + {}, + [], + function noop() {} + ]; + + for ( i = 0; i < values.length; i++ ) { + t.throws( badValue( values[ i ] ), TypeError, 'throws an error when provided ' + values[ i ] ); + } + t.end(); + + function badValue( value ) { + var x = array( new Float64Array( 10 ) ); + var y = array( new Float64Array( 10 ) ); + + return function badValue() { + ddot( x, y, value ); + }; + } +}); + +tape( 'the function throws an error if provided a third argument which is not a negative integer (multi-dimensional arrays)', function test( t ) { + var values; + var i; + + values = [ + '5', + 0, + 5, + NaN, + -3.14, + true, + false, + null, + void 0, + {}, + [], + function noop() {} + ]; + + for ( i = 0; i < values.length; i++ ) { + t.throws( badValue( values[ i ] ), TypeError, 'throws an error when provided ' + values[ i ] ); + } + t.end(); + + function badValue( value ) { + var opts = { + 'shape': [ 2, 5 ] + }; + var x = array( new Float64Array( 10 ), opts ); + var y = array( new Float64Array( 10 ), opts ); + + return function badValue() { + ddot( x, y, value ); + }; + } +}); + +tape( 'the function throws an error if provided a third argument which is out-of-bounds (vectors)', function test( t ) { + var values; + var i; + + values = [ + -2, + -3, + -4, + -5 + ]; + + for ( i = 0; i < values.length; i++ ) { + t.throws( badValue( values[ i ] ), RangeError, 'throws an error when provided ' + values[ i ] ); + } + t.end(); + + function badValue( value ) { + var x = array( new Float64Array( 10 ) ); + var y = array( new Float64Array( 10 ) ); + + return function badValue() { + ddot( x, y, value ); + }; + } +}); + +tape( 'the function throws an error if provided a third argument which is out-of-bounds (multi-dimensional arrays)', function test( t ) { + var values; + var i; + + values = [ + -3, + -4, + -5, + -10 + ]; + + for ( i = 0; i < values.length; i++ ) { + t.throws( badValue( values[ i ] ), RangeError, 'throws an error when provided ' + values[ i ] ); + } + t.end(); + + function badValue( value ) { + var opts = { + 'shape': [ 2, 5 ] + }; + var x = array( new Float64Array( 10 ), opts ); + var y = array( new Float64Array( 10 ), opts ); + + return function badValue() { + ddot( x, y, value ); + }; + } +}); + +tape( 'the function throws an error if provided arrays having an unequal contracted dimension (vectors)', function test( t ) { t.throws( badValue, RangeError, 'throws an error' ); t.end(); @@ -113,6 +316,37 @@ tape( 'the function throws an error if provided unequal length vectors', functio ddot( x, y ); } }); + +tape( 'the function throws an error if provided arrays having an unequal contracted dimension (multi-dimensional)', function test( t ) { + t.throws( badValue, RangeError, 'throws an error' ); + t.end(); + + function badValue() { + var x = array( new Float64Array( 100 ), { + 'shape': [ 25, 4 ] + }); + var y = array( new Float64Array( 100 ), { + 'shape': [ 50, 2 ] + }); + ddot( x, y, -1 ); + } +}); + +tape( 'the function throws an error if provided broadcast-incompatible input arrays', function test( t ) { + t.throws( badValue, Error, 'throws an error' ); + t.end(); + + function badValue() { + var x = array( new Float64Array( 100 ), { + 'shape': [ 5, 10, 2 ] + }); + var y = array( new Float64Array( 100 ), { + 'shape': [ 50, 1, 2 ] + }); + ddot( x, y, -1 ); + } +}); + tape( 'the function calculates the dot product of vectors `x` and `y`', function test( t ) { var dot; var x; @@ -125,12 +359,132 @@ tape( 'the function calculates the dot product of vectors `x` and `y`', function y = array( y ); dot = ddot( x, y ); - t.strictEqual( dot, -17.0, 'returns expected value' ); + + t.strictEqual( dot instanceof ndarray, true, 'returns expected value' ); + t.strictEqual( ndims( dot ), ndims( x )-1, 'returns expected value' ); + t.deepEqual( shape( dot ), [], 'returns expected value' ); + t.strictEqual( dot.get(), -17.0, 'returns expected value' ); + + t.end(); +}); + +tape( 'the function supports operating on stacks of vectors (default)', function test( t ) { + var opts; + var dot; + var x; + var y; + + x = new Float64Array( [ 4.0, 2.0, -3.0, 5.0, -1.0, 2.0, -5.0, 6.0 ] ); + y = new Float64Array( [ 2.0, 6.0, -1.0, -4.0, 8.0, 8.0, 2.0, -3.0 ] ); + + opts = { + 'shape': [ 4, 2 ] + }; + x = array( x, opts ); + y = array( y, opts ); + + dot = ddot( x, y ); + + t.strictEqual( dot instanceof ndarray, true, 'returns expected value' ); + t.strictEqual( ndims( dot ), ndims( x )-1, 'returns expected value' ); + t.deepEqual( shape( dot ), [ 4 ], 'returns expected value' ); + t.strictEqual( dot.get( 0 ), 20.0, 'returns expected value' ); + t.strictEqual( dot.get( 1 ), -17.0, 'returns expected value' ); + t.strictEqual( dot.get( 2 ), 8.0, 'returns expected value' ); + t.strictEqual( dot.get( 3 ), -28.0, 'returns expected value' ); + + t.end(); +}); + +tape( 'the function supports operating on stacks of vectors (dim=-1)', function test( t ) { + var opts; + var dot; + var x; + var y; + + x = new Float64Array( [ 4.0, 2.0, -3.0, 5.0, -1.0, 2.0, -5.0, 6.0 ] ); + y = new Float64Array( [ 2.0, 6.0, -1.0, -4.0, 8.0, 8.0, 2.0, -3.0 ] ); + + opts = { + 'shape': [ 4, 2 ] + }; + x = array( x, opts ); + y = array( y, opts ); + + dot = ddot( x, y, -1 ); + + t.strictEqual( dot instanceof ndarray, true, 'returns expected value' ); + t.strictEqual( ndims( dot ), ndims( x )-1, 'returns expected value' ); + t.deepEqual( shape( dot ), [ 4 ], 'returns expected value' ); + t.strictEqual( dot.get( 0 ), 20.0, 'returns expected value' ); + t.strictEqual( dot.get( 1 ), -17.0, 'returns expected value' ); + t.strictEqual( dot.get( 2 ), 8.0, 'returns expected value' ); + t.strictEqual( dot.get( 3 ), -28.0, 'returns expected value' ); + + t.end(); +}); + +tape( 'the function supports operating on stacks of vectors (dim=-2)', function test( t ) { + var opts; + var dot; + var x; + var y; + + x = new Float64Array( [ 4.0, 2.0, -3.0, 5.0, -1.0, 2.0, -5.0, 6.0 ] ); + y = new Float64Array( [ 2.0, 6.0, -1.0, -4.0, 8.0, 8.0, 2.0, -3.0 ] ); + + opts = { + 'shape': [ 2, 4 ] + }; + x = array( x, opts ); + y = array( y, opts ); + + dot = ddot( x, y, -2 ); + + t.strictEqual( dot instanceof ndarray, true, 'returns expected value' ); + t.strictEqual( ndims( dot ), ndims( x )-1, 'returns expected value' ); + t.deepEqual( shape( dot ), [ 4 ], 'returns expected value' ); + t.strictEqual( dot.get( 0 ), 0.0, 'returns expected value' ); + t.strictEqual( dot.get( 1 ), 28.0, 'returns expected value' ); + t.strictEqual( dot.get( 2 ), -7.0, 'returns expected value' ); + t.strictEqual( dot.get( 3 ), -38.0, 'returns expected value' ); + + t.end(); +}); + +tape( 'the function supports broadcasting', function test( t ) { + var opts; + var dot; + var x; + var y; + + x = new Float64Array( [ 4.0, 2.0, -3.0, 5.0, -1.0, 2.0, -5.0, 6.0 ] ); + y = new Float64Array( [ 2.0, 6.0 ] ); + + opts = { + 'shape': [ 4, 2 ] + }; + x = array( x, opts ); + + opts = { + 'shape': [ 1, 2 ] + }; + y = array( y, opts ); + + dot = ddot( x, y, -1 ); + + t.strictEqual( dot instanceof ndarray, true, 'returns expected value' ); + t.strictEqual( ndims( dot ), ndims( x )-1, 'returns expected value' ); + t.deepEqual( shape( dot ), [ 4 ], 'returns expected value' ); + t.strictEqual( dot.get( 0 ), 20.0, 'returns expected value' ); + t.strictEqual( dot.get( 1 ), 24.0, 'returns expected value' ); + t.strictEqual( dot.get( 2 ), 10.0, 'returns expected value' ); + t.strictEqual( dot.get( 3 ), 26.0, 'returns expected value' ); t.end(); }); -tape( 'if provided empty vectors, the function returns `0`', function test( t ) { +tape( 'if provided empty vectors, the dot product is `0`', function test( t ) { var dot; var x; var y; @@ -142,7 +496,11 @@ tape( 'if provided empty vectors, the function returns `0`', function test( t ) y = array( y ); dot = ddot( x, y ); - t.strictEqual( dot, 0.0, 'returns expected value' ); + + t.strictEqual( dot instanceof ndarray, true, 'returns expected value' ); + t.strictEqual( ndims( dot ), ndims( x )-1, 'returns expected value' ); + t.deepEqual( shape( dot ), [], 'returns expected value' ); + t.strictEqual( dot.get(), 0.0, 'returns expected value' ); t.end(); }); @@ -169,7 +527,11 @@ tape( 'the function supports a strided vector for the first argument', function y = ndarray( 'float64', y, [ 3 ], [ 1 ], 0, 'row-major' ); dot = ddot( x, y ); - t.strictEqual( dot, -12.0, 'returns expected value' ); + + t.strictEqual( dot instanceof ndarray, true, 'returns expected value' ); + t.strictEqual( ndims( dot ), ndims( x )-1, 'returns expected value' ); + t.deepEqual( shape( dot ), [], 'returns expected value' ); + t.strictEqual( dot.get(), -12.0, 'returns expected value' ); t.end(); }); @@ -197,7 +559,11 @@ tape( 'the function supports a strided vector for the second argument', function y = ndarray( 'float64', y, [ 3 ], [ 2 ], 0, 'row-major' ); dot = ddot( x, y ); - t.strictEqual( dot, 45.0, 'returns expected value' ); + + t.strictEqual( dot instanceof ndarray, true, 'returns expected value' ); + t.strictEqual( ndims( dot ), ndims( x )-1, 'returns expected value' ); + t.deepEqual( shape( dot ), [], 'returns expected value' ); + t.strictEqual( dot.get(), 45.0, 'returns expected value' ); t.end(); }); @@ -224,7 +590,11 @@ tape( 'the function supports negative strides', function test( t ) { y = ndarray( 'float64', y, [ 3 ], [ -1 ], y.length-1, 'row-major' ); dot = ddot( x, y ); - t.strictEqual( dot, 67.0, 'returns expected value' ); + + t.strictEqual( dot instanceof ndarray, true, 'returns expected value' ); + t.strictEqual( ndims( dot ), ndims( x )-1, 'returns expected value' ); + t.deepEqual( shape( dot ), [], 'returns expected value' ); + t.strictEqual( dot.get(), 67.0, 'returns expected value' ); t.end(); }); @@ -251,7 +621,11 @@ tape( 'the function supports complex access patterns', function test( t ) { y = ndarray( 'float64', y, [ 3 ], [ -1 ], y.length-1, 'row-major' ); dot = ddot( x, y ); - t.strictEqual( dot, 59.0, 'returns expected value' ); + + t.strictEqual( dot instanceof ndarray, true, 'returns expected value' ); + t.strictEqual( ndims( dot ), ndims( x )-1, 'returns expected value' ); + t.deepEqual( shape( dot ), [], 'returns expected value' ); + t.strictEqual( dot.get(), 59.0, 'returns expected value' ); t.end(); }); @@ -281,7 +655,11 @@ tape( 'the function supports strided vectors having offsets', function test( t ) y = ndarray( 'float64', y, [ 3 ], [ 1 ], 2, 'row-major' ); dot = ddot( x, y ); - t.strictEqual( dot, -12.0, 'returns expected value' ); + + t.strictEqual( dot instanceof ndarray, true, 'returns expected value' ); + t.strictEqual( ndims( dot ), ndims( x )-1, 'returns expected value' ); + t.deepEqual( shape( dot ), [], 'returns expected value' ); + t.strictEqual( dot.get(), -12.0, 'returns expected value' ); t.end(); }); @@ -317,7 +695,11 @@ tape( 'the function supports underlying data buffers with view offsets', functio y1 = ndarray( 'float64', y1, [ 3 ], [ 1 ], 0, 'row-major' ); dot = ddot( x1, y1 ); - t.strictEqual( dot, 124.0, 'returns expected value' ); + + t.strictEqual( dot instanceof ndarray, true, 'returns expected value' ); + t.strictEqual( ndims( dot ), ndims( x1 )-1, 'returns expected value' ); + t.deepEqual( shape( dot ), [], 'returns expected value' ); + t.strictEqual( dot.get(), 124.0, 'returns expected value' ); t.end(); });