-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
436 lines (320 loc) · 15.2 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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
123
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
const EventEmitter = require('events');
/*
run-petri
This index is the entire code of the run-petri model.
Three classes are defined and two of them are exposed to the node.js requirment system.
the three classes are
1) pNode - the Petri net node. These are usually seen as circles in the diagrams - the operate as resource holders
2) pTransition - the Petri net transition - These are the active parts of the net, usually seen as vertical lines in the diagrams.
3) RunPetri - the is the manager of the net. This takes in the net definition object, and operates when the 'step' method is called.
The classes that are exposed are 1) pNode and 2) RunPetri.
The pNode class is exposed in order to allow for derived classes for applications needing more than counts.
The RunPetri class is not expressly intended for override, although it is possible. RunPetri works with a
general notion of a pNode class or descendenat as do the transition objects.
So, customization may be best done just working with the pNode class.
The RunPetri processing expects that there will be three identifiable kinds of pNodes on any level of specialization.
The node types, indicated by a type field, are 1) input nodes, 2) internal nodes, and 3) output nodes.
Applications specifying instances of these kinds of nodes provide a means for transitions to identify them as upstream and downstreams
nodes that the transitions can move resources from and to. Input nodes, have no upstream transitions, and outpus are terminals to the
directed acyclic flow.
Applications can provide callback functions to the output nodes in order to handle emitting values, data, or simple event triggers.
The pNodes do not have to be overriden in order to determine an output operations. A callback should be provided.
In the case where tokens moving through the net have scalar values or have structure, the pNode can be overridden to keep track of the
values. A recommendation: move references in small objects that are treated as resources.
*/
function clonify(obj) {
if ( typeof obj === "object" ) {
var cv = JSON.parse(JSON.toString(obj))
return(cv);
} else {
return(obj)
}
}
// pNode - Basic pNode behavior.
//
// The pNode is mostly an accessed object.
// A pNode virtually contains a token when it has a resource or resources.
//
// In the basic case, a pNode has a resource if it has a count of tokens.
//
// The following methods would be overridden to specialize pNode behavior.
//
// reportObject
// count
// addResource
// consume
//
// The pNode method forward, is call when a transition moves resources from the pNode on to another.
// The transition calls 'consume' on one pNode and then calls forward for downstream pNodes with a value constructed from
// the results of reducing the consumed resources. (This is a very localised version of map-reduce.
//
// If a pNode has a contraint checking method (determined by descendant classes) the contraint check will have to be passed
// before 'forward' can operate. After this check, the type of the pNode will be important.
// If the pNode is an output and there is an exit node callback, the callback will be called.
// Otherwise, the resource value is added be a call to addResource.
//
// ------ ------ ------ ------
class pNode {
// nodeType - source, exit, internal
//
constructor(id,nodeType,target) {
this.id = id;
this.type = nodeType;
if ( this.type === undefined ) {
this.type = "internal"
}
this.contraints = undefined; // descendents may
this.exitCallBack = undefined;
this.inhibits = false;
if ( nodeType == "inhibit" ) {
this.inhibits = target;
}
this.resource = 0; // default is a count
}
identity() {
return(this.id)
}
hasResource(label) {
var marked = (this.count() > 0);
if ( this.inhibits ) {
if ( this.inhibits === label ) return(!marked)
}
return(marked);
}
// forward -- move a value to final output or store the value for a transition during stepping.
// A transition will call this, allowing transition outputs to fan out.
forward(value) {
var v = clonify(value); // if the value is a sum of inputs, this will be overkill (useful when object are in transit)
if ( this.contraints !== undefined ) { // The values is coming from a transition (after reduction)
// This exposes contraints on forwarding to the JSON definition. By restricting override to the node,
// this allows for transitions values to be filtered as if they were on the transition.
// (check other version for customizing this behavior on the transition)
if ( !(this.contraints(v)) ) return(false) //
}
if ( this.type === "exit" ) { // An exit node will work on emiting values to networks or hardware.
if ( this.exitCallBack ) {
this.exitCallBack(v);
}
} else {
this.addResource(v); // If not sending the value away, then store it on the node
}
return(true);
}
// Nodes of type "exit" - these are terminals of the DAG.
// see above forward(value)
// the cb method is a consumer of "value". cb does not return a result
setExitCB(cb) {
this.exitCallBack = cb;
}
// overrides start here....
reportObject() {
return(this.resource)
}
count() {
return(this.resource)
}
// addResource
// -- Descendants may override this method in order to utilize
// -- their own storage module for value.
// -- this is then called by the private _add_resource methods which emits values to transitions.
addResource(value) {
this.resource += parseInt(value); // default is to add how many
}
consume() {
this.resource -= 1;
return(1);
}
clear() {
this.resource = 0;
}
}
module.exports.pNode = pNode;
// TRANSITIONS
// pTransition -
//
//
class pTransition {
constructor(label) {
//
this.preNodes = [];
this.postNodes = [];
this.nodeLookup = {};
this.resourceGroup = [];
//
this.label = label;
this.reducer = (accumulator, currentValue) => accumulator + currentValue; // default
this.initAccumulator = 0; /// default
this.forwardValue = 0; // default;
}
addPostNode(pnode) {
//
if ( this.nodeLookup[pnode.identity()] == undefined ) {
this.nodeLookup[pnode.identity()] = pnode;
//
this.postNodes.push(pnode);
} else {
throw new Exception("Adding node to post transition twice.")
}
}
addPreNode(pnode) {
//
if ( this.nodeLookup[pnode.identity()] == undefined ) {
this.nodeLookup[pnode.identity()] = pnode;
//
this.preNodes.push(pnode);
} else {
throw new Exception("Adding node to pre transition twice.")
}
}
// The transition will be invoked only if it is enabled.
// it is enabled if all prenodes have resource and do not
// inhibit this node.
all_preNodes_active() {
var all_ready = this.preNodes.every(pnode => {
return(pnode.hasResource(this.label)); // the label identifies this transition
})
return(all_ready);
}
// consume_preNode_resources
// step 1: Go through all nodes sending values to this transition
// Form an array of outputs determined by the each node's 'consume' method
// step 2: Reduce the array using either default initialization and reduction,
// or use the custom initializer and reducer set by 'setSpecialReduction'
// which is specified in the network's JSON input
consume_preNode_resources() {
this.resourceGroup = this.preNodes.map(pnode => { return(pnode.consume()); } );
this.forwardValue = this.resourceGroup.reduce(this.reducer,this.initAccumulator);
}
// output_resource_to_postNodes
// For each node that takes input from this transition,
// take the value to be forwarded, forwardValue, which was computed in 'consume_preNode_resources'
// and assign the value to the given node by calling the node's 'forward' method.
output_resource_to_postNodes() {
this.postNodes.forEach(pnode => {
pnode.forward(this.forwardValue);
});
}
setSpecialReduction(reducer,initAccumulator) {
this.reducer = reducer; // default
this.initAccumulator = initAccumulator; /// default
}
}
// RunPetri
//
// This is the operation container for a single Petr net instance.
module.exports.RunPetri = class RunPetri extends EventEmitter {
constructor() {
super();
this.nodes = {};
this.transitions = [];
this.sourceNodes = {};
this.exitNodes = {};
}
setNetworkFromJson(net_def,cbGen,nodeClasses) {
var nodes = net_def.nodes.map(nodeDef => {
var id = nodeDef.id;
var type = nodeDef.type;
if ( type === "source" ) {
this.on(id,this.reactor(id));
}
var target = undefined;
if ( nodeDef.transition ) {
target = nodeDef.transition;
}
if ( nodeDef.class && nodeClasses ) {
var nodeClass = nodeClasses[nodeDef.class];
return(new nodeClass(id,type,target));
} else {
return(new pNode(id,type,target));
}
});
this.loadGraph(nodes,net_def.transitions,cbGen);
}
loadGraph(nodes,transitions,cbGen) {
if ( nodes === undefined ) { throw new Exception("no nodes specified"); }
if ( transitions === undefined ) { throw new Exception("no transitions specified"); }
if ( cbGen === undefined ) { throw new Exception("no node callback generator specified"); }
this.nodes = {};
this.sourceNodes = {};
this.exitNodes = {};
this.claimed = {};
nodes.forEach(pnode => {
this.nodes[pnode.identity()] = pnode; // add it in
//source, exit, internal
if ( pnode.type === "source" ) {
this.sourceNodes[pnode.identity()] = pnode;
}
if ( pnode.type === "exit" ) {
this.exitNodes[pnode.identity()] = pnode;
if ( pnode.exitCallBack === undefined ) {
this.setExitCB(pnode.identity(),cbGen(pnode.identity(),'exit'))
}
}
});
this.transitions = transitions.map(transDef => {
var trans = new pTransition(transDef.label);
transDef.inputs.forEach(input => {
var nn = this.nodes[input];
if ( this.claimed[input] && (nn.inhibits != transDef.label) ) {
throw new Error(`${input} used more than once in transitions''`)
} else if (nn.inhibits != transDef.label) {
this.claimed[input] = true;
}
trans.addPreNode(nn);
})
transDef.outputs.forEach(output => {
var nn = this.nodes[output];
trans.addPostNode(nn);
})
if ( transDef.reduction ) {
if ( transDef.reduction.reducer && transDef.reduction.initAccumulator ) {
var reduct = cbGen(transDef.reduction.reducer,'reduce')
trans.setSpecialReduction(reduct,transDef.reduction.initAccumulator);
}
}
return(trans);
})
}
setExitCB(nodeName,cb) {
//
if ( typeof cb != "function" ) {
throw(new Error(nodeName + " exit value call back is not a function."))
}
//
if ( this.exitNodes[nodeName] === undefined ) {
throw(new Error(nodeName + " Exit value call back cannot be set for non exiting node."))
}
//
this.exitNodes[nodeName].setExitCB(cb);
}
/*
pnet.on("level-sensor1", this.reactor("level-sensor1") );
pnet.emit( "level-sensor1", "0.5" );
*/
reactor(sourceName) {
return((value) => {
if ( this.sourceNodes[sourceName] ) {
var pnode = this.sourceNodes[sourceName];
pnode.forward(value);
}
})
}
// step
// -- Look at all the transitions in this Petri-net and fire them
// -- if all their precondtions are set
step() {
this.transitions.forEach(trans => {
if ( trans.all_preNodes_active() ) { // all_preNodes_active -- all their precondtions are set
trans.consume_preNode_resources(); // Get the value from the input nodes
trans.output_resource_to_postNodes(); // transition computed values to output nodes.
}
})
}
report() {
var reportObj = {};
for ( var nid in this.nodes ) {
reportObj[nid] = this.nodes[nid].reportObject();
}
return(reportObj);
}
}