linny-r 1.5.8 → 1.6.1

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
@@ -4101,7 +4319,7 @@ class VirtualMachine {
4101
4319
  // `cbl` is the cropped block length (applies only to last block).
4102
4320
  let bb = (block - 1) * MODEL.block_length + 1,
4103
4321
  abl = this.chunk_length,
4104
- cbl = this.actualBlockLength;
4322
+ cbl = this.actualBlockLength(block);
4105
4323
  // For the last block, crop the actual block length so it does not
4106
4324
  // extend beyond the simulation period (these results should be ignored).
4107
4325
  // If no results computed, preserve those already computed for the
@@ -4543,7 +4761,7 @@ class VirtualMachine {
4543
4761
 
4544
4762
  setupBlock() {
4545
4763
  if(DEBUGGING) this.logCode();
4546
- const abl = this.actualBlockLength;
4764
+ const abl = this.actualBlockLength(this.block_count);
4547
4765
  // NOTE: Tableau segment length is the number of time steps between
4548
4766
  // updates of the progress needle. The default progress needle interval
4549
4767
  // is calibrated for 1000 VMI instructions.
@@ -4737,12 +4955,12 @@ class VirtualMachine {
4737
4955
  setTimeout(() => VM.solveBlock(), 0);
4738
4956
  }
4739
4957
 
4740
- get actualBlockLength() {
4958
+ actualBlockLength(block) {
4741
4959
  // The actual block length is the number of time steps to be considered
4742
4960
  // by the solver; the abl of the last block is likely to be shorter
4743
4961
  // than the standard, as it should not go beyond the end time plus
4744
4962
  // look-ahead.
4745
- if(this.block_count < this.nr_of_blocks) return this.chunk_length;
4963
+ if(block < this.nr_of_blocks) return this.chunk_length;
4746
4964
  // Last block length equals remainder of simulation period divided
4747
4965
  // by block length.
4748
4966
  let rem = (MODEL.runLength - MODEL.look_ahead) % MODEL.block_length;
@@ -4787,7 +5005,7 @@ class VirtualMachine {
4787
5005
  // behavior can still be generated by limiting time series length to
4788
5006
  // the simulation period.
4789
5007
  const
4790
- abl = this.actualBlockLength,
5008
+ abl = this.actualBlockLength(this.block_count),
4791
5009
  // Get the number digits for variable names
4792
5010
  z = this.columnsInBlock.toString().length,
4793
5011
  // LP_solve uses semicolon as separator between equations
@@ -5022,7 +5240,7 @@ class VirtualMachine {
5022
5240
  // instead of row-based, hence for each column a separate string list.
5023
5241
  // NOTE: Columns are numbered from 1 to N, hence a dummy list for c=0.
5024
5242
  const
5025
- abl = this.actualBlockLength,
5243
+ abl = this.actualBlockLength(this.block_count),
5026
5244
  cols = [[]],
5027
5245
  rhs = [];
5028
5246
  let nrow = this.matrix.length,
@@ -5030,7 +5248,7 @@ class VirtualMachine {
5030
5248
  c,
5031
5249
  p,
5032
5250
  r;
5033
- this.numeric_issue = '';
5251
+ this.numeric_issue = '';
5034
5252
  this.lines = '';
5035
5253
  for(c = 1; c <= ncol; c++) cols.push([]);
5036
5254
  this.decimals = Math.max(nrow, ncol).toString().length;
@@ -5204,7 +5422,7 @@ class VirtualMachine {
5204
5422
  // Add the SOS section.
5205
5423
  if(this.sos_var_indices.length > 0) {
5206
5424
  this.lines += 'SOS\n';
5207
- const abl = this.actualBlockLength;
5425
+ const abl = this.actualBlockLength(this.block_count);
5208
5426
  let sos = 1;
5209
5427
  for(let j = 0; j < abl; j++) {
5210
5428
  for(let i = 0; i < this.sos_var_indices.length; i++) {
@@ -5376,7 +5594,7 @@ Solver status = ${json.status}`);
5376
5594
  const
5377
5595
  bwr = this.blockWithRound,
5378
5596
  fromt = (this.block_count - 1) * MODEL.block_length + 1,
5379
- abl = this.actualBlockLength;
5597
+ abl = this.actualBlockLength(this.block_count);
5380
5598
  MONITOR.updateBlockNumber(bwr);
5381
5599
  // NOTE: Add blank line to message to visually separate rounds.
5382
5600
  this.logMessage(this.block_count, ['\nSetting up block #', bwr,
@@ -5408,7 +5626,8 @@ Solver status = ${json.status}`);
5408
5626
  }
5409
5627
  // Generate lines of code in format that should be accepted by solver.
5410
5628
  if(this.solver_name === 'gurobi') {
5411
- this.writeMPSFormat();
5629
+ //this.writeMPSFormat();
5630
+ this.writeLpFormat(true);
5412
5631
  } else if(this.solver_name === 'scip' || this.solver_name === 'cplex') {
5413
5632
  // NOTE: The CPLEX LP format that is also used by SCIP differs from
5414
5633
  // the LP_solve format that was used by the first versions of Linny-R.
@@ -5533,96 +5752,99 @@ Solver status = ${json.status}`);
5533
5752
  // automaton instruction has parameters x and a, where x is the computing
5534
5753
  // expression and a the argument, which may be a single number or a list
5535
5754
  // (array) of objects. When no arguments need to be passed, the second
5536
- // parameter is named 'empty' (and is not used).
5755
+ // parameter is omitted.
5537
5756
 
5538
5757
  function VMI_push_number(x, number) {
5539
- // Pushes a numeric constant on the VM stack
5758
+ // Push a numeric constant on the VM stack.
5540
5759
  if(DEBUGGING) console.log('push number = ' + number);
5541
5760
  x.push(number);
5542
5761
  }
5543
5762
 
5544
- function VMI_push_time_step(x, empty) {
5545
- // Pushes the current time step.
5546
- // NOTE: this is the "local" time step for expression `x` (which always
5547
- // starts at 1), adjusted for the first time step of the simulation period
5763
+ function VMI_push_time_step(x) {
5764
+ // Push the current time step.
5765
+ // NOTE: This is the "local" time step for expression `x` (which always
5766
+ // starts at 1), adjusted for the first time step of the simulation period.
5548
5767
  const t = x.step[x.step.length - 1] + MODEL.start_period - 1;
5549
5768
  if(DEBUGGING) console.log('push absolute t = ' + t);
5550
5769
  x.push(t);
5551
5770
  }
5552
5771
 
5553
- function VMI_push_delta_t(x, empty) {
5554
- // Pushes the duration of 1 time step (in hours).
5772
+ function VMI_push_delta_t(x) {
5773
+ // Push the duration of 1 time step (in hours).
5555
5774
  const dt = MODEL.time_scale * VM.time_unit_values[MODEL.time_unit];
5556
5775
  if(DEBUGGING) console.log('push delta-t = ' + dt);
5557
5776
  x.push(dt);
5558
5777
  }
5559
5778
 
5560
- function VMI_push_relative_time(x, empty) {
5561
- // Pushes the "local" time step for expression `x` (which always starts at 1)
5779
+ function VMI_push_relative_time(x) {
5780
+ // Push the "local" time step for expression `x`.
5781
+ // NOTE: Time step for optimization period always starts at 1.
5562
5782
  const t = x.step[x.step.length - 1];
5563
5783
  if(DEBUGGING) console.log('push relative t = ' + t);
5564
5784
  x.push(t);
5565
5785
  }
5566
5786
 
5567
- function VMI_push_block_time(x, empty) {
5568
- // Pushes the "local" time step for expression `x` (which always starts at 1)
5569
- // adjusted for the first time step of the current block
5570
- const lt = x.step[x.step.length - 1] - 1,
5571
- bnr = Math.floor(lt / MODEL.block_length),
5572
- t = lt - bnr * MODEL.block_length + 1;
5787
+ function VMI_push_block_time(x) {
5788
+ // Push the "local" time step for expression `x` (which always starts
5789
+ // at 1) adjusted for the first time step of the current block.
5790
+ const
5791
+ lt = x.step[x.step.length - 1] - 1,
5792
+ bnr = Math.floor(lt / MODEL.block_length),
5793
+ t = lt - bnr * MODEL.block_length + 1;
5573
5794
  if(DEBUGGING) console.log('push block time bt = ' + t);
5574
5795
  x.push(t);
5575
5796
  }
5576
5797
 
5577
- function VMI_push_block_number(x, empty) {
5578
- // Pushes the block currently being optimized (block numbering starts at 1)
5798
+ function VMI_push_block_number(x) {
5799
+ // Push the number of the block currently being optimized.
5800
+ // NOTE: Block numbering starts at 1.
5579
5801
  const local_t = x.step[x.step.length - 1] - 1,
5580
5802
  bnr = Math.floor(local_t / MODEL.block_length) + 1;
5581
5803
  if(DEBUGGING) console.log('push current block number = ' + bnr);
5582
5804
  x.push(bnr);
5583
5805
  }
5584
5806
 
5585
- function VMI_push_run_length(x, empty) {
5586
- // Pushes the run length (excl. look-ahead!)
5807
+ function VMI_push_run_length(x) {
5808
+ // Push the run length (excl. look-ahead!).
5587
5809
  const n = MODEL.end_period - MODEL.start_period + 1;
5588
5810
  if(DEBUGGING) console.log('push run length N = ' + n);
5589
5811
  x.push(n);
5590
5812
  }
5591
5813
 
5592
- function VMI_push_block_length(x, empty) {
5593
- // Pushes the block length (is set via model settings dialog)
5814
+ function VMI_push_block_length(x) {
5815
+ // Push the block length.
5594
5816
  if(DEBUGGING) console.log('push block length n = ' + MODEL.block_length);
5595
5817
  x.push(MODEL.block_length);
5596
5818
  }
5597
5819
 
5598
- function VMI_push_look_ahead(x, empty) {
5599
- // Pushes the look-ahead
5820
+ function VMI_push_look_ahead(x) {
5821
+ // Push the look-ahead.
5600
5822
  if(DEBUGGING) console.log('push look-ahead l = ' + MODEL.look_ahead);
5601
5823
  x.push(MODEL.look_ahead);
5602
5824
  }
5603
5825
 
5604
- function VMI_push_round(x, empty) {
5605
- // Pushes the current round number (a=1, z=26, etc.)
5826
+ function VMI_push_round(x) {
5827
+ // Push the current round number (a=1, z=26, etc.).
5606
5828
  const r = VM.round_letters.indexOf(VM.round_sequence[VM.current_round]);
5607
5829
  if(DEBUGGING) console.log('push round number R = ' + r);
5608
5830
  x.push(r);
5609
5831
  }
5610
5832
 
5611
- function VMI_push_last_round(x, empty) {
5612
- // Pushes the last round number (a=1, z=26, etc.)
5833
+ function VMI_push_last_round(x) {
5834
+ // Push the last round number (a=1, z=26, etc.).
5613
5835
  const r = VM.round_letters.indexOf(VM.round_sequence[MODEL.rounds - 1]);
5614
5836
  if(DEBUGGING) console.log('push last round number LR = ' + r);
5615
5837
  x.push(r);
5616
5838
  }
5617
5839
 
5618
- function VMI_push_number_of_rounds(x, empty) {
5619
- // Pushes the number of rounds (= length of round sequence)
5840
+ function VMI_push_number_of_rounds(x) {
5841
+ // Push the number of rounds (= length of round sequence).
5620
5842
  if(DEBUGGING) console.log('push number of rounds NR = ' + MODEL.rounds);
5621
5843
  x.push(MODEL.rounds);
5622
5844
  }
5623
5845
 
5624
- function VMI_push_run_number(x, empty) {
5625
- // Pushes the number of the current run in the selected experiment (or 0)
5846
+ function VMI_push_run_number(x) {
5847
+ // Push the number of the current run in the selected experiment (or 0).
5626
5848
  const
5627
5849
  sx = EXPERIMENT_MANAGER.selected_experiment,
5628
5850
  nox = (sx ? ` (in ${sx.title})` : ' (no experiment)'),
@@ -5631,8 +5853,8 @@ function VMI_push_run_number(x, empty) {
5631
5853
  x.push(xr);
5632
5854
  }
5633
5855
 
5634
- function VMI_push_number_of_runs(x, empty) {
5635
- // Pushes the number of runs in the current experiment (0 if no experiment)
5856
+ function VMI_push_number_of_runs(x) {
5857
+ // Push the number of runs in the current experiment (0 if no experiment).
5636
5858
  const
5637
5859
  sx = EXPERIMENT_MANAGER.selected_experiment,
5638
5860
  nox = (sx ? `(in ${sx.title})` : '(no experiment)'),
@@ -5641,40 +5863,41 @@ function VMI_push_number_of_runs(x, empty) {
5641
5863
  x.push(nx);
5642
5864
  }
5643
5865
 
5644
- function VMI_push_random(x, empty) {
5645
- // Pushes a random number from the interval [0, 1)
5866
+ function VMI_push_random(x) {
5867
+ // Push a random number from the interval [0, 1).
5646
5868
  const r = Math.random();
5647
5869
  if(DEBUGGING) console.log('push random =', r);
5648
5870
  x.push(r);
5649
5871
  }
5650
5872
 
5651
- function VMI_push_pi(x, empty) {
5652
- // Pushes the goniometric constant pi
5873
+ function VMI_push_pi(x) {
5874
+ // Push the goniometric constant pi.
5653
5875
  if(DEBUGGING) console.log('push pi');
5654
5876
  x.push(Math.PI);
5655
5877
  }
5656
5878
 
5657
- function VMI_push_true(x, empty) {
5658
- // pushes the Boolean constant TRUE
5879
+ function VMI_push_true(x) {
5880
+ // Push the Boolean constant TRUE.
5659
5881
  if(DEBUGGING) console.log('push TRUE');
5660
5882
  x.push(1);
5661
5883
  }
5662
5884
 
5663
- function VMI_push_false(x, empty) {
5664
- // Pushes the Boolean constant FALSE
5885
+ function VMI_push_false(x) {
5886
+ // Push the Boolean constant FALSE.
5665
5887
  if(DEBUGGING) console.log('push FALSE');
5666
5888
  x.push(0);
5667
5889
  }
5668
5890
 
5669
- function VMI_push_infinity(x, empty) {
5670
- // Pushes the constant representing infinity for the solver
5891
+ function VMI_push_infinity(x) {
5892
+ // Push the constant representing infinity for the solver.
5671
5893
  if(DEBUGGING) console.log('push +INF');
5672
5894
  x.push(VM.PLUS_INFINITY);
5673
5895
  }
5674
5896
 
5675
5897
  function valueOfIndexVariable(v) {
5676
- // AUXILIARY FUNCTION for the VMI_push_(i, j or k) instructions
5677
- // Returns value of iterator index variable for the current experiment
5898
+ // AUXILIARY FUNCTION for the VMI_push_(i, j or k) instructions.
5899
+ // Return the value of the iterator index variable for the current
5900
+ // experiment.
5678
5901
  if(MODEL.running_experiment) {
5679
5902
  const
5680
5903
  lead = v + '=',
@@ -5687,68 +5910,69 @@ function valueOfIndexVariable(v) {
5687
5910
  return 0;
5688
5911
  }
5689
5912
 
5690
- function VMI_push_i(x, empty) {
5691
- // Pushes the value of iterator index i
5913
+ function VMI_push_i(x) {
5914
+ // Push the value of iterator index i.
5692
5915
  const i = valueOfIndexVariable('i');
5693
5916
  if(DEBUGGING) console.log('push i = ' + i);
5694
5917
  x.push(i);
5695
5918
  }
5696
5919
 
5697
- function VMI_push_j(x, empty) {
5698
- // Pushes the value of iterator index j
5920
+ function VMI_push_j(x) {
5921
+ // Push the value of iterator index j.
5699
5922
  const j = valueOfIndexVariable('j');
5700
5923
  if(DEBUGGING) console.log('push j = ' + j);
5701
5924
  x.push(j);
5702
5925
  }
5703
5926
 
5704
- function VMI_push_k(x, empty) {
5705
- // Pushes the value of iterator index k
5927
+ function VMI_push_k(x) {
5928
+ // Push the value of iterator index k.
5706
5929
  const k = valueOfIndexVariable('k');
5707
5930
  if(DEBUGGING) console.log('push k = ' + k);
5708
5931
  x.push(k);
5709
5932
  }
5710
5933
 
5711
5934
  function pushTimeStepsPerTimeUnit(x, unit) {
5712
- // AUXILIARY FUNCTION for the VMI_push_(time unit) instructions
5713
- // Pushes the number of model time steps represented by 1 unit
5935
+ // AUXILIARY FUNCTION for the VMI_push_(time unit) instructions.
5936
+ // Push the number of model time steps represented by 1 unit.
5937
+ // NOTE: This will typically be a real number -- no rounding.
5714
5938
  const t = VM.time_unit_values[unit] / MODEL.time_scale /
5715
5939
  VM.time_unit_values[MODEL.time_unit];
5716
5940
  if(DEBUGGING) console.log(`push ${unit} = ${VM.sig4Dig(t)}`);
5717
5941
  x.push(t);
5718
5942
  }
5719
5943
 
5720
- function VMI_push_year(x, empty) {
5721
- // Pushes the number of time steps in one year
5944
+ function VMI_push_year(x) {
5945
+ // Push the number of time steps in one year.
5722
5946
  pushTimeStepsPerTimeUnit(x, 'year');
5723
5947
  }
5724
5948
 
5725
- function VMI_push_week(x, empty) {
5726
- // Pushes the number of time steps in one week
5949
+ function VMI_push_week(x) {
5950
+ // Push the number of time steps in one week.
5727
5951
  pushTimeStepsPerTimeUnit(x, 'week');
5728
5952
  }
5729
5953
 
5730
- function VMI_push_day(x, empty) {
5731
- // Pushes the number of time steps in one day
5954
+ function VMI_push_day(x) {
5955
+ // Push the number of time steps in one day.
5732
5956
  pushTimeStepsPerTimeUnit(x, 'day');
5733
5957
  }
5734
5958
 
5735
- function VMI_push_hour(x, empty) {
5736
- // Pushes the number of time steps in one hour
5959
+ function VMI_push_hour(x) {
5960
+ // Push the number of time steps in one hour.
5737
5961
  pushTimeStepsPerTimeUnit(x, 'hour');
5738
5962
  }
5739
5963
 
5740
- function VMI_push_minute(x, empty) {
5741
- // Pushes the number of time steps in one minute
5964
+ function VMI_push_minute(x) {
5965
+ // Push the number of time steps in one minute.
5742
5966
  pushTimeStepsPerTimeUnit(x, 'minute');
5743
5967
  }
5744
5968
 
5745
- function VMI_push_second(x, empty) {
5746
- // Pushes the number of time steps in one minute
5969
+ function VMI_push_second(x) {
5970
+ // Push the number of time steps in one second.
5747
5971
  pushTimeStepsPerTimeUnit(x, 'second');
5748
5972
  }
5749
5973
 
5750
- function VMI_push_contextual_number(x, empty) {
5751
- // Pushes the numeric value of the context-sensitive number #
5974
+ function VMI_push_contextual_number(x) {
5975
+ // Push the numeric value of the context-sensitive number #.
5752
5976
  const n = valueOfNumberSign(x);
5753
5977
  if(DEBUGGING) {
5754
5978
  console.log('push contextual number: # = ' + VM.sig2Dig(n));
@@ -5759,11 +5983,12 @@ function VMI_push_contextual_number(x, empty) {
5759
5983
  /* VM instruction helper functions */
5760
5984
 
5761
5985
  function valueOfNumberSign(x) {
5762
- // Pushes the numeric value of the # sign for the context of expression `x`
5763
- // NOTE: this can be a wildcard match, an active experiment run selector
5764
- // ending on digits, or tne number context of an entity. The latter typically
5765
- // is the number its name or any of its prefixes ends on, but notes are
5766
- // more "creative" and can return the number context of nearby entities.
5986
+ // Push the numeric value of the # sign for the context of expression `x`.
5987
+ // NOTE: This can be a wildcard match, an active experiment run selector
5988
+ // ending on digits, or the number context of an entity. The latter
5989
+ // typically is the number its name or any of its prefixes ends on, but
5990
+ // notes are more "creative" and can return the number context of nearby
5991
+ // entities.
5767
5992
  let s = '!NO SELECTOR',
5768
5993
  m = '!NO MATCH',
5769
5994
  n = VM.UNDEFINED;
@@ -5792,7 +6017,7 @@ function valueOfNumberSign(x) {
5792
6017
  }
5793
6018
  }
5794
6019
  // If selector contains no wildcards, get number context (typically
5795
- // inferred from a number in the name of the object)
6020
+ // inferred from a number in the name of the object).
5796
6021
  if(s.indexOf('*') < 0 && s.indexOf('?') < 0) {
5797
6022
  const d = x.object.numberContext;
5798
6023
  if(d) {
@@ -5802,7 +6027,7 @@ function valueOfNumberSign(x) {
5802
6027
  }
5803
6028
  }
5804
6029
  }
5805
- // For datasets, set the parent anchor to be the context-sensitive number
6030
+ // For datasets, set the parent anchor to be the context-sensitive number.
5806
6031
  if(x.object instanceof Dataset) x.object.parent_anchor = n;
5807
6032
  if(DEBUGGING) {
5808
6033
  console.log(`context for # in expression for ${x.variableName}
@@ -5813,7 +6038,7 @@ function valueOfNumberSign(x) {
5813
6038
  }
5814
6039
 
5815
6040
  function relativeTimeStep(t, anchor, offset, dtm, x) {
5816
- // Returns the relative time step, given t, anchor, offset,
6041
+ // Return the relative time step, given t, anchor, offset,
5817
6042
  // delta-t-multiplier and the expression being evaluated (to provide
5818
6043
  // context for anchor #).
5819
6044
  // NOTE: t = 1 corresponds with first time step of simulation period.
@@ -5833,7 +6058,7 @@ function relativeTimeStep(t, anchor, offset, dtm, x) {
5833
6058
  if(DEBUGGING) {
5834
6059
  console.log('Parent anchor', x.object.parent_anchor);
5835
6060
  }
5836
- // NOTE: For not array-type datasets, ^ is equivalent to #
6061
+ // NOTE: For not array-type datasets, ^ is equivalent to #.
5837
6062
  return x.object.parent_anchor;
5838
6063
  }
5839
6064
  return valueOfNumberSign(x) + offset;
@@ -5872,17 +6097,18 @@ function relativeTimeStep(t, anchor, offset, dtm, x) {
5872
6097
  }
5873
6098
 
5874
6099
  function twoOffsetTimeStep(t, a1, o1, a2, o2, dtm, x) {
5875
- // Returns the list [rt, ao1, ao2] where rt is the time step, and ao1 and ao2
5876
- // are anchor-offset shorthand for the debugging message, given t, two anchors
5877
- // and offsets, and the delta-t-multiplier
5878
- // NOTE: `dtm` will differ from 1 only for experiment results
5879
- // NOTE: expression `x` is passed to provide context for evaluation of #
6100
+ // Return the list [rt, ao1, ao2] where `rt` is the time step, and
6101
+ // `ao1` and `ao2` are anchor-offset shorthand for the debugging message,
6102
+ // given `t`, the two anchors plus offsets, and the delta-t-multiplier.
6103
+ // NOTES:
6104
+ // (1) `dtm` will differ from 1 only for experiment results.
6105
+ // (2) Expression `x` is passed to provide context for evaluation of #.
5880
6106
  let t1 = relativeTimeStep(t, a1, o1, dtm, x),
5881
6107
  ao1 = [' @ ', a1, (o1 > 0 ? '+' : ''), (o1 ? o1 : ''),
5882
6108
  ' = ', t1].join(''),
5883
6109
  ao2 = '';
5884
6110
  if(o2 !== o1 || a2 !== a1) {
5885
- // Two different offsets => use the midpoint as time (NO aggregation!)
6111
+ // Two different offsets => use the midpoint as time (NO aggregation!).
5886
6112
  const t2 = relativeTimeStep(t, a2, o2, dtm, x);
5887
6113
  ao2 = [' : ', a2, (o2 > 0 ? '+' : ''), (o2 ? o2 : ''), ' = ', t2].join('');
5888
6114
  t1 = Math.floor((t1 + t2) / 2);
@@ -5894,51 +6120,51 @@ function twoOffsetTimeStep(t, a1, o1, a2, o2, dtm, x) {
5894
6120
  /* VM instructions (continued) */
5895
6121
 
5896
6122
  function VMI_push_var(x, args) {
5897
- // Pushes the value of the variable specified by `args`, being the list
6123
+ // Push the value of the variable specified by `args`, being the list
5898
6124
  // [obj, anchor1, offset1, anchor2, offset2] where `obj` can be a vector
5899
- // or an expression, or a cluster unit balance specifier
6125
+ // or an expression, or a cluster unit balance specifier.
5900
6126
  const
5901
6127
  obj = args[0],
5902
- // NOTE: use the "local" time step for expression x
6128
+ // NOTE: Use the "local" time step for expression `x`.
5903
6129
  tot = twoOffsetTimeStep(x.step[x.step.length - 1],
5904
6130
  args[1], args[2], args[3], args[4], 1, x);
5905
6131
  let t = tot[0];
5906
- // Negative time step is evaluated as t = 0 (initial value), while t beyond
5907
- // optimization period is evaluated as its last time step UNLESS t is
5908
- // used in a self-referencing variable
6132
+ // Negative time step is evaluated as t = 0 (initial value), while t
6133
+ // beyond the optimization period is evaluated as its last time step
6134
+ // UNLESS t is used in a self-referencing variable.
5909
6135
  const xv = obj.hasOwnProperty('xv');
5910
6136
  if(!xv) {
5911
6137
  t = Math.max(0, Math.min(
5912
6138
  MODEL.end_period - MODEL.start_period + MODEL.look_ahead + 1, t));
5913
6139
  }
5914
- // Trace only now that time step t has been computed
6140
+ // Trace only now that time step t has been computed.
5915
6141
  if(DEBUGGING) {
5916
6142
  console.log('push var:', (xv ? '[SELF]' :
5917
6143
  (obj instanceof Expression ? obj.text : '[' + obj.toString() + ']')),
5918
6144
  tot[1] + ' ' + tot[2]);
5919
6145
  }
5920
6146
  if(Array.isArray(obj)) {
5921
- // Object is a vector
6147
+ // Object is a vector.
5922
6148
  let v = t < obj.length ? obj[t] : VM.UNDEFINED;
5923
- // NOTE: when the vector is the "active" parameter for sensitivity
5924
- // analysis, the value is multiplied by 1 + delta %
6149
+ // NOTE: When the vector is the "active" parameter for sensitivity
6150
+ // analysis, the value is multiplied by 1 + delta %.
5925
6151
  if(obj === MODEL.active_sensitivity_parameter) {
5926
- // NOTE: do NOT scale exceptional values
6152
+ // NOTE: Do NOT scale exceptional values.
5927
6153
  if(v > VM.MINUS_INFINITY && v < VM.PLUS_INFINITY) {
5928
6154
  v *= (1 + MODEL.sensitivity_delta * 0.01);
5929
6155
  }
5930
6156
  }
5931
6157
  x.push(v);
5932
6158
  } else if(xv) {
5933
- // Variable references an earlier value computed for this expression `x`
6159
+ // Variable references an earlier value computed for this expression `x`.
5934
6160
  x.push(t >= 0 && t < x.vector.length ? x.vector[t] : obj.dv);
5935
6161
  } else if(obj.hasOwnProperty('c') && obj.hasOwnProperty('u')) {
5936
- // Object holds link lists for cluster balance computation
6162
+ // Object holds link lists for cluster balance computation.
5937
6163
  x.push(MODEL.flowBalance(obj, t));
5938
6164
  } else if(obj instanceof Expression) {
5939
6165
  x.push(obj.result(t));
5940
6166
  } else if(typeof obj === 'number') {
5941
- // Object is a number
6167
+ // Object is a number.
5942
6168
  x.push(obj);
5943
6169
  } else {
5944
6170
  console.log('ERROR: VMI_push_var object =', obj);
@@ -5947,17 +6173,17 @@ function VMI_push_var(x, args) {
5947
6173
  }
5948
6174
 
5949
6175
  function VMI_push_entity(x, args) {
5950
- // Pushes a special "entity reference" object based on `args`, being the
6176
+ // Push a special "entity reference" object based on `args`, being the
5951
6177
  // 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 attribute
5954
- // to use, and the time interval
6178
+ // format {r: entity object, a: attribute}.
6179
+ // The object that is pushed on the stack passes the entity, the
6180
+ // attribute to use, and the time interval.
5955
6181
  const
5956
- // NOTE: use the "local" time step for expression x
6182
+ // NOTE: Use the "local" time step for expression `x`.
5957
6183
  tot = twoOffsetTimeStep(x.step[x.step.length - 1],
5958
6184
  args[1], args[2], args[3], args[4], 1, x),
5959
6185
  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
6186
+ // Trace only now that time step t has been computed.
5961
6187
  if(DEBUGGING) {
5962
6188
  console.log(['push entity: ', er.entity.displayName, '|', er.attribute,
5963
6189
  ', t = ', er.t1, ' - ', er.t2].join(''));
@@ -5965,27 +6191,94 @@ function VMI_push_entity(x, args) {
5965
6191
  x.push(er);
5966
6192
  }
5967
6193
 
6194
+ function VMI_push_method(x, args) {
6195
+ // Push the result of the expression associated with the method (a
6196
+ // dataset modifier with a selector that starts with a colon).
6197
+ // The first element of the argument list specifies the method,
6198
+ // and possibly also the entity to be used as its object.
6199
+ // NOTE: Methods can only be called "as is" (without prefix) in a
6200
+ // method expression. The object of such "as is" method calls is
6201
+ // the object of the calling method expression `x`.
6202
+ const
6203
+ method = args[0].meq,
6204
+ mex = method.expression,
6205
+ // NOTE: If method object prefix is not specified in the first
6206
+ // argument, use the MOP of the calling method (if specified).
6207
+ mo_prefix = args[0].mo || x.method_object_prefix,
6208
+ // NOTE: Use the "local" time step for expression `x`.
6209
+ tot = twoOffsetTimeStep(x.step[x.step.length - 1],
6210
+ args[1], args[2], args[3], args[4], 1, x);
6211
+ if(!x.method_object && !mo_prefix) {
6212
+ console.log('ERROR: Undefined method object', x);
6213
+ x.push(VM.BAD_REF);
6214
+ return;
6215
+ }
6216
+ if(x.method_object) {
6217
+ // Set the method object to be used in VMI_push_wildcard_entity.
6218
+ mex.method_object = x.method_object;
6219
+ } else if(mex.isEligible(mo_prefix)) {
6220
+ mex.method_object_prefix = mo_prefix;
6221
+ } else {
6222
+ console.log('ERROR: ', mo_prefix, 'is not in eligible list of',
6223
+ method.selector, method.eligible_prefixes);
6224
+ x.push(VM.BAD_REF);
6225
+ return;
6226
+ }
6227
+ const
6228
+ t = tot[0],
6229
+ v = mex.result(t);
6230
+ // Clear the method object & prefix -- just to be neat.
6231
+ mex.method_object = null;
6232
+ mex.method_object_prefix = '';
6233
+ // Trace only now that time step t has been computed.
6234
+ if(DEBUGGING) {
6235
+ console.log('push method:', obj.displayName, method.selector,
6236
+ tot[1] + (tot[2] ? ':' + tot[2] : ''), 'value =', VM.sig4Dig(v));
6237
+ }
6238
+ x.push(v);
6239
+ }
6240
+
5968
6241
  function VMI_push_wildcard_entity(x, args) {
5969
- // Pushes the value of (or reference to) an entity attribute, based on
6242
+ // Push the value of (or reference to) an entity attribute, based on
5970
6243
  // `args`, being the list [obj, anchor1, offset1, anchor2, offset2]
5971
6244
  // where `obj` has the format {ee: list of eligible entities,
5972
6245
  // n: name (with wildcard #), a: attribute, br: by reference (boolean)}
5973
- // First select the first entity in `ee` that matches the wildcard vector
5974
- // index of the expression `x` being executed.
6246
+ let obj = null,
6247
+ nn = args[0].n;
5975
6248
  const el = args[0].ee;
5976
- let nn = args[0].n.replace('#', x.wildcard_vector_index),
5977
- obj = null;
5978
- for(let i = 0; !obj && i < el.length; i++) {
5979
- if(el[i].name === nn) obj = el[i];
5980
- }
5981
- // If no match, then this indicates a bad reference.
5982
- if(!obj) {
5983
- console.log(`ERROR: no match for "${nn}" in eligible entity list`, el);
5984
- x.push(VM.BAD_REF);
5985
- return;
6249
+ // NOTE: Variables in method expressions that reference the object of
6250
+ // the method also code with this VM instruction, but then pass the
6251
+ // string "MO" instead of the list of eligible entities. This indicates
6252
+ // that the `method_object` property of expression `x` should be used.
6253
+ if(el === 'MO') {
6254
+ obj = x.method_object;
6255
+ if(!obj && x.method_object_prefix) {
6256
+ // Try to identity the object by the prefixed name.
6257
+ obj = MODEL.objectByName(x.method_object_prefix +
6258
+ UI.PREFIXER + nn.substring(1));
6259
+ }
6260
+ if(!obj) {
6261
+ console.log(`ERROR: Undefined method object`, x);
6262
+ x.push(VM.BAD_REF);
6263
+ return;
6264
+ }
6265
+ } else {
6266
+ // Select the first entity in `ee` that matches the wildcard vector
6267
+ // index of the expression `x` being executed.
6268
+ nn = nn.replace('#', x.wildcard_vector_index);
6269
+ for(let i = 0; !obj && i < el.length; i++) {
6270
+ if(el[i].name === nn) obj = el[i];
6271
+ }
6272
+ // If no match, then this indicates a bad reference.
6273
+ if(!obj) {
6274
+ console.log(`ERROR: no match for "${nn}" in eligible entity list`, el);
6275
+ x.push(VM.BAD_REF);
6276
+ return;
6277
+ }
5986
6278
  }
5987
- // Otherwise, if args[0] indicates "by reference", then VMI_push_entity
5988
- // can be called with the appropriate parameters.
6279
+ // Now `obj` should be an existing model entity.
6280
+ // If args[0] indicates "by reference", then VMI_push_entity can be
6281
+ // called with the appropriate parameters.
5989
6282
  const attr = args[0].a || obj.defaultAttribute;
5990
6283
  if(args[0].br) {
5991
6284
  VMI_push_entity(x, {r: obj, a: attr});
@@ -6395,7 +6688,7 @@ function VMI_push_statistic(x, args) {
6395
6688
  x.push(VM.UNDEFINED);
6396
6689
  }
6397
6690
 
6398
- function VMI_replace_undefined(x, empty) {
6691
+ function VMI_replace_undefined(x) {
6399
6692
  // Replaces one of the two top numbers on the stack by the other if the one
6400
6693
  // is undefined
6401
6694
  const d = x.pop(true); // TRUE denotes that "undefined" should be ignored as issue
@@ -6408,7 +6701,7 @@ function VMI_replace_undefined(x, empty) {
6408
6701
  // NOTE: when the VM computes logical OR, AND and NOT, any non-zero number
6409
6702
  // is interpreted as TRUE
6410
6703
 
6411
- function VMI_or(x, empty) {
6704
+ function VMI_or(x) {
6412
6705
  // Performs a logical OR on the two top numbers on the stack
6413
6706
  const d = x.pop();
6414
6707
  if(d !== false) {
@@ -6417,7 +6710,7 @@ function VMI_or(x, empty) {
6417
6710
  }
6418
6711
  }
6419
6712
 
6420
- function VMI_and(x, empty) {
6713
+ function VMI_and(x) {
6421
6714
  // Performs a logical AND on the two top numbers on the stack
6422
6715
  const d = x.pop();
6423
6716
  if(d !== false) {
@@ -6426,7 +6719,7 @@ function VMI_and(x, empty) {
6426
6719
  }
6427
6720
  }
6428
6721
 
6429
- function VMI_not(x, empty) {
6722
+ function VMI_not(x) {
6430
6723
  // Performs a logical NOT on the top number of the stack
6431
6724
  const d = x.top();
6432
6725
  if(d !== false) {
@@ -6435,7 +6728,7 @@ function VMI_not(x, empty) {
6435
6728
  }
6436
6729
  }
6437
6730
 
6438
- function VMI_abs(x, empty) {
6731
+ function VMI_abs(x) {
6439
6732
  // Replaces the top number of the stack by its absolute value
6440
6733
  const d = x.top();
6441
6734
  if(d !== false) {
@@ -6444,7 +6737,7 @@ function VMI_abs(x, empty) {
6444
6737
  }
6445
6738
  }
6446
6739
 
6447
- function VMI_eq(x, empty) {
6740
+ function VMI_eq(x) {
6448
6741
  // Tests equality of the two top numbers on the stack
6449
6742
  const d = x.pop();
6450
6743
  if(d !== false) {
@@ -6453,7 +6746,7 @@ function VMI_eq(x, empty) {
6453
6746
  }
6454
6747
  }
6455
6748
 
6456
- function VMI_ne(x, empty) {
6749
+ function VMI_ne(x) {
6457
6750
  // Tests inequality of the two top numbers on the stack
6458
6751
  const d = x.pop();
6459
6752
  if(d !== false) {
@@ -6462,7 +6755,7 @@ function VMI_ne(x, empty) {
6462
6755
  }
6463
6756
  }
6464
6757
 
6465
- function VMI_lt(x, empty) {
6758
+ function VMI_lt(x) {
6466
6759
  // Tests whether second number on the stack is less than the top number
6467
6760
  const d = x.pop();
6468
6761
  if(d !== false) {
@@ -6471,7 +6764,7 @@ function VMI_lt(x, empty) {
6471
6764
  }
6472
6765
  }
6473
6766
 
6474
- function VMI_gt(x, empty) {
6767
+ function VMI_gt(x) {
6475
6768
  // Tests whether second number on the stack is greater than the top number
6476
6769
  const d = x.pop();
6477
6770
  if(d !== false) {
@@ -6480,7 +6773,7 @@ function VMI_gt(x, empty) {
6480
6773
  }
6481
6774
  }
6482
6775
 
6483
- function VMI_le(x, empty) {
6776
+ function VMI_le(x) {
6484
6777
  // Tests whether second number on the stack is less than, or equal to,
6485
6778
  // the top number
6486
6779
  const d = x.pop();
@@ -6490,7 +6783,7 @@ function VMI_le(x, empty) {
6490
6783
  }
6491
6784
  }
6492
6785
 
6493
- function VMI_ge(x, empty) {
6786
+ function VMI_ge(x) {
6494
6787
  // Tests whether second number on the stack is greater than, or equal to,
6495
6788
  // the top number
6496
6789
  const d = x.pop();
@@ -6500,7 +6793,7 @@ function VMI_ge(x, empty) {
6500
6793
  }
6501
6794
  }
6502
6795
 
6503
- function VMI_add(x, empty) {
6796
+ function VMI_add(x) {
6504
6797
  // Pops the top number on the stack and adds it to the new top number
6505
6798
  const d = x.pop();
6506
6799
  if(d !== false) {
@@ -6509,7 +6802,7 @@ function VMI_add(x, empty) {
6509
6802
  }
6510
6803
  }
6511
6804
 
6512
- function VMI_sub(x, empty) {
6805
+ function VMI_sub(x) {
6513
6806
  // Pops the top number on the stack and subtracts it from the new
6514
6807
  // top number
6515
6808
  const d = x.pop();
@@ -6519,7 +6812,7 @@ function VMI_sub(x, empty) {
6519
6812
  }
6520
6813
  }
6521
6814
 
6522
- function VMI_mul(x, empty) {
6815
+ function VMI_mul(x) {
6523
6816
  // Pops the top number on the stack and multiplies it with the new
6524
6817
  // top number
6525
6818
  const d = x.pop();
@@ -6529,7 +6822,7 @@ function VMI_mul(x, empty) {
6529
6822
  }
6530
6823
  }
6531
6824
 
6532
- function VMI_div(x, empty) {
6825
+ function VMI_div(x) {
6533
6826
  // Pops the top number on the stack and divides the new top number
6534
6827
  // by it. In case of division by zero, the top is replaced by #DIV0!
6535
6828
  const d = x.pop();
@@ -6543,7 +6836,7 @@ function VMI_div(x, empty) {
6543
6836
  }
6544
6837
  }
6545
6838
 
6546
- function VMI_mod(x, empty) {
6839
+ function VMI_mod(x) {
6547
6840
  // Pops the top number on the stack, divides the new top number by it
6548
6841
  // (if non-zero, or it pushes error code #DIV0!), takes the fraction
6549
6842
  // part, and multiplies this with the divider; in other words, it
@@ -6559,7 +6852,7 @@ function VMI_mod(x, empty) {
6559
6852
  }
6560
6853
  }
6561
6854
 
6562
- function VMI_negate(x, empty) {
6855
+ function VMI_negate(x) {
6563
6856
  // Performs a negation on the top number of the stack
6564
6857
  const d = x.top();
6565
6858
  if(d !== false) {
@@ -6568,7 +6861,7 @@ function VMI_negate(x, empty) {
6568
6861
  }
6569
6862
  }
6570
6863
 
6571
- function VMI_power(x, empty) {
6864
+ function VMI_power(x) {
6572
6865
  // Pops the top number on the stack and raises the new top number
6573
6866
  // to its power
6574
6867
  const d = x.pop();
@@ -6578,7 +6871,7 @@ function VMI_power(x, empty) {
6578
6871
  }
6579
6872
  }
6580
6873
 
6581
- function VMI_sqrt(x, empty) {
6874
+ function VMI_sqrt(x) {
6582
6875
  // Replaces the top number of the stack by its square root, or by
6583
6876
  // error code #VALUE! if the top number is negative
6584
6877
  const d = x.top();
@@ -6592,7 +6885,7 @@ function VMI_sqrt(x, empty) {
6592
6885
  }
6593
6886
  }
6594
6887
 
6595
- function VMI_sin(x, empty) {
6888
+ function VMI_sin(x) {
6596
6889
  // Replaces the top number X of the stack by sin(X)
6597
6890
  const d = x.top();
6598
6891
  if(d !== false) {
@@ -6601,7 +6894,7 @@ function VMI_sin(x, empty) {
6601
6894
  }
6602
6895
  }
6603
6896
 
6604
- function VMI_cos(x, empty) {
6897
+ function VMI_cos(x) {
6605
6898
  // Replaces the top number X of the stack by cos(X)
6606
6899
  const d = x.top();
6607
6900
  if(d !== false) {
@@ -6610,7 +6903,7 @@ function VMI_cos(x, empty) {
6610
6903
  }
6611
6904
  }
6612
6905
 
6613
- function VMI_atan(x, empty) {
6906
+ function VMI_atan(x) {
6614
6907
  // Replaces the top number X of the stack by atan(X)
6615
6908
  const d = x.top();
6616
6909
  if(d !== false) {
@@ -6619,7 +6912,7 @@ function VMI_atan(x, empty) {
6619
6912
  }
6620
6913
  }
6621
6914
 
6622
- function VMI_ln(x, empty) {
6915
+ function VMI_ln(x) {
6623
6916
  // Replaces the top number X of the stack by ln(X), or by error
6624
6917
  // code #VALUE! if X is negative
6625
6918
  const d = x.top();
@@ -6633,7 +6926,7 @@ function VMI_ln(x, empty) {
6633
6926
  }
6634
6927
  }
6635
6928
 
6636
- function VMI_exp(x, empty) {
6929
+ function VMI_exp(x) {
6637
6930
  // Replaces the top number X of the stack by exp(X)
6638
6931
  const d = x.top();
6639
6932
  if(d !== false) {
@@ -6642,7 +6935,7 @@ function VMI_exp(x, empty) {
6642
6935
  }
6643
6936
  }
6644
6937
 
6645
- function VMI_log(x, empty) {
6938
+ function VMI_log(x) {
6646
6939
  // Pops the top number B from the stack and replaces the new top
6647
6940
  // number A by A log B. NOTE: x = A log B <=> x = ln(B) / ln(A)
6648
6941
  let d = x.pop();
@@ -6657,7 +6950,7 @@ function VMI_log(x, empty) {
6657
6950
  }
6658
6951
  }
6659
6952
 
6660
- function VMI_round(x, empty) {
6953
+ function VMI_round(x) {
6661
6954
  // Replaces the top number X of the stack by round(X)
6662
6955
  const d = x.top();
6663
6956
  if(d !== false) {
@@ -6666,7 +6959,7 @@ function VMI_round(x, empty) {
6666
6959
  }
6667
6960
  }
6668
6961
 
6669
- function VMI_int(x, empty) {
6962
+ function VMI_int(x) {
6670
6963
  // Replaces the top number X of the stack by its integer part
6671
6964
  const d = x.top();
6672
6965
  if(d !== false) {
@@ -6675,7 +6968,7 @@ function VMI_int(x, empty) {
6675
6968
  }
6676
6969
  }
6677
6970
 
6678
- function VMI_fract(x, empty) {
6971
+ function VMI_fract(x) {
6679
6972
  // Replaces the top number X of the stack by its fraction part
6680
6973
  const d = x.top();
6681
6974
  if(d !== false) {
@@ -6684,7 +6977,7 @@ function VMI_fract(x, empty) {
6684
6977
  }
6685
6978
  }
6686
6979
 
6687
- function VMI_exponential(x, empty) {
6980
+ function VMI_exponential(x) {
6688
6981
  // Replaces the top number X of the stack by a random number from the
6689
6982
  // negative exponential distribution with parameter X (so X is the lambda,
6690
6983
  // and the mean will be 1/X)
@@ -6696,7 +6989,7 @@ function VMI_exponential(x, empty) {
6696
6989
  }
6697
6990
  }
6698
6991
 
6699
- function VMI_poisson(x, empty) {
6992
+ function VMI_poisson(x) {
6700
6993
  // Replaces the top number X of the stack by a random number from the
6701
6994
  // poisson distribution with parameter X (so X is the mean value lambda)
6702
6995
  const d = x.top();
@@ -6707,7 +7000,7 @@ function VMI_poisson(x, empty) {
6707
7000
  }
6708
7001
  }
6709
7002
 
6710
- function VMI_binomial(x, empty) {
7003
+ function VMI_binomial(x) {
6711
7004
  // Replaces the top list (!) A of the stack by Bin(A[0], A[1]), i.e., a random
6712
7005
  // number from the binomial distribution with n = A[0] and p = A[1]
6713
7006
  const d = x.top();
@@ -6723,7 +7016,7 @@ function VMI_binomial(x, empty) {
6723
7016
  }
6724
7017
  }
6725
7018
 
6726
- function VMI_normal(x, empty) {
7019
+ function VMI_normal(x) {
6727
7020
  // Replaces the top list (!) A of the stack by N(A[0], A[1]), i.e., a random
6728
7021
  // number from the normal distribution with mu = A[0] and sigma = A[1]
6729
7022
  const d = x.top();
@@ -6739,7 +7032,7 @@ function VMI_normal(x, empty) {
6739
7032
  }
6740
7033
  }
6741
7034
 
6742
- function VMI_weibull(x, empty) {
7035
+ function VMI_weibull(x) {
6743
7036
  // Replaces the top list (!) A of the stack by Weibull(A[0], A[1]), i.e., a
6744
7037
  // random number from the Weibull distribution with lambda = A[0] and k = A[1]
6745
7038
  const d = x.top();
@@ -6755,7 +7048,7 @@ function VMI_weibull(x, empty) {
6755
7048
  }
6756
7049
  }
6757
7050
 
6758
- function VMI_triangular(x, empty) {
7051
+ function VMI_triangular(x) {
6759
7052
  // Replaces the top list (!) A of the stack by Tri(A[0], A[1]), A[2]), i.e.,
6760
7053
  // a random number from the triangular distribution with a = A[0], b = A[1],
6761
7054
  // and c = A[2]. NOTE: if only 2 parameters are passed, c is assumed to equal
@@ -6773,7 +7066,7 @@ function VMI_triangular(x, empty) {
6773
7066
  }
6774
7067
  }
6775
7068
 
6776
- function VMI_npv(x, empty) {
7069
+ function VMI_npv(x) {
6777
7070
  // Replaces the top list (!) A of the stack by the net present value (NPV)
6778
7071
  // of the arguments in A. A[0] is the interest rate r, A[1] is the number of
6779
7072
  // time periods n. If A has only 1 or 2 elements, the NPV is 0. If A has 3
@@ -6813,7 +7106,7 @@ function VMI_npv(x, empty) {
6813
7106
  }
6814
7107
  }
6815
7108
 
6816
- function VMI_min(x, empty) {
7109
+ function VMI_min(x) {
6817
7110
  // Replaces the top list (!) A of the stack by the lowest value in this list
6818
7111
  // NOTE: if A is not a list, A is left on the stack
6819
7112
  const d = x.top();
@@ -6825,7 +7118,7 @@ function VMI_min(x, empty) {
6825
7118
  }
6826
7119
  }
6827
7120
 
6828
- function VMI_max(x, empty) {
7121
+ function VMI_max(x) {
6829
7122
  // Replaces the top list (!) A of the stack by the highest value in this list
6830
7123
  // NOTE: if A is not a list, A is left on the stack
6831
7124
  const d = x.top();
@@ -6837,7 +7130,7 @@ function VMI_max(x, empty) {
6837
7130
  }
6838
7131
  }
6839
7132
 
6840
- function VMI_concat(x, empty) {
7133
+ function VMI_concat(x) {
6841
7134
  // Pops the top number B from the stack, and then replaces the new top
6842
7135
  // element A by [A, B] if A is a number, or adds B to A is A is a list
6843
7136
  // of numbers (!) or
@@ -6882,20 +7175,20 @@ function VMI_jump_if_false(x, index) {
6882
7175
  }
6883
7176
  }
6884
7177
 
6885
- function VMI_pop_false(x, empty) {
7178
+ function VMI_pop_false(x) {
6886
7179
  // Removes the top value from the stack, which should be 0 or
6887
7180
  // VM.UNDEFINED (but this is not checked)
6888
7181
  const r = x.stack.pop();
6889
7182
  if(DEBUGGING) console.log(`POP-FALSE (${r})`);
6890
7183
  }
6891
7184
 
6892
- function VMI_if_then(x, empty) {
7185
+ function VMI_if_then(x) {
6893
7186
  // NO operation -- as of version 1.0.14, this function only serves as
6894
7187
  // operator symbol, and its executions would indicate an error
6895
7188
  console.log('WARNING: this IF-THEN instruction is obsolete!');
6896
7189
  }
6897
7190
 
6898
- function VMI_if_else(x, empty) {
7191
+ function VMI_if_else(x) {
6899
7192
  // NO operation -- as of version 1.0.14, this function only serves as
6900
7193
  // operator symbol, and its executions would indicate an error
6901
7194
  console.log('WARNING: this IF-THEN instruction is obsolete!');
@@ -7824,7 +8117,7 @@ const
7824
8117
 
7825
8118
  // Each custom operator must have its own Virtual Machine instruction
7826
8119
 
7827
- function VMI_profitable_units(x, empty) {
8120
+ function VMI_profitable_units(x) {
7828
8121
  // Replaces the argument list that should be at the top of the stack by the
7829
8122
  // number of profitable units having a standard capacity (number), given the
7830
8123
  // level (vector) of the process that represents multiple such units, the
@@ -7958,7 +8251,7 @@ DYNAMIC_SYMBOLS.push('npu');
7958
8251
  LEVEL_BASED_CODES.push(VMI_profitable_units);
7959
8252
 
7960
8253
 
7961
- function VMI_highest_cumulative_consecutive_deviation(x, empty) {
8254
+ function VMI_highest_cumulative_consecutive_deviation(x) {
7962
8255
  // Replaces the argument list that should be at the top of the stack by
7963
8256
  // the HCCD (as in the function name) of the vector V that is passed as
7964
8257
  // the first argument of this function. The HCCD value is computed by