node-red-contrib-prib-functions 0.20.4 → 0.21.0
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/README.md +78 -68
- package/dataAnalysis/dataAnalysis.html +23 -9
- package/dataAnalysis/dataAnalysis.js +63 -43
- package/documentation/DataAnalysisRealtime.JPG +0 -0
- package/documentation/monitorSystem.JPG +0 -0
- package/documentation/monitorSystemTest.JPG +0 -0
- package/matrix/matrix.js +93 -8
- package/matrix/matrixNode.html +88 -55
- package/matrix/matrixNode.js +12 -5
- package/monitor/monitorSystem.html +2 -1
- package/monitor/monitorSystem.js +1 -1
- package/package.json +3 -1
- package/test/data/.config.nodes.json +1 -1
- package/test/data/.config.nodes.json.backup +2 -2
- package/test/data/.flow.json.backup +2350 -1774
- package/test/data/flow.json +2350 -1774
- package/test/data/package-lock.json +1 -1
- package/test/dataAnalysisExtensions.js +0 -1
- package/test/transformConfluence.js +1 -1
- package/testing/test.html +1 -1
- package/testing/test.js +78 -53
- package/arima/index.js +0 -18
package/README.md
CHANGED
|
@@ -4,22 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
* Data Analysis - statistical metrics that has real time option
|
|
6
6
|
* Matrix
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Transform
|
|
8
|
+
* Test
|
|
9
9
|
* Load Injector
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* Monitor Flow
|
|
11
|
+
* append
|
|
12
|
+
* Spawn Process
|
|
13
|
+
* Host Available
|
|
14
|
+
* node.js os metrics
|
|
15
|
+
* Levenshtein Distance
|
|
16
16
|
|
|
17
17
|
------------------------------------------------------------
|
|
18
18
|
|
|
19
19
|
## Data Analysis
|
|
20
20
|
|
|
21
21
|
Real time metrics which are recalculated on single of data point and posted in msg.result.
|
|
22
|
-
Key and value can be selected from msg.payload.
|
|
22
|
+
Key and value can be selected from msg.payload. Includes lag (seasonal) along with delta(defference).
|
|
23
23
|
Sending message with topic"@stats" places message with all stats on second port.
|
|
24
24
|
If realtime metrics then a third port is shown where the message is sent if it is an outlier
|
|
25
25
|
being outside 3 standard deviations from mean. This can be changed to median and number of deviations.
|
|
@@ -27,7 +27,6 @@ being outside 3 standard deviations from mean. This can be changed to median and
|
|
|
27
27
|

|
|
28
28
|

