/** * @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 * * var v = arr.get( 0, 0 ); * // returns 1 * * @example * var opts = { * 'dtype': 'generic', * 'flatten': false * }; * * var arr = array( [ [ 1, 2 ], [ 3, 4 ] ], opts ); * // returns * * 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 * * 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;