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.
- package/lib/NDArray.js +164 -33
- 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
|
|
48
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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 ] =
|
|
209
|
-
this.backStrides[ d ] = ( 1 - this.sizes[ d ] ) *
|
|
210
|
-
|
|
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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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.
|
|
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
|
-
|
|
946
|
-
|
|
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
|
|