linny-r 1.5.7 → 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.
@@ -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 (for all potentially time-dependent model parameters)
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
- // Returns TRUE if the owner is a dataset, and the attribute contains
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
- // Returns dt for dataset if this is a dataset modifier expression;
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
- // Returns a list of entities referenced in this expression.
102
- if(this.text.indexOf('[') < 0) return [];
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
- // NOTE: overrule `is_static` to make that the "initial level" attribute
123
- // is always evaluated for t=1
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
- // Clears result of previous computation (if any)
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: empty expressions (i.e., no text) may default to different
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
- (this.isStatic && !this.isWildcardExpression)) return this.vector;
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
- // Returns the result for the current time step if the model has been solved
401
- // (special values as human-readable string), or the expression as text
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 (undefined => empty cell in Excel)
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
- // Pushes a numeric value onto the computation stack
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
- // Returns the top element of the stack, or FALSE if the stack was empty
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
- // Returns the two top elements A and B as [A, B] after popping the top
445
- // element B from the stack, or FALSE if the stack contains fewer than 2
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 severest error on top of the stack
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 problem(s)? Then return the dyad.
544
+ // No issue(s)? Then return the dyad.
486
545
  return dyad;
487
546
  }
488
547
 
489
548
  retop(value) {
490
- // Replaces the top element of the stack by the new value
491
- // NOTE: does not check the stack length, as this instruction typically
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
- // Replaces occurrences of attribute `a1` by `a2` for all variables that
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 attribute
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 if `a` (without bracket and without spaces) indeed matches `a1`
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
- // Reduce whitespace to single space.
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
- let by_reference = name.startsWith('!');
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
- // Variable name may start with a colon to denote that the owner
1139
- // prefix should be added.
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
- // Start with wildcard equations, as these are likely to be few
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.data.length > 1 || (obj.data.length > 0 && !obj.periodic) ||
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
- if(obj.data.length > 1 || obj.data.length > 0 && !obj.periodic) {
1382
- this.is_static = false;
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
- // Unusual (?) combi, so let's assume dynamic.
1588
+ } else if(new Set(arg0).size > 1) {
1589
+ // Not all values qre equal => dynamic.
1390
1590
  this.is_static = false;
1391
- this.log('probably dynamic -- check below:');
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
- // Gets the next substring in the expression that is a valid symbol
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
- // Compiles expression into array of VM instructions `code`
1656
- // NOTE: always create a new code array instance, as it will typically
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 (semicolon)
1672
- // is pushed, and FALSE when a reducing operator (min, max, normal, weibull,
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 one
1729
- // to be pushed EXCEPT when this priority equals 9, as monadic operators
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: as of version 1.0.14, (a ? b : c) is implemented with
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: if : is not omitted, the code for the ELSE part must
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(block) {
2962
+ variablesLegend() {
2745
2963
  // Return a string with each variable code and full name on a
2746
2964
  // separate line.
2747
2965
  const
@@ -4743,8 +4961,12 @@ class VirtualMachine {
4743
4961
  // than the standard, as it should not go beyond the end time plus
4744
4962
  // look-ahead.
4745
4963
  if(this.block_count < this.nr_of_blocks) return this.chunk_length;
4746
- let rem = MODEL.runLength % MODEL.block_length;
4747
- if(rem === 0) rem = MODEL.block_length;
4964
+ // Last block length equals remainder of simulation period divided
4965
+ // by block length.
4966
+ let rem = (MODEL.runLength - MODEL.look_ahead) % MODEL.block_length;
4967
+ // If this remainder equals 0, the last block is a full chunk.
4968
+ if(rem === 0) return this.chunk_length;
4969
+ // Otherwise, the last block if remainder + look-ahead time steps.
4748
4970
  return rem + MODEL.look_ahead;
4749
4971
  }
4750
4972
 
@@ -5529,96 +5751,99 @@ Solver status = ${json.status}`);
5529
5751
  // automaton instruction has parameters x and a, where x is the computing
5530
5752
  // expression and a the argument, which may be a single number or a list
5531
5753
  // (array) of objects. When no arguments need to be passed, the second
5532
- // parameter is named 'empty' (and is not used).
5754
+ // parameter is omitted.
5533
5755
 
5534
5756
  function VMI_push_number(x, number) {
5535
- // Pushes a numeric constant on the VM stack
5757
+ // Push a numeric constant on the VM stack.
5536
5758
  if(DEBUGGING) console.log('push number = ' + number);
5537
5759
  x.push(number);
5538
5760
  }
5539
5761
 
5540
- function VMI_push_time_step(x, empty) {
5541
- // Pushes the current time step.
5542
- // NOTE: this is the "local" time step for expression `x` (which always
5543
- // 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.
5544
5766
  const t = x.step[x.step.length - 1] + MODEL.start_period - 1;
5545
5767
  if(DEBUGGING) console.log('push absolute t = ' + t);
5546
5768
  x.push(t);
5547
5769
  }
5548
5770
 
5549
- function VMI_push_delta_t(x, empty) {
5550
- // Pushes the duration of 1 time step (in hours).
5771
+ function VMI_push_delta_t(x) {
5772
+ // Push the duration of 1 time step (in hours).
5551
5773
  const dt = MODEL.time_scale * VM.time_unit_values[MODEL.time_unit];
5552
5774
  if(DEBUGGING) console.log('push delta-t = ' + dt);
5553
5775
  x.push(dt);
5554
5776
  }
5555
5777
 
5556
- function VMI_push_relative_time(x, empty) {
5557
- // Pushes the "local" time step for expression `x` (which always starts at 1)
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.
5558
5781
  const t = x.step[x.step.length - 1];
5559
5782
  if(DEBUGGING) console.log('push relative t = ' + t);
5560
5783
  x.push(t);
5561
5784
  }
5562
5785
 
5563
- function VMI_push_block_time(x, empty) {
5564
- // Pushes the "local" time step for expression `x` (which always starts at 1)
5565
- // adjusted for the first time step of the current block
5566
- const lt = x.step[x.step.length - 1] - 1,
5567
- bnr = Math.floor(lt / MODEL.block_length),
5568
- t = lt - bnr * MODEL.block_length + 1;
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;
5569
5793
  if(DEBUGGING) console.log('push block time bt = ' + t);
5570
5794
  x.push(t);
5571
5795
  }
5572
5796
 
5573
- function VMI_push_block_number(x, empty) {
5574
- // Pushes the block currently being optimized (block numbering starts at 1)
5797
+ function VMI_push_block_number(x) {
5798
+ // Push the number of the block currently being optimized.
5799
+ // NOTE: Block numbering starts at 1.
5575
5800
  const local_t = x.step[x.step.length - 1] - 1,
5576
5801
  bnr = Math.floor(local_t / MODEL.block_length) + 1;
5577
5802
  if(DEBUGGING) console.log('push current block number = ' + bnr);
5578
5803
  x.push(bnr);
5579
5804
  }
5580
5805
 
5581
- function VMI_push_run_length(x, empty) {
5582
- // Pushes the run length (excl. look-ahead!)
5806
+ function VMI_push_run_length(x) {
5807
+ // Push the run length (excl. look-ahead!).
5583
5808
  const n = MODEL.end_period - MODEL.start_period + 1;
5584
5809
  if(DEBUGGING) console.log('push run length N = ' + n);
5585
5810
  x.push(n);
5586
5811
  }
5587
5812
 
5588
- function VMI_push_block_length(x, empty) {
5589
- // Pushes the block length (is set via model settings dialog)
5813
+ function VMI_push_block_length(x) {
5814
+ // Push the block length.
5590
5815
  if(DEBUGGING) console.log('push block length n = ' + MODEL.block_length);
5591
5816
  x.push(MODEL.block_length);
5592
5817
  }
5593
5818
 
5594
- function VMI_push_look_ahead(x, empty) {
5595
- // Pushes the look-ahead
5819
+ function VMI_push_look_ahead(x) {
5820
+ // Push the look-ahead.
5596
5821
  if(DEBUGGING) console.log('push look-ahead l = ' + MODEL.look_ahead);
5597
5822
  x.push(MODEL.look_ahead);
5598
5823
  }
5599
5824
 
5600
- function VMI_push_round(x, empty) {
5601
- // Pushes the current round number (a=1, z=26, etc.)
5825
+ function VMI_push_round(x) {
5826
+ // Push the current round number (a=1, z=26, etc.).
5602
5827
  const r = VM.round_letters.indexOf(VM.round_sequence[VM.current_round]);
5603
5828
  if(DEBUGGING) console.log('push round number R = ' + r);
5604
5829
  x.push(r);
5605
5830
  }
5606
5831
 
5607
- function VMI_push_last_round(x, empty) {
5608
- // Pushes the last round number (a=1, z=26, etc.)
5832
+ function VMI_push_last_round(x) {
5833
+ // Push the last round number (a=1, z=26, etc.).
5609
5834
  const r = VM.round_letters.indexOf(VM.round_sequence[MODEL.rounds - 1]);
5610
5835
  if(DEBUGGING) console.log('push last round number LR = ' + r);
5611
5836
  x.push(r);
5612
5837
  }
5613
5838
 
5614
- function VMI_push_number_of_rounds(x, empty) {
5615
- // Pushes the number of rounds (= length of round sequence)
5839
+ function VMI_push_number_of_rounds(x) {
5840
+ // Push the number of rounds (= length of round sequence).
5616
5841
  if(DEBUGGING) console.log('push number of rounds NR = ' + MODEL.rounds);
5617
5842
  x.push(MODEL.rounds);
5618
5843
  }
5619
5844
 
5620
- function VMI_push_run_number(x, empty) {
5621
- // Pushes the number of the current run in the selected experiment (or 0)
5845
+ function VMI_push_run_number(x) {
5846
+ // Push the number of the current run in the selected experiment (or 0).
5622
5847
  const
5623
5848
  sx = EXPERIMENT_MANAGER.selected_experiment,
5624
5849
  nox = (sx ? ` (in ${sx.title})` : ' (no experiment)'),
@@ -5627,8 +5852,8 @@ function VMI_push_run_number(x, empty) {
5627
5852
  x.push(xr);
5628
5853
  }
5629
5854
 
5630
- function VMI_push_number_of_runs(x, empty) {
5631
- // Pushes the number of runs in the current experiment (0 if no experiment)
5855
+ function VMI_push_number_of_runs(x) {
5856
+ // Push the number of runs in the current experiment (0 if no experiment).
5632
5857
  const
5633
5858
  sx = EXPERIMENT_MANAGER.selected_experiment,
5634
5859
  nox = (sx ? `(in ${sx.title})` : '(no experiment)'),
@@ -5637,40 +5862,41 @@ function VMI_push_number_of_runs(x, empty) {
5637
5862
  x.push(nx);
5638
5863
  }
5639
5864
 
5640
- function VMI_push_random(x, empty) {
5641
- // Pushes a random number from the interval [0, 1)
5865
+ function VMI_push_random(x) {
5866
+ // Push a random number from the interval [0, 1).
5642
5867
  const r = Math.random();
5643
5868
  if(DEBUGGING) console.log('push random =', r);
5644
5869
  x.push(r);
5645
5870
  }
5646
5871
 
5647
- function VMI_push_pi(x, empty) {
5648
- // Pushes the goniometric constant pi
5872
+ function VMI_push_pi(x) {
5873
+ // Push the goniometric constant pi.
5649
5874
  if(DEBUGGING) console.log('push pi');
5650
5875
  x.push(Math.PI);
5651
5876
  }
5652
5877
 
5653
- function VMI_push_true(x, empty) {
5654
- // pushes the Boolean constant TRUE
5878
+ function VMI_push_true(x) {
5879
+ // Push the Boolean constant TRUE.
5655
5880
  if(DEBUGGING) console.log('push TRUE');
5656
5881
  x.push(1);
5657
5882
  }
5658
5883
 
5659
- function VMI_push_false(x, empty) {
5660
- // Pushes the Boolean constant FALSE
5884
+ function VMI_push_false(x) {
5885
+ // Push the Boolean constant FALSE.
5661
5886
  if(DEBUGGING) console.log('push FALSE');
5662
5887
  x.push(0);
5663
5888
  }
5664
5889
 
5665
- function VMI_push_infinity(x, empty) {
5666
- // Pushes the constant representing infinity for the solver
5890
+ function VMI_push_infinity(x) {
5891
+ // Push the constant representing infinity for the solver.
5667
5892
  if(DEBUGGING) console.log('push +INF');
5668
5893
  x.push(VM.PLUS_INFINITY);
5669
5894
  }
5670
5895
 
5671
5896
  function valueOfIndexVariable(v) {
5672
- // AUXILIARY FUNCTION for the VMI_push_(i, j or k) instructions
5673
- // Returns value of iterator index variable for the current experiment
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.
5674
5900
  if(MODEL.running_experiment) {
5675
5901
  const
5676
5902
  lead = v + '=',
@@ -5683,68 +5909,69 @@ function valueOfIndexVariable(v) {
5683
5909
  return 0;
5684
5910
  }
5685
5911
 
5686
- function VMI_push_i(x, empty) {
5687
- // Pushes the value of iterator index i
5912
+ function VMI_push_i(x) {
5913
+ // Push the value of iterator index i.
5688
5914
  const i = valueOfIndexVariable('i');
5689
5915
  if(DEBUGGING) console.log('push i = ' + i);
5690
5916
  x.push(i);
5691
5917
  }
5692
5918
 
5693
- function VMI_push_j(x, empty) {
5694
- // Pushes the value of iterator index j
5919
+ function VMI_push_j(x) {
5920
+ // Push the value of iterator index j.
5695
5921
  const j = valueOfIndexVariable('j');
5696
5922
  if(DEBUGGING) console.log('push j = ' + j);
5697
5923
  x.push(j);
5698
5924
  }
5699
5925
 
5700
- function VMI_push_k(x, empty) {
5701
- // Pushes the value of iterator index k
5926
+ function VMI_push_k(x) {
5927
+ // Push the value of iterator index k.
5702
5928
  const k = valueOfIndexVariable('k');
5703
5929
  if(DEBUGGING) console.log('push k = ' + k);
5704
5930
  x.push(k);
5705
5931
  }
5706
5932
 
5707
5933
  function pushTimeStepsPerTimeUnit(x, unit) {
5708
- // AUXILIARY FUNCTION for the VMI_push_(time unit) instructions
5709
- // Pushes the number of model time steps represented by 1 unit
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.
5710
5937
  const t = VM.time_unit_values[unit] / MODEL.time_scale /
5711
5938
  VM.time_unit_values[MODEL.time_unit];
5712
5939
  if(DEBUGGING) console.log(`push ${unit} = ${VM.sig4Dig(t)}`);
5713
5940
  x.push(t);
5714
5941
  }
5715
5942
 
5716
- function VMI_push_year(x, empty) {
5717
- // Pushes the number of time steps in one year
5943
+ function VMI_push_year(x) {
5944
+ // Push the number of time steps in one year.
5718
5945
  pushTimeStepsPerTimeUnit(x, 'year');
5719
5946
  }
5720
5947
 
5721
- function VMI_push_week(x, empty) {
5722
- // Pushes the number of time steps in one week
5948
+ function VMI_push_week(x) {
5949
+ // Push the number of time steps in one week.
5723
5950
  pushTimeStepsPerTimeUnit(x, 'week');
5724
5951
  }
5725
5952
 
5726
- function VMI_push_day(x, empty) {
5727
- // Pushes the number of time steps in one day
5953
+ function VMI_push_day(x) {
5954
+ // Push the number of time steps in one day.
5728
5955
  pushTimeStepsPerTimeUnit(x, 'day');
5729
5956
  }
5730
5957
 
5731
- function VMI_push_hour(x, empty) {
5732
- // Pushes the number of time steps in one hour
5958
+ function VMI_push_hour(x) {
5959
+ // Push the number of time steps in one hour.
5733
5960
  pushTimeStepsPerTimeUnit(x, 'hour');
5734
5961
  }
5735
5962
 
5736
- function VMI_push_minute(x, empty) {
5737
- // Pushes the number of time steps in one minute
5963
+ function VMI_push_minute(x) {
5964
+ // Push the number of time steps in one minute.
5738
5965
  pushTimeStepsPerTimeUnit(x, 'minute');
5739
5966
  }
5740
5967
 
5741
- function VMI_push_second(x, empty) {
5742
- // Pushes the number of time steps in one minute
5968
+ function VMI_push_second(x) {
5969
+ // Push the number of time steps in one second.
5743
5970
  pushTimeStepsPerTimeUnit(x, 'second');
5744
5971
  }
5745
5972
 
5746
- function VMI_push_contextual_number(x, empty) {
5747
- // Pushes the numeric value of the context-sensitive number #
5973
+ function VMI_push_contextual_number(x) {
5974
+ // Push the numeric value of the context-sensitive number #.
5748
5975
  const n = valueOfNumberSign(x);
5749
5976
  if(DEBUGGING) {
5750
5977
  console.log('push contextual number: # = ' + VM.sig2Dig(n));
@@ -5755,11 +5982,12 @@ function VMI_push_contextual_number(x, empty) {
5755
5982
  /* VM instruction helper functions */
5756
5983
 
5757
5984
  function valueOfNumberSign(x) {
5758
- // Pushes the numeric value of the # sign for the context of expression `x`
5759
- // NOTE: this can be a wildcard match, an active experiment run selector
5760
- // ending on digits, or tne number context of an entity. The latter typically
5761
- // is the number its name or any of its prefixes ends on, but notes are
5762
- // more "creative" and can return the number context of nearby entities.
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.
5763
5991
  let s = '!NO SELECTOR',
5764
5992
  m = '!NO MATCH',
5765
5993
  n = VM.UNDEFINED;
@@ -5788,7 +6016,7 @@ function valueOfNumberSign(x) {
5788
6016
  }
5789
6017
  }
5790
6018
  // If selector contains no wildcards, get number context (typically
5791
- // inferred from a number in the name of the object)
6019
+ // inferred from a number in the name of the object).
5792
6020
  if(s.indexOf('*') < 0 && s.indexOf('?') < 0) {
5793
6021
  const d = x.object.numberContext;
5794
6022
  if(d) {
@@ -5798,7 +6026,7 @@ function valueOfNumberSign(x) {
5798
6026
  }
5799
6027
  }
5800
6028
  }
5801
- // 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.
5802
6030
  if(x.object instanceof Dataset) x.object.parent_anchor = n;
5803
6031
  if(DEBUGGING) {
5804
6032
  console.log(`context for # in expression for ${x.variableName}
@@ -5809,7 +6037,7 @@ function valueOfNumberSign(x) {
5809
6037
  }
5810
6038
 
5811
6039
  function relativeTimeStep(t, anchor, offset, dtm, x) {
5812
- // Returns the relative time step, given t, anchor, offset,
6040
+ // Return the relative time step, given t, anchor, offset,
5813
6041
  // delta-t-multiplier and the expression being evaluated (to provide
5814
6042
  // context for anchor #).
5815
6043
  // NOTE: t = 1 corresponds with first time step of simulation period.
@@ -5829,7 +6057,7 @@ function relativeTimeStep(t, anchor, offset, dtm, x) {
5829
6057
  if(DEBUGGING) {
5830
6058
  console.log('Parent anchor', x.object.parent_anchor);
5831
6059
  }
5832
- // NOTE: For not array-type datasets, ^ is equivalent to #
6060
+ // NOTE: For not array-type datasets, ^ is equivalent to #.
5833
6061
  return x.object.parent_anchor;
5834
6062
  }
5835
6063
  return valueOfNumberSign(x) + offset;
@@ -5868,17 +6096,18 @@ function relativeTimeStep(t, anchor, offset, dtm, x) {
5868
6096
  }
5869
6097
 
5870
6098
  function twoOffsetTimeStep(t, a1, o1, a2, o2, dtm, x) {
5871
- // Returns the list [rt, ao1, ao2] where rt is the time step, and ao1 and ao2
5872
- // are anchor-offset shorthand for the debugging message, given t, two anchors
5873
- // and offsets, and the delta-t-multiplier
5874
- // NOTE: `dtm` will differ from 1 only for experiment results
5875
- // NOTE: expression `x` is passed to provide context for evaluation of #
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 #.
5876
6105
  let t1 = relativeTimeStep(t, a1, o1, dtm, x),
5877
6106
  ao1 = [' @ ', a1, (o1 > 0 ? '+' : ''), (o1 ? o1 : ''),
5878
6107
  ' = ', t1].join(''),
5879
6108
  ao2 = '';
5880
6109
  if(o2 !== o1 || a2 !== a1) {
5881
- // Two different offsets => use the midpoint as time (NO aggregation!)
6110
+ // Two different offsets => use the midpoint as time (NO aggregation!).
5882
6111
  const t2 = relativeTimeStep(t, a2, o2, dtm, x);
5883
6112
  ao2 = [' : ', a2, (o2 > 0 ? '+' : ''), (o2 ? o2 : ''), ' = ', t2].join('');
5884
6113
  t1 = Math.floor((t1 + t2) / 2);
@@ -5890,51 +6119,51 @@ function twoOffsetTimeStep(t, a1, o1, a2, o2, dtm, x) {
5890
6119
  /* VM instructions (continued) */
5891
6120
 
5892
6121
  function VMI_push_var(x, args) {
5893
- // Pushes the value of the variable specified by `args`, being the list
6122
+ // Push the value of the variable specified by `args`, being the list
5894
6123
  // [obj, anchor1, offset1, anchor2, offset2] where `obj` can be a vector
5895
- // or an expression, or a cluster unit balance specifier
6124
+ // or an expression, or a cluster unit balance specifier.
5896
6125
  const
5897
6126
  obj = args[0],
5898
- // NOTE: use the "local" time step for expression x
6127
+ // NOTE: Use the "local" time step for expression `x`.
5899
6128
  tot = twoOffsetTimeStep(x.step[x.step.length - 1],
5900
6129
  args[1], args[2], args[3], args[4], 1, x);
5901
6130
  let t = tot[0];
5902
- // Negative time step is evaluated as t = 0 (initial value), while t beyond
5903
- // optimization period is evaluated as its last time step UNLESS t is
5904
- // 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.
5905
6134
  const xv = obj.hasOwnProperty('xv');
5906
6135
  if(!xv) {
5907
6136
  t = Math.max(0, Math.min(
5908
6137
  MODEL.end_period - MODEL.start_period + MODEL.look_ahead + 1, t));
5909
6138
  }
5910
- // Trace only now that time step t has been computed
6139
+ // Trace only now that time step t has been computed.
5911
6140
  if(DEBUGGING) {
5912
6141
  console.log('push var:', (xv ? '[SELF]' :
5913
6142
  (obj instanceof Expression ? obj.text : '[' + obj.toString() + ']')),
5914
6143
  tot[1] + ' ' + tot[2]);
5915
6144
  }
5916
6145
  if(Array.isArray(obj)) {
5917
- // Object is a vector
6146
+ // Object is a vector.
5918
6147
  let v = t < obj.length ? obj[t] : VM.UNDEFINED;
5919
- // NOTE: when the vector is the "active" parameter for sensitivity
5920
- // 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 %.
5921
6150
  if(obj === MODEL.active_sensitivity_parameter) {
5922
- // NOTE: do NOT scale exceptional values
6151
+ // NOTE: Do NOT scale exceptional values.
5923
6152
  if(v > VM.MINUS_INFINITY && v < VM.PLUS_INFINITY) {
5924
6153
  v *= (1 + MODEL.sensitivity_delta * 0.01);
5925
6154
  }
5926
6155
  }
5927
6156
  x.push(v);
5928
6157
  } else if(xv) {
5929
- // Variable references an earlier value computed for this expression `x`
6158
+ // Variable references an earlier value computed for this expression `x`.
5930
6159
  x.push(t >= 0 && t < x.vector.length ? x.vector[t] : obj.dv);
5931
6160
  } else if(obj.hasOwnProperty('c') && obj.hasOwnProperty('u')) {
5932
- // Object holds link lists for cluster balance computation
6161
+ // Object holds link lists for cluster balance computation.
5933
6162
  x.push(MODEL.flowBalance(obj, t));
5934
6163
  } else if(obj instanceof Expression) {
5935
6164
  x.push(obj.result(t));
5936
6165
  } else if(typeof obj === 'number') {
5937
- // Object is a number
6166
+ // Object is a number.
5938
6167
  x.push(obj);
5939
6168
  } else {
5940
6169
  console.log('ERROR: VMI_push_var object =', obj);
@@ -5943,17 +6172,17 @@ function VMI_push_var(x, args) {
5943
6172
  }
5944
6173
 
5945
6174
  function VMI_push_entity(x, args) {
5946
- // Pushes a special "entity reference" object based on `args`, being the
6175
+ // Push a special "entity reference" object based on `args`, being the
5947
6176
  // list [obj, anchor1, offset1, anchor2, offset2] where `obj` has the
5948
- // format {r: entity object, a: attribute}
5949
- // The object that is pushed on the stack passes the entity, the attribute
5950
- // 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.
5951
6180
  const
5952
- // NOTE: use the "local" time step for expression x
6181
+ // NOTE: Use the "local" time step for expression `x`.
5953
6182
  tot = twoOffsetTimeStep(x.step[x.step.length - 1],
5954
6183
  args[1], args[2], args[3], args[4], 1, x),
5955
6184
  er = {entity: args[0].r, attribute: args[0].a, t1: tot[0], t2: tot[1]};
5956
- // Trace only now that time step t has been computed
6185
+ // Trace only now that time step t has been computed.
5957
6186
  if(DEBUGGING) {
5958
6187
  console.log(['push entity: ', er.entity.displayName, '|', er.attribute,
5959
6188
  ', t = ', er.t1, ' - ', er.t2].join(''));
@@ -5961,27 +6190,94 @@ function VMI_push_entity(x, args) {
5961
6190
  x.push(er);
5962
6191
  }
5963
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
+
5964
6240
  function VMI_push_wildcard_entity(x, args) {
5965
- // Pushes the value of (or reference to) an entity attribute, based on
6241
+ // Push the value of (or reference to) an entity attribute, based on
5966
6242
  // `args`, being the list [obj, anchor1, offset1, anchor2, offset2]
5967
6243
  // where `obj` has the format {ee: list of eligible entities,
5968
6244
  // n: name (with wildcard #), a: attribute, br: by reference (boolean)}
5969
- // First select the first entity in `ee` that matches the wildcard vector
5970
- // index of the expression `x` being executed.
6245
+ let obj = null,
6246
+ nn = args[0].n;
5971
6247
  const el = args[0].ee;
5972
- let nn = args[0].n.replace('#', x.wildcard_vector_index),
5973
- obj = null;
5974
- for(let i = 0; !obj && i < el.length; i++) {
5975
- if(el[i].name === nn) obj = el[i];
5976
- }
5977
- // If no match, then this indicates a bad reference.
5978
- if(!obj) {
5979
- console.log(`ERROR: no match for "${nn}" in eligible entity list`, el);
5980
- x.push(VM.BAD_REF);
5981
- return;
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
+ }
5982
6277
  }
5983
- // Otherwise, if args[0] indicates "by reference", then VMI_push_entity
5984
- // can be called with the appropriate parameters.
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.
5985
6281
  const attr = args[0].a || obj.defaultAttribute;
5986
6282
  if(args[0].br) {
5987
6283
  VMI_push_entity(x, {r: obj, a: attr});
@@ -6391,7 +6687,7 @@ function VMI_push_statistic(x, args) {
6391
6687
  x.push(VM.UNDEFINED);
6392
6688
  }
6393
6689
 
6394
- function VMI_replace_undefined(x, empty) {
6690
+ function VMI_replace_undefined(x) {
6395
6691
  // Replaces one of the two top numbers on the stack by the other if the one
6396
6692
  // is undefined
6397
6693
  const d = x.pop(true); // TRUE denotes that "undefined" should be ignored as issue
@@ -6404,7 +6700,7 @@ function VMI_replace_undefined(x, empty) {
6404
6700
  // NOTE: when the VM computes logical OR, AND and NOT, any non-zero number
6405
6701
  // is interpreted as TRUE
6406
6702
 
6407
- function VMI_or(x, empty) {
6703
+ function VMI_or(x) {
6408
6704
  // Performs a logical OR on the two top numbers on the stack
6409
6705
  const d = x.pop();
6410
6706
  if(d !== false) {
@@ -6413,7 +6709,7 @@ function VMI_or(x, empty) {
6413
6709
  }
6414
6710
  }
6415
6711
 
6416
- function VMI_and(x, empty) {
6712
+ function VMI_and(x) {
6417
6713
  // Performs a logical AND on the two top numbers on the stack
6418
6714
  const d = x.pop();
6419
6715
  if(d !== false) {
@@ -6422,7 +6718,7 @@ function VMI_and(x, empty) {
6422
6718
  }
6423
6719
  }
6424
6720
 
6425
- function VMI_not(x, empty) {
6721
+ function VMI_not(x) {
6426
6722
  // Performs a logical NOT on the top number of the stack
6427
6723
  const d = x.top();
6428
6724
  if(d !== false) {
@@ -6431,7 +6727,7 @@ function VMI_not(x, empty) {
6431
6727
  }
6432
6728
  }
6433
6729
 
6434
- function VMI_abs(x, empty) {
6730
+ function VMI_abs(x) {
6435
6731
  // Replaces the top number of the stack by its absolute value
6436
6732
  const d = x.top();
6437
6733
  if(d !== false) {
@@ -6440,7 +6736,7 @@ function VMI_abs(x, empty) {
6440
6736
  }
6441
6737
  }
6442
6738
 
6443
- function VMI_eq(x, empty) {
6739
+ function VMI_eq(x) {
6444
6740
  // Tests equality of the two top numbers on the stack
6445
6741
  const d = x.pop();
6446
6742
  if(d !== false) {
@@ -6449,7 +6745,7 @@ function VMI_eq(x, empty) {
6449
6745
  }
6450
6746
  }
6451
6747
 
6452
- function VMI_ne(x, empty) {
6748
+ function VMI_ne(x) {
6453
6749
  // Tests inequality of the two top numbers on the stack
6454
6750
  const d = x.pop();
6455
6751
  if(d !== false) {
@@ -6458,7 +6754,7 @@ function VMI_ne(x, empty) {
6458
6754
  }
6459
6755
  }
6460
6756
 
6461
- function VMI_lt(x, empty) {
6757
+ function VMI_lt(x) {
6462
6758
  // Tests whether second number on the stack is less than the top number
6463
6759
  const d = x.pop();
6464
6760
  if(d !== false) {
@@ -6467,7 +6763,7 @@ function VMI_lt(x, empty) {
6467
6763
  }
6468
6764
  }
6469
6765
 
6470
- function VMI_gt(x, empty) {
6766
+ function VMI_gt(x) {
6471
6767
  // Tests whether second number on the stack is greater than the top number
6472
6768
  const d = x.pop();
6473
6769
  if(d !== false) {
@@ -6476,7 +6772,7 @@ function VMI_gt(x, empty) {
6476
6772
  }
6477
6773
  }
6478
6774
 
6479
- function VMI_le(x, empty) {
6775
+ function VMI_le(x) {
6480
6776
  // Tests whether second number on the stack is less than, or equal to,
6481
6777
  // the top number
6482
6778
  const d = x.pop();
@@ -6486,7 +6782,7 @@ function VMI_le(x, empty) {
6486
6782
  }
6487
6783
  }
6488
6784
 
6489
- function VMI_ge(x, empty) {
6785
+ function VMI_ge(x) {
6490
6786
  // Tests whether second number on the stack is greater than, or equal to,
6491
6787
  // the top number
6492
6788
  const d = x.pop();
@@ -6496,7 +6792,7 @@ function VMI_ge(x, empty) {
6496
6792
  }
6497
6793
  }
6498
6794
 
6499
- function VMI_add(x, empty) {
6795
+ function VMI_add(x) {
6500
6796
  // Pops the top number on the stack and adds it to the new top number
6501
6797
  const d = x.pop();
6502
6798
  if(d !== false) {
@@ -6505,7 +6801,7 @@ function VMI_add(x, empty) {
6505
6801
  }
6506
6802
  }
6507
6803
 
6508
- function VMI_sub(x, empty) {
6804
+ function VMI_sub(x) {
6509
6805
  // Pops the top number on the stack and subtracts it from the new
6510
6806
  // top number
6511
6807
  const d = x.pop();
@@ -6515,7 +6811,7 @@ function VMI_sub(x, empty) {
6515
6811
  }
6516
6812
  }
6517
6813
 
6518
- function VMI_mul(x, empty) {
6814
+ function VMI_mul(x) {
6519
6815
  // Pops the top number on the stack and multiplies it with the new
6520
6816
  // top number
6521
6817
  const d = x.pop();
@@ -6525,7 +6821,7 @@ function VMI_mul(x, empty) {
6525
6821
  }
6526
6822
  }
6527
6823
 
6528
- function VMI_div(x, empty) {
6824
+ function VMI_div(x) {
6529
6825
  // Pops the top number on the stack and divides the new top number
6530
6826
  // by it. In case of division by zero, the top is replaced by #DIV0!
6531
6827
  const d = x.pop();
@@ -6539,7 +6835,7 @@ function VMI_div(x, empty) {
6539
6835
  }
6540
6836
  }
6541
6837
 
6542
- function VMI_mod(x, empty) {
6838
+ function VMI_mod(x) {
6543
6839
  // Pops the top number on the stack, divides the new top number by it
6544
6840
  // (if non-zero, or it pushes error code #DIV0!), takes the fraction
6545
6841
  // part, and multiplies this with the divider; in other words, it
@@ -6555,7 +6851,7 @@ function VMI_mod(x, empty) {
6555
6851
  }
6556
6852
  }
6557
6853
 
6558
- function VMI_negate(x, empty) {
6854
+ function VMI_negate(x) {
6559
6855
  // Performs a negation on the top number of the stack
6560
6856
  const d = x.top();
6561
6857
  if(d !== false) {
@@ -6564,7 +6860,7 @@ function VMI_negate(x, empty) {
6564
6860
  }
6565
6861
  }
6566
6862
 
6567
- function VMI_power(x, empty) {
6863
+ function VMI_power(x) {
6568
6864
  // Pops the top number on the stack and raises the new top number
6569
6865
  // to its power
6570
6866
  const d = x.pop();
@@ -6574,7 +6870,7 @@ function VMI_power(x, empty) {
6574
6870
  }
6575
6871
  }
6576
6872
 
6577
- function VMI_sqrt(x, empty) {
6873
+ function VMI_sqrt(x) {
6578
6874
  // Replaces the top number of the stack by its square root, or by
6579
6875
  // error code #VALUE! if the top number is negative
6580
6876
  const d = x.top();
@@ -6588,7 +6884,7 @@ function VMI_sqrt(x, empty) {
6588
6884
  }
6589
6885
  }
6590
6886
 
6591
- function VMI_sin(x, empty) {
6887
+ function VMI_sin(x) {
6592
6888
  // Replaces the top number X of the stack by sin(X)
6593
6889
  const d = x.top();
6594
6890
  if(d !== false) {
@@ -6597,7 +6893,7 @@ function VMI_sin(x, empty) {
6597
6893
  }
6598
6894
  }
6599
6895
 
6600
- function VMI_cos(x, empty) {
6896
+ function VMI_cos(x) {
6601
6897
  // Replaces the top number X of the stack by cos(X)
6602
6898
  const d = x.top();
6603
6899
  if(d !== false) {
@@ -6606,7 +6902,7 @@ function VMI_cos(x, empty) {
6606
6902
  }
6607
6903
  }
6608
6904
 
6609
- function VMI_atan(x, empty) {
6905
+ function VMI_atan(x) {
6610
6906
  // Replaces the top number X of the stack by atan(X)
6611
6907
  const d = x.top();
6612
6908
  if(d !== false) {
@@ -6615,7 +6911,7 @@ function VMI_atan(x, empty) {
6615
6911
  }
6616
6912
  }
6617
6913
 
6618
- function VMI_ln(x, empty) {
6914
+ function VMI_ln(x) {
6619
6915
  // Replaces the top number X of the stack by ln(X), or by error
6620
6916
  // code #VALUE! if X is negative
6621
6917
  const d = x.top();
@@ -6629,7 +6925,7 @@ function VMI_ln(x, empty) {
6629
6925
  }
6630
6926
  }
6631
6927
 
6632
- function VMI_exp(x, empty) {
6928
+ function VMI_exp(x) {
6633
6929
  // Replaces the top number X of the stack by exp(X)
6634
6930
  const d = x.top();
6635
6931
  if(d !== false) {
@@ -6638,7 +6934,7 @@ function VMI_exp(x, empty) {
6638
6934
  }
6639
6935
  }
6640
6936
 
6641
- function VMI_log(x, empty) {
6937
+ function VMI_log(x) {
6642
6938
  // Pops the top number B from the stack and replaces the new top
6643
6939
  // number A by A log B. NOTE: x = A log B <=> x = ln(B) / ln(A)
6644
6940
  let d = x.pop();
@@ -6653,7 +6949,7 @@ function VMI_log(x, empty) {
6653
6949
  }
6654
6950
  }
6655
6951
 
6656
- function VMI_round(x, empty) {
6952
+ function VMI_round(x) {
6657
6953
  // Replaces the top number X of the stack by round(X)
6658
6954
  const d = x.top();
6659
6955
  if(d !== false) {
@@ -6662,7 +6958,7 @@ function VMI_round(x, empty) {
6662
6958
  }
6663
6959
  }
6664
6960
 
6665
- function VMI_int(x, empty) {
6961
+ function VMI_int(x) {
6666
6962
  // Replaces the top number X of the stack by its integer part
6667
6963
  const d = x.top();
6668
6964
  if(d !== false) {
@@ -6671,7 +6967,7 @@ function VMI_int(x, empty) {
6671
6967
  }
6672
6968
  }
6673
6969
 
6674
- function VMI_fract(x, empty) {
6970
+ function VMI_fract(x) {
6675
6971
  // Replaces the top number X of the stack by its fraction part
6676
6972
  const d = x.top();
6677
6973
  if(d !== false) {
@@ -6680,7 +6976,7 @@ function VMI_fract(x, empty) {
6680
6976
  }
6681
6977
  }
6682
6978
 
6683
- function VMI_exponential(x, empty) {
6979
+ function VMI_exponential(x) {
6684
6980
  // Replaces the top number X of the stack by a random number from the
6685
6981
  // negative exponential distribution with parameter X (so X is the lambda,
6686
6982
  // and the mean will be 1/X)
@@ -6692,7 +6988,7 @@ function VMI_exponential(x, empty) {
6692
6988
  }
6693
6989
  }
6694
6990
 
6695
- function VMI_poisson(x, empty) {
6991
+ function VMI_poisson(x) {
6696
6992
  // Replaces the top number X of the stack by a random number from the
6697
6993
  // poisson distribution with parameter X (so X is the mean value lambda)
6698
6994
  const d = x.top();
@@ -6703,7 +6999,7 @@ function VMI_poisson(x, empty) {
6703
6999
  }
6704
7000
  }
6705
7001
 
6706
- function VMI_binomial(x, empty) {
7002
+ function VMI_binomial(x) {
6707
7003
  // Replaces the top list (!) A of the stack by Bin(A[0], A[1]), i.e., a random
6708
7004
  // number from the binomial distribution with n = A[0] and p = A[1]
6709
7005
  const d = x.top();
@@ -6719,7 +7015,7 @@ function VMI_binomial(x, empty) {
6719
7015
  }
6720
7016
  }
6721
7017
 
6722
- function VMI_normal(x, empty) {
7018
+ function VMI_normal(x) {
6723
7019
  // Replaces the top list (!) A of the stack by N(A[0], A[1]), i.e., a random
6724
7020
  // number from the normal distribution with mu = A[0] and sigma = A[1]
6725
7021
  const d = x.top();
@@ -6735,7 +7031,7 @@ function VMI_normal(x, empty) {
6735
7031
  }
6736
7032
  }
6737
7033
 
6738
- function VMI_weibull(x, empty) {
7034
+ function VMI_weibull(x) {
6739
7035
  // Replaces the top list (!) A of the stack by Weibull(A[0], A[1]), i.e., a
6740
7036
  // random number from the Weibull distribution with lambda = A[0] and k = A[1]
6741
7037
  const d = x.top();
@@ -6751,7 +7047,7 @@ function VMI_weibull(x, empty) {
6751
7047
  }
6752
7048
  }
6753
7049
 
6754
- function VMI_triangular(x, empty) {
7050
+ function VMI_triangular(x) {
6755
7051
  // Replaces the top list (!) A of the stack by Tri(A[0], A[1]), A[2]), i.e.,
6756
7052
  // a random number from the triangular distribution with a = A[0], b = A[1],
6757
7053
  // and c = A[2]. NOTE: if only 2 parameters are passed, c is assumed to equal
@@ -6769,7 +7065,7 @@ function VMI_triangular(x, empty) {
6769
7065
  }
6770
7066
  }
6771
7067
 
6772
- function VMI_npv(x, empty) {
7068
+ function VMI_npv(x) {
6773
7069
  // Replaces the top list (!) A of the stack by the net present value (NPV)
6774
7070
  // of the arguments in A. A[0] is the interest rate r, A[1] is the number of
6775
7071
  // time periods n. If A has only 1 or 2 elements, the NPV is 0. If A has 3
@@ -6809,7 +7105,7 @@ function VMI_npv(x, empty) {
6809
7105
  }
6810
7106
  }
6811
7107
 
6812
- function VMI_min(x, empty) {
7108
+ function VMI_min(x) {
6813
7109
  // Replaces the top list (!) A of the stack by the lowest value in this list
6814
7110
  // NOTE: if A is not a list, A is left on the stack
6815
7111
  const d = x.top();
@@ -6821,7 +7117,7 @@ function VMI_min(x, empty) {
6821
7117
  }
6822
7118
  }
6823
7119
 
6824
- function VMI_max(x, empty) {
7120
+ function VMI_max(x) {
6825
7121
  // Replaces the top list (!) A of the stack by the highest value in this list
6826
7122
  // NOTE: if A is not a list, A is left on the stack
6827
7123
  const d = x.top();
@@ -6833,7 +7129,7 @@ function VMI_max(x, empty) {
6833
7129
  }
6834
7130
  }
6835
7131
 
6836
- function VMI_concat(x, empty) {
7132
+ function VMI_concat(x) {
6837
7133
  // Pops the top number B from the stack, and then replaces the new top
6838
7134
  // element A by [A, B] if A is a number, or adds B to A is A is a list
6839
7135
  // of numbers (!) or
@@ -6878,20 +7174,20 @@ function VMI_jump_if_false(x, index) {
6878
7174
  }
6879
7175
  }
6880
7176
 
6881
- function VMI_pop_false(x, empty) {
7177
+ function VMI_pop_false(x) {
6882
7178
  // Removes the top value from the stack, which should be 0 or
6883
7179
  // VM.UNDEFINED (but this is not checked)
6884
7180
  const r = x.stack.pop();
6885
7181
  if(DEBUGGING) console.log(`POP-FALSE (${r})`);
6886
7182
  }
6887
7183
 
6888
- function VMI_if_then(x, empty) {
7184
+ function VMI_if_then(x) {
6889
7185
  // NO operation -- as of version 1.0.14, this function only serves as
6890
7186
  // operator symbol, and its executions would indicate an error
6891
7187
  console.log('WARNING: this IF-THEN instruction is obsolete!');
6892
7188
  }
6893
7189
 
6894
- function VMI_if_else(x, empty) {
7190
+ function VMI_if_else(x) {
6895
7191
  // NO operation -- as of version 1.0.14, this function only serves as
6896
7192
  // operator symbol, and its executions would indicate an error
6897
7193
  console.log('WARNING: this IF-THEN instruction is obsolete!');
@@ -7820,7 +8116,7 @@ const
7820
8116
 
7821
8117
  // Each custom operator must have its own Virtual Machine instruction
7822
8118
 
7823
- function VMI_profitable_units(x, empty) {
8119
+ function VMI_profitable_units(x) {
7824
8120
  // Replaces the argument list that should be at the top of the stack by the
7825
8121
  // number of profitable units having a standard capacity (number), given the
7826
8122
  // level (vector) of the process that represents multiple such units, the
@@ -7954,7 +8250,7 @@ DYNAMIC_SYMBOLS.push('npu');
7954
8250
  LEVEL_BASED_CODES.push(VMI_profitable_units);
7955
8251
 
7956
8252
 
7957
- function VMI_highest_cumulative_consecutive_deviation(x, empty) {
8253
+ function VMI_highest_cumulative_consecutive_deviation(x) {
7958
8254
  // Replaces the argument list that should be at the top of the stack by
7959
8255
  // the HCCD (as in the function name) of the vector V that is passed as
7960
8256
  // the first argument of this function. The HCCD value is computed by