array-kit 0.2.12 → 0.2.14

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 +202 -38
  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,100 @@ NDArray.prototype.flipAll = function() {
306
375
 
307
376
 
308
377
 
378
+ // Transpose: swap coordinates orders
379
+ NDArray.prototype.transpose = function( ... dimensionList ) {
380
+ // It's not possible to swap because each dimension look up for the source at its original place
381
+ const dMax = Math.min( dimensionList.length , this.dimensions ) ,
382
+ sizes = new Array( this.dimensions ) ,
383
+ mins = new Array( this.dimensions ) ,
384
+ maxs = new Array( this.dimensions ) ,
385
+ order = new Array( this.dimensions ) ,
386
+ strides = new Array( this.dimensions ) ,
387
+ backStrides = new Array( this.dimensions ) ;
388
+
389
+ for ( let d = 0 ; d < dMax ; d ++ ) {
390
+ const fromD = dimensionList[ d ] ;
391
+ sizes[ d ] = this.sizes[ fromD ] ;
392
+ mins[ d ] = this.mins[ fromD ] ;
393
+ maxs[ d ] = this.maxs[ fromD ] ;
394
+ order[ d ] = this.order[ fromD ] ;
395
+ strides[ d ] = this.strides[ fromD ] ;
396
+ backStrides[ d ] = this.backStrides[ fromD ] ;
397
+ }
398
+
399
+ this.sizes = sizes ;
400
+ this.mins = mins ;
401
+ this.maxs = maxs ;
402
+ this.order = order ;
403
+ this.strides = strides ;
404
+ this.backStrides = backStrides ;
405
+
406
+ return this ;
407
+ } ;
408
+
409
+
410
+
411
+ /*
412
+ Fix some axis and reduce the number of dimensions.
413
+ Like .pick() in the "ndarray" package.
414
+
415
+ Examples:
416
+ .select( null , 3 , null ) fix the second coordinate to 3, the ND-array will now have 2 dimensions instead of 3
417
+ .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
418
+ */
419
+ NDArray.prototype.select = function( ... coords ) {
420
+ this.size = 1 ;
421
+
422
+ // dimensions should be cached because it's modified along the way
423
+ for ( let d = 0 , newD = 0 , dimensions = this.dimensions ; d < dimensions ; d ++ ) {
424
+ const coord = coords[ d ] ;
425
+
426
+ if ( coord === null || coord === undefined ) {
427
+ this.size *= this.sizes[ newD ] ;
428
+ newD ++ ;
429
+ }
430
+ else {
431
+ this.offset += ( coord - this.mins[ newD ] ) * this.strides[ newD ] ;
432
+ this.sizes.splice( newD , 1 ) ;
433
+ this.mins.splice( newD , 1 ) ;
434
+ this.maxs.splice( newD , 1 ) ;
435
+ this.strides.splice( newD , 1 ) ;
436
+ this.backStrides.splice( newD , 1 ) ;
437
+ this.dimensions -- ;
438
+ }
439
+ }
440
+
441
+ this._initOrderFromStrides() ;
442
+ this._initStartEndFromOffsetSizesStrides() ;
443
+ this._initIsContiguousFromStridesSizes() ;
444
+
445
+ return this ;
446
+ } ;
447
+
448
+
449
+
450
+ // Clone only the view, the data storage is the same
451
+ NDArray.prototype.cloneView =
452
+ NDArray.prototype.view = function() {
453
+ const newNDArray = this._cloneLogical( true ) ;
454
+ newNDArray.data = this.data ;
455
+ return newNDArray ;
456
+ } ;
457
+
458
+
459
+
460
+ // Full clone
461
+ NDArray.prototype.clone = function() {
462
+ const newNDArray = this._cloneLogical() ;
463
+ newNDArray._createDataFrom( this.data , this.dataStart , this.dataEnd ) ;
464
+ return newNDArray ;
465
+ } ;
466
+
467
+
468
+
309
469
  // 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() {
470
+ // The new data is limited to the used size (dataStart = 0) except if keepDataStart is set to true.
471
+ NDArray.prototype._cloneLogical = function( keepDataStart = false ) {
312
472
  const newNDArray = new NDArray( this.dimensions ) ;
313
473
 
314
474
  // Copy geometry
@@ -320,9 +480,16 @@ NDArray.prototype._cloneGeometry = function() {
320
480
  Object.assign( newNDArray.strides , this.strides ) ;
321
481
  Object.assign( newNDArray.backStrides , this.backStrides ) ;
322
482
 
323
- // Start at index=0 in the physical storage
324
- newNDArray.offset = this.offset - this.dataStart ;
325
- newNDArray.dataEnd = this.dataEnd - this.dataStart ;
483
+ if ( keepDataStart ) {
484
+ newNDArray.dataStart = this.dataStart ;
485
+ newNDArray.dataEnd = this.dataEnd ;
486
+ newNDArray.offset = this.offset ;
487
+ }
488
+ else {
489
+ // Start at index=0 in the physical storage
490
+ newNDArray.offset = this.offset - this.dataStart ;
491
+ newNDArray.dataEnd = this.dataEnd - this.dataStart ;
492
+ }
326
493
 
327
494
  return newNDArray ;
328
495
  } ;
@@ -330,12 +497,12 @@ NDArray.prototype._cloneGeometry = function() {
330
497
 
331
498
 
332
499
  // Clone with a modified geometry
333
- NDArray.prototype._resizeGeometry = function( mins , maxs ) {
500
+ NDArray.prototype._resizeLogical = function( mins , maxs ) {
334
501
  const newNDArray = new NDArray( this.dimensions ) ;
335
502
 
336
503
  Object.assign( newNDArray.order , this.order ) ;
337
504
  newNDArray._initSizesFromMinsMaxs( mins , maxs ) ;
338
- newNDArray._initStridesUsingSizes() ;
505
+ newNDArray._initStridesFromSizes() ;
339
506
  newNDArray._initOffsetEndFromStartSizesStrides() ;
340
507
 
341
508
  return newNDArray ;
@@ -343,18 +510,6 @@ NDArray.prototype._resizeGeometry = function( mins , maxs ) {
343
510
 
344
511
 
345
512
 
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
513
  // Internal version without any unnessessary test
359
514
  // Classic strided array access is:
360
515
  // index = coords[ 0 ] * strides[ 0 ] + coords[ 1 ] * strides[ 1 ] + ... + coords[ n ] * strides[ n ]
@@ -367,7 +522,6 @@ NDArray.prototype._getIndex = function( coords ) {
367
522
  NDArray.prototype._getIndexCheck = function( coords ) {
368
523
  let index = this.offset ;
369
524
 
370
- //for ( const d of this.order ) {
371
525
  for ( let d = 0 ; d < this.dimensions ; d ++ ) {
372
526
  const c = coords[ d ] ;
373
527
 
@@ -942,8 +1096,18 @@ NDArray.prototype._eachVectorIndexInRegionBackward = function*( strideStartCoord
942
1096
 
943
1097
 
944
1098
  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 ; }
1099
+ if ( this.isContiguous ) {
1100
+ // Fast path: iterate directly on the physical data
1101
+ for ( let i = this.dataStart ; i < this.dataEnd ; i ++ ) {
1102
+ this.data[ i ] = value ;
1103
+ }
1104
+ }
1105
+ else {
1106
+ for ( const { index } of this._each() ) {
1107
+ this.data[ index ] = value ;
1108
+ }
1109
+ }
1110
+
947
1111
  return this ;
948
1112
  } ;
949
1113
 
@@ -983,7 +1147,7 @@ NDArray.prototype.fillVectorInRegion = function( mins , maxs , vector ) {
983
1147
  The classic .map(), returning a new ND-Array with its own data storage.
984
1148
  */
985
1149
  NDArray.prototype.map = function( callback ) {
986
- const newNDArray = this._cloneGeometry() ;
1150
+ const newNDArray = this._cloneLogical() ;
987
1151
  newNDArray.data = this._newStorageOfTheSameKind() ;
988
1152
 
989
1153
  this._forEach( ( value , coords , index ) => {
@@ -1007,7 +1171,7 @@ NDArray.prototype.mapInRegion = function( mins , maxs , callback ) {
1007
1171
  }
1008
1172
 
1009
1173
  this._checkRegion( mins , maxs ) ;
1010
- const newNDArray = this._resizeGeometry( mins , maxs ) ;
1174
+ const newNDArray = this._resizeLogical( mins , maxs ) ;
1011
1175
  newNDArray.data = this._newStorageOfTheSameKind( newNDArray.dataEnd ) ;
1012
1176
 
1013
1177
  this._dualStepCallback( newNDArray , mins , maxs , mins , maxs , callback ) ;
@@ -1038,7 +1202,7 @@ NDArray.prototype.mapVectorInRegion = function( mins , maxs , callback ) {
1038
1202
  strideEndCoords[ vectorDimension ] = this.maxs[ vectorDimension ] ;
1039
1203
 
1040
1204
  this._checkRegion( strideStartCoords , strideEndCoords , vectorDimension ) ;
1041
- const newNDArray = this._resizeGeometry( strideStartCoords , strideEndCoords ) ;
1205
+ const newNDArray = this._resizeLogical( strideStartCoords , strideEndCoords ) ;
1042
1206
  newNDArray.data = this._newStorageOfTheSameKind( newNDArray.dataEnd ) ;
1043
1207
 
1044
1208
  this._dualStepVectorCallback( newNDArray , strideStartCoords , strideEndCoords , strideStartCoords , strideEndCoords , vectorDimension , callback ) ;
@@ -1123,7 +1287,7 @@ NDArray.prototype.extractRegion = function( mins , maxs ) {
1123
1287
  }
1124
1288
 
1125
1289
  this._checkRegion( mins , maxs ) ;
1126
- const newNDArray = this._resizeGeometry( mins , maxs ) ;
1290
+ const newNDArray = this._resizeLogical( mins , maxs ) ;
1127
1291
  newNDArray.data = this._newStorageOfTheSameKind( newNDArray.dataEnd ) ;
1128
1292
 
1129
1293
  this._dualStepCopy( newNDArray , mins , maxs , mins , maxs ) ;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "array-kit",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "An array manipulation toolbox.",
5
5
  "main": "lib/array-kit.js",
6
6
  "directories": {