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;