time-to-botec/js/node_modules/@stdlib/ndarray/array/lib/main.js

327 lines
12 KiB
JavaScript
Raw Normal View History

/**
* @license Apache-2.0
*
* Copyright (c) 2018 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 hasOwnProp = require( '@stdlib/assert/has-own-property' );
var isObject = require( '@stdlib/assert/is-plain-object' );
var isBoolean = require( '@stdlib/assert/is-boolean' ).isPrimitive;
var isArray = require( '@stdlib/assert/is-array' );
var isNonNegativeInteger = require( '@stdlib/assert/is-nonnegative-integer' ).isPrimitive;
var isndarrayLike = require( '@stdlib/assert/is-ndarray-like' );
var shape2strides = require( './../../base/shape2strides' );
var strides2offset = require( './../../base/strides2offset' );
var strides2order = require( './../../base/strides2order' );
var numel = require( './../../base/numel' );
var ndarray = require( './../../ctor' );
var isDataType = require( './../../base/assert/is-data-type' );
var isOrder = require( './../../base/assert/is-order' );
var isCastingMode = require( './../../base/assert/is-casting-mode' );
var isAllowedCast = require( './../../base/assert/is-allowed-data-type-cast' );
var createBuffer = require( './../../base/buffer' );
var getType = require( './../../base/buffer-dtype' );
var arrayShape = require( '@stdlib/array/shape' );
var flattenArray = require( '@stdlib/utils/flatten-array' );
var isArrayLikeObject = require( './is_array_like_object.js' );
var defaults = require( './defaults.json' );
var castBuffer = require( './cast_buffer.js' );
var copyView = require( './copy_view.js' );
var expandShape = require( './expand_shape.js' );
var expandStrides = require( './expand_strides.js' );
// MAIN //
/**
* Returns a multidimensional array.
*
* @param {(ArrayLikeObject|TypedArrayLike|Buffer|ndarrayLike)} [buffer] - data source
* @param {Options} [options] - function options
* @param {(ArrayLikeObject|TypedArrayLike|Buffer|ndarrayLike)} [options.buffer] - data source
* @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)
* @param {string} [options.order="row-major"] - specifies the memory layout of the array as either row-major (C-style) or column-major (Fortran-style)
* @param {NonNegativeIntegerArray} [options.shape] - array shape
* @param {string} [options.mode="throw"] - specifies how to handle indices which exceed array dimensions
* @param {StringArray} [options.submode=["throw"]] - specifies how to handle subscripts which exceed array dimensions on a per dimension basis
* @param {boolean} [options.copy=false] - boolean indicating whether to copy source data to a new data buffer
* @param {boolean} [options.flatten=true] - boolean indicating whether to automatically flatten generic array data sources
* @param {NonNegativeInteger} [options.ndmin=0] - minimum number of dimensions
* @param {string} [options.casting="safe"] - casting rule used to determine what constitutes an acceptable cast
* @throws {TypeError} options argument must be an object
* @throws {TypeError} must provide valid options
* @throws {Error} must provide either an array shape, data source, or both
* @throws {Error} invalid cast
* @throws {RangeError} data source must be compatible with specified meta data
* @returns {ndarray} ndarray instance
*
* @example
* var arr = array( [ [ 1, 2 ], [ 3, 4 ] ] );
* // returns <ndarray>
*
* var v = arr.get( 0, 0 );
* // returns 1
*
* @example
* var opts = {
* 'dtype': 'generic',
* 'flatten': false
* };
*
* var arr = array( [ [ 1, 2 ], [ 3, 4 ] ], opts );
* // returns <ndarray>
*
* var v = arr.get( 0 );
* // returns [ 1, 2 ]
*
* @example
* var Float64Array = require( '@stdlib/array/float64' );
*
* var opts = {
* 'shape': [ 2, 2 ]
* };
*
* var arr = array( new Float64Array( [ 1.0, 2.0, 3.0, 4.0 ] ), opts );
* // returns <ndarray>
*
* var v = arr.get( 0, 0 );
* // returns 1.0
*/
function array() {
var options;
var strides;
var buffer;
var offset;
var order;
var dtype;
var btype;
var shape;
var ndims;
var nopts;
var opts;
var len;
var ord;
var FLG;
if ( arguments.length === 1 ) {
if ( isArrayLikeObject( arguments[ 0 ] ) ) {
buffer = arguments[ 0 ];
options = {};
} else {
options = arguments[ 0 ];
if ( !isObject( options ) ) {
throw new TypeError( 'invalid argument. Must provide either a valid data source, options argument, or both. Value: `' + options + '`.' );
}
if ( hasOwnProp( options, 'buffer' ) ) {
buffer = options.buffer;
if ( !isArrayLikeObject( buffer ) ) { // weak test
throw new TypeError( 'invalid option. `buffer` option must be an array-like object, typed-array-like, a Buffer, or an ndarray. Option: `' + buffer + '`.' );
}
}
}
} else {
buffer = arguments[ 0 ];
if ( !isArrayLikeObject( buffer ) ) { // weak test
throw new TypeError( 'invalid option. Data source must be an array-like object, typed-array-like, a Buffer, or an ndarray. Value: `' + buffer + '`.' );
}
options = arguments[ 1 ];
if ( !isObject( options ) ) {
throw new TypeError( 'invalid argument. Options argument must be an object. Value: `' + options + '`.' );
}
// Note: we ignore whether `options` has a `buffer` property
}
if ( buffer ) {
if ( isndarrayLike( buffer ) ) {
btype = buffer.dtype;
FLG = true;
} else {
btype = getType( buffer );
FLG = false;
}
}
nopts = {};
opts = {};
// Validate some options before others...
if ( hasOwnProp( options, 'casting' ) ) {
opts.casting = options.casting;
if ( !isCastingMode( opts.casting ) ) {
throw new TypeError( 'invalid option. `casting` option must be a recognized casting mode. Option: `' + opts.casting + '`.' );
}
} else {
opts.casting = defaults.casting;
}
if ( hasOwnProp( options, 'flatten' ) ) {
opts.flatten = options.flatten;
if ( !isBoolean( opts.flatten ) ) {
throw new TypeError( 'invalid option. `flatten` option must be a boolean. Option: `' + opts.flatten + '`.' );
}
} else {
opts.flatten = defaults.flatten;
}
if ( hasOwnProp( options, 'ndmin' ) ) {
opts.ndmin = options.ndmin;
if ( !isNonNegativeInteger( opts.ndmin ) ) {
throw new TypeError( 'invalid option. `ndmin` option must be a nonnegative integer. Option: `' + opts.ndmin + '`.' );
}
// 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)
} else {
opts.ndmin = defaults.ndmin;
}
// Validate the remaining options...
if ( hasOwnProp( options, 'dtype' ) ) {
dtype = options.dtype;
if ( !isDataType( dtype ) ) {
throw new TypeError( 'invalid option. `dtype` option must be a recognized data type. Option: `' + dtype + '`.' );
}
if ( btype && !isAllowedCast( btype, dtype, opts.casting ) ) {
throw new Error( 'invalid option. Data type cast is not allowed. Casting mode: `' + opts.casting + '`. From: `' + btype + '`. To: `' + dtype + '`.' );
}
} else if ( btype ) {
// 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.
// Only cast generic array data sources when not provided an ndarray...
if ( !FLG && btype === 'generic' ) {
dtype = defaults.dtype;
} else {
dtype = btype;
}
} else {
dtype = defaults.dtype;
}
if ( hasOwnProp( options, 'order' ) ) {
order = options.order;
if ( order === 'any' || order === 'same' ) {
if ( FLG ) {
// 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....
if ( order === 'any' ) {
// Compute the layout order in order to ascertain whether an ndarray can be considered both "row-major" and "column-major":
ord = strides2order( buffer.strides );
// 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...
if ( ord === 3 ) {
order = defaults.order;
} else {
order = buffer.order;
}
}
// Otherwise, use the same order as the provided ndarray...
else if ( order === 'same' ) {
order = buffer.order;
}
} else {
order = defaults.order;
}
} else if ( !isOrder( order ) ) {
throw new TypeError( 'invalid option. `order` option must be a recognized order. Option: `' + order + '`.' );
}
} else {
order = defaults.order;
}
if ( hasOwnProp( options, 'mode' ) ) {
nopts.mode = options.mode;
} else {
nopts.mode = defaults.mode;
}
if ( hasOwnProp( options, 'submode' ) ) {
nopts.submode = options.submode;
} else {
nopts.submode = [ nopts.mode ];
}
if ( hasOwnProp( options, 'copy' ) ) {
opts.copy = options.copy;
if ( !isBoolean( opts.copy ) ) {
throw new TypeError( 'invalid option. `copy` option must be a boolean. Option: `' + opts.copy + '`.' );
}
} else {
opts.copy = defaults.copy;
}
// If not provided a shape, infer from a provided data source...
if ( hasOwnProp( options, 'shape' ) ) {
shape = options.shape;
if ( !isArrayLikeObject( shape ) ) { // weak test
throw new TypeError( 'invalid option. `shape` option must be an array-like object containing nonnegative integers. Option: `' + shape + '`.' );
}
ndims = shape.length;
len = numel( shape );
} else if ( buffer ) {
if ( FLG ) {
shape = buffer.shape;
ndims = buffer.ndims;
len = buffer.length;
} else if ( opts.flatten && isArray( buffer ) ) {
shape = arrayShape( buffer );
ndims = shape.length;
len = numel( shape );
} else {
ndims = 1;
len = buffer.length;
shape = [ len ]; // assume a 1-dimensional array (vector)
}
} else {
throw new Error( 'invalid arguments. Must provide either a data source, array shape, or both.' );
}
// Adjust the array shape to satisfy the minimum number of dimensions...
if ( ndims < opts.ndmin ) {
shape = expandShape( ndims, shape, opts.ndmin );
ndims = opts.ndmin;
}
// 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...
if ( FLG ) {
if ( buffer.length !== len ) {
throw new RangeError( 'invalid arguments. Array shape is incompatible with provided data source. Number of data source elements does not match array shape.' );
}
if ( btype !== dtype || opts.copy ) {
buffer = copyView( buffer, dtype );
} else {
strides = buffer.strides;
offset = buffer.offset;
buffer = buffer.data;
if ( strides.length < ndims ) {
// Account for augmented dimensions (note: expanding the strides array to account for prepended singleton dimensions does **not** affect the index offset):
strides = expandStrides( ndims, shape, strides, order );
}
}
} else if ( buffer ) {
if ( btype === 'generic' && opts.flatten ) {
buffer = flattenArray( buffer );
}
if ( buffer.length !== len ) {
throw new RangeError( 'invalid arguments. Array shape is incompatible with provided data source. Number of data source elements does not match array shape.' );
}
if ( btype !== dtype || opts.copy ) {
buffer = castBuffer( buffer, len, dtype );
}
} else {
buffer = createBuffer( dtype, len );
}
// 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...
if ( strides === void 0 ) {
strides = shape2strides( shape, order );
offset = strides2offset( shape, strides );
}
return new ndarray( dtype, buffer, shape, strides, offset, order, nopts );
}
// EXPORTS //
module.exports = array;