time-to-botec

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

main.js (12521B)


      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 hasOwnProp = require( '@stdlib/assert/has-own-property' );
     24 var isObject = require( '@stdlib/assert/is-plain-object' );
     25 var isBoolean = require( '@stdlib/assert/is-boolean' ).isPrimitive;
     26 var isArray = require( '@stdlib/assert/is-array' );
     27 var isNonNegativeInteger = require( '@stdlib/assert/is-nonnegative-integer' ).isPrimitive;
     28 var isndarrayLike = require( '@stdlib/assert/is-ndarray-like' );
     29 var shape2strides = require( './../../base/shape2strides' );
     30 var strides2offset = require( './../../base/strides2offset' );
     31 var strides2order = require( './../../base/strides2order' );
     32 var numel = require( './../../base/numel' );
     33 var ndarray = require( './../../ctor' );
     34 var isDataType = require( './../../base/assert/is-data-type' );
     35 var isOrder = require( './../../base/assert/is-order' );
     36 var isCastingMode = require( './../../base/assert/is-casting-mode' );
     37 var isAllowedCast = require( './../../base/assert/is-allowed-data-type-cast' );
     38 var createBuffer = require( './../../base/buffer' );
     39 var getType = require( './../../base/buffer-dtype' );
     40 var arrayShape = require( '@stdlib/array/shape' );
     41 var flattenArray = require( '@stdlib/utils/flatten-array' );
     42 var isArrayLikeObject = require( './is_array_like_object.js' );
     43 var defaults = require( './defaults.json' );
     44 var castBuffer = require( './cast_buffer.js' );
     45 var copyView = require( './copy_view.js' );
     46 var expandShape = require( './expand_shape.js' );
     47 var expandStrides = require( './expand_strides.js' );
     48 
     49 
     50 // MAIN //
     51 
     52 /**
     53 * Returns a multidimensional array.
     54 *
     55 * @param {(ArrayLikeObject|TypedArrayLike|Buffer|ndarrayLike)} [buffer] - data source
     56 * @param {Options} [options] - function options
     57 * @param {(ArrayLikeObject|TypedArrayLike|Buffer|ndarrayLike)} [options.buffer] - data source
     58 * @param {string} [options.dtype="float64"] - underlying storage data type (if the input data is not of the same type, this option specifies the data type to which to cast the input data)
     59 * @param {string} [options.order="row-major"] - specifies the memory layout of the array as either row-major (C-style) or column-major (Fortran-style)
     60 * @param {NonNegativeIntegerArray} [options.shape] - array shape
     61 * @param {string} [options.mode="throw"] - specifies how to handle indices which exceed array dimensions
     62 * @param {StringArray} [options.submode=["throw"]] - specifies how to handle subscripts which exceed array dimensions on a per dimension basis
     63 * @param {boolean} [options.copy=false] - boolean indicating whether to copy source data to a new data buffer
     64 * @param {boolean} [options.flatten=true] - boolean indicating whether to automatically flatten generic array data sources
     65 * @param {NonNegativeInteger} [options.ndmin=0] - minimum number of dimensions
     66 * @param {string} [options.casting="safe"] - casting rule used to determine what constitutes an acceptable cast
     67 * @throws {TypeError} options argument must be an object
     68 * @throws {TypeError} must provide valid options
     69 * @throws {Error} must provide either an array shape, data source, or both
     70 * @throws {Error} invalid cast
     71 * @throws {RangeError} data source must be compatible with specified meta data
     72 * @returns {ndarray} ndarray instance
     73 *
     74 * @example
     75 * var arr = array( [ [ 1, 2 ], [ 3, 4 ] ] );
     76 * // returns <ndarray>
     77 *
     78 * var v = arr.get( 0, 0 );
     79 * // returns 1
     80 *
     81 * @example
     82 * var opts = {
     83 *     'dtype': 'generic',
     84 *     'flatten': false
     85 * };
     86 *
     87 * var arr = array( [ [ 1, 2 ], [ 3, 4 ] ], opts );
     88 * // returns <ndarray>
     89 *
     90 * var v = arr.get( 0 );
     91 * // returns [ 1, 2 ]
     92 *
     93 * @example
     94 * var Float64Array = require( '@stdlib/array/float64' );
     95 *
     96 * var opts = {
     97 *     'shape': [ 2, 2 ]
     98 * };
     99 *
    100 * var arr = array( new Float64Array( [ 1.0, 2.0, 3.0, 4.0 ] ), opts );
    101 * // returns <ndarray>
    102 *
    103 * var v = arr.get( 0, 0 );
    104 * // returns 1.0
    105 */
    106 function array() {
    107 	var options;
    108 	var strides;
    109 	var buffer;
    110 	var offset;
    111 	var order;
    112 	var dtype;
    113 	var btype;
    114 	var shape;
    115 	var ndims;
    116 	var nopts;
    117 	var opts;
    118 	var len;
    119 	var ord;
    120 	var FLG;
    121 
    122 	if ( arguments.length === 1 ) {
    123 		if ( isArrayLikeObject( arguments[ 0 ] ) ) {
    124 			buffer = arguments[ 0 ];
    125 			options = {};
    126 		} else {
    127 			options = arguments[ 0 ];
    128 			if ( !isObject( options ) ) {
    129 				throw new TypeError( 'invalid argument. Must provide either a valid data source, options argument, or both. Value: `' + options + '`.' );
    130 			}
    131 			if ( hasOwnProp( options, 'buffer' ) ) {
    132 				buffer = options.buffer;
    133 				if ( !isArrayLikeObject( buffer ) ) { // weak test
    134 					throw new TypeError( 'invalid option. `buffer` option must be an array-like object, typed-array-like, a Buffer, or an ndarray. Option: `' + buffer + '`.' );
    135 				}
    136 			}
    137 		}
    138 	} else {
    139 		buffer = arguments[ 0 ];
    140 		if ( !isArrayLikeObject( buffer ) ) { // weak test
    141 			throw new TypeError( 'invalid option. Data source must be an array-like object, typed-array-like, a Buffer, or an ndarray. Value: `' + buffer + '`.' );
    142 		}
    143 		options = arguments[ 1 ];
    144 		if ( !isObject( options ) ) {
    145 			throw new TypeError( 'invalid argument. Options argument must be an object. Value: `' + options + '`.' );
    146 		}
    147 		// Note: we ignore whether `options` has a `buffer` property
    148 	}
    149 	if ( buffer ) {
    150 		if ( isndarrayLike( buffer ) ) {
    151 			btype = buffer.dtype;
    152 			FLG = true;
    153 		} else {
    154 			btype = getType( buffer );
    155 			FLG = false;
    156 		}
    157 	}
    158 	nopts = {};
    159 	opts = {};
    160 
    161 	// Validate some options before others...
    162 	if ( hasOwnProp( options, 'casting' ) ) {
    163 		opts.casting = options.casting;
    164 		if ( !isCastingMode( opts.casting ) ) {
    165 			throw new TypeError( 'invalid option. `casting` option must be a recognized casting mode. Option: `' + opts.casting + '`.' );
    166 		}
    167 	} else {
    168 		opts.casting = defaults.casting;
    169 	}
    170 	if ( hasOwnProp( options, 'flatten' ) ) {
    171 		opts.flatten = options.flatten;
    172 		if ( !isBoolean( opts.flatten ) ) {
    173 			throw new TypeError( 'invalid option. `flatten` option must be a boolean. Option: `' + opts.flatten + '`.' );
    174 		}
    175 	} else {
    176 		opts.flatten = defaults.flatten;
    177 	}
    178 	if ( hasOwnProp( options, 'ndmin' ) ) {
    179 		opts.ndmin = options.ndmin;
    180 		if ( !isNonNegativeInteger( opts.ndmin ) ) {
    181 			throw new TypeError( 'invalid option. `ndmin` option must be a nonnegative integer. Option: `' + opts.ndmin + '`.' );
    182 		}
    183 		// TODO: validate that minimum number of dimensions does not exceed the maximum number of possible dimensions (in theory, infinite; in practice, determined by max array length; see https://github.com/stdlib-js/stdlib/blob/ac350059877c036640775d6b30d0e98e840d07cf/lib/node_modules/%40stdlib/ndarray/ctor/lib/main.js#L57)
    184 	} else {
    185 		opts.ndmin = defaults.ndmin;
    186 	}
    187 
    188 	// Validate the remaining options...
    189 	if ( hasOwnProp( options, 'dtype' ) ) {
    190 		dtype = options.dtype;
    191 		if ( !isDataType( dtype ) ) {
    192 			throw new TypeError( 'invalid option. `dtype` option must be a recognized data type. Option: `' + dtype + '`.' );
    193 		}
    194 		if ( btype && !isAllowedCast( btype, dtype, opts.casting ) ) {
    195 			throw new Error( 'invalid option. Data type cast is not allowed. Casting mode: `' + opts.casting + '`. From: `' + btype + '`. To: `' + dtype + '`.' );
    196 		}
    197 	} else if ( btype ) {
    198 		// TODO: reconcile difference in behavior when provided a generic array and no `dtype` option. Currently, we cast here, but do not allow casting a generic array (by default) when explicitly providing a `dtype` option.
    199 
    200 		// Only cast generic array data sources when not provided an ndarray...
    201 		if ( !FLG && btype === 'generic' ) {
    202 			dtype = defaults.dtype;
    203 		} else {
    204 			dtype = btype;
    205 		}
    206 	} else {
    207 		dtype = defaults.dtype;
    208 	}
    209 	if ( hasOwnProp( options, 'order' ) ) {
    210 		order = options.order;
    211 		if ( order === 'any' || order === 'same' ) {
    212 			if ( FLG ) {
    213 				// If the user indicated that "any" order suffices (meaning the user does not care about ndarray order), then we use the default order, unless the input ndarray is either unequivocally "row-major" or "column-major" or configured as such....
    214 				if ( order === 'any' ) {
    215 					// Compute the layout order in order to ascertain whether an ndarray can be considered both "row-major" and "column-major":
    216 					ord = strides2order( buffer.strides );
    217 
    218 					// If the ndarray can be considered both "row-major" and "column-major", then use the default order; otherwise, use the ndarray's stated layout order...
    219 					if ( ord === 3 ) {
    220 						order = defaults.order;
    221 					} else {
    222 						order = buffer.order;
    223 					}
    224 				}
    225 				// Otherwise, use the same order as the provided ndarray...
    226 				else if ( order === 'same' ) {
    227 					order = buffer.order;
    228 				}
    229 			} else {
    230 				order = defaults.order;
    231 			}
    232 		} else if ( !isOrder( order ) ) {
    233 			throw new TypeError( 'invalid option. `order` option must be a recognized order. Option: `' + order + '`.' );
    234 		}
    235 	} else {
    236 		order = defaults.order;
    237 	}
    238 	if ( hasOwnProp( options, 'mode' ) ) {
    239 		nopts.mode = options.mode;
    240 	} else {
    241 		nopts.mode = defaults.mode;
    242 	}
    243 	if ( hasOwnProp( options, 'submode' ) ) {
    244 		nopts.submode = options.submode;
    245 	} else {
    246 		nopts.submode = [ nopts.mode ];
    247 	}
    248 	if ( hasOwnProp( options, 'copy' ) ) {
    249 		opts.copy = options.copy;
    250 		if ( !isBoolean( opts.copy ) ) {
    251 			throw new TypeError( 'invalid option. `copy` option must be a boolean. Option: `' + opts.copy + '`.' );
    252 		}
    253 	} else {
    254 		opts.copy = defaults.copy;
    255 	}
    256 	// If not provided a shape, infer from a provided data source...
    257 	if ( hasOwnProp( options, 'shape' ) ) {
    258 		shape = options.shape;
    259 		if ( !isArrayLikeObject( shape ) ) { // weak test
    260 			throw new TypeError( 'invalid option. `shape` option must be an array-like object containing nonnegative integers. Option: `' + shape + '`.' );
    261 		}
    262 		ndims = shape.length;
    263 		len = numel( shape );
    264 	} else if ( buffer ) {
    265 		if ( FLG ) {
    266 			shape = buffer.shape;
    267 			ndims = buffer.ndims;
    268 			len = buffer.length;
    269 		} else if ( opts.flatten && isArray( buffer ) ) {
    270 			shape = arrayShape( buffer );
    271 			ndims = shape.length;
    272 			len = numel( shape );
    273 		} else {
    274 			ndims = 1;
    275 			len = buffer.length;
    276 			shape = [ len ]; // assume a 1-dimensional array (vector)
    277 		}
    278 	} else {
    279 		throw new Error( 'invalid arguments. Must provide either a data source, array shape, or both.' );
    280 	}
    281 	// Adjust the array shape to satisfy the minimum number of dimensions...
    282 	if ( ndims < opts.ndmin ) {
    283 		shape = expandShape( ndims, shape, opts.ndmin );
    284 		ndims = opts.ndmin;
    285 	}
    286 	// If not provided a data buffer, create it; otherwise, see if we need to cast a provided data buffer to another data type or perform a copy...
    287 	if ( FLG ) {
    288 		if ( buffer.length !== len ) {
    289 			throw new RangeError( 'invalid arguments. Array shape is incompatible with provided data source. Number of data source elements does not match array shape.' );
    290 		}
    291 		if ( btype !== dtype || opts.copy ) {
    292 			buffer = copyView( buffer, dtype );
    293 		} else {
    294 			strides = buffer.strides;
    295 			offset = buffer.offset;
    296 			buffer = buffer.data;
    297 			if ( strides.length < ndims ) {
    298 				// Account for augmented dimensions (note: expanding the strides array to account for prepended singleton dimensions does **not** affect the index offset):
    299 				strides = expandStrides( ndims, shape, strides, order );
    300 			}
    301 		}
    302 	} else if ( buffer ) {
    303 		if ( btype === 'generic' && opts.flatten ) {
    304 			buffer = flattenArray( buffer );
    305 		}
    306 		if ( buffer.length !== len ) {
    307 			throw new RangeError( 'invalid arguments. Array shape is incompatible with provided data source. Number of data source elements does not match array shape.' );
    308 		}
    309 		if ( btype !== dtype || opts.copy ) {
    310 			buffer = castBuffer( buffer, len, dtype );
    311 		}
    312 	} else {
    313 		buffer = createBuffer( dtype, len );
    314 	}
    315 	// If we have yet to determine array strides, we assume that we can compute the strides, along with the index offset, for a **contiguous** data source based solely on the array shape and specified memory layout order...
    316 	if ( strides === void 0 ) {
    317 		strides = shape2strides( shape, order );
    318 		offset = strides2offset( shape, strides );
    319 	}
    320 	return new ndarray( dtype, buffer, shape, strides, offset, order, nopts );
    321 }
    322 
    323 
    324 // EXPORTS //
    325 
    326 module.exports = array;