linny-r 1.5.8 → 1.6.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/package.json +1 -1
- package/static/index.html +7 -3
- package/static/linny-r.css +19 -0
- package/static/scripts/linny-r-gui-actor-manager.js +3 -3
- package/static/scripts/linny-r-gui-chart-manager.js +10 -9
- package/static/scripts/linny-r-gui-controller.js +2 -1
- package/static/scripts/linny-r-gui-dataset-manager.js +17 -49
- package/static/scripts/linny-r-gui-documentation-manager.js +40 -20
- package/static/scripts/linny-r-gui-equation-manager.js +17 -4
- package/static/scripts/linny-r-gui-experiment-manager.js +41 -23
- package/static/scripts/linny-r-gui-expression-editor.js +26 -21
- package/static/scripts/linny-r-gui-monitor.js +4 -4
- package/static/scripts/linny-r-model.js +248 -77
- package/static/scripts/linny-r-utils.js +50 -11
- package/static/scripts/linny-r-vm.js +582 -290
@@ -33,7 +33,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
33
33
|
SOFTWARE.
|
34
34
|
*/
|
35
35
|
|
36
|
-
// CLASS Expression
|
36
|
+
// CLASS Expression
|
37
37
|
class Expression {
|
38
38
|
constructor(obj, attr, text) {
|
39
39
|
// Expressions are typically defined for some attribute of some
|
@@ -42,12 +42,22 @@ class Expression {
|
|
42
42
|
this.object = obj;
|
43
43
|
this.attribute = attr;
|
44
44
|
this.text = text;
|
45
|
+
// For method expressions only: the object to which they apply.
|
46
|
+
// This will be set dynamically by the VMI_push_method instruction.
|
47
|
+
this.method_object = null;
|
48
|
+
// Likewise, VMI_push_method may set the method object prefix if
|
49
|
+
// the specific entity needs to be inferred dynamically.
|
50
|
+
this.method_object_prefix = '';
|
45
51
|
// A stack for local time step (to allow lazy evaluation).
|
46
52
|
this.step = [];
|
47
53
|
// An operand stack for computation (elements must be numeric).
|
48
54
|
this.stack = [];
|
49
55
|
// NOTE: code = NULL indicates: not compiled yet.
|
50
56
|
this.code = null;
|
57
|
+
// Error message when last compiled.
|
58
|
+
this.compile_issue = '';
|
59
|
+
// Error message when last computed.
|
60
|
+
this.compute_issue = '';
|
51
61
|
// NOTE: Use a semaphore to prevent cyclic recursion.
|
52
62
|
this.compiling = false;
|
53
63
|
// While compiling, check whether any operand depends on time.
|
@@ -69,28 +79,67 @@ class Expression {
|
|
69
79
|
// by VMI_push_dataset_modifier (read and/or write) and by
|
70
80
|
// VMI_push_contextual_number (read only).
|
71
81
|
this.wildcard_vector_index = false;
|
82
|
+
// Method expressions are similar to wildcard expressions, but have
|
83
|
+
// not natural numbering scheme. By keeping a list of all objects
|
84
|
+
// to which a method has been applied, the index of such an object
|
85
|
+
// in this list serves as the vector number.
|
86
|
+
this.method_object_list = [];
|
72
87
|
// Special instructions can store results as cache properties to save
|
73
88
|
// (re)computation time; cache is cleared when expression is reset.
|
74
89
|
this.cache = {};
|
75
90
|
}
|
76
91
|
|
77
92
|
get isWildcardExpression() {
|
78
|
-
//
|
93
|
+
// Return TRUE if the owner is a dataset, and the attribute contains
|
79
94
|
// wildcards.
|
80
95
|
return this.object instanceof Dataset &&
|
81
96
|
this.object.isWildcardSelector(this.attribute);
|
82
97
|
}
|
83
98
|
|
99
|
+
get isMethod() {
|
100
|
+
// Return TRUE if the owner is the equations dataset, and the
|
101
|
+
// attribute starts with a colon.
|
102
|
+
return this.object === MODEL.equations_dataset &&
|
103
|
+
this.attribute.startsWith(':');
|
104
|
+
}
|
105
|
+
|
106
|
+
get noMethodObject() {
|
107
|
+
// Return TRUE if expression is a method that does not apply to
|
108
|
+
// any entity group.
|
109
|
+
return this.isMethod && !(this.eligible_prefixes &&
|
110
|
+
Object.keys(this.eligible_prefixes).length > 0);
|
111
|
+
}
|
112
|
+
|
113
|
+
matchWithEligiblePrefixes(pref) {
|
114
|
+
// Return the entity for which `pref` matches with an eligible prefix
|
115
|
+
// of this expression.
|
116
|
+
// NOTE: This expression must have been compiled to "know" its
|
117
|
+
// eligible prefixes.
|
118
|
+
this.compile();
|
119
|
+
// NOTE: Prevent infinite recursion, but do not generate a warning;
|
120
|
+
// the expression parser will do this.
|
121
|
+
if(this.compiling || !this.eligible_prefixes) return false;
|
122
|
+
return this.eligible_prefixes[pref.toLowerCase()] || false;
|
123
|
+
}
|
124
|
+
|
125
|
+
isEligible(prefix) {
|
126
|
+
// Return TRUE if `prefix` is an eligible prefix for this method.
|
127
|
+
if(this.eligible_prefixes) {
|
128
|
+
return this.eligible_prefixes[prefix.toLowerCase()] || false;
|
129
|
+
}
|
130
|
+
return false;
|
131
|
+
}
|
132
|
+
|
84
133
|
get variableName() {
|
85
|
-
// Return the name of the variable computed by this expression
|
134
|
+
// Return the name of the variable computed by this expression.
|
86
135
|
if(this.object === MODEL.equations_dataset) return 'equation ' + this.attribute;
|
87
136
|
if(this.object) return this.object.displayName + UI.OA_SEPARATOR + this.attribute;
|
88
137
|
return 'Unknown variable (no object)';
|
89
138
|
}
|
90
139
|
|
91
140
|
get timeStepDuration() {
|
92
|
-
//
|
93
|
-
// otherwise dt for the current model
|
141
|
+
// Return dt for dataset if this is a dataset modifier expression;
|
142
|
+
// otherwise dt for the current model.
|
94
143
|
if(this.object instanceof Dataset) {
|
95
144
|
return this.object.time_scale * VM.time_unit_values[this.object.time_unit];
|
96
145
|
}
|
@@ -98,47 +147,39 @@ class Expression {
|
|
98
147
|
}
|
99
148
|
|
100
149
|
get referencedEntities() {
|
101
|
-
//
|
102
|
-
|
103
|
-
const
|
104
|
-
el = [],
|
105
|
-
ml = [...this.text.matchAll(/\[(\{[^\}]+\}){0,1}([^\]]+)\]/g)];
|
106
|
-
for(let i = 0; i < ml.length; i++) {
|
107
|
-
const n = ml[i][2].trim();
|
108
|
-
let sep = n.lastIndexOf('|');
|
109
|
-
if(sep < 0) sep = n.lastIndexOf('@');
|
110
|
-
const
|
111
|
-
en = (sep < 0 ? n : n.substring(0, sep)),
|
112
|
-
e = MODEL.objectByName(en.trim());
|
113
|
-
if(e) addDistinct(e, el);
|
114
|
-
}
|
115
|
-
return el;
|
150
|
+
// Return a list of entities referenced in this expression.
|
151
|
+
return MODEL.entitiesInString(this.text);
|
116
152
|
}
|
117
|
-
|
153
|
+
|
118
154
|
update(parser) {
|
119
|
-
// Must be called after successful compilation by the expression parser
|
155
|
+
// Must be called after successful compilation by the expression parser.
|
120
156
|
this.text = parser.expr;
|
121
157
|
this.code = parser.code;
|
122
|
-
|
123
|
-
//
|
158
|
+
this.eligible_prefixes = parser.eligible_prefixes;
|
159
|
+
// NOTE: Overrule `is_static` to make that the "initial level" attribute
|
160
|
+
// is always evaluated for t=1.
|
124
161
|
this.is_static = (this.attribute === 'IL' ? true : parser.is_static);
|
125
162
|
this.is_level_based = parser.is_level_based;
|
126
163
|
this.reset();
|
127
164
|
}
|
128
165
|
|
129
166
|
reset(default_value=VM.NOT_COMPUTED) {
|
130
|
-
//
|
167
|
+
// Clear result of previous computation (if any).
|
168
|
+
this.method_object = null;
|
169
|
+
this.compile_issue = '';
|
170
|
+
this.compute_issue = '';
|
131
171
|
this.step.length = 0;
|
132
172
|
this.stack.length = 0;
|
133
173
|
this.wildcard_vectors = {};
|
134
174
|
this.wildcard_vector_index = false;
|
175
|
+
this.method_object_list.length = 0;
|
135
176
|
this.cache = {};
|
136
177
|
this.compile(); // if(!this.compiled) REMOVED to ensure correct isStatic!!
|
137
178
|
// Static expressions only need a vector with one element (having index 0)
|
138
179
|
if(this.is_static) {
|
139
|
-
// NOTE:
|
180
|
+
// NOTE: Empty expressions (i.e., no text) may default to different
|
140
181
|
// values: typically 0 for lower bounds, infinite for upper process
|
141
|
-
// bounds, etc., so this value must be passed as parameter
|
182
|
+
// bounds, etc., so this value must be passed as parameter.
|
142
183
|
this.vector.length = 1;
|
143
184
|
if(this.text.length === 0) {
|
144
185
|
this.vector[0] = default_value;
|
@@ -180,6 +221,7 @@ class Expression {
|
|
180
221
|
}
|
181
222
|
this.update(xp);
|
182
223
|
} else {
|
224
|
+
this.compile_issue = xp.error;
|
183
225
|
this.is_static = true;
|
184
226
|
this.vector.length = 0;
|
185
227
|
this.vector[0] = VM.INVALID;
|
@@ -271,9 +313,22 @@ class Expression {
|
|
271
313
|
|
272
314
|
chooseVector(number) {
|
273
315
|
// Return the vector to use for computation (defaults to "own" vector).
|
274
|
-
// NOTE: Static wildcard expressions must also choose a vector!
|
275
|
-
if(typeof number !== 'number' ||
|
276
|
-
|
316
|
+
// NOTE: Static wildcard and method expressions must also choose a vector!
|
317
|
+
if((typeof number !== 'number' ||
|
318
|
+
(this.isStatic && !this.isWildcardExpression)) &&
|
319
|
+
!this.isMethod) return this.vector;
|
320
|
+
// Method expressions are not "numbered" but differentiate by the
|
321
|
+
// entity to which they are applied. Their "vector number" is then
|
322
|
+
// inferred by looking up this entity in a method object list.
|
323
|
+
const mop = (this.method_object && this.method_object.identifier) ||
|
324
|
+
this.method_object_prefix || '';
|
325
|
+
if(mop) {
|
326
|
+
number = this.method_object_list.indexOf(mop);
|
327
|
+
if(number < 0) {
|
328
|
+
this.method_object_list.push(mop);
|
329
|
+
number = this.method_object_list.length - 1;
|
330
|
+
}
|
331
|
+
}
|
277
332
|
// Use the vector for the wildcard number (create it if necessary).
|
278
333
|
if(!this.wildcard_vectors.hasOwnProperty(number)) {
|
279
334
|
this.wildcard_vectors[number] = [];
|
@@ -358,8 +413,10 @@ class Expression {
|
|
358
413
|
this.wildcard_vector_index = false;
|
359
414
|
// If error, display the call stack (only once).
|
360
415
|
// NOTE: "undefined", "not computed" and "still computing" are NOT
|
361
|
-
// problematic unless they result in an error (stack over/underflow)
|
416
|
+
// problematic unless they result in an error (stack over/underflow).
|
362
417
|
if(v[t] <= VM.ERROR) {
|
418
|
+
// NOTE: Record the first issue that is detected.
|
419
|
+
if(!this.compute_issue) this.compute_issue = VM.errorMessage(v[t]);
|
363
420
|
MONITOR.showCallStack(t);
|
364
421
|
VM.logCallStack(t);
|
365
422
|
}
|
@@ -397,17 +454,19 @@ class Expression {
|
|
397
454
|
}
|
398
455
|
|
399
456
|
get asAttribute() {
|
400
|
-
//
|
401
|
-
// (special values as human-readable string), or the
|
457
|
+
// Return the result for the current time step if the model has been
|
458
|
+
// solved (with special values as human-readable string), or the
|
459
|
+
// expression as text.
|
402
460
|
if(!(MODEL.solved || this.isStatic)) return this.text;
|
403
461
|
const sv = VM.specialValue(this.result(MODEL.t))[1];
|
404
|
-
// NOTE: ?? is replaced by empty string
|
462
|
+
// NOTE: ?? is replaced by empty string to facilitate copy/paste to
|
463
|
+
// Excel-like spreadsheets, where an empty cell indicates "undefined".
|
405
464
|
if(sv === '\u2047') return '';
|
406
465
|
return sv;
|
407
466
|
}
|
408
467
|
|
409
468
|
push(value) {
|
410
|
-
//
|
469
|
+
// Push a numeric value onto the computation stack.
|
411
470
|
if(this.stack.length >= VM.MAX_STACK) {
|
412
471
|
this.trace('STACK OVERFLOW');
|
413
472
|
this.stack.push(VM.OVERFLOW);
|
@@ -419,7 +478,7 @@ class Expression {
|
|
419
478
|
}
|
420
479
|
|
421
480
|
top(no_check=false) {
|
422
|
-
//
|
481
|
+
// Return the top element of the stack, or FALSE if the stack was empty.
|
423
482
|
if(this.stack.length < 1) {
|
424
483
|
this.trace('TOP: UNDERFLOW');
|
425
484
|
this.stack = [VM.UNDERFLOW];
|
@@ -427,12 +486,12 @@ class Expression {
|
|
427
486
|
return false;
|
428
487
|
}
|
429
488
|
const top = this.stack[this.stack.length - 1];
|
430
|
-
// Check for errors, "undefined", "not computed", and "still computing"
|
489
|
+
// Check for errors, "undefined", "not computed", and "still computing".
|
431
490
|
if(top < VM.MINUS_INFINITY || top > VM.EXCEPTION) {
|
432
|
-
// If error or exception, ignore UNDEFINED if `no_check` is TRUE
|
491
|
+
// If error or exception, ignore UNDEFINED if `no_check` is TRUE.
|
433
492
|
if(no_check && top <= VM.UNDEFINED) return top;
|
434
493
|
// Otherwise, leave the special value on top of the stack, and
|
435
|
-
// return FALSE so that the VM instruction will not alter it
|
494
|
+
// return FALSE so that the VM instruction will not alter it.
|
436
495
|
this.trace(
|
437
496
|
VM.errorMessage(top) + ' at top of stack: ' + this.stack.toString());
|
438
497
|
return false;
|
@@ -441,26 +500,26 @@ class Expression {
|
|
441
500
|
}
|
442
501
|
|
443
502
|
pop(no_check=false) {
|
444
|
-
//
|
445
|
-
// element B from the stack, or FALSE if the stack contains fewer
|
446
|
-
// elements, or if A and/or B are error values
|
503
|
+
// Return the two top elements A and B as [A, B] after popping the
|
504
|
+
// top element B from the stack, or FALSE if the stack contains fewer
|
505
|
+
// than 2 elements, or if A and/or B are error values.
|
447
506
|
if(this.stack.length < 2) {
|
448
507
|
this.trace('POP: UNDERFLOW');
|
449
508
|
this.stack.push(VM.UNDERFLOW);
|
450
509
|
this.computed = true;
|
451
510
|
return false;
|
452
511
|
}
|
453
|
-
// Get the top two numbers on the stack as a list
|
512
|
+
// Get the top two numbers on the stack as a list.
|
454
513
|
const dyad = this.stack.slice(-2);
|
455
|
-
// Pop only the top one
|
514
|
+
// Pop only the top one.
|
456
515
|
this.stack.pop();
|
457
|
-
// Check whether either number is an error code
|
516
|
+
// Check whether either number is an error code.
|
458
517
|
let check = Math.min(dyad[0], dyad[1]);
|
459
518
|
if(check < VM.MINUS_INFINITY &&
|
460
519
|
// Exception: "array index out of bounds" error may also be
|
461
520
|
// ignored by using the | operator.
|
462
521
|
!(no_check && check === VM.ARRAY_INDEX)) {
|
463
|
-
// If error, leave the
|
522
|
+
// If error, leave the most severe error on top of the stack.
|
464
523
|
this.retop(check);
|
465
524
|
this.trace(VM.errorMessage(check) + ' in dyad: ' + dyad.toString());
|
466
525
|
return false;
|
@@ -482,38 +541,40 @@ class Expression {
|
|
482
541
|
this.trace(VM.errorMessage(check) + ' in dyad: ' + dyad.toString());
|
483
542
|
return false;
|
484
543
|
}
|
485
|
-
// No
|
544
|
+
// No issue(s)? Then return the dyad.
|
486
545
|
return dyad;
|
487
546
|
}
|
488
547
|
|
489
548
|
retop(value) {
|
490
|
-
//
|
491
|
-
// NOTE:
|
492
|
-
// follows a TOP or POP instruction
|
549
|
+
// Replace the top element of the stack by the new value.
|
550
|
+
// NOTE: Do not check the stack length, as this instruction typically
|
551
|
+
// follows a TOP or POP instruction.
|
493
552
|
this.stack[this.stack.length - 1] = value;
|
494
553
|
return true;
|
495
554
|
}
|
496
555
|
|
497
556
|
replaceAttribute(re, a1, a2) {
|
498
|
-
//
|
499
|
-
// match the regular expression `re
|
557
|
+
// Replace occurrences of attribute `a1` by `a2` for all variables
|
558
|
+
// that match the regular expression `re`.
|
500
559
|
let n = 0;
|
501
560
|
const matches = this.text.match(re);
|
502
561
|
if(matches) {
|
503
|
-
// Match is case-insensitive, so check each for matching case of
|
562
|
+
// Match is case-insensitive, so check each for matching case of
|
563
|
+
// attribute.
|
504
564
|
for(let i = 0; i < matches.length; i++) {
|
505
565
|
const
|
506
566
|
m = matches[i],
|
507
567
|
e = m.split('|');
|
508
|
-
// Let `ao` be attribute + offset (if any) without right bracket
|
568
|
+
// Let `ao` be attribute + offset (if any) without right bracket.
|
509
569
|
let ao = e.pop().slice(0, -1),
|
510
|
-
// Then also trim offset and spaces
|
570
|
+
// Then also trim offset and spaces.
|
511
571
|
a = ao.split('@')[0].trim();
|
512
|
-
// Check
|
572
|
+
// Check whether `a` (without bracket and without spaces) indeed
|
573
|
+
// matches `a1`.
|
513
574
|
if(a === a1) {
|
514
575
|
// If so, append new attribute plus offset plus right bracket...
|
515
576
|
e.push(ao.replace(a, a2) + ']');
|
516
|
-
// ... and replace the original match by the ensemble
|
577
|
+
// ... and replace the original match by the ensemble.
|
517
578
|
this.text = this.text.replace(m, e.join('|'));
|
518
579
|
n += 1;
|
519
580
|
}
|
@@ -564,13 +625,20 @@ class ExpressionParser {
|
|
564
625
|
constructor(text, owner=null, attribute='') {
|
565
626
|
// Setting TRACE to TRUE will log parsing information to the console.
|
566
627
|
this.TRACE = false;
|
567
|
-
// `text` is the expression string to be parsed.
|
568
|
-
this.expr = text;
|
569
628
|
// NOTE: When expressions for dataset modifiers or equations are
|
570
629
|
// parsed, `owner` is their dataset, and `attribute` is their name.
|
571
630
|
this.owner = owner;
|
572
631
|
this.owner_prefix = '';
|
573
632
|
this.attribute = attribute;
|
633
|
+
// `text` is the expression string to be parsed.
|
634
|
+
this.expr = text;
|
635
|
+
this.expansions = [];
|
636
|
+
// Initialize eligible entities as NULL so it will be initialized
|
637
|
+
// when the first method expression variable is parsed.
|
638
|
+
this.eligible_prefixes = null;
|
639
|
+
// When parsing a method expression, keep a list of all attributes
|
640
|
+
// used in variables.
|
641
|
+
this.method_attributes = [];
|
574
642
|
this.dataset = null;
|
575
643
|
this.dot = null;
|
576
644
|
this.selector = '';
|
@@ -652,7 +720,7 @@ class ExpressionParser {
|
|
652
720
|
// Set the above IF condition to FALSE to profile dynamic expressions.
|
653
721
|
console.log(`Expression for ${this.ownerName}: ${this.expr}\n${msg}`);
|
654
722
|
}
|
655
|
-
|
723
|
+
|
656
724
|
// The method parseVariable(name) checks whether `name` fits this pattern:
|
657
725
|
// {run}statistic$entity|attribute@offset_1:offset_2
|
658
726
|
// allowing spaces within {run} and around | and @ and :
|
@@ -667,8 +735,8 @@ class ExpressionParser {
|
|
667
735
|
// NOTE: this array is used as argument for the virtual machine instructions
|
668
736
|
// VMI_push_var, VMI_push_statistic and VMI_push_run_result.
|
669
737
|
parseVariable(name) {
|
670
|
-
//
|
671
|
-
name = name.replace(/\s+/g, ' ');
|
738
|
+
// Remove non-functional whitespace.
|
739
|
+
name = name.replace(/\s+/g, ' ').trim();
|
672
740
|
|
673
741
|
// For debugging, TRACE can be used to log to the console for
|
674
742
|
// specific expressions and/or variables, for example:
|
@@ -975,23 +1043,6 @@ class ExpressionParser {
|
|
975
1043
|
return false;
|
976
1044
|
}
|
977
1045
|
}
|
978
|
-
/*
|
979
|
-
// DEPRECATED -- Modeler can deal with this by smartly using AND
|
980
|
-
// clauses like "&x: &y:" to limit set to specific prefixes.
|
981
|
-
|
982
|
-
// Deal with "prefix inheritance" when pattern starts with a colon.
|
983
|
-
if(pat.startsWith(':') && this.owner_prefix) {
|
984
|
-
// Add a "must start with" AND condition to all OR clauses of the
|
985
|
-
// pattern.
|
986
|
-
// NOTE: Issues may occur when prefix contains &, ^ or #.
|
987
|
-
// @@TO DO: See if this can be easily prohibited.
|
988
|
-
const oc = pat.substring(1).split('|');
|
989
|
-
for(let i = 0; i < oc.length; i++) {
|
990
|
-
oc[i] = `~${this.owner_prefix}&${oc[i]}`;
|
991
|
-
}
|
992
|
-
pat = oc.join('|');
|
993
|
-
}
|
994
|
-
*/
|
995
1046
|
// NOTE: For patterns, assume that # *always* denotes the context-
|
996
1047
|
// sensitive number #, because if modelers wishes to include
|
997
1048
|
// ANY number, they can make their pattern less selective.
|
@@ -1118,7 +1169,7 @@ class ExpressionParser {
|
|
1118
1169
|
}
|
1119
1170
|
// A leading "!" denotes: pass variable reference instead of its value.
|
1120
1171
|
// NOTE: This also applies to the "dot", so [!.] is a valid variable.
|
1121
|
-
|
1172
|
+
const by_reference = name.startsWith('!');
|
1122
1173
|
if(by_reference) name = name.substring(1);
|
1123
1174
|
// When `name` is a single dot, it refers to the dataset for which the
|
1124
1175
|
// modifier expression is being parsed. Like all datasets, the "dot"
|
@@ -1134,11 +1185,163 @@ class ExpressionParser {
|
|
1134
1185
|
return false;
|
1135
1186
|
}
|
1136
1187
|
// Check whether name refers to a Linny-R entity defined by the model.
|
1188
|
+
|
1189
|
+
// NOTE: When parsing the expression of a "method", variables starting
|
1190
|
+
// with a colon may be special cases.
|
1191
|
+
if(this.attribute.startsWith(':') && name.startsWith(':')) {
|
1192
|
+
// When `name` identifies a method ":m" then this method can be
|
1193
|
+
// called "as is".
|
1194
|
+
const method = MODEL.equationByID(UI.nameToID(name));
|
1195
|
+
if(method) {
|
1196
|
+
// Check for auto-reference.
|
1197
|
+
if(method.selector === this.attribute) {
|
1198
|
+
this.error = 'Method cannot reference itself';
|
1199
|
+
return false;
|
1200
|
+
}
|
1201
|
+
if(attr) {
|
1202
|
+
// Methods are expressions and hence always return a number,
|
1203
|
+
// not an entity.
|
1204
|
+
this.error = 'Method cannot have an attribute';
|
1205
|
+
return false;
|
1206
|
+
}
|
1207
|
+
// NOTE: If it has no eligible prefixes yet, the method being
|
1208
|
+
// parsed "inherits" those of an "as is" method, or should
|
1209
|
+
// intersect its eligible prefixes with the "inherited" ones.
|
1210
|
+
method.expression.compile();
|
1211
|
+
if(method.expression.compiling) {
|
1212
|
+
this.error = 'Cannot resolve method "' + method.selector +
|
1213
|
+
'" because this would create a cyclic reference';
|
1214
|
+
return false;
|
1215
|
+
}
|
1216
|
+
const
|
1217
|
+
ep = {},
|
1218
|
+
mep = method.expression.eligible_prefixes,
|
1219
|
+
prefs = Object.keys(mep);
|
1220
|
+
// NOTE: Prefix keys will always be in lower case.
|
1221
|
+
for(let i = 0; i < prefs.length; i++) {
|
1222
|
+
const pref = prefs[i];
|
1223
|
+
if(this.eligible_prefixes === null || this.eligible_prefixes[pref]) {
|
1224
|
+
ep[pref] = true;
|
1225
|
+
}
|
1226
|
+
}
|
1227
|
+
this.eligible_prefixes = ep;
|
1228
|
+
// NOTE: The method may be dynamic and/or level-dependent.
|
1229
|
+
if(!method.expression.isStatic) {
|
1230
|
+
this.is_static = false;
|
1231
|
+
this.log('dynamic because dynamic method is used');
|
1232
|
+
}
|
1233
|
+
this.is_level_based = this.is_level_based ||
|
1234
|
+
method.expression.is_level_based;
|
1235
|
+
// Generate "call method" VM instruction with no additional
|
1236
|
+
// arguments; the method equation will be applied to the object
|
1237
|
+
// of the calling method.
|
1238
|
+
return [{meq: method}, anchor1, offset1, anchor2, offset2];
|
1239
|
+
}
|
1240
|
+
// If `name` does not identify a method, it must match the "tail"
|
1241
|
+
// of some prefixed entity "prefix: name", because a method can
|
1242
|
+
// only be used as [prefix: method name] in another expression.
|
1243
|
+
// When compiling a method, a list of eligible prefixes is made.
|
1244
|
+
// This should not be empty when a method reference is parsed.
|
1245
|
+
const
|
1246
|
+
tail = UI.PREFIXER + name.substring(1).trim(),
|
1247
|
+
ee = MODEL.entitiesEndingOn(tail, attr),
|
1248
|
+
ep = {};
|
1249
|
+
for(let i = 0; i < ee.length; i++) {
|
1250
|
+
const
|
1251
|
+
en = ee[i].displayName,
|
1252
|
+
pref = en.substring(0, en.length - tail.length).toLowerCase();
|
1253
|
+
if(this.eligible_prefixes === null || this.eligible_prefixes[pref]) {
|
1254
|
+
ep[pref] = true;
|
1255
|
+
}
|
1256
|
+
}
|
1257
|
+
this.eligible_prefixes = ep;
|
1258
|
+
const uca = attr.toUpperCase();
|
1259
|
+
// Capitalize `attr` if it is a standard entity attribute.
|
1260
|
+
if(VM.attribute_names[uca]) attr = uca;
|
1261
|
+
// Add attribute to method attribute list (for post-parsing check).
|
1262
|
+
this.method_attributes.push(attr);
|
1263
|
+
if(Object.keys(this.eligible_prefixes).length <= 0) {
|
1264
|
+
const n = name + (attr ? `|${attr}` : '');
|
1265
|
+
this.error =
|
1266
|
+
`No match for variable [${n}] in this method expression`;
|
1267
|
+
return false;
|
1268
|
+
}
|
1269
|
+
// NOTE: Some attributes make the method expression level-dependent.
|
1270
|
+
this.is_level_based = this.is_level_based ||
|
1271
|
+
VM.level_based_attr.indexOf(attr) >= 0;
|
1272
|
+
// NOTE: Postpone check whether method will make the expression
|
1273
|
+
// dynamic to after the expression has been parsed and the exact
|
1274
|
+
// set of eligible entities and the set of attributes is known.
|
1275
|
+
|
1276
|
+
// Colon-prefixed variables in method expressions are similar to
|
1277
|
+
// wildcard variables, so the same VM instruction is coded for,
|
1278
|
+
// except that the entity that is the object of the method will
|
1279
|
+
// be set (as `method_object` property) for expressions that "call"
|
1280
|
+
// the method. The distinction is indicated by passing the string
|
1281
|
+
// "MO" instead of the list of eligible entities.
|
1282
|
+
if(this.TRACE) console.log('TRACE: Variable', name,
|
1283
|
+
'references the method object. Attribute used:', attr);
|
1284
|
+
return [{n: name, ee: 'MO', a: attr, br: by_reference},
|
1285
|
+
anchor1, offset1, anchor2, offset2];
|
1286
|
+
}
|
1287
|
+
|
1288
|
+
// Special "method-parsing" cases will now have been handled.
|
1289
|
+
// The other cases apply also to normal expressions.
|
1137
1290
|
if(!obj) {
|
1138
|
-
//
|
1139
|
-
//
|
1291
|
+
// If variable name starts with a colon, then the owner prefix
|
1292
|
+
// should be added.
|
1140
1293
|
name = UI.colonPrefixedName(name, this.owner_prefix);
|
1141
|
-
//
|
1294
|
+
// Now check whether the variable appends a method.
|
1295
|
+
const
|
1296
|
+
parts = name.split(UI.PREFIXER),
|
1297
|
+
tail = parts.pop();
|
1298
|
+
if(parts.length > 0) {
|
1299
|
+
// Name contains at least one prefix => last part *could* be a
|
1300
|
+
// method name, so look it up after adding a leading colon.
|
1301
|
+
const method = MODEL.equationByID(UI.nameToID(':' + tail));
|
1302
|
+
// If tail matches with a method, the head must identify an
|
1303
|
+
// entity.
|
1304
|
+
if(method) {
|
1305
|
+
const
|
1306
|
+
en = parts.join(UI.PREFIXER),
|
1307
|
+
mep = method.expression.matchWithEligiblePrefixes(en);
|
1308
|
+
if(!mep) {
|
1309
|
+
if(method.expression.compiling) {
|
1310
|
+
this.error = `Cannot resolve "${en}", possibly because ` +
|
1311
|
+
`method "${method.selector}" creates a cyclic reference`;
|
1312
|
+
} else {
|
1313
|
+
this.error = 'Method "'+ method.selector +
|
1314
|
+
`" does not apply to "${en}"`;
|
1315
|
+
}
|
1316
|
+
return false;
|
1317
|
+
}
|
1318
|
+
// NOTE: The method may be dynamic and/or level-dependent.
|
1319
|
+
if(!method.expression.isStatic) {
|
1320
|
+
this.is_static = false;
|
1321
|
+
this.log('dynamic because dynamic method is used');
|
1322
|
+
}
|
1323
|
+
this.is_level_based = this.is_level_based ||
|
1324
|
+
method.expression.is_level_based;
|
1325
|
+
// NOTE: `en` may be an incomplete identification of the object
|
1326
|
+
// of the method, which can be completed only at execution time.
|
1327
|
+
// For example: [x: m] is a valid method call when "x: a: b"
|
1328
|
+
// and "x: c" identify model entities, and the expression of
|
1329
|
+
// method "m" contains variables [:a :b] and [:c] as operands.
|
1330
|
+
// These operands code as VMI_push_dataset_modifier instructions
|
1331
|
+
// with specifier {n: name, ee: "MO"} where, for the examples
|
1332
|
+
// above, "name" would be ":a :b" and ":c". By passing `en`
|
1333
|
+
// as the "method object prefix" to VMI_push_method, this
|
1334
|
+
// instruction can set the `method_object_prefix` attribute
|
1335
|
+
// of the expression so that it can be used by the VMI_push_
|
1336
|
+
// _dataset_modifier instruction to identify the method object
|
1337
|
+
// by assembling prefix + name (with its leading colon replaced
|
1338
|
+
// by the prefixer ": ").
|
1339
|
+
return [{meq: method, mo: en}, anchor1, offset1, anchor2, offset2];
|
1340
|
+
}
|
1341
|
+
}
|
1342
|
+
}
|
1343
|
+
if(!obj) {
|
1344
|
+
// Now check wildcard equations, as these are likely to be few
|
1142
1345
|
// (so a quick scan) and constitute a special case.
|
1143
1346
|
const
|
1144
1347
|
id = UI.nameToID(name),
|
@@ -1151,7 +1354,7 @@ class ExpressionParser {
|
|
1151
1354
|
// so this equation must be evaluated for that number.
|
1152
1355
|
return [
|
1153
1356
|
{d: w[0].dataset, s: w[1], x: w[0].expression},
|
1154
|
-
anchor1, offset1, anchor2, offset2];
|
1357
|
+
anchor1, offset1, anchor2, offset2];
|
1155
1358
|
}
|
1156
1359
|
// If no match, try to match the object ID with any type of entity.
|
1157
1360
|
obj = MODEL.objectByID(id);
|
@@ -1313,8 +1516,7 @@ class ExpressionParser {
|
|
1313
1516
|
// No explicit selector means that this variable is dynamic if
|
1314
1517
|
// the dataset has time series data, or if some of its modifier
|
1315
1518
|
// expressions are dynamic.
|
1316
|
-
if(obj.
|
1317
|
-
!obj.allModifiersAreStatic) {
|
1519
|
+
if(obj.mayBeDynamic) {
|
1318
1520
|
this.is_static = false;
|
1319
1521
|
this.log('dynamic because dataset without explicit selector is used');
|
1320
1522
|
}
|
@@ -1377,19 +1579,18 @@ class ExpressionParser {
|
|
1377
1579
|
// NOTE: `arg0` can now be a single value, a vector, or NULL.
|
1378
1580
|
if(arg0 === null) arg0 = obj.attributeValue(attr);
|
1379
1581
|
if(Array.isArray(arg0)) {
|
1380
|
-
if(obj instanceof Dataset) {
|
1381
|
-
|
1382
|
-
|
1383
|
-
this.log('dynamic because dataset vector is used');
|
1384
|
-
}
|
1582
|
+
if(obj instanceof Dataset && obj.mayBeDynamic) {
|
1583
|
+
this.is_static = false;
|
1584
|
+
this.log('dynamic because dataset vector is used');
|
1385
1585
|
} else if(VM.level_based_attr.indexOf(attr) >= 0) {
|
1386
1586
|
this.is_static = false;
|
1387
1587
|
this.log('dynamic because level-based attribute');
|
1388
|
-
} else {
|
1389
|
-
//
|
1588
|
+
} else if(new Set(arg0).size > 1) {
|
1589
|
+
// Not all values qre equal => dynamic.
|
1390
1590
|
this.is_static = false;
|
1391
|
-
this.log('
|
1392
|
-
console.log('ANOMALY: array for', obj.displayName, obj, attr, arg0);
|
1591
|
+
this.log('Dynamic because array contains different values');
|
1592
|
+
// console.log('ANOMALY: array for', obj.type, obj.displayName, obj, attr, arg0);
|
1593
|
+
// console.log('Expression for', this.ownerName, '; text =', this.expr);
|
1393
1594
|
}
|
1394
1595
|
if(this.TRACE) console.log('TRACE: arg[0] is a vector');
|
1395
1596
|
}
|
@@ -1441,10 +1642,10 @@ class ExpressionParser {
|
|
1441
1642
|
}
|
1442
1643
|
|
1443
1644
|
getSymbol() {
|
1444
|
-
//
|
1645
|
+
// Get the next substring in the expression that is a valid symbol
|
1445
1646
|
// while advancing the position-in-text (`pit`) and length-of-symbol
|
1446
1647
|
// (`los`), which are used to highlight the position of a syntax error
|
1447
|
-
// in the expression editor
|
1648
|
+
// in the expression editor.
|
1448
1649
|
let c, f, i, l, v;
|
1449
1650
|
this.prev_sym = this.sym;
|
1450
1651
|
this.sym = null;
|
@@ -1652,35 +1853,35 @@ class ExpressionParser {
|
|
1652
1853
|
}
|
1653
1854
|
|
1654
1855
|
compile() {
|
1655
|
-
//
|
1656
|
-
// NOTE:
|
1657
|
-
// become the code attribute of an expression object
|
1856
|
+
// Compile expression into array of VM instructions `code`.
|
1857
|
+
// NOTE: Always create a new code array instance, as it will typically
|
1858
|
+
// become the code attribute of an expression object.
|
1658
1859
|
if(DEBUGGING) console.log('COMPILING', this.ownerName, ':\n',
|
1659
1860
|
this.expr, '\ncontext number =', this.context_number);
|
1660
1861
|
this.code = [];
|
1661
|
-
// Position in text
|
1862
|
+
// Position in text.
|
1662
1863
|
this.pit = 0;
|
1663
|
-
// Length of symbol
|
1864
|
+
// Length of symbol.
|
1664
1865
|
this.los = 0;
|
1665
|
-
// Error message also serves as flag: stop compiling if not empty
|
1866
|
+
// Error message also serves as flag: stop compiling if not empty.
|
1666
1867
|
this.error = '';
|
1667
|
-
// `is_static` becomes FALSE when a time-dependent operand is detected
|
1868
|
+
// `is_static` becomes FALSE when a time-dependent operand is detected.
|
1668
1869
|
this.is_static = true;
|
1669
|
-
// `is_level_based` becomes TRUE when a level-based variable is detected
|
1870
|
+
// `is_level_based` becomes TRUE when a level-based variable is detected.
|
1670
1871
|
this.is_level_based = false;
|
1671
|
-
// `concatenating` becomes TRUE when a concatenation operator
|
1672
|
-
// is pushed, and FALSE when a reducing operator (min, max,
|
1673
|
-
// triangular) is pushed
|
1872
|
+
// `concatenating` becomes TRUE when a concatenation operator
|
1873
|
+
// (semicolon) is pushed, and FALSE when a reducing operator (min, max,
|
1874
|
+
// normal, weibull, triangular) is pushed.
|
1674
1875
|
this.concatenating = false;
|
1675
|
-
// An empty expression should return the "undefined" value
|
1876
|
+
// An empty expression should return the "undefined" value.
|
1676
1877
|
if(this.expr.trim() === '') {
|
1677
1878
|
this.code.push([VMI_push_number, VM.UNDEFINED]);
|
1678
1879
|
return;
|
1679
1880
|
}
|
1680
|
-
// Parse the expression using Edsger Dijkstra's shunting-yard algorithm
|
1681
|
-
// vmi = virtual machine instruction (a function)
|
1881
|
+
// Parse the expression using Edsger Dijkstra's shunting-yard algorithm.
|
1882
|
+
// vmi = virtual machine instruction (a function).
|
1682
1883
|
let vmi;
|
1683
|
-
// eot = end of text (index of last character in string)
|
1884
|
+
// eot = end of text (index of last character in string).
|
1684
1885
|
this.eot = this.expr.length - 1;
|
1685
1886
|
this.sym = null; // current symbol
|
1686
1887
|
this.prev_sym = null; // previous symbol
|
@@ -1693,27 +1894,27 @@ class ExpressionParser {
|
|
1693
1894
|
this.getSymbol();
|
1694
1895
|
if(this.error !== '') break;
|
1695
1896
|
if(this.sym === '(') {
|
1696
|
-
// Opening parenthesis is ALWAYS pushed onto the stack
|
1897
|
+
// Opening parenthesis is ALWAYS pushed onto the stack.
|
1697
1898
|
this.op_stack.push(this.sym);
|
1698
1899
|
} else if(this.sym === ')') {
|
1699
1900
|
// Closing parenthesis => pop all operators until its matching
|
1700
|
-
// opening parenthesis is found
|
1901
|
+
// opening parenthesis is found.
|
1701
1902
|
if(this.op_stack.indexOf('(') < 0) {
|
1702
1903
|
this.error = 'Unmatched \')\'';
|
1703
1904
|
} else if(this.prev_sym === '(' ||
|
1704
1905
|
OPERATOR_CODES.indexOf(this.prev_sym) >= 0) {
|
1705
|
-
// Parenthesis immediately after an operator => missing operand
|
1906
|
+
// Parenthesis immediately after an operator => missing operand.
|
1706
1907
|
this.error = 'Missing operand';
|
1707
1908
|
} else {
|
1708
|
-
// Pop all operators up to and including the matching parenthesis
|
1909
|
+
// Pop all operators up to and including the matching parenthesis.
|
1709
1910
|
vmi = null;
|
1710
1911
|
while(this.op_stack.length > 0 &&
|
1711
1912
|
this.op_stack[this.op_stack.length - 1] !== '(') {
|
1712
|
-
// Pop the operator
|
1913
|
+
// Pop the operator.
|
1713
1914
|
vmi = this.op_stack.pop();
|
1714
1915
|
this.codeOperation(vmi);
|
1715
1916
|
}
|
1716
|
-
// Also pop the opening parenthesis
|
1917
|
+
// Also pop the opening parenthesis.
|
1717
1918
|
this.op_stack.pop();
|
1718
1919
|
}
|
1719
1920
|
} else if(this.sym === VMI_if_else &&
|
@@ -1725,13 +1926,13 @@ class ExpressionParser {
|
|
1725
1926
|
this.op_stack[this.op_stack.length - 1] : null),
|
1726
1927
|
topprio = PRIORITIES[OPERATOR_CODES.indexOf(topop)],
|
1727
1928
|
symprio = PRIORITIES[OPERATOR_CODES.indexOf(this.sym)];
|
1728
|
-
// Pop all operators having a higher or equal priority than the
|
1729
|
-
// to be pushed EXCEPT when this priority equals 9, as monadic
|
1730
|
-
// bind right-to-left
|
1929
|
+
// Pop all operators having a higher or equal priority than the
|
1930
|
+
// one to be pushed EXCEPT when this priority equals 9, as monadic
|
1931
|
+
// operators bind right-to-left.
|
1731
1932
|
while(this.op_stack.length > 0 && OPERATOR_CODES.indexOf(topop) >= 0 &&
|
1732
1933
|
topprio >= symprio && symprio !== 9) {
|
1733
1934
|
// The stack may be emptied, but if it contains a (, this
|
1734
|
-
// parenthesis is unmatched
|
1935
|
+
// parenthesis is unmatched.
|
1735
1936
|
if(topop === '(') {
|
1736
1937
|
this.error = 'Missing \')\'';
|
1737
1938
|
} else {
|
@@ -1747,27 +1948,27 @@ class ExpressionParser {
|
|
1747
1948
|
}
|
1748
1949
|
}
|
1749
1950
|
|
1750
|
-
// NOTE:
|
1951
|
+
// NOTE: As of version 1.0.14, (a ? b : c) is implemented with
|
1751
1952
|
// "jump"-instructions so that only b OR c is evaluated instead
|
1752
|
-
// of both
|
1953
|
+
// of both.
|
1753
1954
|
if(this.sym === VMI_if_then) {
|
1754
1955
|
// Push index of JUMP-IF-FALSE instruction on if_stack so that
|
1755
1956
|
// later its dummy argument (NULL) can be replaced by the
|
1756
|
-
// index of the first instruction after the THEN part
|
1957
|
+
// index of the first instruction after the THEN part.
|
1757
1958
|
this.if_stack.push(this.code.length);
|
1758
1959
|
this.code.push([VMI_jump_if_false, null]);
|
1759
1960
|
} else if(this.sym === VMI_if_else) {
|
1760
1961
|
this.then_stack.push(this.code.length);
|
1761
1962
|
this.code.push([VMI_jump, null]);
|
1762
|
-
// NOTE:
|
1763
|
-
// start by popping the FALSE result of the IF condition
|
1963
|
+
// NOTE: If : is not omitted, the code for the ELSE part must
|
1964
|
+
// start by popping the FALSE result of the IF condition.
|
1764
1965
|
this.code.push([VMI_pop_false, null]);
|
1765
1966
|
}
|
1766
1967
|
// END of new code for IF-THEN-ELSE
|
1767
1968
|
|
1768
1969
|
this.op_stack.push(this.sym);
|
1769
1970
|
} else if(this.sym !== null) {
|
1770
|
-
// Symbol is an operand
|
1971
|
+
// Symbol is an operand.
|
1771
1972
|
if(CONSTANT_CODES.indexOf(this.sym) >= 0) {
|
1772
1973
|
this.code.push([this.sym, null]);
|
1773
1974
|
} else if(Array.isArray(this.sym)) {
|
@@ -1778,6 +1979,8 @@ class ExpressionParser {
|
|
1778
1979
|
this.code.push([VMI_push_statistic, this.sym]);
|
1779
1980
|
} else if(this.sym[0].hasOwnProperty('d')) {
|
1780
1981
|
this.code.push([VMI_push_dataset_modifier, this.sym]);
|
1982
|
+
} else if(this.sym[0].hasOwnProperty('meq')) {
|
1983
|
+
this.code.push([VMI_push_method, this.sym]);
|
1781
1984
|
} else if(this.sym[0].hasOwnProperty('ee')) {
|
1782
1985
|
this.code.push([VMI_push_wildcard_entity, this.sym]);
|
1783
1986
|
} else if(this.sym[0].hasOwnProperty('x')) {
|
@@ -1811,6 +2014,21 @@ class ExpressionParser {
|
|
1811
2014
|
this.error = 'Invalid parameter list';
|
1812
2015
|
}
|
1813
2016
|
}
|
2017
|
+
// When compiling a method, check for all eligible prefixes whether
|
2018
|
+
// they might make the expression dynamic.
|
2019
|
+
if(this.is_static && this.eligible_prefixes) {
|
2020
|
+
const epl = Object.keys(this.eligible_prefixes);
|
2021
|
+
for(let i = 0; i < epl.length; i++) {
|
2022
|
+
const ep = epl[i];
|
2023
|
+
for(let j = 0; j < ep.length; j++) {
|
2024
|
+
if(ep[j] instanceof Dataset && ep[j].mayBeDynamic) {
|
2025
|
+
this.is_static = false;
|
2026
|
+
this.log('dynamic because some modifiers of eligible datasets are dynamic');
|
2027
|
+
break;
|
2028
|
+
}
|
2029
|
+
}
|
2030
|
+
}
|
2031
|
+
}
|
1814
2032
|
if(this.TRACE || DEBUGGING) console.log('PARSED', this.ownerName, ':',
|
1815
2033
|
this.expr, this.code);
|
1816
2034
|
}
|
@@ -2741,7 +2959,7 @@ class VirtualMachine {
|
|
2741
2959
|
return prior_level;
|
2742
2960
|
}
|
2743
2961
|
|
2744
|
-
variablesLegend(
|
2962
|
+
variablesLegend() {
|
2745
2963
|
// Return a string with each variable code and full name on a
|
2746
2964
|
// separate line.
|
2747
2965
|
const
|
@@ -5533,96 +5751,99 @@ Solver status = ${json.status}`);
|
|
5533
5751
|
// automaton instruction has parameters x and a, where x is the computing
|
5534
5752
|
// expression and a the argument, which may be a single number or a list
|
5535
5753
|
// (array) of objects. When no arguments need to be passed, the second
|
5536
|
-
// parameter is
|
5754
|
+
// parameter is omitted.
|
5537
5755
|
|
5538
5756
|
function VMI_push_number(x, number) {
|
5539
|
-
//
|
5757
|
+
// Push a numeric constant on the VM stack.
|
5540
5758
|
if(DEBUGGING) console.log('push number = ' + number);
|
5541
5759
|
x.push(number);
|
5542
5760
|
}
|
5543
5761
|
|
5544
|
-
function VMI_push_time_step(x
|
5545
|
-
//
|
5546
|
-
// NOTE:
|
5547
|
-
// starts at 1), adjusted for the first time step of the simulation period
|
5762
|
+
function VMI_push_time_step(x) {
|
5763
|
+
// Push the current time step.
|
5764
|
+
// NOTE: This is the "local" time step for expression `x` (which always
|
5765
|
+
// starts at 1), adjusted for the first time step of the simulation period.
|
5548
5766
|
const t = x.step[x.step.length - 1] + MODEL.start_period - 1;
|
5549
5767
|
if(DEBUGGING) console.log('push absolute t = ' + t);
|
5550
5768
|
x.push(t);
|
5551
5769
|
}
|
5552
5770
|
|
5553
|
-
function VMI_push_delta_t(x
|
5554
|
-
//
|
5771
|
+
function VMI_push_delta_t(x) {
|
5772
|
+
// Push the duration of 1 time step (in hours).
|
5555
5773
|
const dt = MODEL.time_scale * VM.time_unit_values[MODEL.time_unit];
|
5556
5774
|
if(DEBUGGING) console.log('push delta-t = ' + dt);
|
5557
5775
|
x.push(dt);
|
5558
5776
|
}
|
5559
5777
|
|
5560
|
-
function VMI_push_relative_time(x
|
5561
|
-
//
|
5778
|
+
function VMI_push_relative_time(x) {
|
5779
|
+
// Push the "local" time step for expression `x`.
|
5780
|
+
// NOTE: Time step for optimization period always starts at 1.
|
5562
5781
|
const t = x.step[x.step.length - 1];
|
5563
5782
|
if(DEBUGGING) console.log('push relative t = ' + t);
|
5564
5783
|
x.push(t);
|
5565
5784
|
}
|
5566
5785
|
|
5567
|
-
function VMI_push_block_time(x
|
5568
|
-
//
|
5569
|
-
// adjusted for the first time step of the current block
|
5570
|
-
const
|
5571
|
-
|
5572
|
-
|
5786
|
+
function VMI_push_block_time(x) {
|
5787
|
+
// Push the "local" time step for expression `x` (which always starts
|
5788
|
+
// at 1) adjusted for the first time step of the current block.
|
5789
|
+
const
|
5790
|
+
lt = x.step[x.step.length - 1] - 1,
|
5791
|
+
bnr = Math.floor(lt / MODEL.block_length),
|
5792
|
+
t = lt - bnr * MODEL.block_length + 1;
|
5573
5793
|
if(DEBUGGING) console.log('push block time bt = ' + t);
|
5574
5794
|
x.push(t);
|
5575
5795
|
}
|
5576
5796
|
|
5577
|
-
function VMI_push_block_number(x
|
5578
|
-
//
|
5797
|
+
function VMI_push_block_number(x) {
|
5798
|
+
// Push the number of the block currently being optimized.
|
5799
|
+
// NOTE: Block numbering starts at 1.
|
5579
5800
|
const local_t = x.step[x.step.length - 1] - 1,
|
5580
5801
|
bnr = Math.floor(local_t / MODEL.block_length) + 1;
|
5581
5802
|
if(DEBUGGING) console.log('push current block number = ' + bnr);
|
5582
5803
|
x.push(bnr);
|
5583
5804
|
}
|
5584
5805
|
|
5585
|
-
function VMI_push_run_length(x
|
5586
|
-
//
|
5806
|
+
function VMI_push_run_length(x) {
|
5807
|
+
// Push the run length (excl. look-ahead!).
|
5587
5808
|
const n = MODEL.end_period - MODEL.start_period + 1;
|
5588
5809
|
if(DEBUGGING) console.log('push run length N = ' + n);
|
5589
5810
|
x.push(n);
|
5590
5811
|
}
|
5591
5812
|
|
5592
|
-
function VMI_push_block_length(x
|
5593
|
-
//
|
5813
|
+
function VMI_push_block_length(x) {
|
5814
|
+
// Push the block length.
|
5594
5815
|
if(DEBUGGING) console.log('push block length n = ' + MODEL.block_length);
|
5595
5816
|
x.push(MODEL.block_length);
|
5596
5817
|
}
|
5597
5818
|
|
5598
|
-
function VMI_push_look_ahead(x
|
5599
|
-
//
|
5819
|
+
function VMI_push_look_ahead(x) {
|
5820
|
+
// Push the look-ahead.
|
5600
5821
|
if(DEBUGGING) console.log('push look-ahead l = ' + MODEL.look_ahead);
|
5601
5822
|
x.push(MODEL.look_ahead);
|
5602
5823
|
}
|
5603
5824
|
|
5604
|
-
function VMI_push_round(x
|
5605
|
-
//
|
5825
|
+
function VMI_push_round(x) {
|
5826
|
+
// Push the current round number (a=1, z=26, etc.).
|
5606
5827
|
const r = VM.round_letters.indexOf(VM.round_sequence[VM.current_round]);
|
5607
5828
|
if(DEBUGGING) console.log('push round number R = ' + r);
|
5608
5829
|
x.push(r);
|
5609
5830
|
}
|
5610
5831
|
|
5611
|
-
function VMI_push_last_round(x
|
5612
|
-
//
|
5832
|
+
function VMI_push_last_round(x) {
|
5833
|
+
// Push the last round number (a=1, z=26, etc.).
|
5613
5834
|
const r = VM.round_letters.indexOf(VM.round_sequence[MODEL.rounds - 1]);
|
5614
5835
|
if(DEBUGGING) console.log('push last round number LR = ' + r);
|
5615
5836
|
x.push(r);
|
5616
5837
|
}
|
5617
5838
|
|
5618
|
-
function VMI_push_number_of_rounds(x
|
5619
|
-
//
|
5839
|
+
function VMI_push_number_of_rounds(x) {
|
5840
|
+
// Push the number of rounds (= length of round sequence).
|
5620
5841
|
if(DEBUGGING) console.log('push number of rounds NR = ' + MODEL.rounds);
|
5621
5842
|
x.push(MODEL.rounds);
|
5622
5843
|
}
|
5623
5844
|
|
5624
|
-
function VMI_push_run_number(x
|
5625
|
-
//
|
5845
|
+
function VMI_push_run_number(x) {
|
5846
|
+
// Push the number of the current run in the selected experiment (or 0).
|
5626
5847
|
const
|
5627
5848
|
sx = EXPERIMENT_MANAGER.selected_experiment,
|
5628
5849
|
nox = (sx ? ` (in ${sx.title})` : ' (no experiment)'),
|
@@ -5631,8 +5852,8 @@ function VMI_push_run_number(x, empty) {
|
|
5631
5852
|
x.push(xr);
|
5632
5853
|
}
|
5633
5854
|
|
5634
|
-
function VMI_push_number_of_runs(x
|
5635
|
-
//
|
5855
|
+
function VMI_push_number_of_runs(x) {
|
5856
|
+
// Push the number of runs in the current experiment (0 if no experiment).
|
5636
5857
|
const
|
5637
5858
|
sx = EXPERIMENT_MANAGER.selected_experiment,
|
5638
5859
|
nox = (sx ? `(in ${sx.title})` : '(no experiment)'),
|
@@ -5641,40 +5862,41 @@ function VMI_push_number_of_runs(x, empty) {
|
|
5641
5862
|
x.push(nx);
|
5642
5863
|
}
|
5643
5864
|
|
5644
|
-
function VMI_push_random(x
|
5645
|
-
//
|
5865
|
+
function VMI_push_random(x) {
|
5866
|
+
// Push a random number from the interval [0, 1).
|
5646
5867
|
const r = Math.random();
|
5647
5868
|
if(DEBUGGING) console.log('push random =', r);
|
5648
5869
|
x.push(r);
|
5649
5870
|
}
|
5650
5871
|
|
5651
|
-
function VMI_push_pi(x
|
5652
|
-
//
|
5872
|
+
function VMI_push_pi(x) {
|
5873
|
+
// Push the goniometric constant pi.
|
5653
5874
|
if(DEBUGGING) console.log('push pi');
|
5654
5875
|
x.push(Math.PI);
|
5655
5876
|
}
|
5656
5877
|
|
5657
|
-
function VMI_push_true(x
|
5658
|
-
//
|
5878
|
+
function VMI_push_true(x) {
|
5879
|
+
// Push the Boolean constant TRUE.
|
5659
5880
|
if(DEBUGGING) console.log('push TRUE');
|
5660
5881
|
x.push(1);
|
5661
5882
|
}
|
5662
5883
|
|
5663
|
-
function VMI_push_false(x
|
5664
|
-
//
|
5884
|
+
function VMI_push_false(x) {
|
5885
|
+
// Push the Boolean constant FALSE.
|
5665
5886
|
if(DEBUGGING) console.log('push FALSE');
|
5666
5887
|
x.push(0);
|
5667
5888
|
}
|
5668
5889
|
|
5669
|
-
function VMI_push_infinity(x
|
5670
|
-
//
|
5890
|
+
function VMI_push_infinity(x) {
|
5891
|
+
// Push the constant representing infinity for the solver.
|
5671
5892
|
if(DEBUGGING) console.log('push +INF');
|
5672
5893
|
x.push(VM.PLUS_INFINITY);
|
5673
5894
|
}
|
5674
5895
|
|
5675
5896
|
function valueOfIndexVariable(v) {
|
5676
|
-
// AUXILIARY FUNCTION for the VMI_push_(i, j or k) instructions
|
5677
|
-
//
|
5897
|
+
// AUXILIARY FUNCTION for the VMI_push_(i, j or k) instructions.
|
5898
|
+
// Return the value of the iterator index variable for the current
|
5899
|
+
// experiment.
|
5678
5900
|
if(MODEL.running_experiment) {
|
5679
5901
|
const
|
5680
5902
|
lead = v + '=',
|
@@ -5687,68 +5909,69 @@ function valueOfIndexVariable(v) {
|
|
5687
5909
|
return 0;
|
5688
5910
|
}
|
5689
5911
|
|
5690
|
-
function VMI_push_i(x
|
5691
|
-
//
|
5912
|
+
function VMI_push_i(x) {
|
5913
|
+
// Push the value of iterator index i.
|
5692
5914
|
const i = valueOfIndexVariable('i');
|
5693
5915
|
if(DEBUGGING) console.log('push i = ' + i);
|
5694
5916
|
x.push(i);
|
5695
5917
|
}
|
5696
5918
|
|
5697
|
-
function VMI_push_j(x
|
5698
|
-
//
|
5919
|
+
function VMI_push_j(x) {
|
5920
|
+
// Push the value of iterator index j.
|
5699
5921
|
const j = valueOfIndexVariable('j');
|
5700
5922
|
if(DEBUGGING) console.log('push j = ' + j);
|
5701
5923
|
x.push(j);
|
5702
5924
|
}
|
5703
5925
|
|
5704
|
-
function VMI_push_k(x
|
5705
|
-
//
|
5926
|
+
function VMI_push_k(x) {
|
5927
|
+
// Push the value of iterator index k.
|
5706
5928
|
const k = valueOfIndexVariable('k');
|
5707
5929
|
if(DEBUGGING) console.log('push k = ' + k);
|
5708
5930
|
x.push(k);
|
5709
5931
|
}
|
5710
5932
|
|
5711
5933
|
function pushTimeStepsPerTimeUnit(x, unit) {
|
5712
|
-
// AUXILIARY FUNCTION for the VMI_push_(time unit) instructions
|
5713
|
-
//
|
5934
|
+
// AUXILIARY FUNCTION for the VMI_push_(time unit) instructions.
|
5935
|
+
// Push the number of model time steps represented by 1 unit.
|
5936
|
+
// NOTE: This will typically be a real number -- no rounding.
|
5714
5937
|
const t = VM.time_unit_values[unit] / MODEL.time_scale /
|
5715
5938
|
VM.time_unit_values[MODEL.time_unit];
|
5716
5939
|
if(DEBUGGING) console.log(`push ${unit} = ${VM.sig4Dig(t)}`);
|
5717
5940
|
x.push(t);
|
5718
5941
|
}
|
5719
5942
|
|
5720
|
-
function VMI_push_year(x
|
5721
|
-
//
|
5943
|
+
function VMI_push_year(x) {
|
5944
|
+
// Push the number of time steps in one year.
|
5722
5945
|
pushTimeStepsPerTimeUnit(x, 'year');
|
5723
5946
|
}
|
5724
5947
|
|
5725
|
-
function VMI_push_week(x
|
5726
|
-
//
|
5948
|
+
function VMI_push_week(x) {
|
5949
|
+
// Push the number of time steps in one week.
|
5727
5950
|
pushTimeStepsPerTimeUnit(x, 'week');
|
5728
5951
|
}
|
5729
5952
|
|
5730
|
-
function VMI_push_day(x
|
5731
|
-
//
|
5953
|
+
function VMI_push_day(x) {
|
5954
|
+
// Push the number of time steps in one day.
|
5732
5955
|
pushTimeStepsPerTimeUnit(x, 'day');
|
5733
5956
|
}
|
5734
5957
|
|
5735
|
-
function VMI_push_hour(x
|
5736
|
-
//
|
5958
|
+
function VMI_push_hour(x) {
|
5959
|
+
// Push the number of time steps in one hour.
|
5737
5960
|
pushTimeStepsPerTimeUnit(x, 'hour');
|
5738
5961
|
}
|
5739
5962
|
|
5740
|
-
function VMI_push_minute(x
|
5741
|
-
//
|
5963
|
+
function VMI_push_minute(x) {
|
5964
|
+
// Push the number of time steps in one minute.
|
5742
5965
|
pushTimeStepsPerTimeUnit(x, 'minute');
|
5743
5966
|
}
|
5744
5967
|
|
5745
|
-
function VMI_push_second(x
|
5746
|
-
//
|
5968
|
+
function VMI_push_second(x) {
|
5969
|
+
// Push the number of time steps in one second.
|
5747
5970
|
pushTimeStepsPerTimeUnit(x, 'second');
|
5748
5971
|
}
|
5749
5972
|
|
5750
|
-
function VMI_push_contextual_number(x
|
5751
|
-
//
|
5973
|
+
function VMI_push_contextual_number(x) {
|
5974
|
+
// Push the numeric value of the context-sensitive number #.
|
5752
5975
|
const n = valueOfNumberSign(x);
|
5753
5976
|
if(DEBUGGING) {
|
5754
5977
|
console.log('push contextual number: # = ' + VM.sig2Dig(n));
|
@@ -5759,11 +5982,12 @@ function VMI_push_contextual_number(x, empty) {
|
|
5759
5982
|
/* VM instruction helper functions */
|
5760
5983
|
|
5761
5984
|
function valueOfNumberSign(x) {
|
5762
|
-
//
|
5763
|
-
// NOTE:
|
5764
|
-
// ending on digits, or
|
5765
|
-
// is the number its name or any of its prefixes ends on, but
|
5766
|
-
// more "creative" and can return the number context of nearby
|
5985
|
+
// Push the numeric value of the # sign for the context of expression `x`.
|
5986
|
+
// NOTE: This can be a wildcard match, an active experiment run selector
|
5987
|
+
// ending on digits, or the number context of an entity. The latter
|
5988
|
+
// typically is the number its name or any of its prefixes ends on, but
|
5989
|
+
// notes are more "creative" and can return the number context of nearby
|
5990
|
+
// entities.
|
5767
5991
|
let s = '!NO SELECTOR',
|
5768
5992
|
m = '!NO MATCH',
|
5769
5993
|
n = VM.UNDEFINED;
|
@@ -5792,7 +6016,7 @@ function valueOfNumberSign(x) {
|
|
5792
6016
|
}
|
5793
6017
|
}
|
5794
6018
|
// If selector contains no wildcards, get number context (typically
|
5795
|
-
// inferred from a number in the name of the object)
|
6019
|
+
// inferred from a number in the name of the object).
|
5796
6020
|
if(s.indexOf('*') < 0 && s.indexOf('?') < 0) {
|
5797
6021
|
const d = x.object.numberContext;
|
5798
6022
|
if(d) {
|
@@ -5802,7 +6026,7 @@ function valueOfNumberSign(x) {
|
|
5802
6026
|
}
|
5803
6027
|
}
|
5804
6028
|
}
|
5805
|
-
// For datasets, set the parent anchor to be the context-sensitive number
|
6029
|
+
// For datasets, set the parent anchor to be the context-sensitive number.
|
5806
6030
|
if(x.object instanceof Dataset) x.object.parent_anchor = n;
|
5807
6031
|
if(DEBUGGING) {
|
5808
6032
|
console.log(`context for # in expression for ${x.variableName}
|
@@ -5813,7 +6037,7 @@ function valueOfNumberSign(x) {
|
|
5813
6037
|
}
|
5814
6038
|
|
5815
6039
|
function relativeTimeStep(t, anchor, offset, dtm, x) {
|
5816
|
-
//
|
6040
|
+
// Return the relative time step, given t, anchor, offset,
|
5817
6041
|
// delta-t-multiplier and the expression being evaluated (to provide
|
5818
6042
|
// context for anchor #).
|
5819
6043
|
// NOTE: t = 1 corresponds with first time step of simulation period.
|
@@ -5833,7 +6057,7 @@ function relativeTimeStep(t, anchor, offset, dtm, x) {
|
|
5833
6057
|
if(DEBUGGING) {
|
5834
6058
|
console.log('Parent anchor', x.object.parent_anchor);
|
5835
6059
|
}
|
5836
|
-
// NOTE: For not array-type datasets, ^ is equivalent to
|
6060
|
+
// NOTE: For not array-type datasets, ^ is equivalent to #.
|
5837
6061
|
return x.object.parent_anchor;
|
5838
6062
|
}
|
5839
6063
|
return valueOfNumberSign(x) + offset;
|
@@ -5872,17 +6096,18 @@ function relativeTimeStep(t, anchor, offset, dtm, x) {
|
|
5872
6096
|
}
|
5873
6097
|
|
5874
6098
|
function twoOffsetTimeStep(t, a1, o1, a2, o2, dtm, x) {
|
5875
|
-
//
|
5876
|
-
// are anchor-offset shorthand for the debugging message,
|
5877
|
-
//
|
5878
|
-
//
|
5879
|
-
//
|
6099
|
+
// Return the list [rt, ao1, ao2] where `rt` is the time step, and
|
6100
|
+
// `ao1` and `ao2` are anchor-offset shorthand for the debugging message,
|
6101
|
+
// given `t`, the two anchors plus offsets, and the delta-t-multiplier.
|
6102
|
+
// NOTES:
|
6103
|
+
// (1) `dtm` will differ from 1 only for experiment results.
|
6104
|
+
// (2) Expression `x` is passed to provide context for evaluation of #.
|
5880
6105
|
let t1 = relativeTimeStep(t, a1, o1, dtm, x),
|
5881
6106
|
ao1 = [' @ ', a1, (o1 > 0 ? '+' : ''), (o1 ? o1 : ''),
|
5882
6107
|
' = ', t1].join(''),
|
5883
6108
|
ao2 = '';
|
5884
6109
|
if(o2 !== o1 || a2 !== a1) {
|
5885
|
-
// Two different offsets => use the midpoint as time (NO aggregation!)
|
6110
|
+
// Two different offsets => use the midpoint as time (NO aggregation!).
|
5886
6111
|
const t2 = relativeTimeStep(t, a2, o2, dtm, x);
|
5887
6112
|
ao2 = [' : ', a2, (o2 > 0 ? '+' : ''), (o2 ? o2 : ''), ' = ', t2].join('');
|
5888
6113
|
t1 = Math.floor((t1 + t2) / 2);
|
@@ -5894,51 +6119,51 @@ function twoOffsetTimeStep(t, a1, o1, a2, o2, dtm, x) {
|
|
5894
6119
|
/* VM instructions (continued) */
|
5895
6120
|
|
5896
6121
|
function VMI_push_var(x, args) {
|
5897
|
-
//
|
6122
|
+
// Push the value of the variable specified by `args`, being the list
|
5898
6123
|
// [obj, anchor1, offset1, anchor2, offset2] where `obj` can be a vector
|
5899
|
-
// or an expression, or a cluster unit balance specifier
|
6124
|
+
// or an expression, or a cluster unit balance specifier.
|
5900
6125
|
const
|
5901
6126
|
obj = args[0],
|
5902
|
-
// NOTE:
|
6127
|
+
// NOTE: Use the "local" time step for expression `x`.
|
5903
6128
|
tot = twoOffsetTimeStep(x.step[x.step.length - 1],
|
5904
6129
|
args[1], args[2], args[3], args[4], 1, x);
|
5905
6130
|
let t = tot[0];
|
5906
|
-
// Negative time step is evaluated as t = 0 (initial value), while t
|
5907
|
-
// optimization period is evaluated as its last time step
|
5908
|
-
// used in a self-referencing variable
|
6131
|
+
// Negative time step is evaluated as t = 0 (initial value), while t
|
6132
|
+
// beyond the optimization period is evaluated as its last time step
|
6133
|
+
// UNLESS t is used in a self-referencing variable.
|
5909
6134
|
const xv = obj.hasOwnProperty('xv');
|
5910
6135
|
if(!xv) {
|
5911
6136
|
t = Math.max(0, Math.min(
|
5912
6137
|
MODEL.end_period - MODEL.start_period + MODEL.look_ahead + 1, t));
|
5913
6138
|
}
|
5914
|
-
// Trace only now that time step t has been computed
|
6139
|
+
// Trace only now that time step t has been computed.
|
5915
6140
|
if(DEBUGGING) {
|
5916
6141
|
console.log('push var:', (xv ? '[SELF]' :
|
5917
6142
|
(obj instanceof Expression ? obj.text : '[' + obj.toString() + ']')),
|
5918
6143
|
tot[1] + ' ' + tot[2]);
|
5919
6144
|
}
|
5920
6145
|
if(Array.isArray(obj)) {
|
5921
|
-
// Object is a vector
|
6146
|
+
// Object is a vector.
|
5922
6147
|
let v = t < obj.length ? obj[t] : VM.UNDEFINED;
|
5923
|
-
// NOTE:
|
5924
|
-
// analysis, the value is multiplied by 1 + delta
|
6148
|
+
// NOTE: When the vector is the "active" parameter for sensitivity
|
6149
|
+
// analysis, the value is multiplied by 1 + delta %.
|
5925
6150
|
if(obj === MODEL.active_sensitivity_parameter) {
|
5926
|
-
// NOTE:
|
6151
|
+
// NOTE: Do NOT scale exceptional values.
|
5927
6152
|
if(v > VM.MINUS_INFINITY && v < VM.PLUS_INFINITY) {
|
5928
6153
|
v *= (1 + MODEL.sensitivity_delta * 0.01);
|
5929
6154
|
}
|
5930
6155
|
}
|
5931
6156
|
x.push(v);
|
5932
6157
|
} else if(xv) {
|
5933
|
-
// Variable references an earlier value computed for this expression `x
|
6158
|
+
// Variable references an earlier value computed for this expression `x`.
|
5934
6159
|
x.push(t >= 0 && t < x.vector.length ? x.vector[t] : obj.dv);
|
5935
6160
|
} else if(obj.hasOwnProperty('c') && obj.hasOwnProperty('u')) {
|
5936
|
-
// Object holds link lists for cluster balance computation
|
6161
|
+
// Object holds link lists for cluster balance computation.
|
5937
6162
|
x.push(MODEL.flowBalance(obj, t));
|
5938
6163
|
} else if(obj instanceof Expression) {
|
5939
6164
|
x.push(obj.result(t));
|
5940
6165
|
} else if(typeof obj === 'number') {
|
5941
|
-
// Object is a number
|
6166
|
+
// Object is a number.
|
5942
6167
|
x.push(obj);
|
5943
6168
|
} else {
|
5944
6169
|
console.log('ERROR: VMI_push_var object =', obj);
|
@@ -5947,17 +6172,17 @@ function VMI_push_var(x, args) {
|
|
5947
6172
|
}
|
5948
6173
|
|
5949
6174
|
function VMI_push_entity(x, args) {
|
5950
|
-
//
|
6175
|
+
// Push a special "entity reference" object based on `args`, being the
|
5951
6176
|
// list [obj, anchor1, offset1, anchor2, offset2] where `obj` has the
|
5952
|
-
// format {r: entity object, a: attribute}
|
5953
|
-
// The object that is pushed on the stack passes the entity, the
|
5954
|
-
// to use, and the time interval
|
6177
|
+
// format {r: entity object, a: attribute}.
|
6178
|
+
// The object that is pushed on the stack passes the entity, the
|
6179
|
+
// attribute to use, and the time interval.
|
5955
6180
|
const
|
5956
|
-
// NOTE:
|
6181
|
+
// NOTE: Use the "local" time step for expression `x`.
|
5957
6182
|
tot = twoOffsetTimeStep(x.step[x.step.length - 1],
|
5958
6183
|
args[1], args[2], args[3], args[4], 1, x),
|
5959
6184
|
er = {entity: args[0].r, attribute: args[0].a, t1: tot[0], t2: tot[1]};
|
5960
|
-
// Trace only now that time step t has been computed
|
6185
|
+
// Trace only now that time step t has been computed.
|
5961
6186
|
if(DEBUGGING) {
|
5962
6187
|
console.log(['push entity: ', er.entity.displayName, '|', er.attribute,
|
5963
6188
|
', t = ', er.t1, ' - ', er.t2].join(''));
|
@@ -5965,27 +6190,94 @@ function VMI_push_entity(x, args) {
|
|
5965
6190
|
x.push(er);
|
5966
6191
|
}
|
5967
6192
|
|
6193
|
+
function VMI_push_method(x, args) {
|
6194
|
+
// Push the result of the expression associated with the method (a
|
6195
|
+
// dataset modifier with a selector that starts with a colon).
|
6196
|
+
// The first element of the argument list specifies the method,
|
6197
|
+
// and possibly also the entity to be used as its object.
|
6198
|
+
// NOTE: Methods can only be called "as is" (without prefix) in a
|
6199
|
+
// method expression. The object of such "as is" method calls is
|
6200
|
+
// the object of the calling method expression `x`.
|
6201
|
+
const
|
6202
|
+
method = args[0].meq,
|
6203
|
+
mex = method.expression,
|
6204
|
+
// NOTE: If method object prefix is not specified in the first
|
6205
|
+
// argument, use the MOP of the calling method (if specified).
|
6206
|
+
mo_prefix = args[0].mo || x.method_object_prefix,
|
6207
|
+
// NOTE: Use the "local" time step for expression `x`.
|
6208
|
+
tot = twoOffsetTimeStep(x.step[x.step.length - 1],
|
6209
|
+
args[1], args[2], args[3], args[4], 1, x);
|
6210
|
+
if(!x.method_object && !mo_prefix) {
|
6211
|
+
console.log('ERROR: Undefined method object', x);
|
6212
|
+
x.push(VM.BAD_REF);
|
6213
|
+
return;
|
6214
|
+
}
|
6215
|
+
if(x.method_object) {
|
6216
|
+
// Set the method object to be used in VMI_push_wildcard_entity.
|
6217
|
+
mex.method_object = x.method_object;
|
6218
|
+
} else if(mex.isEligible(mo_prefix)) {
|
6219
|
+
mex.method_object_prefix = mo_prefix;
|
6220
|
+
} else {
|
6221
|
+
console.log('ERROR: ', mo_prefix, 'is not in eligible list of',
|
6222
|
+
method.selector, method.eligible_prefixes);
|
6223
|
+
x.push(VM.BAD_REF);
|
6224
|
+
return;
|
6225
|
+
}
|
6226
|
+
const
|
6227
|
+
t = tot[0],
|
6228
|
+
v = mex.result(t);
|
6229
|
+
// Clear the method object & prefix -- just to be neat.
|
6230
|
+
mex.method_object = null;
|
6231
|
+
mex.method_object_prefix = '';
|
6232
|
+
// Trace only now that time step t has been computed.
|
6233
|
+
if(DEBUGGING) {
|
6234
|
+
console.log('push method:', obj.displayName, method.selector,
|
6235
|
+
tot[1] + (tot[2] ? ':' + tot[2] : ''), 'value =', VM.sig4Dig(v));
|
6236
|
+
}
|
6237
|
+
x.push(v);
|
6238
|
+
}
|
6239
|
+
|
5968
6240
|
function VMI_push_wildcard_entity(x, args) {
|
5969
|
-
//
|
6241
|
+
// Push the value of (or reference to) an entity attribute, based on
|
5970
6242
|
// `args`, being the list [obj, anchor1, offset1, anchor2, offset2]
|
5971
6243
|
// where `obj` has the format {ee: list of eligible entities,
|
5972
6244
|
// n: name (with wildcard #), a: attribute, br: by reference (boolean)}
|
5973
|
-
|
5974
|
-
|
6245
|
+
let obj = null,
|
6246
|
+
nn = args[0].n;
|
5975
6247
|
const el = args[0].ee;
|
5976
|
-
|
5977
|
-
|
5978
|
-
|
5979
|
-
|
5980
|
-
|
5981
|
-
|
5982
|
-
|
5983
|
-
|
5984
|
-
|
5985
|
-
|
6248
|
+
// NOTE: Variables in method expressions that reference the object of
|
6249
|
+
// the method also code with this VM instruction, but then pass the
|
6250
|
+
// string "MO" instead of the list of eligible entities. This indicates
|
6251
|
+
// that the `method_object` property of expression `x` should be used.
|
6252
|
+
if(el === 'MO') {
|
6253
|
+
obj = x.method_object;
|
6254
|
+
if(!obj && x.method_object_prefix) {
|
6255
|
+
// Try to identity the object by the prefixed name.
|
6256
|
+
obj = MODEL.objectByName(x.method_object_prefix +
|
6257
|
+
UI.PREFIXER + nn.substring(1));
|
6258
|
+
}
|
6259
|
+
if(!obj) {
|
6260
|
+
console.log(`ERROR: Undefined method object`, x);
|
6261
|
+
x.push(VM.BAD_REF);
|
6262
|
+
return;
|
6263
|
+
}
|
6264
|
+
} else {
|
6265
|
+
// Select the first entity in `ee` that matches the wildcard vector
|
6266
|
+
// index of the expression `x` being executed.
|
6267
|
+
nn = nn.replace('#', x.wildcard_vector_index);
|
6268
|
+
for(let i = 0; !obj && i < el.length; i++) {
|
6269
|
+
if(el[i].name === nn) obj = el[i];
|
6270
|
+
}
|
6271
|
+
// If no match, then this indicates a bad reference.
|
6272
|
+
if(!obj) {
|
6273
|
+
console.log(`ERROR: no match for "${nn}" in eligible entity list`, el);
|
6274
|
+
x.push(VM.BAD_REF);
|
6275
|
+
return;
|
6276
|
+
}
|
5986
6277
|
}
|
5987
|
-
//
|
5988
|
-
//
|
6278
|
+
// Now `obj` should be an existing model entity.
|
6279
|
+
// If args[0] indicates "by reference", then VMI_push_entity can be
|
6280
|
+
// called with the appropriate parameters.
|
5989
6281
|
const attr = args[0].a || obj.defaultAttribute;
|
5990
6282
|
if(args[0].br) {
|
5991
6283
|
VMI_push_entity(x, {r: obj, a: attr});
|
@@ -6395,7 +6687,7 @@ function VMI_push_statistic(x, args) {
|
|
6395
6687
|
x.push(VM.UNDEFINED);
|
6396
6688
|
}
|
6397
6689
|
|
6398
|
-
function VMI_replace_undefined(x
|
6690
|
+
function VMI_replace_undefined(x) {
|
6399
6691
|
// Replaces one of the two top numbers on the stack by the other if the one
|
6400
6692
|
// is undefined
|
6401
6693
|
const d = x.pop(true); // TRUE denotes that "undefined" should be ignored as issue
|
@@ -6408,7 +6700,7 @@ function VMI_replace_undefined(x, empty) {
|
|
6408
6700
|
// NOTE: when the VM computes logical OR, AND and NOT, any non-zero number
|
6409
6701
|
// is interpreted as TRUE
|
6410
6702
|
|
6411
|
-
function VMI_or(x
|
6703
|
+
function VMI_or(x) {
|
6412
6704
|
// Performs a logical OR on the two top numbers on the stack
|
6413
6705
|
const d = x.pop();
|
6414
6706
|
if(d !== false) {
|
@@ -6417,7 +6709,7 @@ function VMI_or(x, empty) {
|
|
6417
6709
|
}
|
6418
6710
|
}
|
6419
6711
|
|
6420
|
-
function VMI_and(x
|
6712
|
+
function VMI_and(x) {
|
6421
6713
|
// Performs a logical AND on the two top numbers on the stack
|
6422
6714
|
const d = x.pop();
|
6423
6715
|
if(d !== false) {
|
@@ -6426,7 +6718,7 @@ function VMI_and(x, empty) {
|
|
6426
6718
|
}
|
6427
6719
|
}
|
6428
6720
|
|
6429
|
-
function VMI_not(x
|
6721
|
+
function VMI_not(x) {
|
6430
6722
|
// Performs a logical NOT on the top number of the stack
|
6431
6723
|
const d = x.top();
|
6432
6724
|
if(d !== false) {
|
@@ -6435,7 +6727,7 @@ function VMI_not(x, empty) {
|
|
6435
6727
|
}
|
6436
6728
|
}
|
6437
6729
|
|
6438
|
-
function VMI_abs(x
|
6730
|
+
function VMI_abs(x) {
|
6439
6731
|
// Replaces the top number of the stack by its absolute value
|
6440
6732
|
const d = x.top();
|
6441
6733
|
if(d !== false) {
|
@@ -6444,7 +6736,7 @@ function VMI_abs(x, empty) {
|
|
6444
6736
|
}
|
6445
6737
|
}
|
6446
6738
|
|
6447
|
-
function VMI_eq(x
|
6739
|
+
function VMI_eq(x) {
|
6448
6740
|
// Tests equality of the two top numbers on the stack
|
6449
6741
|
const d = x.pop();
|
6450
6742
|
if(d !== false) {
|
@@ -6453,7 +6745,7 @@ function VMI_eq(x, empty) {
|
|
6453
6745
|
}
|
6454
6746
|
}
|
6455
6747
|
|
6456
|
-
function VMI_ne(x
|
6748
|
+
function VMI_ne(x) {
|
6457
6749
|
// Tests inequality of the two top numbers on the stack
|
6458
6750
|
const d = x.pop();
|
6459
6751
|
if(d !== false) {
|
@@ -6462,7 +6754,7 @@ function VMI_ne(x, empty) {
|
|
6462
6754
|
}
|
6463
6755
|
}
|
6464
6756
|
|
6465
|
-
function VMI_lt(x
|
6757
|
+
function VMI_lt(x) {
|
6466
6758
|
// Tests whether second number on the stack is less than the top number
|
6467
6759
|
const d = x.pop();
|
6468
6760
|
if(d !== false) {
|
@@ -6471,7 +6763,7 @@ function VMI_lt(x, empty) {
|
|
6471
6763
|
}
|
6472
6764
|
}
|
6473
6765
|
|
6474
|
-
function VMI_gt(x
|
6766
|
+
function VMI_gt(x) {
|
6475
6767
|
// Tests whether second number on the stack is greater than the top number
|
6476
6768
|
const d = x.pop();
|
6477
6769
|
if(d !== false) {
|
@@ -6480,7 +6772,7 @@ function VMI_gt(x, empty) {
|
|
6480
6772
|
}
|
6481
6773
|
}
|
6482
6774
|
|
6483
|
-
function VMI_le(x
|
6775
|
+
function VMI_le(x) {
|
6484
6776
|
// Tests whether second number on the stack is less than, or equal to,
|
6485
6777
|
// the top number
|
6486
6778
|
const d = x.pop();
|
@@ -6490,7 +6782,7 @@ function VMI_le(x, empty) {
|
|
6490
6782
|
}
|
6491
6783
|
}
|
6492
6784
|
|
6493
|
-
function VMI_ge(x
|
6785
|
+
function VMI_ge(x) {
|
6494
6786
|
// Tests whether second number on the stack is greater than, or equal to,
|
6495
6787
|
// the top number
|
6496
6788
|
const d = x.pop();
|
@@ -6500,7 +6792,7 @@ function VMI_ge(x, empty) {
|
|
6500
6792
|
}
|
6501
6793
|
}
|
6502
6794
|
|
6503
|
-
function VMI_add(x
|
6795
|
+
function VMI_add(x) {
|
6504
6796
|
// Pops the top number on the stack and adds it to the new top number
|
6505
6797
|
const d = x.pop();
|
6506
6798
|
if(d !== false) {
|
@@ -6509,7 +6801,7 @@ function VMI_add(x, empty) {
|
|
6509
6801
|
}
|
6510
6802
|
}
|
6511
6803
|
|
6512
|
-
function VMI_sub(x
|
6804
|
+
function VMI_sub(x) {
|
6513
6805
|
// Pops the top number on the stack and subtracts it from the new
|
6514
6806
|
// top number
|
6515
6807
|
const d = x.pop();
|
@@ -6519,7 +6811,7 @@ function VMI_sub(x, empty) {
|
|
6519
6811
|
}
|
6520
6812
|
}
|
6521
6813
|
|
6522
|
-
function VMI_mul(x
|
6814
|
+
function VMI_mul(x) {
|
6523
6815
|
// Pops the top number on the stack and multiplies it with the new
|
6524
6816
|
// top number
|
6525
6817
|
const d = x.pop();
|
@@ -6529,7 +6821,7 @@ function VMI_mul(x, empty) {
|
|
6529
6821
|
}
|
6530
6822
|
}
|
6531
6823
|
|
6532
|
-
function VMI_div(x
|
6824
|
+
function VMI_div(x) {
|
6533
6825
|
// Pops the top number on the stack and divides the new top number
|
6534
6826
|
// by it. In case of division by zero, the top is replaced by #DIV0!
|
6535
6827
|
const d = x.pop();
|
@@ -6543,7 +6835,7 @@ function VMI_div(x, empty) {
|
|
6543
6835
|
}
|
6544
6836
|
}
|
6545
6837
|
|
6546
|
-
function VMI_mod(x
|
6838
|
+
function VMI_mod(x) {
|
6547
6839
|
// Pops the top number on the stack, divides the new top number by it
|
6548
6840
|
// (if non-zero, or it pushes error code #DIV0!), takes the fraction
|
6549
6841
|
// part, and multiplies this with the divider; in other words, it
|
@@ -6559,7 +6851,7 @@ function VMI_mod(x, empty) {
|
|
6559
6851
|
}
|
6560
6852
|
}
|
6561
6853
|
|
6562
|
-
function VMI_negate(x
|
6854
|
+
function VMI_negate(x) {
|
6563
6855
|
// Performs a negation on the top number of the stack
|
6564
6856
|
const d = x.top();
|
6565
6857
|
if(d !== false) {
|
@@ -6568,7 +6860,7 @@ function VMI_negate(x, empty) {
|
|
6568
6860
|
}
|
6569
6861
|
}
|
6570
6862
|
|
6571
|
-
function VMI_power(x
|
6863
|
+
function VMI_power(x) {
|
6572
6864
|
// Pops the top number on the stack and raises the new top number
|
6573
6865
|
// to its power
|
6574
6866
|
const d = x.pop();
|
@@ -6578,7 +6870,7 @@ function VMI_power(x, empty) {
|
|
6578
6870
|
}
|
6579
6871
|
}
|
6580
6872
|
|
6581
|
-
function VMI_sqrt(x
|
6873
|
+
function VMI_sqrt(x) {
|
6582
6874
|
// Replaces the top number of the stack by its square root, or by
|
6583
6875
|
// error code #VALUE! if the top number is negative
|
6584
6876
|
const d = x.top();
|
@@ -6592,7 +6884,7 @@ function VMI_sqrt(x, empty) {
|
|
6592
6884
|
}
|
6593
6885
|
}
|
6594
6886
|
|
6595
|
-
function VMI_sin(x
|
6887
|
+
function VMI_sin(x) {
|
6596
6888
|
// Replaces the top number X of the stack by sin(X)
|
6597
6889
|
const d = x.top();
|
6598
6890
|
if(d !== false) {
|
@@ -6601,7 +6893,7 @@ function VMI_sin(x, empty) {
|
|
6601
6893
|
}
|
6602
6894
|
}
|
6603
6895
|
|
6604
|
-
function VMI_cos(x
|
6896
|
+
function VMI_cos(x) {
|
6605
6897
|
// Replaces the top number X of the stack by cos(X)
|
6606
6898
|
const d = x.top();
|
6607
6899
|
if(d !== false) {
|
@@ -6610,7 +6902,7 @@ function VMI_cos(x, empty) {
|
|
6610
6902
|
}
|
6611
6903
|
}
|
6612
6904
|
|
6613
|
-
function VMI_atan(x
|
6905
|
+
function VMI_atan(x) {
|
6614
6906
|
// Replaces the top number X of the stack by atan(X)
|
6615
6907
|
const d = x.top();
|
6616
6908
|
if(d !== false) {
|
@@ -6619,7 +6911,7 @@ function VMI_atan(x, empty) {
|
|
6619
6911
|
}
|
6620
6912
|
}
|
6621
6913
|
|
6622
|
-
function VMI_ln(x
|
6914
|
+
function VMI_ln(x) {
|
6623
6915
|
// Replaces the top number X of the stack by ln(X), or by error
|
6624
6916
|
// code #VALUE! if X is negative
|
6625
6917
|
const d = x.top();
|
@@ -6633,7 +6925,7 @@ function VMI_ln(x, empty) {
|
|
6633
6925
|
}
|
6634
6926
|
}
|
6635
6927
|
|
6636
|
-
function VMI_exp(x
|
6928
|
+
function VMI_exp(x) {
|
6637
6929
|
// Replaces the top number X of the stack by exp(X)
|
6638
6930
|
const d = x.top();
|
6639
6931
|
if(d !== false) {
|
@@ -6642,7 +6934,7 @@ function VMI_exp(x, empty) {
|
|
6642
6934
|
}
|
6643
6935
|
}
|
6644
6936
|
|
6645
|
-
function VMI_log(x
|
6937
|
+
function VMI_log(x) {
|
6646
6938
|
// Pops the top number B from the stack and replaces the new top
|
6647
6939
|
// number A by A log B. NOTE: x = A log B <=> x = ln(B) / ln(A)
|
6648
6940
|
let d = x.pop();
|
@@ -6657,7 +6949,7 @@ function VMI_log(x, empty) {
|
|
6657
6949
|
}
|
6658
6950
|
}
|
6659
6951
|
|
6660
|
-
function VMI_round(x
|
6952
|
+
function VMI_round(x) {
|
6661
6953
|
// Replaces the top number X of the stack by round(X)
|
6662
6954
|
const d = x.top();
|
6663
6955
|
if(d !== false) {
|
@@ -6666,7 +6958,7 @@ function VMI_round(x, empty) {
|
|
6666
6958
|
}
|
6667
6959
|
}
|
6668
6960
|
|
6669
|
-
function VMI_int(x
|
6961
|
+
function VMI_int(x) {
|
6670
6962
|
// Replaces the top number X of the stack by its integer part
|
6671
6963
|
const d = x.top();
|
6672
6964
|
if(d !== false) {
|
@@ -6675,7 +6967,7 @@ function VMI_int(x, empty) {
|
|
6675
6967
|
}
|
6676
6968
|
}
|
6677
6969
|
|
6678
|
-
function VMI_fract(x
|
6970
|
+
function VMI_fract(x) {
|
6679
6971
|
// Replaces the top number X of the stack by its fraction part
|
6680
6972
|
const d = x.top();
|
6681
6973
|
if(d !== false) {
|
@@ -6684,7 +6976,7 @@ function VMI_fract(x, empty) {
|
|
6684
6976
|
}
|
6685
6977
|
}
|
6686
6978
|
|
6687
|
-
function VMI_exponential(x
|
6979
|
+
function VMI_exponential(x) {
|
6688
6980
|
// Replaces the top number X of the stack by a random number from the
|
6689
6981
|
// negative exponential distribution with parameter X (so X is the lambda,
|
6690
6982
|
// and the mean will be 1/X)
|
@@ -6696,7 +6988,7 @@ function VMI_exponential(x, empty) {
|
|
6696
6988
|
}
|
6697
6989
|
}
|
6698
6990
|
|
6699
|
-
function VMI_poisson(x
|
6991
|
+
function VMI_poisson(x) {
|
6700
6992
|
// Replaces the top number X of the stack by a random number from the
|
6701
6993
|
// poisson distribution with parameter X (so X is the mean value lambda)
|
6702
6994
|
const d = x.top();
|
@@ -6707,7 +6999,7 @@ function VMI_poisson(x, empty) {
|
|
6707
6999
|
}
|
6708
7000
|
}
|
6709
7001
|
|
6710
|
-
function VMI_binomial(x
|
7002
|
+
function VMI_binomial(x) {
|
6711
7003
|
// Replaces the top list (!) A of the stack by Bin(A[0], A[1]), i.e., a random
|
6712
7004
|
// number from the binomial distribution with n = A[0] and p = A[1]
|
6713
7005
|
const d = x.top();
|
@@ -6723,7 +7015,7 @@ function VMI_binomial(x, empty) {
|
|
6723
7015
|
}
|
6724
7016
|
}
|
6725
7017
|
|
6726
|
-
function VMI_normal(x
|
7018
|
+
function VMI_normal(x) {
|
6727
7019
|
// Replaces the top list (!) A of the stack by N(A[0], A[1]), i.e., a random
|
6728
7020
|
// number from the normal distribution with mu = A[0] and sigma = A[1]
|
6729
7021
|
const d = x.top();
|
@@ -6739,7 +7031,7 @@ function VMI_normal(x, empty) {
|
|
6739
7031
|
}
|
6740
7032
|
}
|
6741
7033
|
|
6742
|
-
function VMI_weibull(x
|
7034
|
+
function VMI_weibull(x) {
|
6743
7035
|
// Replaces the top list (!) A of the stack by Weibull(A[0], A[1]), i.e., a
|
6744
7036
|
// random number from the Weibull distribution with lambda = A[0] and k = A[1]
|
6745
7037
|
const d = x.top();
|
@@ -6755,7 +7047,7 @@ function VMI_weibull(x, empty) {
|
|
6755
7047
|
}
|
6756
7048
|
}
|
6757
7049
|
|
6758
|
-
function VMI_triangular(x
|
7050
|
+
function VMI_triangular(x) {
|
6759
7051
|
// Replaces the top list (!) A of the stack by Tri(A[0], A[1]), A[2]), i.e.,
|
6760
7052
|
// a random number from the triangular distribution with a = A[0], b = A[1],
|
6761
7053
|
// and c = A[2]. NOTE: if only 2 parameters are passed, c is assumed to equal
|
@@ -6773,7 +7065,7 @@ function VMI_triangular(x, empty) {
|
|
6773
7065
|
}
|
6774
7066
|
}
|
6775
7067
|
|
6776
|
-
function VMI_npv(x
|
7068
|
+
function VMI_npv(x) {
|
6777
7069
|
// Replaces the top list (!) A of the stack by the net present value (NPV)
|
6778
7070
|
// of the arguments in A. A[0] is the interest rate r, A[1] is the number of
|
6779
7071
|
// time periods n. If A has only 1 or 2 elements, the NPV is 0. If A has 3
|
@@ -6813,7 +7105,7 @@ function VMI_npv(x, empty) {
|
|
6813
7105
|
}
|
6814
7106
|
}
|
6815
7107
|
|
6816
|
-
function VMI_min(x
|
7108
|
+
function VMI_min(x) {
|
6817
7109
|
// Replaces the top list (!) A of the stack by the lowest value in this list
|
6818
7110
|
// NOTE: if A is not a list, A is left on the stack
|
6819
7111
|
const d = x.top();
|
@@ -6825,7 +7117,7 @@ function VMI_min(x, empty) {
|
|
6825
7117
|
}
|
6826
7118
|
}
|
6827
7119
|
|
6828
|
-
function VMI_max(x
|
7120
|
+
function VMI_max(x) {
|
6829
7121
|
// Replaces the top list (!) A of the stack by the highest value in this list
|
6830
7122
|
// NOTE: if A is not a list, A is left on the stack
|
6831
7123
|
const d = x.top();
|
@@ -6837,7 +7129,7 @@ function VMI_max(x, empty) {
|
|
6837
7129
|
}
|
6838
7130
|
}
|
6839
7131
|
|
6840
|
-
function VMI_concat(x
|
7132
|
+
function VMI_concat(x) {
|
6841
7133
|
// Pops the top number B from the stack, and then replaces the new top
|
6842
7134
|
// element A by [A, B] if A is a number, or adds B to A is A is a list
|
6843
7135
|
// of numbers (!) or
|
@@ -6882,20 +7174,20 @@ function VMI_jump_if_false(x, index) {
|
|
6882
7174
|
}
|
6883
7175
|
}
|
6884
7176
|
|
6885
|
-
function VMI_pop_false(x
|
7177
|
+
function VMI_pop_false(x) {
|
6886
7178
|
// Removes the top value from the stack, which should be 0 or
|
6887
7179
|
// VM.UNDEFINED (but this is not checked)
|
6888
7180
|
const r = x.stack.pop();
|
6889
7181
|
if(DEBUGGING) console.log(`POP-FALSE (${r})`);
|
6890
7182
|
}
|
6891
7183
|
|
6892
|
-
function VMI_if_then(x
|
7184
|
+
function VMI_if_then(x) {
|
6893
7185
|
// NO operation -- as of version 1.0.14, this function only serves as
|
6894
7186
|
// operator symbol, and its executions would indicate an error
|
6895
7187
|
console.log('WARNING: this IF-THEN instruction is obsolete!');
|
6896
7188
|
}
|
6897
7189
|
|
6898
|
-
function VMI_if_else(x
|
7190
|
+
function VMI_if_else(x) {
|
6899
7191
|
// NO operation -- as of version 1.0.14, this function only serves as
|
6900
7192
|
// operator symbol, and its executions would indicate an error
|
6901
7193
|
console.log('WARNING: this IF-THEN instruction is obsolete!');
|
@@ -7824,7 +8116,7 @@ const
|
|
7824
8116
|
|
7825
8117
|
// Each custom operator must have its own Virtual Machine instruction
|
7826
8118
|
|
7827
|
-
function VMI_profitable_units(x
|
8119
|
+
function VMI_profitable_units(x) {
|
7828
8120
|
// Replaces the argument list that should be at the top of the stack by the
|
7829
8121
|
// number of profitable units having a standard capacity (number), given the
|
7830
8122
|
// level (vector) of the process that represents multiple such units, the
|
@@ -7958,7 +8250,7 @@ DYNAMIC_SYMBOLS.push('npu');
|
|
7958
8250
|
LEVEL_BASED_CODES.push(VMI_profitable_units);
|
7959
8251
|
|
7960
8252
|
|
7961
|
-
function VMI_highest_cumulative_consecutive_deviation(x
|
8253
|
+
function VMI_highest_cumulative_consecutive_deviation(x) {
|
7962
8254
|
// Replaces the argument list that should be at the top of the stack by
|
7963
8255
|
// the HCCD (as in the function name) of the vector V that is passed as
|
7964
8256
|
// the first argument of this function. The HCCD value is computed by
|