|
|
29
29
|
|
|
30
|
-
|
|
31
30
|
A set of data analysis functions that can be run over an array of data
|
|
32
31
|
|
|
33
32
|
Single value metrics:
|
|
@@ -78,7 +77,7 @@ example:
|
|
|
78
77
|
|
|
79
78
|
Define a matrix and perform various functions
|
|
80
79
|
|
|
81
|
-
* Define / Define Empty / Create / Create Like/ clone
|
|
80
|
+
* Define / Define Empty / Create / Create Like/ clone
|
|
82
81
|
* Add / Add Row to Row / Add to Cell / Add Row / Subtract Cell
|
|
83
82
|
* Multiple / Multiple Cell / Divide Cell / Divide Row
|
|
84
83
|
* Transpose
|
|
@@ -110,48 +109,48 @@ Messages generates a message for each row or record.
|
|
|
110
109
|
|
|
111
110
|
Transformations:
|
|
112
111
|
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
112
|
+
* Array to CSV
|
|
113
|
+
* Array to HTML
|
|
114
|
+
* Array to ISO8385
|
|
115
|
+
* Array to Messages
|
|
116
|
+
* Array to xlsx / xlsx object (excel uses [xlsx][7])
|
|
117
|
+
* AVRO to JSON (uses [avsc][6])
|
|
119
118
|
* Buffer to comprossed
|
|
120
|
-
*
|
|
119
|
+
* Confluence to JSON
|
|
121
120
|
* Compressed to Buffer
|
|
122
121
|
* Compressed to String
|
|
123
122
|
* COmpressed to JSON
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
123
|
+
* CSV to Array
|
|
124
|
+
* CSV to HTML
|
|
125
|
+
* CSV to Messages
|
|
126
|
+
* CSVWithHeader to Array
|
|
127
|
+
* CSVWithHeader to HTML
|
|
128
|
+
* CSVWithHeader to JSON
|
|
129
|
+
* ISO8385 to Array
|
|
130
|
+
* ISO8385 to JSON
|
|
131
|
+
* JSON to Array
|
|
132
|
+
* JSON to Confluence
|
|
133
|
+
* JSON to CSV
|
|
134
|
+
* JSON to AVRO (uses [avsc][6])
|
|
135
|
+
* JSON to ISO8385
|
|
136
|
+
* JSON to Messages
|
|
137
|
+
* JSON to String
|
|
138
|
+
* JSON to xlsx / xlsx object (excel uses [xlsx][7])
|
|
139
|
+
* JSON to XML (uses [fast-xml-parser][4])
|
|
140
|
+
* String to JSON
|
|
141
|
+
* path to Basename
|
|
142
|
+
* path to Dirname
|
|
143
|
+
* path to Extname
|
|
144
|
+
* path to Format
|
|
145
|
+
* path to Is Absolute
|
|
146
|
+
* path to Join
|
|
147
|
+
* path to Parse
|
|
148
|
+
* path to Normalize
|
|
149
|
+
* path to Resolve
|
|
150
|
+
* snappy compress (uses [snappy][5], must install separately)
|
|
151
|
+
* snappy uncompress (uses [snappy][5], must install separately)
|
|
152
|
+
* xlsx / xlsx object to array/JSON (excel uses [xlsx][7])
|
|
153
|
+
* XML to JSON (uses [fast-xml-parser][4])
|
|
155
154
|
|
|
156
155
|
Note, snappy needs to be installed separately as can have issues with auto install as build binaries.
|
|
157
156
|
|
|
@@ -219,7 +218,6 @@ Test example:
|
|
|
219
218
|
|
|
220
219
|

|
|
221
220
|
|
|
222
|
-
|
|
223
221
|
------------------------------------------------------------
|
|
224
222
|
|
|
225
223
|
## Monitor Flow
|
|
@@ -235,34 +233,29 @@ Test example:
|
|
|
235
233
|
|
|
236
234
|
------------------------------------------------------------
|
|
237
235
|
|
|
238
|
-
##
|
|
236
|
+
## Host Available
|
|
239
237
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
Takes in messages that starts a process with ability to add environment values.
|
|
244
|
-
Message can be sent to kill the process.
|
|
238
|
+
Test if host is available sending msg to up or down port so action can be taken.
|
|
239
|
+
Message only sent on state change or if message is sent which doesn't have topic refreshHostAvailable.
|
|
240
|
+
This topic forces a check rather than time check which can be set.
|
|
245
241
|
|
|
246
|
-

|
|
247
243
|
|
|
248
244
|
Test example:
|
|
249
245
|
|
|
250
|
-

|
|
252
247
|
|
|
253
248
|
------------------------------------------------------------
|
|
254
249
|
|
|
255
|
-
##
|
|
250
|
+
## Monitor System
|
|
256
251
|
|
|
257
|
-
|
|
258
|
-
Message only sent on state change or if message is sent which doesn't have topic refreshHostAvailable.
|
|
259
|
-
This topic forces a check rather than time check which can be set.
|
|
252
|
+
System monitoring metrics
|
|
260
253
|
|
|
261
|
-

|
|
262
255
|
|
|
263
256
|
Test example:
|
|
264
257
|
|
|
265
|
-

|
|
266
259
|
|
|
267
260
|
------------------------------------------------------------
|
|
268
261
|
|
|
@@ -278,6 +271,22 @@ Test example:
|
|
|
278
271
|
|
|
279
272
|
------------------------------------------------------------
|
|
280
273
|
|
|
274
|
+
## Spawn Process
|
|
275
|
+
|
|
276
|
+
Spawn process as per node.js manual with ability to set working directory, environment variables
|
|
277
|
+
and argument passed to process. STDOUT and STDERR are sent as individual messages.
|
|
278
|
+
RC port is sent a message on closure.
|
|
279
|
+
Takes in messages that starts a process with ability to add environment values.
|
|
280
|
+
Message can be sent to kill the process.
|
|
281
|
+
|
|
282
|
+

|
|
283
|
+
|
|
284
|
+
Test example:
|
|
285
|
+
|
|
286
|
+

