array-kit 0.2.10 → 0.2.12
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 +150 -55
- package/package.json +1 -1
package/lib/NDArray.js
CHANGED
|
@@ -43,9 +43,13 @@
|
|
|
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
45
|
reverse: syntactic sugar, like params.order = [ N , ... , 2 , 1 , 0 ]
|
|
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
47
|
stride: (default: 1) the stride of the fastest moving dimension, usually 1 except if there is more dimensions fastest
|
|
48
48
|
dimensions that are removed from the view
|
|
49
|
+
|
|
50
|
+
Not added yet:
|
|
51
|
+
offset: the offset of the first logical element of the ND-array
|
|
52
|
+
dataEnd: the index in the data storage where the ND-array ends, excluded (e.g. the end of the view in the Buffer)
|
|
49
53
|
*/
|
|
50
54
|
function NDArray( dataOrConstructor , sizes , params ) {
|
|
51
55
|
let dimensions , noInit = false ;
|
|
@@ -64,7 +68,9 @@ function NDArray( dataOrConstructor , sizes , params ) {
|
|
|
64
68
|
// Define the size in each axis
|
|
65
69
|
this.sizes = new Array( dimensions ) ;
|
|
66
70
|
|
|
67
|
-
// The full size of the ND-array, each axis-size multiplied
|
|
71
|
+
// The full logical size of the ND-array, each axis-size multiplied
|
|
72
|
+
// /!\ IT'S NOT THE PHYSICAL SIZE /!\
|
|
73
|
+
// See .dataStart and .dataEnd for that.
|
|
68
74
|
this.size = 1 ;
|
|
69
75
|
|
|
70
76
|
// Define the min for each axis, when the ND-array is not zero-based
|
|
@@ -80,9 +86,16 @@ function NDArray( dataOrConstructor , sizes , params ) {
|
|
|
80
86
|
// Define the layout of the array in memory, strides are products of all faster dimensions
|
|
81
87
|
this.strides = new Array( dimensions ) ;
|
|
82
88
|
|
|
83
|
-
//
|
|
89
|
+
// Use to speed up the whole array iteration
|
|
90
|
+
this.backStrides = new Array( dimensions ) ;
|
|
91
|
+
|
|
92
|
+
// This is the index of the first logical element in the physical data storage
|
|
84
93
|
this.offset = 0 ;
|
|
85
94
|
|
|
95
|
+
// This is physical start and end of the ND-array in the data storage
|
|
96
|
+
this.dataStart = 0 ;
|
|
97
|
+
this.dataEnd = 0 ;
|
|
98
|
+
|
|
86
99
|
// The underlying data store: Array, TypedArray, Buffer... anything that is indexed
|
|
87
100
|
this.data = null ;
|
|
88
101
|
|
|
@@ -120,24 +133,28 @@ NDArray.prototype._init = function( dataOrConstructor , sizes , params = {} ) {
|
|
|
120
133
|
// Compute the stride
|
|
121
134
|
this._initStridesUsingSizes( params.stride ) ;
|
|
122
135
|
|
|
123
|
-
//
|
|
124
|
-
this.
|
|
136
|
+
// The index where the ND-array region starts
|
|
137
|
+
this.dataStart = params.dataStart || 0 ;
|
|
138
|
+
|
|
139
|
+
// Compute data store start and end
|
|
140
|
+
//this._initStartEndFromOffsetSizesStrides() ;
|
|
141
|
+
this._initOffsetEndFromStartSizesStrides() ;
|
|
125
142
|
|
|
126
143
|
// Set or create a new data store
|
|
127
144
|
if ( typeof dataOrConstructor === 'function' ) {
|
|
128
145
|
if ( typeof dataOrConstructor.allocUnsafe === 'function' ) {
|
|
129
146
|
// Detect Node.js's Buffer referencing it (avoid browser compatibility layer)
|
|
130
|
-
this.data = dataOrConstructor.allocUnsafe( this.
|
|
147
|
+
this.data = dataOrConstructor.allocUnsafe( this.dataEnd ) ;
|
|
131
148
|
}
|
|
132
149
|
else {
|
|
133
|
-
this.data = new dataOrConstructor( this.
|
|
150
|
+
this.data = new dataOrConstructor( this.dataEnd ) ;
|
|
134
151
|
}
|
|
135
152
|
}
|
|
136
153
|
else {
|
|
137
154
|
/*
|
|
138
155
|
// Not sure it's good to check the length, since Array is extensible and only rarer TypedArray is fixed
|
|
139
|
-
if ( dataOrConstructor.length < this.
|
|
140
|
-
throw new RangeError( "Provided data store is too small (expecting at least " +
|
|
156
|
+
if ( dataOrConstructor.length < this.dataEnd ) {
|
|
157
|
+
throw new RangeError( "Provided data store is too small (expecting at least " + this.dataEnd + " but got " + dataOrConstructor.length ) ;
|
|
141
158
|
}
|
|
142
159
|
*/
|
|
143
160
|
this.data = dataOrConstructor ;
|
|
@@ -189,13 +206,49 @@ NDArray.prototype._initMinsMaxsFromSizes = function( sizes ) {
|
|
|
189
206
|
NDArray.prototype._initStridesUsingSizes = function( stride = 1 ) {
|
|
190
207
|
for ( const d of this.order ) {
|
|
191
208
|
this.strides[ d ] = stride ;
|
|
209
|
+
this.backStrides[ d ] = ( 1 - this.sizes[ d ] ) * stride ;
|
|
192
210
|
stride *= this.sizes[ d ] ;
|
|
193
211
|
}
|
|
194
212
|
} ;
|
|
195
213
|
|
|
214
|
+
// Offset, sizes and strides are not passed as argument, they should already be computed on the instance
|
|
215
|
+
NDArray.prototype._initOffsetEndFromStartSizesStrides = function() {
|
|
216
|
+
this.dataEnd = this.dataStart + 1 ;
|
|
217
|
+
this.offset = this.dataStart ;
|
|
218
|
+
|
|
219
|
+
for ( let d = 0 ; d < this.dimensions ; d ++ ) {
|
|
220
|
+
const shift = this.strides[ d ] * ( this.sizes[ d ] - 1 ) ;
|
|
221
|
+
|
|
222
|
+
if ( shift >= 0 ) {
|
|
223
|
+
this.dataEnd += shift ;
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
this.dataEnd -= shift ;
|
|
227
|
+
this.offset -= shift ;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
} ;
|
|
231
|
+
|
|
232
|
+
/* Not sure if it's useful to compute based on offset
|
|
233
|
+
// Offset, sizes and strides are not passed as argument, they should already be computed on the instance
|
|
234
|
+
NDArray.prototype._initStartEndFromOffsetSizesStrides = function() {
|
|
235
|
+
this.dataStart = this.offset ;
|
|
236
|
+
this.dataEnd = this.offset + 1 ;
|
|
237
|
+
|
|
238
|
+
for ( const d of this.order ) {
|
|
239
|
+
if ( this.strides[ d ] >= 0 ) {
|
|
240
|
+
this.dataEnd += this.strides[ d ] * ( this.sizes[ d ] - 1 ) ;
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
this.dataStart += this.strides[ d ] * ( this.sizes[ d ] - 1 ) ;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
} ;
|
|
247
|
+
*/
|
|
248
|
+
|
|
196
249
|
|
|
197
250
|
|
|
198
|
-
// Make the array zero-based, or based
|
|
251
|
+
// Make the array zero-based, or based on the provided mins vector
|
|
199
252
|
NDArray.prototype.rebase = function( mins ) {
|
|
200
253
|
if ( mins ) {
|
|
201
254
|
for ( let d = 0 ; d < this.dimensions ; d ++ ) {
|
|
@@ -215,8 +268,46 @@ NDArray.prototype.rebase = function( mins ) {
|
|
|
215
268
|
|
|
216
269
|
|
|
217
270
|
|
|
271
|
+
// Translate, moving the coordinates of each value
|
|
272
|
+
NDArray.prototype.translate = function( translation ) {
|
|
273
|
+
for ( let d = 0 ; d < this.dimensions ; d ++ ) {
|
|
274
|
+
this.mins[ d ] += translation[ d ] ;
|
|
275
|
+
this.maxs[ d ] += translation[ d ] ;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return this ;
|
|
279
|
+
} ;
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
// Flip coordinates along a dimension, but preserving min and max
|
|
284
|
+
NDArray.prototype.flip = function( dimension ) {
|
|
285
|
+
if ( dimension < 0 || dimension >= this.dimensions ) {
|
|
286
|
+
throw new RangeError( "Bad dimension, should be between [0," + this.dimensions + ") but got: " + dimension ) ;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
this.strides[ dimension ] *= - 1 ;
|
|
290
|
+
this.backStrides[ dimension ] *= - 1 ;
|
|
291
|
+
this._initOffsetEndFromStartSizesStrides() ;
|
|
292
|
+
|
|
293
|
+
return this ;
|
|
294
|
+
} ;
|
|
295
|
+
|
|
296
|
+
// Flip coordinates along all dimensions at once, but preserving min and max
|
|
297
|
+
NDArray.prototype.flipAll = function() {
|
|
298
|
+
for ( let d = 0 ; d < this.dimensions ; d ++ ) {
|
|
299
|
+
this.strides[ d ] *= - 1 ;
|
|
300
|
+
this.backStrides[ d ] *= - 1 ;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
this._initOffsetEndFromStartSizesStrides() ;
|
|
304
|
+
return this ;
|
|
305
|
+
} ;
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
|
|
218
309
|
// Create a new clone of the current NDArray with the same geometry but an empty (but ready) data
|
|
219
|
-
// The new data is limited to the used size (
|
|
310
|
+
// The new data is limited to the used size (dataStart = 0).
|
|
220
311
|
NDArray.prototype._cloneGeometry = function() {
|
|
221
312
|
const newNDArray = new NDArray( this.dimensions ) ;
|
|
222
313
|
|
|
@@ -227,6 +318,11 @@ NDArray.prototype._cloneGeometry = function() {
|
|
|
227
318
|
Object.assign( newNDArray.mins , this.mins ) ;
|
|
228
319
|
Object.assign( newNDArray.maxs , this.maxs ) ;
|
|
229
320
|
Object.assign( newNDArray.strides , this.strides ) ;
|
|
321
|
+
Object.assign( newNDArray.backStrides , this.backStrides ) ;
|
|
322
|
+
|
|
323
|
+
// Start at index=0 in the physical storage
|
|
324
|
+
newNDArray.offset = this.offset - this.dataStart ;
|
|
325
|
+
newNDArray.dataEnd = this.dataEnd - this.dataStart ;
|
|
230
326
|
|
|
231
327
|
return newNDArray ;
|
|
232
328
|
} ;
|
|
@@ -240,6 +336,7 @@ NDArray.prototype._resizeGeometry = function( mins , maxs ) {
|
|
|
240
336
|
Object.assign( newNDArray.order , this.order ) ;
|
|
241
337
|
newNDArray._initSizesFromMinsMaxs( mins , maxs ) ;
|
|
242
338
|
newNDArray._initStridesUsingSizes() ;
|
|
339
|
+
newNDArray._initOffsetEndFromStartSizesStrides() ;
|
|
243
340
|
|
|
244
341
|
return newNDArray ;
|
|
245
342
|
} ;
|
|
@@ -247,7 +344,7 @@ NDArray.prototype._resizeGeometry = function( mins , maxs ) {
|
|
|
247
344
|
|
|
248
345
|
|
|
249
346
|
// Create a new data store of the same type
|
|
250
|
-
NDArray.prototype._newStorageOfTheSameKind = function( size = this.
|
|
347
|
+
NDArray.prototype._newStorageOfTheSameKind = function( size = this.dataEnd - this.dataStart ) {
|
|
251
348
|
if ( typeof this.data?.constructor?.allocUnsafe === 'function' ) {
|
|
252
349
|
// Detect Node.js's Buffer referencing it (avoid browser compatibility layer)
|
|
253
350
|
return this.data.constructor.allocUnsafe( size ) ;
|
|
@@ -263,7 +360,6 @@ NDArray.prototype._newStorageOfTheSameKind = function( size = this.size ) {
|
|
|
263
360
|
// index = coords[ 0 ] * strides[ 0 ] + coords[ 1 ] * strides[ 1 ] + ... + coords[ n ] * strides[ n ]
|
|
264
361
|
NDArray.prototype._getIndex = function( coords ) {
|
|
265
362
|
let index = this.offset ;
|
|
266
|
-
//for ( const d of this.order ) { index += ( coords[ d ] - this.mins[ d ] ) * this.strides[ d ] ; }
|
|
267
363
|
for ( let d = 0 ; d < this.dimensions ; d ++ ) { index += ( coords[ d ] - this.mins[ d ] ) * this.strides[ d ] ; }
|
|
268
364
|
return index ;
|
|
269
365
|
} ;
|
|
@@ -308,8 +404,8 @@ NDArray.prototype._getCoords = function( index , coords = new Array( this.dimens
|
|
|
308
404
|
|
|
309
405
|
NDArray.prototype._getCoordsCheck =
|
|
310
406
|
NDArray.prototype.getCoords = function( index , coords ) {
|
|
311
|
-
if ( index < this.
|
|
312
|
-
throw new RangeError( "Index " + index + " out of bounds, which is [" + this.
|
|
407
|
+
if ( index < this.dataStart || index >= this.dataEnd ) {
|
|
408
|
+
throw new RangeError( "Index " + index + " out of bounds, which is [" + this.dataStart + "," + this.dataEnd + ")" ) ;
|
|
313
409
|
}
|
|
314
410
|
|
|
315
411
|
coords = coords ?? new Array( this.dimensions ) ;
|
|
@@ -450,32 +546,7 @@ NDArray.prototype._getStrideStartAndVectorDimension = function( coords ) {
|
|
|
450
546
|
*/
|
|
451
547
|
NDArray.prototype.forEach =
|
|
452
548
|
NDArray.prototype._forEach = function( callback ) {
|
|
453
|
-
|
|
454
|
-
const stride0 = this.strides[ this.order[ 0 ] ] ;
|
|
455
|
-
const coords = Array.from( this.mins ) ;
|
|
456
|
-
const dataEnd = this.offset + this.size ;
|
|
457
|
-
let index = this.offset ;
|
|
458
|
-
|
|
459
|
-
for ( ;; ) {
|
|
460
|
-
callback( this.data[ index ] , coords , index , this ) ;
|
|
461
|
-
|
|
462
|
-
index += stride0 ;
|
|
463
|
-
if ( index >= dataEnd ) { return ; }
|
|
464
|
-
|
|
465
|
-
for ( let i = 0 ; i < this.dimensions ; i ++ ) {
|
|
466
|
-
const d = this.order[ i ] ;
|
|
467
|
-
coords[ d ] ++ ;
|
|
468
|
-
if (
|
|
469
|
-
i < this.dimensions - 1
|
|
470
|
-
&& coords[ d ] - this.mins[ d ] >= this.strides[ this.order[ i + 1 ] ]
|
|
471
|
-
) {
|
|
472
|
-
coords[ d ] = this.mins[ d ] ;
|
|
473
|
-
}
|
|
474
|
-
else {
|
|
475
|
-
break ;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
549
|
+
return this._forEachInRegion( this.mins , this.maxs , callback , true ) ;
|
|
479
550
|
} ;
|
|
480
551
|
|
|
481
552
|
|
|
@@ -511,10 +582,19 @@ NDArray.prototype._forEachInRegionCheck = function( mins , maxs , callback ) {
|
|
|
511
582
|
this._forEachInRegion( mins , maxs , callback ) ;
|
|
512
583
|
} ;
|
|
513
584
|
|
|
514
|
-
|
|
515
|
-
|
|
585
|
+
// wholeArray=true when coming from ._forEach()
|
|
586
|
+
NDArray.prototype._forEachInRegion = function( mins , maxs , callback , wholeArray = false ) {
|
|
587
|
+
let backStrides , index ;
|
|
516
588
|
const coords = Array.from( mins ) ;
|
|
517
|
-
|
|
589
|
+
|
|
590
|
+
if ( wholeArray ) {
|
|
591
|
+
backStrides = this.backStrides ;
|
|
592
|
+
index = this.offset ;
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
backStrides = mins.map( ( min , d ) => ( min - maxs[ d ] ) * this.strides[ d ] ) ;
|
|
596
|
+
index = this._getIndex( coords ) ;
|
|
597
|
+
}
|
|
518
598
|
|
|
519
599
|
for ( ;; ) {
|
|
520
600
|
callback( this.data[ index ] , coords , index , this ) ;
|
|
@@ -591,6 +671,13 @@ NDArray.prototype._forEachVectorInRegion = function( strideStartCoords , maxs ,
|
|
|
591
671
|
|
|
592
672
|
|
|
593
673
|
|
|
674
|
+
// Return a Generator
|
|
675
|
+
// .each()
|
|
676
|
+
NDArray.prototype.each =
|
|
677
|
+
NDArray.prototype._each = function() {
|
|
678
|
+
return this._eachInRegion( this.mins , this.maxs , true ) ;
|
|
679
|
+
} ;
|
|
680
|
+
|
|
594
681
|
// Return a Generator
|
|
595
682
|
// .eachInRegion( region )
|
|
596
683
|
// .eachInRegion( mins , maxs )
|
|
@@ -610,10 +697,20 @@ NDArray.prototype._eachInRegionCheck = function( mins , maxs ) {
|
|
|
610
697
|
|
|
611
698
|
// Generator
|
|
612
699
|
// The yielded entry { coords , index , value } should be cloned, as well as entry.coords, if userland want to modify it
|
|
613
|
-
|
|
614
|
-
|
|
700
|
+
// wholeArray=true when coming from ._each()
|
|
701
|
+
NDArray.prototype._eachInRegion = function*( mins , maxs , wholeArray = false ) {
|
|
702
|
+
let backStrides , index ;
|
|
615
703
|
const coords = Array.from( mins ) ;
|
|
616
|
-
|
|
704
|
+
|
|
705
|
+
if ( wholeArray ) {
|
|
706
|
+
backStrides = this.backStrides ;
|
|
707
|
+
index = this.offset ;
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
backStrides = mins.map( ( min , d ) => ( min - maxs[ d ] ) * this.strides[ d ] ) ;
|
|
711
|
+
index = this._getIndex( coords ) ;
|
|
712
|
+
}
|
|
713
|
+
|
|
617
714
|
const entry = { coords , index , value: undefined } ;
|
|
618
715
|
|
|
619
716
|
for ( ;; ) {
|
|
@@ -845,10 +942,8 @@ NDArray.prototype._eachVectorIndexInRegionBackward = function*( strideStartCoord
|
|
|
845
942
|
|
|
846
943
|
|
|
847
944
|
NDArray.prototype.fill = function( value ) {
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
}
|
|
851
|
-
|
|
945
|
+
const stride0 = this.strides[ this.order[ 0 ] ] ;
|
|
946
|
+
for ( let i = this.dataStart ; i < this.dataEnd ; i += stride0 ) { this.data[ i ] = value ; }
|
|
852
947
|
return this ;
|
|
853
948
|
} ;
|
|
854
949
|
|
|
@@ -892,7 +987,7 @@ NDArray.prototype.map = function( callback ) {
|
|
|
892
987
|
newNDArray.data = this._newStorageOfTheSameKind() ;
|
|
893
988
|
|
|
894
989
|
this._forEach( ( value , coords , index ) => {
|
|
895
|
-
newNDArray.data[ index - this.
|
|
990
|
+
newNDArray.data[ index - this.dataStart ] = callback( value , coords , index , this ) ;
|
|
896
991
|
} ) ;
|
|
897
992
|
|
|
898
993
|
return newNDArray ;
|
|
@@ -913,7 +1008,7 @@ NDArray.prototype.mapInRegion = function( mins , maxs , callback ) {
|
|
|
913
1008
|
|
|
914
1009
|
this._checkRegion( mins , maxs ) ;
|
|
915
1010
|
const newNDArray = this._resizeGeometry( mins , maxs ) ;
|
|
916
|
-
newNDArray.data = this._newStorageOfTheSameKind( newNDArray.
|
|
1011
|
+
newNDArray.data = this._newStorageOfTheSameKind( newNDArray.dataEnd ) ;
|
|
917
1012
|
|
|
918
1013
|
this._dualStepCallback( newNDArray , mins , maxs , mins , maxs , callback ) ;
|
|
919
1014
|
|
|
@@ -944,7 +1039,7 @@ NDArray.prototype.mapVectorInRegion = function( mins , maxs , callback ) {
|
|
|
944
1039
|
|
|
945
1040
|
this._checkRegion( strideStartCoords , strideEndCoords , vectorDimension ) ;
|
|
946
1041
|
const newNDArray = this._resizeGeometry( strideStartCoords , strideEndCoords ) ;
|
|
947
|
-
newNDArray.data = this._newStorageOfTheSameKind( newNDArray.
|
|
1042
|
+
newNDArray.data = this._newStorageOfTheSameKind( newNDArray.dataEnd ) ;
|
|
948
1043
|
|
|
949
1044
|
this._dualStepVectorCallback( newNDArray , strideStartCoords , strideEndCoords , strideStartCoords , strideEndCoords , vectorDimension , callback ) ;
|
|
950
1045
|
|
|
@@ -1029,7 +1124,7 @@ NDArray.prototype.extractRegion = function( mins , maxs ) {
|
|
|
1029
1124
|
|
|
1030
1125
|
this._checkRegion( mins , maxs ) ;
|
|
1031
1126
|
const newNDArray = this._resizeGeometry( mins , maxs ) ;
|
|
1032
|
-
newNDArray.data = this._newStorageOfTheSameKind( newNDArray.
|
|
1127
|
+
newNDArray.data = this._newStorageOfTheSameKind( newNDArray.dataEnd ) ;
|
|
1033
1128
|
|
|
1034
1129
|
this._dualStepCopy( newNDArray , mins , maxs , mins , maxs ) ;
|
|
1035
1130
|
|