array-kit 0.2.12 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/lib/NDArray.js +164 -33
  2. package/package.json +1 -1
package/lib/NDArray.js CHANGED
@@ -42,10 +42,10 @@
42
42
  params: an object of optional parameters, where:
43
43
  order: array of coord order stored in the storage, by default [ 0 , 1 , 2 , ..., N ], for 2D it's row-first,
44
44
  if you want column-first, use [ 1 , 0 ], or params.reverse = true
45
- reverse: syntactic sugar, like params.order = [ N , ... , 2 , 1 , 0 ]
45
+ reverse: syntactic sugar, like params.order = [ N , ... , 2 , 1 , 0 ], means that the slowest moving dimension comes first
46
46
  dataStart: the index in the data storage where the ND-array starts (e.g. the start of the view in the Buffer)
47
- stride: (default: 1) the stride of the fastest moving dimension, usually 1 except if there is more dimensions fastest
48
- dimensions that are removed from the view
47
+ stride: (default: 1) the stride of the fastest moving dimension, usually 1 except if there is faster dimensions
48
+ that are removed from the view
49
49
 
50
50
  Not added yet:
51
51
  offset: the offset of the first logical element of the ND-array
@@ -92,11 +92,15 @@ function NDArray( dataOrConstructor , sizes , params ) {
92
92
  // This is the index of the first logical element in the physical data storage
93
93
  this.offset = 0 ;
94
94
 
95
+ // True if the physical backend is contiguous, that can speed up some operation like .fill()
96
+ this.isContiguous = true ;
97
+
95
98
  // This is physical start and end of the ND-array in the data storage
96
99
  this.dataStart = 0 ;
97
100
  this.dataEnd = 0 ;
98
101
 
99
- // The underlying data store: Array, TypedArray, Buffer... anything that is indexed
102
+ // The underlying physical data store backend.
103
+ // Can be: Array, TypedArray, Buffer... anything that is indexed.
100
104
  this.data = null ;
101
105
 
102
106
  if ( ! noInit ) {
@@ -131,7 +135,7 @@ NDArray.prototype._init = function( dataOrConstructor , sizes , params = {} ) {
131
135
  }
132
136
 
133
137
  // Compute the stride
134
- this._initStridesUsingSizes( params.stride ) ;
138
+ this._initStridesFromSizes( params.stride ) ;
135
139
 
136
140
  // The index where the ND-array region starts
137
141
  this.dataStart = params.dataStart || 0 ;
@@ -143,7 +147,7 @@ NDArray.prototype._init = function( dataOrConstructor , sizes , params = {} ) {
143
147
  // Set or create a new data store
144
148
  if ( typeof dataOrConstructor === 'function' ) {
145
149
  if ( typeof dataOrConstructor.allocUnsafe === 'function' ) {
146
- // Detect Node.js's Buffer referencing it (avoid browser compatibility layer)
150
+ // Detect Node.js's Buffer without referencing it (avoid browser compatibility layer)
147
151
  this.data = dataOrConstructor.allocUnsafe( this.dataEnd ) ;
148
152
  }
149
153
  else {
@@ -164,6 +168,8 @@ NDArray.prototype._init = function( dataOrConstructor , sizes , params = {} ) {
164
168
  // Fragmented init, because it can be re-use by various cloning things
165
169
 
166
170
  NDArray.prototype._initMinsMaxsSizesFromRegion = function( region ) {
171
+ this.size = 1 ;
172
+
167
173
  for ( let d = 0 ; d < this.dimensions ; d ++ ) {
168
174
  this.mins[ d ] = region[ d ][ 0 ] ;
169
175
  this.maxs[ d ] = region[ d ][ 1 ] ;
@@ -203,14 +209,23 @@ NDArray.prototype._initMinsMaxsFromSizes = function( sizes ) {
203
209
  } ;
204
210
 
205
211
  // Sizes is not passed as argument, it should already be computed on the instance
206
- NDArray.prototype._initStridesUsingSizes = function( stride = 1 ) {
212
+ NDArray.prototype._initStridesFromSizes = function( nextStride = 1 ) {
213
+ // It's always contiguous when strides start at 1
214
+ this.isContiguous = nextStride === 1 ;
215
+
207
216
  for ( const d of this.order ) {
208
- this.strides[ d ] = stride ;
209
- this.backStrides[ d ] = ( 1 - this.sizes[ d ] ) * stride ;
210
- stride *= this.sizes[ d ] ;
217
+ this.strides[ d ] = nextStride ;
218
+ this.backStrides[ d ] = ( 1 - this.sizes[ d ] ) * nextStride ;
219
+ nextStride *= this.sizes[ d ] ;
211
220
  }
212
221
  } ;
213
222
 
223
+ // Strides is not passed as argument, it should already be computed on the instance
224
+ NDArray.prototype._initOrderFromStrides = function() {
225
+ this.order = Array.from( { length: this.strides.length } , ( v , i ) => i ) ;
226
+ this.order.sort( ( a , b ) => this.strides[ a ] - this.strides[ b ] ) ;
227
+ } ;
228
+
214
229
  // Offset, sizes and strides are not passed as argument, they should already be computed on the instance
215
230
  NDArray.prototype._initOffsetEndFromStartSizesStrides = function() {
216
231
  this.dataEnd = this.dataStart + 1 ;
@@ -229,7 +244,6 @@ NDArray.prototype._initOffsetEndFromStartSizesStrides = function() {
229
244
  }
230
245
  } ;
231
246
 
232
- /* Not sure if it's useful to compute based on offset
233
247
  // Offset, sizes and strides are not passed as argument, they should already be computed on the instance
234
248
  NDArray.prototype._initStartEndFromOffsetSizesStrides = function() {
235
249
  this.dataStart = this.offset ;
@@ -244,7 +258,62 @@ NDArray.prototype._initStartEndFromOffsetSizesStrides = function() {
244
258
  }
245
259
  }
246
260
  } ;
247
- */
261
+
262
+ // Init .isContiguous based on strides and sizes.
263
+ // Usually only called for views, because if ._initStridesFromSizes() is called it already set it to the right value.
264
+ NDArray.prototype._initIsContiguousFromStridesSizes = function() {
265
+ let nextStride = 1 ;
266
+
267
+ for ( const d of this.order ) {
268
+ const absStride = Math.abs( this.strides[ d ] ) ;
269
+
270
+ if ( absStride !== nextStride ) {
271
+ this.isContiguous = false ;
272
+ return ;
273
+ }
274
+
275
+ nextStride *= absStride ;
276
+ }
277
+
278
+ this.isContiguous = true ;
279
+ } ;
280
+
281
+
282
+
283
+ // Create a new data back-end from another one
284
+ // sliceStart and sliceEnd are optional
285
+ NDArray.prototype._createDataFrom = function( data , sliceStart = 0 , sliceEnd = data.length ) {
286
+ if ( Array.isArray( data ) ) {
287
+ this.data = data.slice( sliceStart , sliceEnd ) ;
288
+ }
289
+ else if ( typeof data.constructor.allocUnsafe === 'function' ) {
290
+ // Detect Node.js's Buffer without referencing it (avoid browser compatibility layer)
291
+ this.data = data.constructor.allocUnsafe( sliceEnd - sliceStart ) ;
292
+ data.copy( this.data , 0 , sliceStart , sliceEnd ) ;
293
+ }
294
+ else if ( typeof data.slice === 'function' ) {
295
+ this.data = data.slice( sliceStart , sliceEnd ) ;
296
+ }
297
+ else {
298
+ this.data = new data.constructor( data.length ) ;
299
+
300
+ for ( let src = sliceStart , dst = 0 ; src < sliceEnd ; src ++ , dst ++ ) {
301
+ this.data[ dst ] = data[ src ] ;
302
+ }
303
+ }
304
+ } ;
305
+
306
+
307
+
308
+ // Create a new data store of the same type
309
+ NDArray.prototype._newStorageOfTheSameKind = function( size = this.dataEnd - this.dataStart ) {
310
+ if ( typeof this.data?.constructor?.allocUnsafe === 'function' ) {
311
+ // Detect Node.js's Buffer referencing it (avoid browser compatibility layer)
312
+ return this.data.constructor.allocUnsafe( size ) ;
313
+ }
314
+
315
+ return new this.data.constructor( size ) ;
316
+ } ;
248
317
 
249
318
 
250
319
 
@@ -306,9 +375,67 @@ NDArray.prototype.flipAll = function() {
306
375
 
307
376
 
308
377
 
378
+ /*
379
+ Fix some axis and reduce the number of dimensions.
380
+ Like .pick() in the "ndarray" package.
381
+
382
+ Examples:
383
+ .select( null , 3 , null ) fix the second coordinate to 3, the ND-array will now have 2 dimensions instead of 3
384
+ .select( 1 , 3 , null ) fix the first and second coordinates respectively to 1 and 3, the ND-array will now have 1 dimensions instead of 3
385
+ */
386
+ NDArray.prototype.select = function( ... coords ) {
387
+ this.size = 1 ;
388
+
389
+ // dimensions should be cached because it's modified along the way
390
+ for ( let d = 0 , newD = 0 , dimensions = this.dimensions ; d < dimensions ; d ++ ) {
391
+ const coord = coords[ d ] ;
392
+
393
+ if ( coord === null || coord === undefined ) {
394
+ this.size *= this.sizes[ newD ] ;
395
+ newD ++ ;
396
+ }
397
+ else {
398
+ this.offset += ( coord - this.mins[ newD ] ) * this.strides[ newD ] ;
399
+ this.sizes.splice( newD , 1 ) ;
400
+ this.mins.splice( newD , 1 ) ;
401
+ this.maxs.splice( newD , 1 ) ;
402
+ this.strides.splice( newD , 1 ) ;
403
+ this.backStrides.splice( newD , 1 ) ;
404
+ this.dimensions -- ;
405
+ }
406
+ }
407
+
408
+ this._initOrderFromStrides() ;
409
+ this._initStartEndFromOffsetSizesStrides() ;
410
+ this._initIsContiguousFromStridesSizes() ;
411
+
412
+ return this ;
413
+ } ;
414
+
415
+
416
+
417
+ // Clone only the view, the data storage is the same
418
+ NDArray.prototype.cloneView =
419
+ NDArray.prototype.view = function() {
420
+ const newNDArray = this._cloneGeometry( true ) ;
421
+ newNDArray.data = this.data ;
422
+ return newNDArray ;
423
+ } ;
424
+
425
+
426
+
427
+ // Full clone
428
+ NDArray.prototype.clone = function() {
429
+ const newNDArray = this._cloneGeometry() ;
430
+ newNDArray._createDataFrom( this.data , this.dataStart , this.dataEnd ) ;
431
+ return newNDArray ;
432
+ } ;
433
+
434
+
435
+
309
436
  // Create a new clone of the current NDArray with the same geometry but an empty (but ready) data
310
- // The new data is limited to the used size (dataStart = 0).
311
- NDArray.prototype._cloneGeometry = function() {
437
+ // The new data is limited to the used size (dataStart = 0) except if keepDataStart is set to true.
438
+ NDArray.prototype._cloneGeometry = function( keepDataStart = false ) {
312
439
  const newNDArray = new NDArray( this.dimensions ) ;
313
440
 
314
441
  // Copy geometry
@@ -320,9 +447,16 @@ NDArray.prototype._cloneGeometry = function() {
320
447
  Object.assign( newNDArray.strides , this.strides ) ;
321
448
  Object.assign( newNDArray.backStrides , this.backStrides ) ;
322
449
 
323
- // Start at index=0 in the physical storage
324
- newNDArray.offset = this.offset - this.dataStart ;
325
- newNDArray.dataEnd = this.dataEnd - this.dataStart ;
450
+ if ( keepDataStart ) {
451
+ newNDArray.dataStart = this.dataStart ;
452
+ newNDArray.dataEnd = this.dataEnd ;
453
+ newNDArray.offset = this.offset ;
454
+ }
455
+ else {
456
+ // Start at index=0 in the physical storage
457
+ newNDArray.offset = this.offset - this.dataStart ;
458
+ newNDArray.dataEnd = this.dataEnd - this.dataStart ;
459
+ }
326
460
 
327
461
  return newNDArray ;
328
462
  } ;
@@ -335,7 +469,7 @@ NDArray.prototype._resizeGeometry = function( mins , maxs ) {
335
469
 
336
470
  Object.assign( newNDArray.order , this.order ) ;
337
471
  newNDArray._initSizesFromMinsMaxs( mins , maxs ) ;
338
- newNDArray._initStridesUsingSizes() ;
472
+ newNDArray._initStridesFromSizes() ;
339
473
  newNDArray._initOffsetEndFromStartSizesStrides() ;
340
474
 
341
475
  return newNDArray ;
@@ -343,18 +477,6 @@ NDArray.prototype._resizeGeometry = function( mins , maxs ) {
343
477
 
344
478
 
345
479
 
346
- // Create a new data store of the same type
347
- NDArray.prototype._newStorageOfTheSameKind = function( size = this.dataEnd - this.dataStart ) {
348
- if ( typeof this.data?.constructor?.allocUnsafe === 'function' ) {
349
- // Detect Node.js's Buffer referencing it (avoid browser compatibility layer)
350
- return this.data.constructor.allocUnsafe( size ) ;
351
- }
352
-
353
- return new this.data.constructor( size ) ;
354
- } ;
355
-
356
-
357
-
358
480
  // Internal version without any unnessessary test
359
481
  // Classic strided array access is:
360
482
  // index = coords[ 0 ] * strides[ 0 ] + coords[ 1 ] * strides[ 1 ] + ... + coords[ n ] * strides[ n ]
@@ -367,7 +489,6 @@ NDArray.prototype._getIndex = function( coords ) {
367
489
  NDArray.prototype._getIndexCheck = function( coords ) {
368
490
  let index = this.offset ;
369
491
 
370
- //for ( const d of this.order ) {
371
492
  for ( let d = 0 ; d < this.dimensions ; d ++ ) {
372
493
  const c = coords[ d ] ;
373
494
 
@@ -942,8 +1063,18 @@ NDArray.prototype._eachVectorIndexInRegionBackward = function*( strideStartCoord
942
1063
 
943
1064
 
944
1065
  NDArray.prototype.fill = function( value ) {
945
- const stride0 = this.strides[ this.order[ 0 ] ] ;
946
- for ( let i = this.dataStart ; i < this.dataEnd ; i += stride0 ) { this.data[ i ] = value ; }
1066
+ if ( this.isContiguous ) {
1067
+ // Fast path: iterate directly on the physical data
1068
+ for ( let i = this.dataStart ; i < this.dataEnd ; i ++ ) {
1069
+ this.data[ i ] = value ;
1070
+ }
1071
+ }
1072
+ else {
1073
+ for ( const { index } of this._each() ) {
1074
+ this.data[ index ] = value ;
1075
+ }
1076
+ }
1077
+
947
1078
  return this ;
948
1079
  } ;
949
1080
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "array-kit",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "description": "An array manipulation toolbox.",
5
5
  "main": "lib/array-kit.js",
6
6
  "directories": {