|
|
287
|
+
|
|
288
|
+
------------------------------------------------------------
|
|
289
|
+
|
|
281
290
|
# Install
|
|
282
291
|
|
|
283
292
|
Run the following command in the root directory of your Node-RED install
|
|
@@ -292,15 +301,16 @@ Test/example flow in test/generalTest.json
|
|
|
292
301
|
|
|
293
302
|
# Version
|
|
294
303
|
|
|
304
|
+
0.21.0 Add lag/seasonal to real time data analystics
|
|
305
|
+
|
|
306
|
+
0.20.3 Add difference + monitor system
|
|
307
|
+
|
|
295
308
|
0.19.0 Improve load injector, fix bug in test comparing buffers, add compression tranforms
|
|
296
309
|
|
|
297
310
|
0.18.0 Add matrix node
|
|
298
311
|
|
|
299
312
|
0.17.0 Add finished wire to load injector
|
|
300
313
|
|
|
301
|
-
0.16.0 fix data analysis variance and stddev, add sample, add tests
|
|
302
|
-
|
|
303
|
-
|
|
304
314
|
|
|
305
315
|
# Author
|
|
306
316
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
outputs: {value:(["realtime","realtimePredict"].includes(this.action)?3:2),required:true},
|
|
12
12
|
outliersBase: {value:"avg",required:false},
|
|
13
13
|
outliersStdDevs: {value:"3",required:false},
|
|
14
|
-
term: {value:
|
|
14
|
+
term: {value:10,required:false},
|
|
15
15
|
keyProperty: {value:"msg.topic",required:false},
|
|
16
16
|
dataProperty: {value:"msg.payload",required:false},
|
|
17
17
|
dataProperties: {value:["msg.payload[0]","msg.payload[1]"],required:false}
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
oneditprepare: function() {
|
|
30
30
|
const node=this;
|
|
31
|
+
node.lag??=1
|
|
31
32
|
$("#node-input-keyProperty").change(function() {
|
|
32
33
|
if( [null,""].includes(node.keyProperty) ) {
|
|
33
34
|
$(this).val("msg.topic");
|
|
@@ -46,7 +47,7 @@
|
|
|
46
47
|
}
|
|
47
48
|
});
|
|
48
49
|
$("#node-input-action").change(function() {
|
|
49
|
-
if(["differenceSeasonal","differenceSeasonalSecondOrder"].includes( $(this).val() )) {
|
|
50
|
+
if(["differenceSeasonal","differenceSeasonalSecondOrder","realtime","realtimePredict"].includes( $(this).val() )) {
|
|
50
51
|
$(".form-row-http-in-lag").show();
|
|
51
52
|
} else {
|
|
52
53
|
$(".form-row-http-in-lag").hide();
|
|
@@ -69,10 +70,17 @@
|
|
|
69
70
|
if(node.term>1) $(this).val(1);
|
|
70
71
|
});
|
|
71
72
|
} else {
|
|
73
|
+
$("#node-input-lag").attr({min:1,max:1000,step:1});
|
|
74
|
+
$("#node-input-lag").change(function() {
|
|
75
|
+
const v=$(this).val();
|
|
76
|
+
if(v<1) $(this).val(1);
|
|
77
|
+
if(v>1000) $(this).val(1000);
|
|
78
|
+
});
|
|
72
79
|
$("#node-input-term").attr({min:1,max:100,step:1});
|
|
73
80
|
$("#node-input-term").change(function() {
|
|
74
|
-
|
|
75
|
-
if(
|
|
81
|
+
const v=$(this).val()
|
|
82
|
+
if(v<1) $(this).val(1);
|
|
83
|
+
if(v>100) $(this).val(100);
|
|
76
84
|
});
|
|
77
85
|
}
|
|
78
86
|
if(["realtime","realtimePredict"].includes( $(this).val() )) {
|
|
@@ -94,8 +102,8 @@
|
|
|
94
102
|
$(".node-input-dataProperties-container-row").hide();
|
|
95
103
|
$(".form-row-http-in-dataProperty").show();
|
|
96
104
|
}
|
|
97
|
-
$("#node-input-term").change();
|
|
98
105
|
$("#node-input-lag").change();
|
|
106
|
+
$("#node-input-term").change();
|
|
99
107
|
});
|
|
100
108
|
|
|
101
109
|
node.dataProperties.forEach((r)=>addDataProperty(r));
|
|
@@ -248,12 +256,18 @@ Data is not persisted so metrics start from zero sample set on node recycle.
|
|
|
248
256
|
<p>
|
|
249
257
|
If real-time stats then a message can send directive instruction in topic:
|
|
250
258
|
<dl>
|
|
251
|
-
<dt>"@predict <key>"
|
|
252
|
-
<dt>"@stats"
|
|
253
|
-
<dt>"@stats reset"
|
|
254
|
-
<dt>"@stats set"
|
|
259
|
+
<dt>"@predict <key>"</dt><dd></dd>Send send predictions to second port for selected key.
|
|
260
|
+
<dt>"@stats"</dt><dd></dd>send all stored metrics and retained datapoints to second port.
|
|
261
|
+
<dt>"@stats reset"</dt><dd>Reset all stats and "@stats reset <key>" will reset a particular data point.</dd>
|
|
262
|
+
<dt>"@stats set"</dt><dd>Set stats with ith msg.payload and "@stats set <a data point>" will set a particular data point with msg.payload.</dd>
|
|
255
263
|
</dl>
|
|
256
264
|
</p>
|
|
265
|
+
<dl>
|
|
266
|
+
<dt>Lag</dt><dd>If greater that 1 generates seasonal difference with degree lag</dd>
|
|
267
|
+
<dt>Term</dt><dd>The depth of moving Average</dd>
|
|
268
|
+
</dl>
|
|
269
|
+
<p>
|
|
270
|
+
</p>
|
|
257
271
|
<p>
|
|
258
272
|
Outliers are not within:
|
|
259
273
|
<ul>
|
|
@@ -69,10 +69,10 @@ EMA.prototype.sample=function(value) {
|
|
|
69
69
|
return this;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
function setDataPoint(value,term,node,
|
|
73
|
-
if(logger.active) logger.send({label:"setDataPoint",value:value,term,
|
|
74
|
-
if(!
|
|
75
|
-
Object.assign(
|
|
72
|
+
function setDataPoint(value,term,node,dataPoint) {
|
|
73
|
+
if(logger.active) logger.send({label:"setDataPoint",value:value,term,dataPoint});
|
|
74
|
+
if(!dataPoint.values) {
|
|
75
|
+
Object.assign(dataPoint,{
|
|
76
76
|
values:[],
|
|
77
77
|
avg:0,
|
|
78
78
|
count:0,
|
|
@@ -83,43 +83,44 @@ function setDataPoint(value,term,node,dp) {
|
|
|
83
83
|
sum:0,
|
|
84
84
|
sumSquared:0,
|
|
85
85
|
sumCubed:0,
|
|
86
|
-
term:term,
|
|
86
|
+
term:term??node.term,
|
|
87
87
|
weightedMovingSum:0,
|
|
88
88
|
exponentialWeightedMoving:[new EMA(0.25),new EMA(0.5),new EMA(0.75)]
|
|
89
89
|
});
|
|
90
|
-
}
|
|
91
|
-
;
|
|
92
|
-
const count=++dp.count,values=dp.values;
|
|
90
|
+
};
|
|
91
|
+
const count=++dataPoint.count,values=dataPoint.values;
|
|
93
92
|
values.push(value);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
93
|
+
const movingTerm=Math.min(values.length,dataPoint.term)
|
|
94
|
+
dataPoint.isMaxSize=(values.length>dataPoint.maxSize);
|
|
95
|
+
dataPoint.removedMovingValue=(dataPoint.isMaxSize?values[values.length-dataPoint.term]:0);
|
|
96
|
+
dataPoint.removedValue=(dataPoint.isMaxSize?values.shift():0);
|
|
97
|
+
const removedMovingValue=dataPoint.removedMovingValue;
|
|
98
|
+
dataPoint.max=Math.max(dataPoint.max||value,value);
|
|
99
|
+
dataPoint.min=Math.min(dataPoint.min||value,value);
|
|
100
|
+
dataPoint.range=dataPoint.max-dataPoint.min;
|
|
101
|
+
dataPoint.sum+=value;
|
|
102
|
+
dataPoint.sumSquared+=Math.pow(value,2);
|
|
103
|
+
dataPoint.sumCubed+=Math.pow(value,3);
|
|
104
|
+
dataPoint.movingSum+=value-removedMovingValue;
|
|
105
|
+
dataPoint.movingSumSquared+=Math.pow(value,2)-Math.pow(removedMovingValue,2);
|
|
106
|
+
dataPoint.movingSumCubed+=Math.pow(value,3)-Math.pow(removedMovingValue,3);
|
|
107
|
+
dataPoint.avg=dataPoint.sum/count;
|
|
108
|
+
const avg=dataPoint.avg;
|
|
109
|
+
dataPoint.normalised=dataPoint.range ? (value-avg)/dataPoint.range : 0;
|
|
110
|
+
dataPoint.movingAvg=dataPoint.movingSum/movingTerm;
|
|
111
|
+
dataPoint.variance=dataPoint.sumSquared/count - Math.pow(avg,2);
|
|
112
|
+
dataPoint.stdDev=Math.sqrt(dataPoint.variance);
|
|
113
|
+
dataPoint.movingVariance=dataPoint.movingSumSquared/movingTerm - Math.pow(dataPoint.movingAvg,2);
|
|
114
|
+
dataPoint.movingStdDev=Math.sqrt(dataPoint.movingVariance);
|
|
115
|
+
dataPoint.median=functions.median(values);
|
|
116
|
+
dataPoint.standardized=( (value-avg)/dataPoint.stdDev )||0;
|
|
117
|
+
dataPoint.movingStandardized=( (value-dataPoint.movingAvg)/dataPoint.movingStdDev )||0;
|
|
118
|
+
dataPoint.skewness=(dataPoint.sumCubed-3*avg*dataPoint.variance-Math.pow(avg,3))/dataPoint.variance*dataPoint.stdDev;
|
|
119
|
+
dataPoint.movingSkewness=(dataPoint.movingSumCubed-3*dataPoint.movingAvg*dataPoint.movingVariance-Math.pow(dataPoint.movingAvg,3))/dataPoint.movingVariance*dataPoint.stdDev;
|
|
120
|
+
dataPoint.outlier=node.outliersFunction(node,dataPoint,value);
|
|
121
|
+
dataPoint.weightedMovingSum+=count*value;
|
|
122
|
+
dataPoint.weightedMovingAvg=(dataPoint.weightedMovingAvg*2/count)/(count+1);
|
|
123
|
+
dataPoint.exponentialWeightedMoving.forEach(c=>c.sample(value));
|
|
123
124
|
}
|
|
124
125
|
function getColumns(node) {
|
|
125
126
|
if(node.columns) {
|
|
@@ -223,9 +224,9 @@ functions={
|
|
|
223
224
|
}
|
|
224
225
|
}
|
|
225
226
|
node.samples++;
|
|
226
|
-
for(let
|
|
227
|
-
v=d[i];
|
|
228
|
-
dp=node.dataPoints[i];
|
|
227
|
+
for(let i=0; i<node.dataProperties.length; i++) {
|
|
228
|
+
const v=d[i];
|
|
229
|
+
const dp=node.dataPoints[i];
|
|
229
230
|
dp.sum+=v;
|
|
230
231
|
dp.sumSquared+=v*v;
|
|
231
232
|
for(let j=i+1; j<node.dataProperties.length; j++) {
|
|
@@ -280,10 +281,23 @@ functions={
|
|
|
280
281
|
}
|
|
281
282
|
setDataPoint(d.value,term,node,dp);
|
|
282
283
|
if(dp.delta) {
|
|
283
|
-
|
|
284
|
+
if(dp.values.length>1)
|
|
285
|
+
setDataPoint(d.value-dp.values[dp.values.length-2],term,node,dp.delta);
|
|
284
286
|
} else {
|
|
285
287
|
dp.delta={};
|
|
286
288
|
}
|
|
289
|
+
if(node.lag>1) {
|
|
290
|
+
const vectorSize=dp.values.length
|
|
291
|
+
if(dp.lag) {
|
|
292
|
+
if(node.lag<=vectorSize){
|
|
293
|
+
setDataPoint(d.value-dp.values[vectorSize-node.lag],term,node,dp.lag)
|
|
294
|
+
const values=dp.lag.values
|
|
295
|
+
if(values.length>1) setDataPoint(values[values.length-1]-values[values.length-2],term,node,dp.lag.delta)
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
dp.lag={delta:{}}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
287
301
|
return dp;
|
|
288
302
|
},
|
|
289
303
|
sampleVariance:(d)=>{
|
|
@@ -310,11 +324,15 @@ module.exports = function (RED) {
|
|
|
310
324
|
function dataAnalysisNode(n) {
|
|
311
325
|
RED.nodes.createNode(this, n);
|
|
312
326
|
const node=Object.assign(this,
|
|
313
|
-
{outliersStdDevs:3,crossNormalisedDeltas:crossNormalisedDeltas.bind(this)},
|
|
327
|
+
{outliersStdDevs:3,crossNormalisedDeltas:crossNormalisedDeltas.bind(this),lag:1,term:3},
|
|
314
328
|
n,
|
|
315
329
|
{maxErrorDisplay:10,dataPoint:{}}
|
|
316
330
|
);
|
|
317
331
|
try{
|
|
332
|
+
node.lag=Number(node.lag)
|
|
333
|
+
if(Number(node.term)==NaN)throw Error("term not a number, value:"+JSON.stringify(node.term))
|
|
334
|
+
node.term=Number(node.term)
|
|
335
|
+
node.maxSize=Math.max(node.term,node.lag)
|
|
318
336
|
if(functions.hasOwnProperty(node.action)) {
|
|
319
337
|
node.actionfunction=functions[node.action];
|
|
320
338
|
} else {
|
|
@@ -349,6 +367,7 @@ module.exports = function (RED) {
|
|
|
349
367
|
node.getData=eval(node.getDatafunction);
|
|
350
368
|
node.status({fill:"green",shape:"ring",text:"Ready"});
|
|
351
369
|
} catch(ex) {
|
|
370
|
+
if(logger.active) logger.send({label:"initialise error",action:node.action,message:ex.message,stack:ex.stack});
|
|
352
371
|
logger.send({label:"initialise error",node:n});
|
|
353
372
|
node.error(ex);
|
|
354
373
|
node.status({fill:"red",shape:"ring",text:"Invalid setup "+ex.message});
|
|
@@ -388,6 +407,7 @@ module.exports = function (RED) {
|
|
|
388
407
|
throw Error("unknown");
|
|
389
408
|
}
|
|
390
409
|
} catch(ex) {
|
|
410
|
+
if(logger.active) logger.send({label:"error input",action:node.action,message:ex.message,stack:ex.stack});
|
|
391
411
|
node.error(msg.topic+" failed "+ex.message);
|
|
392
412
|
}
|
|
393
413
|
return;
|
|
@@ -404,10 +424,10 @@ module.exports = function (RED) {
|
|
|
404
424
|
break;
|
|
405
425
|
}
|
|
406
426
|
} catch(ex) {
|
|
407
|
-
if(logger.active) logger.send({label:"error input",action:node.action,message:ex.message,stack:ex.stack});
|
|
408
427
|
msg.error=ex.message;
|
|
409
428
|
if(node.maxErrorDisplay) {
|
|
410
429
|
--node.maxErrorDisplay;
|
|
430
|
+
logger.send({label:"error input",action:node.action,message:ex.message,stack:ex.stack});
|
|
411
431
|
if(node.action=="realtime") {
|
|
412
432
|
node.error(node.action+" error: "+ex.message);
|
|
413
433
|
} else {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/matrix/matrix.js
CHANGED
|
@@ -1,8 +1,54 @@
|
|
|
1
1
|
const logger = new (require("node-red-contrib-logger"))("Matrix");
|
|
2
2
|
logger.sendInfo("Copyright 2022 Jaroslav Peter Prib");
|
|
3
3
|
|
|
4
|
+
const typedArrays= {Array:Array,Int8Array:Int8Array,Uint8Array:Uint8Array,Uint8ClampedArray:Uint8ClampedArray,Int16Array:Int16Array,Uint16Array:Uint16Array,
|
|
5
|
+
Int32Array:Int32Array,Uint32Array:Uint32Array,Float32Array:Float32Array,
|
|
6
|
+
Float64Array:Float64Array,BigInt64Array:BigInt64Array,BigUint64Array:BigUint64Array}
|
|
7
|
+
|
|
8
|
+
Object.keys(typedArrays).map(t=>typedArrays[t]).forEach(object=>{
|
|
9
|
+
if(object.prototype.setAll==null)
|
|
10
|
+
Object.defineProperty(object.prototype, "setAll", {
|
|
11
|
+
value(call,size=this.length) {
|
|
12
|
+
let i=size
|
|
13
|
+
while(i) this[--i]=call();
|
|
14
|
+
return this;
|
|
15
|
+
},
|
|
16
|
+
writable: true,
|
|
17
|
+
configurable: true
|
|
18
|
+
})
|
|
19
|
+
if(object.prototype.setOne==null)
|
|
20
|
+
Object.defineProperty(object.prototype, "setOne", {
|
|
21
|
+
value(call,size=this.length) {
|
|
22
|
+
let i=size
|
|
23
|
+
while(i) this[--i]=1;
|
|
24
|
+
return this;
|
|
25
|
+
},
|
|
26
|
+
writable: true,
|
|
27
|
+
configurable: true
|
|
28
|
+
})
|
|
29
|
+
if(object.prototype.setZero==null)
|
|
30
|
+
Object.defineProperty(object.prototype, "setZero", {
|
|
31
|
+
value(call,size=this.length) {
|
|
32
|
+
let i=size
|
|
33
|
+
while(i) this[--i]=0;
|
|
34
|
+
return this;
|
|
35
|
+
},
|
|
36
|
+
writable: true,
|
|
37
|
+
configurable: true
|
|
38
|
+
})
|
|
39
|
+
if(object.prototype.setRandom==null)
|
|
40
|
+
Object.defineProperty(object.prototype, "setRandom", {
|
|
41
|
+
value(size=this.length) {
|
|
42
|
+
return this.setAll(Math.random,size)
|
|
43
|
+
},
|
|
44
|
+
writable: true,
|
|
45
|
+
configurable: true
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
4
49
|
const zeroFloat32Value=1e-6;
|
|
5
|
-
function Matrix(rows,columns,fill) {
|
|
50
|
+
function Matrix(rows,columns,fill,dataType="Float32Array") {
|
|
51
|
+
this.dataType=dataType
|
|
6
52
|
if(rows instanceof Array) {
|
|
7
53
|
this.rows=rows.length;
|
|
8
54
|
if(this.rows==0) throw Error("expected rows")
|
|
@@ -15,11 +61,19 @@ function Matrix(rows,columns,fill) {
|
|
|
15
61
|
}
|
|
16
62
|
if(rows instanceof Object) {
|
|
17
63
|
Object.assign(this,rows);
|
|
64
|
+
this.dataType??="Float32Array"
|
|
18
65
|
} else {
|
|
19
66
|
this.rows=rows;
|
|
20
67
|
this.columns=columns;
|
|
21
68
|
}
|
|
69
|
+
if(this.columns) this.columns=parseInt(this.columns);
|
|
70
|
+
if(this.rows)this.rows=parseInt(this.rows)
|
|
71
|
+
if(this.rowsMax) this.rowsMax=parseInt(this.rowsMax)
|
|
22
72
|
this.createVector();
|
|
73
|
+
if(fill) {
|
|
74
|
+
if(fill instanceof Function) this.setAll(fill)
|
|
75
|
+
else this.vector.set(fill)
|
|
76
|
+
}
|
|
23
77
|
return this;
|
|
24
78
|
}
|
|
25
79
|
Matrix.prototype.add=function(matrix){
|
|
@@ -52,7 +106,7 @@ Matrix.prototype.addRow2Row=function(rowA,rowB,factor=1,startColumn=0,endColumn=
|
|
|
52
106
|
return this;
|
|
53
107
|
}
|
|
54
108
|
Matrix.prototype.backwardSubstitution=function(){
|
|
55
|
-
const vector=new
|
|
109
|
+
const vector=new typedArrays[this.dataType](this.rows);
|
|
56
110
|
for(let row=this.rows-1; row>=0; row--) {
|
|
57
111
|
vector[row] = this.get(row,this.rows);
|
|
58
112
|
for(let column=row+1; column<this.rows; column++) {
|
|
@@ -101,15 +155,17 @@ Matrix.prototype.createVector=function(){
|
|
|
101
155
|
this.sizeMax=this.rowsMax*this.columns;
|
|
102
156
|
}
|
|
103
157
|
if(this.columns==null) throw Error("columns not specified")
|
|
104
|
-
this.size=this.rows*this.columns
|
|
105
|
-
if(this.sizeMax==null) this.sizeMax=this.size
|
|
106
158
|
} else {
|
|
107
159
|
if(this.columns==null) throw Error("columns not specified")
|
|
160
|
+
if(this.columns==0) throw Error("columns = 0")
|
|
108
161
|
if(this.rows==null){
|
|
109
162
|
this.rows=0;
|
|
110
163
|
}
|
|
111
164
|
}
|
|
112
|
-
this.
|
|
165
|
+
this.size=this.rows*this.columns
|
|
166
|
+
if(this.sizeMax==null) this.sizeMax=this.size
|
|
167
|
+
if(this.sizeMax==null) throw Error("max size not specified or calculated")
|
|
168
|
+
this.vector=new typedArrays[this.dataType](this.sizeMax);
|
|
113
169
|
return this;
|
|
114
170
|
}
|
|
115
171
|
Matrix.prototype.divideCell=function(row,column,value){
|
|
@@ -472,7 +528,6 @@ Matrix.prototype.multiplyRow=function(row,factor){
|
|
|
472
528
|
return this;
|
|
473
529
|
}
|
|
474
530
|
Matrix.prototype.norm=function(){
|
|
475
|
-
|
|
476
531
|
return Math.sqrt(this.reduce((aggregate,cell)=>aggregate+cell*cell))
|
|
477
532
|
}
|
|
478
533
|
Matrix.prototype.reduce=function(call,aggregate=0){
|
|
@@ -560,17 +615,48 @@ Matrix.prototype.set=function(row,column,value){
|
|
|
560
615
|
this.vector[this.getIndex(row,column)]=value;
|
|
561
616
|
return this;
|
|
562
617
|
}
|
|
618
|
+
Matrix.prototype.setAll=function(call){
|
|
619
|
+
for(let offset=0,row=0;row<this.rows;row++) {
|
|
620
|
+
for(let column=0;column<this.columns;column++) {
|
|
621
|
+
this.vector[offset]=call.apply(this,[row,column,this.vector[offset],this.vector,offset,this]);
|
|
622
|
+
offset++
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
this.rows=this.rowsMax
|
|
626
|
+
this.size=this.sizeMax
|
|
627
|
+
return this;
|
|
628
|
+
}
|
|
563
629
|
Matrix.prototype.setDeterminant=function(){
|
|
564
630
|
this.determinant=this.getDeterminantUsingRowEchelonForm();
|
|
565
631
|
return this.determinant;
|
|
566
632
|
}
|
|
633
|
+
Matrix.prototype.setIdentity=function(){
|
|
634
|
+
if(this.columns!=this.rowsMax) throw Error("number of columns not equal rows")
|
|
635
|
+
this.setZero()
|
|
636
|
+
for(let offset=0;offset<this.size;offset+=this.columns+1) this.vector[offset]=1;
|
|
637
|
+
return this;
|
|
638
|
+
}
|
|
639
|
+
Matrix.prototype.setWithFunction=function(f){
|
|
640
|
+
this.vector[f](this.sizeMax)
|
|
641
|
+
this.rows=this.rowsMax
|
|
642
|
+
this.size=this.sizeMax;
|
|
643
|
+
return this;
|
|
644
|
+
}
|
|
645
|
+
Matrix.prototype.setOne=function(){
|
|
646
|
+
return this.setWithFunction("setOne")
|
|
647
|
+
}
|
|
648
|
+
Matrix.prototype.setRandom=function(){
|
|
649
|
+
return this.setWithFunction("setRandom")
|
|
650
|
+
}
|
|
567
651
|
Matrix.prototype.setRow=function(vector,row){
|
|
568
652
|
this.vector.set(vector, row*this.columns);
|
|
569
653
|
return this;
|
|
570
654
|
}
|
|
571
655
|
Matrix.prototype.setRunningSum=function(){
|
|
572
656
|
this.forEachCellLowerTriangle((cell,row,column,vector,offset,object)=>vector[offset]=1);
|
|
573
|
-
|
|
657
|
+
}
|
|
658
|
+
Matrix.prototype.setZero=function(){
|
|
659
|
+
return this.setWithFunction("setZero")
|
|
574
660
|
}
|
|
575
661
|
Matrix.prototype.substract=function(matrix){
|
|
576
662
|
this.forEachCellPairSet(matrix,(cellA,cellB)=>cellA-cellB)
|
|
@@ -610,5 +696,4 @@ Matrix.prototype.transpose=function(){
|
|
|
610
696
|
this.forEachCell((cell,row,column)=>matrix.set(column,row,cell))
|
|
611
697
|
return matrix;
|
|
612
698
|
}
|
|
613
|
-
|
|
614
699
|
module.exports=Matrix;
|