main.js (8498B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2021 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 setReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' ); 24 var isNumber = require( '@stdlib/assert/is-number' ).isPrimitive; 25 var isComplexLike = require( '@stdlib/assert/is-complex-like' ); 26 var isndarrayLike = require( '@stdlib/assert/is-ndarray-like' ); 27 var isCollection = require( '@stdlib/assert/is-collection' ); 28 var dtype = require( '@stdlib/ndarray/base/buffer-dtype' ); 29 var buffer = require( '@stdlib/ndarray/base/buffer' ); 30 var broadcast = require( '@stdlib/ndarray/base/broadcast-array' ); 31 var ndarrayfcn = require( './ndarray.js' ); 32 var odtype = require( './resolve_output_dtype.js' ); 33 var defaults = require( './defaults.json' ); 34 var validateTable = require( './validate_table.js' ); 35 var validateOptions = require( './validate_options.js' ); 36 var validate = require( './validate.js' ); 37 38 39 // MAIN // 40 41 /** 42 * Returns a function which dispatches to specified functions based on input argument types. 43 * 44 * @param {Object} table - resolution table object 45 * @param {(Function|null)} [table.number] - function to invoke upon receiving a number 46 * @param {(Function|null)} [table.complex] - function to invoke upon receiving a complex number 47 * @param {(Function|null)} [table.array] - function to invoke upon receiving an array-like object 48 * @param {(Function|null)} [table.ndarray] - function to invoke upon receiving an ndarray-like object 49 * @param {Options} [options] - options 50 * @param {string} [options.output_dtype_policy='float'] - policy for determining the output array data type 51 * @throws {TypeError} first argument must be an object 52 * @throws {TypeError} first argument must have valid table fields 53 * @throws {Error} each table field value must be either a function or `null` 54 * @throws {TypeError} options argument must be an object 55 * @throws {TypeError} must provide valid options 56 * @returns {Function} dispatch function 57 * 58 * @example 59 * var base = require( '@stdlib/math/base/special/abs' ); 60 * var strided = require( '@stdlib/math/strided/special/abs' ); 61 * var dispatcher = require( '@stdlib/ndarray/dispatch' ); 62 * var unary = require( '@stdlib/ndarray/base/unary' ); 63 * var Float64Array = require( '@stdlib/array/float64' ); 64 * 65 * var types = [ 66 * 'float64', 'float64', 67 * 'float32', 'float32', 68 * 'generic', 'generic' 69 * ]; 70 * var data = [ 71 * base, 72 * base, 73 * base 74 * ]; 75 * var nd = dispatcher( unary, types, data, 2, 1, 1 ); 76 * 77 * var table = { 78 * 'number': base, 79 * 'complex': null, 80 * 'array': strided, 81 * 'ndarray': nd 82 * }; 83 * 84 * var abs = dispatch( table, { 85 * 'output_dtype_policy': 'same' 86 * }); 87 * 88 * var x = new Float64Array( [ -1.0, -2.0, -3.0 ] ); 89 * 90 * var y = abs( x ); 91 * // returns <Float64Array>[ 1.0, 2.0, 3.0 ] 92 */ 93 function dispatch( table, options ) { 94 var OPTS; 95 var err; 96 var fcn; 97 var t; 98 99 t = { 100 'number': null, 101 'complex': null, 102 'array': null, 103 'ndarray': null 104 }; 105 err = validateTable( t, table ); 106 if ( err ) { 107 throw err; 108 } 109 OPTS = { 110 'policy': defaults.output_dtype_policy 111 }; 112 if ( arguments.length > 1 ) { 113 err = validateOptions( OPTS, options ); 114 if ( err ) { 115 throw err; 116 } 117 } 118 fcn = dispatcher; 119 setReadOnly( fcn, 'assign', assign ); 120 return fcn; 121 122 /** 123 * Function interface which performs dispatch. 124 * 125 * @private 126 * @param {(ndarray|Collection|number|Complex)} x - input value 127 * @param {Options} [options] - options 128 * @param {string} [options.dtype] - output array data type 129 * @param {string} [options.order] - output array order (row-major or column-major) 130 * @throws {TypeError} first argument must be a supported data type 131 * @throws {TypeError} options argument must be an object 132 * @throws {TypeError} must provide valid options 133 * @returns {(ndarray|Collection|number|Complex)} results 134 */ 135 function dispatcher( x, options ) { 136 var xdtype; 137 var ydtype; 138 var opts; 139 var err; 140 var y; 141 if ( isNumber( x ) ) { 142 if ( t.number ) { 143 return t.number( x ); 144 } 145 throw new TypeError( 'invalid argument. Providing a number is not supported.' ); 146 } 147 if ( isComplexLike( x ) ) { 148 if ( t.complex ) { 149 return t.complex( x ); 150 } 151 throw new TypeError( 'invalid argument. Providing a complex number is not supported.' ); 152 } 153 opts = {}; 154 if ( arguments.length > 1 ) { 155 err = validate( opts, options ); 156 if ( err ) { 157 throw err; 158 } 159 } 160 if ( isndarrayLike( x ) ) { 161 if ( t.ndarray === null ) { 162 throw new TypeError( 'invalid argument. Providing an ndarray is not supported.' ); 163 } 164 ydtype = opts.dtype || odtype( x.dtype, OPTS.policy ); 165 return ndarrayfcn( t.ndarray, x, ydtype, opts.order || x.order ); 166 } 167 if ( isCollection( x ) ) { 168 if ( t.array === null ) { 169 throw new TypeError( 'invalid argument. Providing an array-like object is not supported.' ); 170 } 171 xdtype = dtype( x ) || 'generic'; 172 ydtype = opts.dtype || odtype( xdtype, OPTS.policy ); 173 y = buffer( ydtype, x.length ); 174 175 // FIXME: need to supply dtype enum argument for each array argument... 176 t.array( x.length, x, 1, y, 1 ); 177 return y; 178 } 179 throw new TypeError( 'invalid argument. Must provide an argument having a supported data type. Value: `' + x + '`.' ); 180 } 181 182 /** 183 * Function interface which performs dispatch and assigns results to a provided output array. 184 * 185 * @private 186 * @param {(ndarray|Collection)} x - input array 187 * @param {(ndarray|Collection)} y - output array 188 * @throws {TypeError} first argument must be a supported data type 189 * @throws {TypeError} second argument must be a supported data type 190 * @throws {TypeError} first and second argument must be the same "kind" (i.e., either both ndarrays or both collections) 191 * @throws {RangeError} output array must have sufficient elements 192 * @throws {Error} unable to broadcast the input array against the output array 193 * @returns {(ndarray|Collection)} output array 194 */ 195 function assign( x, y ) { 196 var xsh; 197 var ysh; 198 var i; 199 if ( isndarrayLike( x ) ) { 200 if ( isndarrayLike( y ) ) { 201 xsh = x.shape; 202 ysh = y.shape; 203 204 // Check whether we need to broadcast `x`... 205 if ( xsh.length === ysh.length ) { 206 for ( i = 0; i < xsh.length; i++ ) { 207 // Check whether dimensions match... 208 if ( xsh[ i ] !== ysh[ i ] ) { 209 // We found a mismatched dimension; delegate to `broadcast` to ensure that `x` is broadcast compatible with the output array shape... 210 x = broadcast( x, ysh ); 211 break; 212 } 213 } 214 } else { 215 // If we are provided arrays with different ranks (i.e., number of dimensions), assume we need to broadcast, delegating to `broadcast` to ensure that `x` is broadcast compatible with the output array shape... 216 x = broadcast( x, ysh ); 217 } 218 t.ndarray( x, y ); 219 return y; 220 } 221 throw new TypeError( 'invalid argument. If the first argument is an ndarray, the second argument must be an ndarray.' ); 222 } 223 if ( isCollection( x ) ) { 224 if ( isCollection( y ) ) { 225 if ( y.length !== x.length ) { 226 throw new RangeError( 'invalid argument. Output array must have the same number of elements (i.e., length) as the input array.' ); 227 } 228 // FIXME: need to supply dtype enum argument for each array argument... 229 t.array( x.length, x, 1, y, 1 ); 230 return y; 231 } 232 throw new TypeError( 'invalid argument. If the first argument is an array-like object, the second argument must be an array-like object.' ); 233 } 234 if ( isNumber( x ) ) { 235 throw new TypeError( 'invalid argument. Providing a number is not supported. Consider providing a zero-dimensional ndarray containing the numeric value.' ); 236 } 237 if ( isComplexLike( x ) ) { 238 throw new TypeError( 'invalid argument. Providing a complex number is not supported. Consider providing a zero-dimensional ndarray containing the complex number value.' ); 239 } 240 throw new TypeError( 'invalid argument. Must provide an argument having a supported data type. Value: `' + x + '`.' ); 241 } 242 } 243 244 245 // EXPORTS // 246 247 module.exports = dispatch;