linny-r 1.3.4 → 1.4.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.
- package/package.json +1 -1
- package/server.js +48 -6
- package/static/index.html +14 -12
- package/static/linny-r.css +28 -2
- package/static/scripts/linny-r-ctrl.js +51 -8
- package/static/scripts/linny-r-gui.js +150 -75
- package/static/scripts/linny-r-model.js +467 -154
- package/static/scripts/linny-r-utils.js +141 -12
- package/static/scripts/linny-r-vm.js +931 -435
@@ -36,36 +36,56 @@ SOFTWARE.
|
|
36
36
|
// CLASS Expression (for all potentially time-dependent model parameters)
|
37
37
|
class Expression {
|
38
38
|
constructor(obj, attr, text) {
|
39
|
+
// Expressions are typically defined for some attribute of some
|
40
|
+
// entity -- legacy convention is to refer to a model entity
|
41
|
+
// as `object` rather than `entity`.
|
39
42
|
this.object = obj;
|
40
43
|
this.attribute = attr;
|
41
44
|
this.text = text;
|
42
|
-
// A stack for local time step (to allow lazy evaluation)
|
45
|
+
// A stack for local time step (to allow lazy evaluation).
|
43
46
|
this.step = [];
|
44
|
-
// An operand stack for computation (elements must be numeric)
|
47
|
+
// An operand stack for computation (elements must be numeric).
|
45
48
|
this.stack = [];
|
46
|
-
// NOTE: code = NULL indicates: not compiled yet
|
49
|
+
// NOTE: code = NULL indicates: not compiled yet.
|
47
50
|
this.code = null;
|
48
|
-
// NOTE:
|
51
|
+
// NOTE: Use a semaphore to prevent cyclic recursion.
|
49
52
|
this.compiling = false;
|
50
|
-
// While compiling, check whether any operand depends on time
|
53
|
+
// While compiling, check whether any operand depends on time.
|
51
54
|
this.is_static = true;
|
52
|
-
// Likewise, check whether any operand is computed by the solver
|
55
|
+
// Likewise, check whether any operand is computed by the solver.
|
53
56
|
this.is_level_based = false;
|
54
|
-
// NOTE: VM expects result to be an array, even when expression is static
|
57
|
+
// NOTE: VM expects result to be an array, even when expression is static.
|
55
58
|
this.vector = [VM.NOT_COMPUTED];
|
56
59
|
// For dataset *wildcard* modifier expressions, results are stored in a
|
57
|
-
// separate vector for each wildcard number
|
58
|
-
|
60
|
+
// separate vector for each wildcard number. The set of vectors expands
|
61
|
+
// "lazily", as new entries (number: vector) are added only when the
|
62
|
+
// expression is evaluated for a different wildcard number.
|
63
|
+
// For all other expressions (typically properties of nodes), the
|
64
|
+
// normal expression vector is used.
|
65
|
+
// NOTE: Wildcard expressions merit special treatment when plotted in
|
66
|
+
// a chart.
|
67
|
+
this.wildcard_vectors = {};
|
68
|
+
// NOTE: `wildcard_number` is used -- only during code execution --
|
69
|
+
// by VMI_push_dataset_modifier (read and/or write) and by
|
70
|
+
// VMI_push_contextual_number (read only).
|
71
|
+
this.wildcard_vector_index = false;
|
59
72
|
// Special instructions can store results as cache properties to save
|
60
|
-
// (re)computation time; cache is cleared when expression is reset
|
73
|
+
// (re)computation time; cache is cleared when expression is reset.
|
61
74
|
this.cache = {};
|
62
75
|
}
|
63
76
|
|
77
|
+
get isWildcardExpression() {
|
78
|
+
// Returns TRUE if the owner is a dataset, and the attribute contains
|
79
|
+
// wildcards.
|
80
|
+
return this.object instanceof Dataset &&
|
81
|
+
this.object.isWildcardSelector(this.attribute);
|
82
|
+
}
|
83
|
+
|
64
84
|
get variableName() {
|
65
85
|
// Return the name of the variable computed by this expression
|
66
|
-
if(this.attribute === 'C') return 'note color expression';
|
67
86
|
if(this.object === MODEL.equations_dataset) return 'equation ' + this.attribute;
|
68
|
-
return this.object.displayName + UI.OA_SEPARATOR + this.attribute;
|
87
|
+
if(this.object) return this.object.displayName + UI.OA_SEPARATOR + this.attribute;
|
88
|
+
return 'Unknown variable (no object)';
|
69
89
|
}
|
70
90
|
|
71
91
|
get timeStepDuration() {
|
@@ -78,7 +98,7 @@ class Expression {
|
|
78
98
|
}
|
79
99
|
|
80
100
|
get referencedEntities() {
|
81
|
-
// Returns a list of entities referenced in this expression
|
101
|
+
// Returns a list of entities referenced in this expression.
|
82
102
|
if(this.text.indexOf('[') < 0) return [];
|
83
103
|
const
|
84
104
|
el = [],
|
@@ -110,11 +130,8 @@ class Expression {
|
|
110
130
|
// Clears result of previous computation (if any)
|
111
131
|
this.step.length = 0;
|
112
132
|
this.stack.length = 0;
|
113
|
-
|
114
|
-
|
115
|
-
// and read only by VMI_push_selector_wildcard
|
116
|
-
this.wildcard_number = false;
|
117
|
-
this.wildcard_vectors = [];
|
133
|
+
this.wildcard_vectors = {};
|
134
|
+
this.wildcard_vector_index = false;
|
118
135
|
this.cache = {};
|
119
136
|
this.compile(); // if(!this.compiled) REMOVED to ensure correct isStatic!!
|
120
137
|
// Static expressions only need a vector with one element (having index 0)
|
@@ -141,20 +158,18 @@ class Expression {
|
|
141
158
|
}
|
142
159
|
|
143
160
|
compile() {
|
144
|
-
// Do not compile recursively
|
161
|
+
// Do not compile recursively.
|
145
162
|
if(this.compiling) return;
|
146
|
-
// Set the "compiling" flag to prevent cyclic recursion
|
163
|
+
// Set the "compiling" flag to prevent cyclic recursion.
|
147
164
|
this.compiling = true;
|
148
|
-
// Clear the VM instruction list
|
165
|
+
// Clear the VM instruction list.
|
149
166
|
this.code = null;
|
150
167
|
const xp = new ExpressionParser(this.text, this.object, this.attribute);
|
151
168
|
if(xp.error === '') {
|
152
|
-
// NOTE:
|
169
|
+
// NOTE: Except for dataset modifiers and note colors, expressions
|
153
170
|
// should not be based on levels-still-to-be-computed-by-the-solver,
|
154
|
-
// so caution the modeler when this appears to be the case
|
155
|
-
|
156
|
-
// to check their syntax; then their object is null
|
157
|
-
if(xp.is_level_based && this.object &&
|
171
|
+
// so caution the modeler when this appears to be the case.
|
172
|
+
if(xp.is_level_based &&
|
158
173
|
!(this.object instanceof Dataset || this.object instanceof Note)) {
|
159
174
|
// NOTE: this should not occur, so log more details
|
160
175
|
console.log('Level-based issue:',
|
@@ -176,7 +191,7 @@ class Expression {
|
|
176
191
|
}
|
177
192
|
|
178
193
|
get asXML() {
|
179
|
-
// Returns XML-encoded expression
|
194
|
+
// Returns XML-encoded expression after replacing "black-boxed" entities.
|
180
195
|
let text = this.text;
|
181
196
|
if(MODEL.black_box) {
|
182
197
|
// Get all entity names that occur in this expression
|
@@ -219,17 +234,21 @@ class Expression {
|
|
219
234
|
}
|
220
235
|
|
221
236
|
get defined() {
|
237
|
+
// Returns TRUE if the expression string is not empty.
|
222
238
|
return this.text !== '';
|
223
239
|
}
|
224
240
|
|
225
241
|
get compiled() {
|
242
|
+
// Returns TRUE if there is code for this expression.
|
243
|
+
// NOTE: The expression parser sets `code` to NULL when compiling an
|
244
|
+
// empty string.
|
226
245
|
return this.code !== null;
|
227
246
|
}
|
228
247
|
|
229
248
|
get isStatic() {
|
230
|
-
// Returns is_static property AFTER compiling if not compiled yet
|
231
|
-
// NOTE:
|
232
|
-
// already being compiled
|
249
|
+
// Returns is_static property AFTER compiling if not compiled yet.
|
250
|
+
// NOTE: To prevent cylic recursion, return FALSE if this expression is
|
251
|
+
// already being compiled.
|
233
252
|
if(this.compiling) return false;
|
234
253
|
if(!this.compiled) this.compile();
|
235
254
|
return this.is_static;
|
@@ -251,53 +270,61 @@ class Expression {
|
|
251
270
|
}
|
252
271
|
|
253
272
|
chooseVector(number) {
|
254
|
-
// Return the vector to use for computation (defaults to "own" vector)
|
255
|
-
|
256
|
-
if(number
|
257
|
-
|
258
|
-
|
259
|
-
|
273
|
+
// 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;
|
277
|
+
// Use the vector for the wildcard number (create it if necessary).
|
278
|
+
if(!this.wildcard_vectors.hasOwnProperty(number)) {
|
279
|
+
this.wildcard_vectors[number] = [];
|
280
|
+
if(this.isStatic) {
|
281
|
+
this.wildcard_vectors[number][0] = VM.NOT_COMPUTED;
|
282
|
+
} else {
|
260
283
|
MODEL.cleanVector(this.wildcard_vectors[number], VM.NOT_COMPUTED);
|
261
284
|
}
|
262
|
-
return this.wildcard_vectors[number];
|
263
285
|
}
|
286
|
+
return this.wildcard_vectors[number];
|
264
287
|
}
|
265
288
|
|
266
289
|
compute(t, number=false) {
|
267
|
-
// Executes the VM code for this expression for time step t
|
268
|
-
// NOTE: `number` is passed
|
290
|
+
// Executes the VM code for this expression for time step `t`.
|
291
|
+
// NOTE: `number` is passed only if context for # is defined.
|
269
292
|
if(!this.compiled) this.compile();
|
270
|
-
// Return FALSE if compilation resulted in error
|
293
|
+
// Return FALSE if compilation resulted in error.
|
271
294
|
if(!this.compiled) return false;
|
272
|
-
// Compute static expressions as if t = 0
|
295
|
+
// Compute static expressions as if t = 0.
|
273
296
|
if(t < 0 || this.isStatic) t = 0;
|
274
|
-
// Select the vector to use
|
297
|
+
// Select the vector to use.
|
275
298
|
const v = this.chooseVector(number);
|
276
|
-
// Check for potential error (that should NOT occur)
|
277
|
-
if(!v || v.length === 0 || t >= v.length) {
|
299
|
+
// Check for potential error (that should NOT occur).
|
300
|
+
if(!Array.isArray(v) || v.length === 0 || t >= v.length) {
|
278
301
|
const msg = 'ERROR: Undefined value during expression evaluation';
|
279
302
|
UI.alert(msg);
|
280
303
|
console.log(this.variableName, ':', this.text, '#', number, '@', t, v);
|
281
|
-
// Throw exception to permit viewing the function call stack
|
304
|
+
// Throw exception to permit viewing the function call stack.
|
282
305
|
throw msg;
|
283
306
|
}
|
284
307
|
// When called while already computing for time step t, signal this
|
285
308
|
// as an error value.
|
286
309
|
if(v[t] === VM.COMPUTING) v[t] = VM.CYCLIC;
|
287
|
-
// Compute a value only once
|
288
|
-
if(v[t] !== VM.NOT_COMPUTED)
|
289
|
-
|
290
|
-
|
291
|
-
|
310
|
+
// Compute a value only once.
|
311
|
+
if(v[t] !== VM.NOT_COMPUTED) {
|
312
|
+
if(DEBUGGING) console.log('Already computed', this.variableName,
|
313
|
+
':', this.text, '#', number, '@', t, v[t]);
|
314
|
+
return true;
|
315
|
+
}
|
316
|
+
// Provide selector context for # (number = FALSE => no wildcard match).
|
317
|
+
this.wildcard_vector_index = number;
|
318
|
+
// Push this expression onto the call stack.
|
292
319
|
VM.call_stack.push(this);
|
293
|
-
// Push time step in case a
|
294
|
-
// this same variable
|
295
|
-
this.trace(
|
320
|
+
// Push time step in case a VMI instruction for another expression
|
321
|
+
// references this same variable.
|
322
|
+
this.trace(`--START: ${this.variableName} (wvi = ${number})`);
|
296
323
|
this.step.push(t);
|
297
|
-
// NOTE:
|
324
|
+
// NOTE: Trace expression AFTER pushing the time step.
|
298
325
|
this.trace(`"${this.text}"`);
|
299
326
|
v[t] = VM.COMPUTING;
|
300
|
-
// Execute the instructions
|
327
|
+
// Execute the instructions.
|
301
328
|
let vmi = null,
|
302
329
|
ok = true,
|
303
330
|
cl = this.code.length;
|
@@ -306,13 +333,13 @@ class Expression {
|
|
306
333
|
this.stack.length = 0;
|
307
334
|
while(ok && this.program_counter < cl && v[t] === VM.COMPUTING) {
|
308
335
|
vmi = this.code[this.program_counter];
|
309
|
-
// Instructions are 2-element arrays [function, [arguments]]
|
310
|
-
//
|
311
|
-
// and the argument list as second parameter
|
336
|
+
// Instructions are 2-element arrays [function, [arguments]].
|
337
|
+
// The function is called with this expression as first parameter,
|
338
|
+
// and the argument list as second parameter.
|
312
339
|
vmi[0](this, vmi[1]);
|
313
340
|
this.program_counter++;
|
314
341
|
}
|
315
|
-
// Stack should now have length 1
|
342
|
+
// Stack should now have length 1.
|
316
343
|
if(this.stack.length > 1) {
|
317
344
|
v[t] = VM.OVERFLOW;
|
318
345
|
} else if(this.stack.length < 1) {
|
@@ -321,19 +348,20 @@ class Expression {
|
|
321
348
|
v[t] = this.stack.pop();
|
322
349
|
}
|
323
350
|
this.trace('RESULT = ' + VM.sig4Dig(v[t]));
|
324
|
-
// Pop the time step
|
351
|
+
// Pop the time step.
|
325
352
|
this.step.pop();
|
326
353
|
this.trace('--STOP: ' + this.variableName);
|
327
|
-
// Clear context for #
|
328
|
-
|
329
|
-
|
354
|
+
// Clear context for # for this expression (no stack needed, as
|
355
|
+
// wildcard expressions cannot reference themselves).
|
356
|
+
this.wildcard_vector_index = false;
|
357
|
+
// If error, display the call stack (only once).
|
330
358
|
// NOTE: "undefined", "not computed" and "still computing" are NOT
|
331
359
|
// problematic unless they result in an error (stack over/underflow)
|
332
360
|
if(v[t] <= VM.ERROR) {
|
333
361
|
MONITOR.showCallStack(t);
|
334
362
|
VM.logCallStack(t);
|
335
363
|
}
|
336
|
-
// Always pop the expression from the call stack
|
364
|
+
// Always pop the expression from the call stack.
|
337
365
|
VM.call_stack.pop(this);
|
338
366
|
return true;
|
339
367
|
}
|
@@ -346,6 +374,10 @@ class Expression {
|
|
346
374
|
// "initial value" (these follow from the variables used in the expression)
|
347
375
|
// Select the vector to use
|
348
376
|
const v = this.chooseVector(number);
|
377
|
+
if(!Array.isArray(v)) {
|
378
|
+
console.log('ANOMALY: No vector for result(t)');
|
379
|
+
return VM.UNDEFINED;
|
380
|
+
}
|
349
381
|
if(t < 0 || this.isStatic) t = 0;
|
350
382
|
if(t >= v.length) return VM.UNDEFINED;
|
351
383
|
if(v[t] === VM.NOT_COMPUTED || v[t] === VM.COMPUTING) {
|
@@ -487,46 +519,157 @@ class Expression {
|
|
487
519
|
|
488
520
|
// CLASS ExpressionParser
|
489
521
|
// Instances of ExpressionParser compile expressions into code, i.e.,
|
490
|
-
// an array of VM instructions. The optional parameters `owner` and
|
491
|
-
// are used to prefix "local" entities, and to implement
|
492
|
-
// that contain the dot that (when used within
|
493
|
-
// of the dataset
|
522
|
+
// an array of VM instructions. The optional parameters `owner` and
|
523
|
+
// `attribute` are used to prefix "local" entities, and also to implement
|
524
|
+
// modifier expressions that contain the "dot" that (when used within
|
525
|
+
// brackets) denotes the data value of the dataset.
|
526
|
+
|
527
|
+
// Since version 1.4.0, a leading colon indicates that the variable
|
528
|
+
// "inherits" the prefixes of its owner. Thus, for example, in the expression
|
529
|
+
// for the upper bound of proces "Storage: Li-ion: battery 1", the variable
|
530
|
+
// [:capacity] will be interpreted as [Storage: Li-ion: capacity].
|
531
|
+
// The prefixed name will be parsed normally, so if "Storage: Li-ion: capacity"
|
532
|
+
// identifies an array-type dataset, [:capacity@#] will work, since the
|
533
|
+
// value of # can be inferred from the expression owner's name
|
534
|
+
// "Storage: Li-ion: battery 1".
|
535
|
+
|
536
|
+
// Also since version 1.4.0, the context sensitive number # can also be used
|
537
|
+
// as a "wildcard" in an entity name. This is useful mainly when combined with
|
538
|
+
// wildcard equations with names like "eq??ion" which can then be referred to
|
539
|
+
// in expressions not only as "eq12ion" (then # in the expression for the
|
540
|
+
// wildcard equation evaluates as 12), but also as "eq#ion" (then # in the
|
541
|
+
// expression for the wildcard equation will have the value of # in the
|
542
|
+
// "calling" expression. This permits, for example, defining as single
|
543
|
+
// equation "partial load ??" with expression "[P#|L] / [P#|UB]", and then
|
544
|
+
// using the variable [partial load 1] to compute the partial load for
|
545
|
+
// process P1.
|
546
|
+
// NOTES:
|
547
|
+
// (1) This applies recursively, so [partial load #] can be used in some other
|
548
|
+
// wildcard equation like, for example, "percent load ??" having expression
|
549
|
+
// "100 * [partial load #]".
|
550
|
+
// (2) The # may be used in patterns, so when a model comprises processes
|
551
|
+
// P1 and P2, and products Q2 and Q3, and a wildcard equation "total level ??"
|
552
|
+
// with expression "[SUM$#|L]", then [total level 1] will evaluate as the level
|
553
|
+
// of P1, and [total level 2] as the level of P2 plus the level of Q3.
|
554
|
+
|
494
555
|
class ExpressionParser {
|
495
556
|
constructor(text, owner=null, attribute='') {
|
496
|
-
//
|
557
|
+
// Setting TRACE to TRUE will log parsing information to the console.
|
558
|
+
this.TRACE = false;
|
559
|
+
// `text` is the expression string to be parsed.
|
497
560
|
this.expr = text;
|
561
|
+
// NOTE: When expressions for dataset modifiers or equations are
|
562
|
+
// parsed, `owner` is their dataset, and `attribute` is their name.
|
498
563
|
this.owner = owner;
|
564
|
+
this.owner_prefix = '';
|
499
565
|
this.attribute = attribute;
|
500
|
-
|
501
|
-
this.
|
502
|
-
this.selector =
|
566
|
+
this.dataset = null;
|
567
|
+
this.dot = null;
|
568
|
+
this.selector = '';
|
569
|
+
this.context_number = '';
|
570
|
+
this.wildcard_selector = false;
|
571
|
+
// Always infer the value for the context-sensitive number #.
|
572
|
+
// NOTE: This this will always be a string. Three possible cases:
|
573
|
+
// (1) a question mark "?" if `owner` is a dataset and `attribute`
|
574
|
+
// wildcards in its selector; this indicates that the value of # cannot be inferred at
|
575
|
+
// compile time.
|
576
|
+
//
|
577
|
+
if(owner) {
|
578
|
+
this.context_number = owner.numberContext;
|
579
|
+
// NOTE: The owner prefix includes the trailing colon+space.
|
580
|
+
if(owner instanceof Link || owner instanceof Constraint) {
|
581
|
+
// For links and constraints, use the longest prefix that
|
582
|
+
// their nodes have in common.
|
583
|
+
this.owner_prefix = UI.sharedPrefix(owner.from_node.displayName,
|
584
|
+
owner.to_node.displayName) + UI.PREFIXER;
|
585
|
+
} else if(owner === MODEL.equations_dataset) {
|
586
|
+
this.owner_prefix = UI.completePrefix(attribute);
|
587
|
+
} else {
|
588
|
+
this.owner_prefix = UI.completePrefix(owner.displayName);
|
589
|
+
}
|
590
|
+
if(owner instanceof Dataset) {
|
591
|
+
this.dataset = owner;
|
592
|
+
// The attribute (if specified) is a dataset modifier selector.
|
593
|
+
// This may be the name of an equation; this can be tested by
|
594
|
+
// checking whether the owner is the equations dataset.
|
595
|
+
this.selector = attribute;
|
596
|
+
// Record whether this selector contains wildcards (? and/or *
|
597
|
+
// for selectors, ?? for equations).
|
598
|
+
this.wildcard_selector = owner.isWildcardSelector(attribute);
|
599
|
+
if(this.wildcard_selector) {
|
600
|
+
// NOTE: Wildcard selectors override the context number that
|
601
|
+
// may have been inferred from the dataset name.
|
602
|
+
this.context_number = '?';
|
603
|
+
} else {
|
604
|
+
// Normal selectors may have a "tail number". If so, this
|
605
|
+
// overrides the tail number of the dataset.
|
606
|
+
const tn = UI.tailNumber(attribute);
|
607
|
+
if(tn) this.context_number = tn;
|
608
|
+
}
|
609
|
+
if(owner !== MODEL.equations_dataset) {
|
610
|
+
// For "normal" modifier expressions, the "dot" (.) can be used
|
611
|
+
// to refer to the dataset of the modifier.
|
612
|
+
this.dot = this.dataset;
|
613
|
+
}
|
614
|
+
}
|
615
|
+
}
|
616
|
+
// Ensure that context number is either '?' or a number or FALSE.
|
617
|
+
if(this.context_number !== '?') {
|
618
|
+
this.context_number = parseInt(this.context_number);
|
619
|
+
if(isNaN(this.context_number)) this.context_number = false;
|
620
|
+
}
|
621
|
+
// Immediately compile; this may generate warnings
|
503
622
|
this.compile();
|
504
623
|
}
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
624
|
+
|
625
|
+
get ownerName() {
|
626
|
+
// FOR TRACING & DEBUGGING: Returns the owner of this equation (if any).
|
627
|
+
if(!this.owner) return '(no owner)';
|
628
|
+
let n = this.owner.displayName;
|
629
|
+
if(this.attribute) n += '|' + this.attribute;
|
630
|
+
if(this.wildcard_selector) {
|
631
|
+
n = [n, ' [wildcard ',
|
632
|
+
(this.dataset === MODEL.equations_dataset ?
|
633
|
+
'equation' : 'modifier'),
|
634
|
+
(this.context_number !== false ?
|
635
|
+
' # = ' + this.context_number : ''),
|
636
|
+
']'].join('');
|
510
637
|
}
|
638
|
+
return n;
|
639
|
+
}
|
640
|
+
|
641
|
+
log(msg) {
|
642
|
+
// NOTE: This method is used only to profile dynamic expressions.
|
643
|
+
if(true) return;
|
644
|
+
// Set the above IF condition to FALSE to profile dynamic expressions.
|
645
|
+
console.log(`Expression for ${this.ownerName}: ${this.expr}\n${msg}`);
|
511
646
|
}
|
512
647
|
|
513
|
-
//
|
648
|
+
// The method parseVariable(name) checks whether `name` fits this pattern:
|
514
649
|
// {run}statistic$entity|attribute@offset_1:offset_2
|
515
650
|
// allowing spaces within {run} and around | and @ and :
|
516
651
|
// The entity is mandatory, but {run} and statistic$ are optional, and
|
517
652
|
// attribute and offset have defaults.
|
518
|
-
//
|
519
|
-
// pattern matches and no statistic, or the 6-element array
|
653
|
+
// It returns array [object, anchor_1, offset_1, anchor_2, offset_2] if
|
654
|
+
// the pattern matches and no statistic, or the 6-element array
|
520
655
|
// [statistic, object list, anchor, offset, anchor_2, offset_2]
|
521
|
-
// if a valid statistic is specified; otherwise FALSE.
|
656
|
+
// if a valid statistic is specified; otherwise it returns FALSE.
|
522
657
|
// The object is either a vector or an expression, or a special object
|
523
658
|
// (dataset specifier, experiment run specifier or unit balance specifier)
|
524
659
|
// NOTE: this array is used as argument for the virtual machine instructions
|
525
|
-
// VMI_push_var, VMI_push_statistic and VMI_push_run_result
|
660
|
+
// VMI_push_var, VMI_push_statistic and VMI_push_run_result.
|
526
661
|
parseVariable(name) {
|
527
|
-
// Reduce whitespace to single space
|
662
|
+
// Reduce whitespace to single space.
|
528
663
|
name = name.replace(/\s+/g, ' ');
|
529
|
-
|
664
|
+
|
665
|
+
// For debugging, TRACE can be used to log to the console for
|
666
|
+
// specific expressions and/or variables, for example:
|
667
|
+
// this.TRACE = name.endsWith('losses') || this.ownerName.endsWith('losses');
|
668
|
+
if(this.TRACE) console.log(
|
669
|
+
`TRACE: Parsing variable "${name}" in expression for`,
|
670
|
+
this.ownerName, ' --> ', this.expr);
|
671
|
+
|
672
|
+
// Initialize possible components.
|
530
673
|
let obj = null,
|
531
674
|
attr = '',
|
532
675
|
use_data = false,
|
@@ -536,9 +679,11 @@ class ExpressionParser {
|
|
536
679
|
anchor2 = '',
|
537
680
|
offset2 = 0,
|
538
681
|
msg = '',
|
682
|
+
arg0 = null,
|
539
683
|
args = null,
|
540
684
|
s = name.split('@');
|
541
685
|
if(s.length > 1) {
|
686
|
+
// [variable@offset] where offset has form (anchor1)number1(:(anchor2)number 2)
|
542
687
|
// Offsets make expression dynamic (for now, ignore exceptional cases)
|
543
688
|
this.is_static = false;
|
544
689
|
this.log('dynamic because of offset');
|
@@ -586,9 +731,7 @@ class ExpressionParser {
|
|
586
731
|
}
|
587
732
|
// Check whether # anchor is meaningful for this expression
|
588
733
|
if((anchor1 === '#' || anchor2 === '#') &&
|
589
|
-
!(this.
|
590
|
-
this.selector.indexOf('?') >= 0 ||
|
591
|
-
this.owner.numberContext)) {
|
734
|
+
!(this.wildcard_selector || this.context_number !== false)) {
|
592
735
|
// Log debugging information for this error
|
593
736
|
console.log(this.owner.displayName, this.owner.type, this.selector);
|
594
737
|
this.error = 'Anchor # is undefined in this context';
|
@@ -597,7 +740,8 @@ class ExpressionParser {
|
|
597
740
|
}
|
598
741
|
}
|
599
742
|
// Run specifier (optional) must be leading and braced
|
600
|
-
// Specifier format: {method$title|run} where method and title are
|
743
|
+
// Specifier format: {method$title|run} where method and title are
|
744
|
+
// optional -- NOTE: # in title or run is NOT seen as a wildcard
|
601
745
|
if(name.startsWith('{')) {
|
602
746
|
s = name.split('}');
|
603
747
|
if(s.length > 1) {
|
@@ -631,20 +775,29 @@ class ExpressionParser {
|
|
631
775
|
}
|
632
776
|
s = s.split('#');
|
633
777
|
let rn = (s.length > 1 ? s[1].trim() : false);
|
634
|
-
// Experiment specifier may contain selectors
|
778
|
+
// Experiment specifier may contain modifier selectors.
|
635
779
|
s = s[0].trim().split(UI.OA_SEPARATOR);
|
636
780
|
if(s.length > 1) {
|
637
|
-
// If so, the selector list may indicate the run number
|
638
|
-
// NOTE: permit selectors to be separated by various characters
|
781
|
+
// If so, the selector list may indicate the run number.
|
782
|
+
// NOTE: permit selectors to be separated by various characters.
|
639
783
|
x.r = s.slice(1).join('|').split(/[\|\,\.\:\;\/\s]+/g);
|
640
784
|
}
|
641
785
|
if(rn) {
|
786
|
+
// NOTE: Special notation for run numbers to permit modelers
|
787
|
+
// to chart results as if run numbers are on the time axis
|
788
|
+
// (with a given step size). The chart will be made as usual,
|
789
|
+
// i.e., plot a point for each time step t, but the value v[t]
|
790
|
+
// will then stay the same for the time interval that corresponds
|
791
|
+
// to simulation period length / number of runs.
|
792
|
+
// NOTE: This will fail to produce a meaningful chart when the
|
793
|
+
// simulation period is small compared to the number of runs.
|
642
794
|
if(rn.startsWith('n')) {
|
643
|
-
// #n may be followed by a range, or defaults to
|
644
|
-
// Of this range, the i-th number will
|
795
|
+
// #n may be followed by a range, or this range defaults to
|
796
|
+
// 0 - last run number. Of this range, the i-th number will
|
797
|
+
// be used, where i is computes as:
|
645
798
|
// floor(current time step * number of runs / period length)
|
646
799
|
const range = rn.substring(1);
|
647
|
-
// Call rangeToList only to validate the range syntax
|
800
|
+
// Call rangeToList only to validate the range syntax.
|
648
801
|
if(rangeToList(range)) {
|
649
802
|
x.nr = range;
|
650
803
|
this.is_static = false;
|
@@ -653,12 +806,12 @@ class ExpressionParser {
|
|
653
806
|
msg = `Invalid experiment run number range "${range}"`;
|
654
807
|
}
|
655
808
|
} else {
|
656
|
-
// Explicit run number
|
809
|
+
// Explicit run number is specified.
|
657
810
|
const n = parseInt(rn);
|
658
811
|
if(isNaN(n)) {
|
659
812
|
msg = `Invalid experiment run number "${rn}"`;
|
660
813
|
} else {
|
661
|
-
// Explicit run number overrules selector list
|
814
|
+
// Explicit run number overrules selector list.
|
662
815
|
x.r = n;
|
663
816
|
}
|
664
817
|
}
|
@@ -666,7 +819,7 @@ class ExpressionParser {
|
|
666
819
|
// NOTE: s[0] still holds the experiment title
|
667
820
|
s = s[0].trim();
|
668
821
|
if(s) {
|
669
|
-
// NOTE: title cannot be parametrized
|
822
|
+
// NOTE: title cannot be parametrized with a # wildcard
|
670
823
|
const n = MODEL.indexOfExperiment(s);
|
671
824
|
if(n < 0) {
|
672
825
|
msg = `Unknown experiment "${s}"`;
|
@@ -674,24 +827,31 @@ class ExpressionParser {
|
|
674
827
|
x.x = MODEL.experiments[n];
|
675
828
|
}
|
676
829
|
}
|
677
|
-
// Variable name may start with a (case insensitive) statistic
|
830
|
+
// Variable name may start with a (case insensitive) statistic
|
831
|
+
// specifier such as SUM or MEAN
|
678
832
|
s = name.split('$');
|
679
833
|
if(s.length > 1) {
|
680
834
|
const stat = s[0].trim().toUpperCase();
|
681
|
-
// NOTE: simply ignore $
|
835
|
+
// NOTE: simply ignore $ (i.e., consider it as part of the
|
836
|
+
// variable name) unless it is preceded by a valid statistic
|
682
837
|
if(VM.outcome_statistics.indexOf(stat) >= 0) {
|
683
838
|
x.s = stat;
|
684
839
|
name = s[1].trim();
|
685
840
|
}
|
686
841
|
}
|
842
|
+
// Variable name may start with a colon to denote that the owner
|
843
|
+
// prefix should be added.
|
844
|
+
name = UI.colonPrefixedName(name, this.owner_prefix);
|
687
845
|
if(x.x) {
|
688
|
-
// NOTE: variable name may be parametrized
|
689
|
-
const vpar = name.split('\\');
|
690
|
-
if(vpar.length > 1) {
|
691
|
-
// @@ TO DO!
|
692
|
-
}
|
693
846
|
// Look up name in experiment outcomes list
|
694
847
|
x.v = x.x.resultIndex(name);
|
848
|
+
if(x.v < 0 && name.indexOf('#') >= 0 &&
|
849
|
+
typeof this.context_number === 'number') {
|
850
|
+
// Variable name may be parametrized with #, but not in
|
851
|
+
// expressions for wildcard selectors
|
852
|
+
name = name.replace('#', this.context_number);
|
853
|
+
x.v = x.x.resultIndex(name);
|
854
|
+
}
|
695
855
|
if(x.v < 0) {
|
696
856
|
msg = ['Variable "', name, '" is not a result of experiment "',
|
697
857
|
x.x.displayName, '"'].join('');
|
@@ -699,15 +859,23 @@ class ExpressionParser {
|
|
699
859
|
} else {
|
700
860
|
// Check outcome list of ALL experiments
|
701
861
|
for(let i = 0; i < MODEL.experiments.length; i++) {
|
702
|
-
|
862
|
+
let xri = MODEL.experiments[i].resultIndex(name);
|
863
|
+
if(xri < 0 && name.indexOf('#') >= 0 &&
|
864
|
+
typeof this.context_number === 'number') {
|
865
|
+
// Variable name may be parametrized with #, but not in
|
866
|
+
// expressions for wildcard selectors.
|
867
|
+
name = name.replace('#', this.context_number);
|
868
|
+
xri = MODEL.experiments[i].resultIndex(name);
|
869
|
+
}
|
870
|
+
if(xri >= 0) {
|
703
871
|
// If some match is found, the name specifies a variable
|
704
|
-
x.v =
|
872
|
+
x.v = xri;
|
705
873
|
break;
|
706
874
|
}
|
707
875
|
}
|
708
876
|
}
|
709
|
-
// NOTE: experiment may still be FALSE, as this will be interpreted
|
710
|
-
// "use current experiment", but run number should be specified
|
877
|
+
// NOTE: experiment may still be FALSE, as this will be interpreted
|
878
|
+
// as "use current experiment", but run number should be specified.
|
711
879
|
if(!msg) {
|
712
880
|
if(x.r === false && x.t === false) {
|
713
881
|
msg = 'Experiment run not specified';
|
@@ -719,107 +887,145 @@ class ExpressionParser {
|
|
719
887
|
this.error = msg;
|
720
888
|
return false;
|
721
889
|
}
|
722
|
-
// Notify modeler when two statistics are used
|
890
|
+
// Notify modeler when two statistics are used.
|
723
891
|
if(x.s && x.m) {
|
724
892
|
UI.notify(`Method statistic (${x.m}) does not apply to ` +
|
725
893
|
`run result statistic (${x.s})`);
|
726
894
|
}
|
727
|
-
// NOTE:
|
728
|
-
// dynamic, so only set is_static to FALSE if NO statistic or method
|
895
|
+
// NOTE: Using AGGREGATED run results does NOT make the expression
|
896
|
+
// dynamic, so only set is_static to FALSE if NO statistic or method.
|
729
897
|
if(!x.s && !x.m) {
|
730
898
|
this.is_static = false;
|
731
899
|
this.log('dynamic because UNaggregated experiment result');
|
732
900
|
}
|
733
|
-
// For experiment run results, default anchor is 't'
|
901
|
+
// For experiment run results, default anchor is 't'.
|
734
902
|
if(!anchor1) anchor1 = 't';
|
735
903
|
if(!anchor2) anchor2 = 't';
|
736
|
-
|
904
|
+
if(this.TRACE) console.log('TRACE: Variable is run result. x =', x);
|
905
|
+
// NOTE: compiler will recognize `x` to indicate "push run results".
|
737
906
|
return [x, anchor1, offset1, anchor2, offset2];
|
738
907
|
}
|
739
908
|
}
|
740
909
|
|
741
910
|
//
|
742
|
-
// NOTE:
|
743
|
-
// a result, so what follows does not apply to experiment results
|
911
|
+
// NOTE: For experiment results, the method will ALWAYS have returned
|
912
|
+
// a result, so what follows does not apply to experiment results.
|
744
913
|
//
|
745
914
|
|
746
|
-
//
|
915
|
+
// If reached this stage, variable must be like this:
|
916
|
+
// [(statistic$)entity name pattern(|attribute)]
|
917
|
+
// Attribute name (optional) follows the object-attribute separator |
|
747
918
|
s = name.split(UI.OA_SEPARATOR);
|
748
919
|
if(s.length > 1) {
|
749
|
-
// Attribute is string after LAST separator
|
920
|
+
// Attribute is string after the LAST separator...
|
750
921
|
attr = s.pop().trim();
|
751
|
-
// ... so restore name
|
922
|
+
// ... so restore `name` in case itself contains other separators.
|
752
923
|
name = s.join(UI.OA_SEPARATOR).trim();
|
753
924
|
if(!attr) {
|
754
925
|
// Explicit *empty* attribute, e.g., [name|]
|
755
|
-
// NOTE:
|
926
|
+
// NOTE: This matters for datasets having specifiers: the vertical
|
756
927
|
// bar indicates "do not infer a modifier from a running experiment,
|
757
|
-
// but use the data"
|
928
|
+
// but use the data".
|
758
929
|
use_data = true;
|
759
930
|
} else if(attr.startsWith('=')) {
|
760
931
|
// Attribute starting with = indicates cluster balance
|
761
|
-
// NOTE: empty string is considered as "any unit"
|
932
|
+
// NOTE: empty string is considered as "any unit".
|
762
933
|
cluster_balance_unit = attr.substring(1).trim();
|
934
|
+
} else if(attr.indexOf('?') >= 0 || attr.indexOf('#') >= 0) {
|
935
|
+
// Wildcard selectors of dataset modifiers cannot be used.
|
936
|
+
this.error = `Invalid attribute "${attr}"`;
|
937
|
+
return false;
|
763
938
|
}
|
764
939
|
}
|
765
940
|
|
766
|
-
// Check whether statistic is specified
|
941
|
+
// Check whether a statistic is specified.
|
767
942
|
let pat = name.split('$');
|
768
943
|
if(pat.length > 1 &&
|
769
944
|
VM.statistic_operators.indexOf(pat[0].toUpperCase()) >= 0) {
|
770
|
-
// For statistics, default anchor is 't'
|
945
|
+
// For statistics, the default anchor is 't'.
|
771
946
|
if(!anchor1) anchor1 = 't';
|
772
947
|
if(!anchor2) anchor2 = 't';
|
773
|
-
// Check whether unit balance for clusters is asked for
|
948
|
+
// Check whether unit balance for clusters is asked for.
|
774
949
|
if(cluster_balance_unit !== false) {
|
775
950
|
this.error = 'Aggregation of unit balance over clusters is not supported';
|
776
951
|
return false;
|
777
952
|
}
|
778
|
-
// Consider only the first $ as statistic separator
|
953
|
+
// Consider only the first $ as statistic separator.
|
779
954
|
const stat = pat.shift().toUpperCase();
|
780
|
-
// Reassemble pattern string, which may itself contain
|
955
|
+
// Reassemble pattern string, which may itself contain $.
|
781
956
|
pat = pat.join('$');
|
782
|
-
// Special case: dataset "dot" is NOT a pattern
|
957
|
+
// Special case: dataset "dot" is NOT a pattern.
|
783
958
|
if(pat === '.') {
|
784
|
-
// NOTE: "dot" dataset is not level-dependent, and statistics
|
785
|
-
// vector do NOT make the expression dynamic
|
786
|
-
if(this.
|
787
|
-
|
959
|
+
// NOTE: The "dot" dataset is not level-dependent, and statistics
|
960
|
+
// over its vector do NOT make the expression dynamic.
|
961
|
+
if(this.dot) {
|
962
|
+
args = [stat, [this.dot.vector], anchor1, offset1, anchor2, offset2];
|
963
|
+
if(this.TRACE) console.log('TRACE: Variable is a statistic:', args);
|
964
|
+
return args;
|
788
965
|
} else {
|
789
|
-
|
966
|
+
this.error = UI.ERROR.NO_DATASET_DOT;
|
790
967
|
return false;
|
791
968
|
}
|
792
969
|
}
|
793
|
-
//
|
794
|
-
|
970
|
+
// Deal with "prefix inheritance" when pattern starts with a colon.
|
971
|
+
if(pat.startsWith(':') && this.owner_prefix) {
|
972
|
+
// Add a "must start with" AND condition to all OR clauses of the
|
973
|
+
// pattern.
|
974
|
+
// NOTE: Issues may occur when prefix contains &, ^ or #.
|
975
|
+
// @@TO DO: See if this can be easily prohibited.
|
976
|
+
const oc = pat.substring(1).split('|');
|
977
|
+
for(let i = 0; i < oc.length; i++) {
|
978
|
+
oc[i] = `~${this.owner_prefix}&${oc[i]}`;
|
979
|
+
}
|
980
|
+
pat = oc.join('|');
|
981
|
+
}
|
982
|
+
// NOTE: For patterns, assume that # *always* denotes the context-
|
983
|
+
// sensitive number #, because if modelers wishes to include
|
984
|
+
// ANY number, they can make their pattern less selective.
|
985
|
+
if(typeof this.context_number === 'number') {
|
986
|
+
pat = pat.replace('#', this.context_number);
|
987
|
+
}
|
988
|
+
// By default, consider all entity types.
|
795
989
|
let et = VM.entity_letters,
|
796
990
|
patstr = pat;
|
797
|
-
// Selection may be limited to specific entity types by
|
991
|
+
// Selection may be limited to specific entity types by prefix "...?"
|
992
|
+
// where ... is one or more entity letters (A for actor, etc.).
|
798
993
|
if(/^[ABCDELPQ]+\?/i.test(pat)) {
|
799
994
|
pat = pat.split('?');
|
800
995
|
et = pat[0].toUpperCase();
|
801
996
|
pat = pat.slice(1).join('=');
|
802
997
|
}
|
803
|
-
// Get the name pattern
|
998
|
+
// Get the name pattern.
|
804
999
|
pat = patternList(pat);
|
805
|
-
// Infer the entity type(s) from the attribute (if defined)
|
1000
|
+
// Infer the entity type(s) from the attribute (if defined).
|
806
1001
|
const
|
807
1002
|
list = [],
|
808
|
-
// NOTE: optional second parameter `et`
|
1003
|
+
// NOTE: The optional second parameter `et` will limit the
|
1004
|
+
// returned list to the specified entity types.
|
809
1005
|
ewa = MODEL.entitiesWithAttribute(attr, et);
|
810
|
-
// Create list of expression objects for the matching entities
|
1006
|
+
// Create list of expression objects for the matching entities.
|
1007
|
+
// Also create a "dict" with, for each matching wildcard number,
|
1008
|
+
// the matching entities as a separate list. This will permit
|
1009
|
+
// narrowing the selection at run time, based on the expression's
|
1010
|
+
// wildcard number.
|
1011
|
+
const wdict = {};
|
811
1012
|
for(let i = 0; i < ewa.length; i++) {
|
812
1013
|
const e = ewa[i];
|
813
1014
|
if(patternMatch(e.displayName, pat)) {
|
814
|
-
|
1015
|
+
const mnr = matchingWildcardNumber(e.displayName, pat);
|
1016
|
+
// NOTE: Attribute may be a single value, a vector, or an expression.
|
815
1017
|
obj = e.attributeValue(attr);
|
816
|
-
//
|
1018
|
+
// If neither a single value nor a vector, it must be an expression.
|
817
1019
|
if(obj === null) obj = e.attributeExpression(attr);
|
818
|
-
// Double-check: only add it if it is not NULL
|
1020
|
+
// Double-check: only add it if it is not NULL.
|
819
1021
|
if(obj) {
|
820
1022
|
list.push(obj);
|
1023
|
+
if(mnr) {
|
1024
|
+
if(!wdict[mnr]) wdict[mnr] = [];
|
1025
|
+
wdict[mnr].push(obj);
|
1026
|
+
}
|
821
1027
|
// Expression becomes dynamic if any element that is added is
|
822
|
-
// neither a single value nor a static expression
|
1028
|
+
// neither a single value nor a static expression.
|
823
1029
|
if(Array.isArray(obj) ||
|
824
1030
|
(obj instanceof Expression && !obj.isStatic)) {
|
825
1031
|
this.is_static = false;
|
@@ -828,28 +1034,35 @@ class ExpressionParser {
|
|
828
1034
|
}
|
829
1035
|
}
|
830
1036
|
}
|
831
|
-
// NOTE: also add expressions for
|
832
|
-
|
833
|
-
|
834
|
-
const
|
835
|
-
if(
|
836
|
-
|
837
|
-
if(
|
838
|
-
|
839
|
-
|
1037
|
+
// NOTE: If no attribute is specified, also add expressions for
|
1038
|
+
// equations that match UNLESS entity type specifier excludes them.
|
1039
|
+
if(!attr && (!et || et.indexOf('E') >= 0)) {
|
1040
|
+
const edm = MODEL.equations_dataset.modifiers;
|
1041
|
+
for(let k in edm) if(edm.hasOwnProperty(k)) {
|
1042
|
+
const m = edm[k];
|
1043
|
+
if(patternMatch(m.selector, pat)) {
|
1044
|
+
list.push(m.expression);
|
1045
|
+
if(!m.expression.isStatic) {
|
1046
|
+
this.is_static = false;
|
1047
|
+
this.log('dynamic because matching equation is dynamic');
|
1048
|
+
}
|
840
1049
|
}
|
841
1050
|
}
|
842
1051
|
}
|
843
1052
|
if(list.length > 0) {
|
844
|
-
// NOTE:
|
845
|
-
//
|
846
|
-
// that modelers know what they're doing
|
1053
|
+
// NOTE: Statistic MAY make expression level-based.
|
1054
|
+
// Assume that this is NOT so when an offset has been specified,
|
1055
|
+
// as this suggests that modelers know what they're doing.
|
847
1056
|
this.is_level_based = this.is_level_based ||
|
848
1057
|
VM.level_based_attr.indexOf(attr) >= 0 &&
|
849
1058
|
anchor1 === 't' && offset1 === 0 &&
|
850
1059
|
anchor2 === 't' && offset2 === 0;
|
851
|
-
|
852
|
-
|
1060
|
+
args = [stat, list, anchor1, offset1, anchor2, offset2];
|
1061
|
+
if(Object.keys(wdict).length > 0) args.push(wdict);
|
1062
|
+
if(this.TRACE) console.log('TRACE: Variable is a statistic:', args);
|
1063
|
+
// NOTE: Compiler will recognize 6- or 7-element list as a
|
1064
|
+
// sign to use the VMI_push_statistic instruction.
|
1065
|
+
return args;
|
853
1066
|
}
|
854
1067
|
this.error = `No entities that match pattern "${patstr}"` +
|
855
1068
|
(attr ? ' and have attribute ' + attr : ' when no attribute is specified');
|
@@ -857,180 +1070,368 @@ class ExpressionParser {
|
|
857
1070
|
}
|
858
1071
|
|
859
1072
|
//
|
860
|
-
// NOTE:
|
861
|
-
// so what follows does not apply to statistics results
|
1073
|
+
// NOTE: For statistics, the method will ALWAYS have returned a result,
|
1074
|
+
// so what follows does not apply to statistics results, but only to
|
1075
|
+
// "plain" variables like [entity name(|attribute)].
|
862
1076
|
//
|
863
1077
|
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
obj = this.dataset;
|
868
|
-
if(!obj) msg = UI.ERROR.NO_DATASET_DOT;
|
869
|
-
} else if(!name && !attr && this.dataset) {
|
870
|
-
// This accepts auto-referencing variables like [@t-1]
|
871
|
-
// Dataset modifier expressions can reference themselves, provided that
|
872
|
-
// an offset is specified
|
873
|
-
// This makes the expression dynamic
|
874
|
-
this.is_static = false;
|
875
|
-
this.log('dynamic because of self-reference');
|
1078
|
+
// For all entity types except array-type datasets, the default anchor
|
1079
|
+
// for offsets is the current time step `t`.
|
1080
|
+
if(!(this.dataset && this.dataset.array)) {
|
876
1081
|
if(!anchor1) anchor1 = 't';
|
877
1082
|
if(!anchor2) anchor2 = 't';
|
1083
|
+
}
|
1084
|
+
// First handle this special case: no name or attribute. This is valid
|
1085
|
+
// only for dataset modifier expressions (and hence also equations).
|
1086
|
+
// Variables like [@t-1] are interpreted as a self-reference. This is
|
1087
|
+
// meaningful when a *negative* offset is specified to denote "use the
|
1088
|
+
// value of this expression for some earlier time step".
|
1089
|
+
// NOTES:
|
1090
|
+
// (1) This makes the expression dynamic.
|
1091
|
+
// (2) It does not apply to array-type datasets, as these have no
|
1092
|
+
// time dimension.
|
1093
|
+
if(!name && !attr && this.dataset && !this.dataset.array) {
|
1094
|
+
this.is_static = false;
|
1095
|
+
this.log('dynamic because of self-reference');
|
878
1096
|
if(('cips'.indexOf(anchor1) >= 0 || anchor1 === 't' && offset1 < 0) &&
|
879
1097
|
('cips'.indexOf(anchor2) >= 0 ||anchor2 === 't' && offset2 < 0)) {
|
1098
|
+
if(this.TRACE) console.log('TRACE: Variable is a self-reference.');
|
880
1099
|
// The `xv` attribute will be recognized by VMI_push_var to denote
|
881
|
-
// "use the vector of the expression for which this VMI is code"
|
1100
|
+
// "use the vector of the expression for which this VMI is code".
|
882
1101
|
return [{xv: true, dv: this.dataset.defaultValue},
|
883
1102
|
anchor1, offset1, anchor2, offset2];
|
884
|
-
} else {
|
885
|
-
this.error = 'Expression can reference only previous values of itself';
|
886
|
-
return false;
|
887
1103
|
}
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
1104
|
+
msg = 'Expression can reference only previous values of itself';
|
1105
|
+
}
|
1106
|
+
// A leading "!" denotes: pass variable reference instead of its value.
|
1107
|
+
// NOTE: This also applies to the "dot", so [!.] is a valid variable.
|
1108
|
+
let by_reference = name.startsWith('!');
|
1109
|
+
if(by_reference) name = name.substring(1);
|
1110
|
+
// When `name` is a single dot, it refers to the dataset for which the
|
1111
|
+
// modifier expression is being parsed. Like all datasets, the "dot"
|
1112
|
+
// may also have an attribute.
|
1113
|
+
if(name === '.') {
|
1114
|
+
obj = this.dot;
|
1115
|
+
if(!obj) msg = UI.ERROR.NO_DATASET_DOT;
|
1116
|
+
} else if(name.indexOf('??') >= 0) {
|
1117
|
+
msg = 'Use # as wildcard, not ??';
|
1118
|
+
}
|
1119
|
+
if(msg) {
|
1120
|
+
this.error = msg;
|
1121
|
+
return false;
|
1122
|
+
}
|
1123
|
+
// Check whether name refers to a Linny-R entity defined by the model.
|
1124
|
+
if(!obj) {
|
1125
|
+
// Variable name may start with a colon to denote that the owner
|
1126
|
+
// prefix should be added.
|
1127
|
+
name = UI.colonPrefixedName(name, this.owner_prefix);
|
1128
|
+
// Start with wildcard equations, as these are likely to be few
|
1129
|
+
// (so a quick scan) and constitute a special case.
|
1130
|
+
const
|
1131
|
+
id = UI.nameToID(name),
|
1132
|
+
w = MODEL.wildcardEquationByID(id);
|
1133
|
+
if(w) {
|
1134
|
+
if(this.TRACE) console.log('TRACE: Variable is a wildcard equation:',
|
1135
|
+
w[0], '-- number is', w[1], '\nTRACE: Equation expression: ',
|
1136
|
+
w[0].expression.text);
|
1137
|
+
// Variable matches wildcard equation w[0] with number w[1],
|
1138
|
+
// so this equation must be evaluated for that number.
|
1139
|
+
return [
|
1140
|
+
{d: w[0].dataset, s: w[1], x: w[0].expression},
|
1141
|
+
anchor1, offset1, anchor2, offset2];
|
1142
|
+
}
|
1143
|
+
// If no match, try to match the object ID with any type of entity.
|
1144
|
+
obj = MODEL.objectByID(id);
|
1145
|
+
}
|
1146
|
+
// If not, try whether wildcards can be substituted.
|
1147
|
+
if(!obj && name.indexOf('#') >= 0) {
|
1148
|
+
if(typeof this.context_number === 'number') {
|
1149
|
+
obj = MODEL.objectByName(name.replace('#', this.context_number));
|
1150
|
+
}
|
1151
|
+
if(obj && TRACE) console.log('TRACE: Matched ', name,
|
1152
|
+
'with entity:', obj.displayName);
|
1153
|
+
if(!obj) {
|
1154
|
+
// If immediate substitution of # does not identify an entity,
|
1155
|
+
// then name may still refer to a wildcard equation.
|
1156
|
+
const wcname = name.replace('#', '??');
|
1157
|
+
// Check for self-reference.
|
1158
|
+
if(wcname === this.attribute) {
|
1159
|
+
msg = 'Equation cannot reference itself';
|
1160
|
+
} else {
|
1161
|
+
obj = MODEL.equationByID(UI.nameToID(wcname));
|
1162
|
+
if(obj) {
|
1163
|
+
// Special case: the parsed variable references a wildcard
|
1164
|
+
// equation, so now `obj` is an instance of DatasetModifier.
|
1165
|
+
if(!(this.wildcard_selector || this.context_number)) {
|
1166
|
+
msg = UI.ERROR.NO_NUMBER_CONTEXT;
|
1167
|
+
} else {
|
1168
|
+
// Acceptable reference to a wildcard equation.
|
1169
|
+
if(!obj.expression.isStatic) {
|
1170
|
+
this.is_static = false;
|
1171
|
+
this.log('dynamic because wildcard equation is dynamic');
|
1172
|
+
}
|
1173
|
+
// NOTE: The referenced expression may be level-dependent.
|
1174
|
+
this.is_level_based = this.is_level_based ||
|
1175
|
+
obj.expression.is_level_based;
|
1176
|
+
if(this.TRACE) console.log('TRACE: Variable ', name,
|
1177
|
+
'is a wildcard equation:', obj.displayName,
|
1178
|
+
'-- number is:', this.context_number,
|
1179
|
+
'\nTRACE: Expression:', obj.expression.text);
|
1180
|
+
// Use the context number as "selector" parameter of the VMI.
|
1181
|
+
return [
|
1182
|
+
{d: obj.dataset, s: this.context_number, x: obj.expression},
|
1183
|
+
anchor1, offset1, anchor2, offset2];
|
1184
|
+
}
|
1185
|
+
}
|
902
1186
|
}
|
903
1187
|
}
|
1188
|
+
if(!obj) {
|
1189
|
+
// Final possibility is a match with a tail-numbered entity name.
|
1190
|
+
// NOTE: also pass `attr` so that only entities having this
|
1191
|
+
// attribute will match.
|
1192
|
+
const ame = MODEL.allMatchingEntities(wildcardMatchRegex(name), attr);
|
1193
|
+
if(ame.length > 0) {
|
1194
|
+
// NOTE: Some attributes make this expression level-dependent.
|
1195
|
+
const uca = attr.toUpperCase();
|
1196
|
+
this.is_level_based = this.is_level_based ||
|
1197
|
+
VM.level_based_attr.indexOf(uca) >= 0;
|
1198
|
+
// Pass the eligible entities along with the selector, so that
|
1199
|
+
// at run time the VM can match with the value of #.
|
1200
|
+
// NOTE: Also pass whether the entity should be pushed
|
1201
|
+
// "by reference".
|
1202
|
+
if(this.TRACE) console.log('TRACE: Variable', name,
|
1203
|
+
'matches with tail-numbered entities:', ame,
|
1204
|
+
'\nTRACE: Attribute used:', uca);
|
1205
|
+
return [{n: name, ee: ame, a: uca, br: by_reference},
|
1206
|
+
anchor1, offset1, anchor2, offset2];
|
1207
|
+
}
|
1208
|
+
// Wildcard selector, but no number context for #.
|
1209
|
+
msg = UI.ERROR.NO_NUMBER_CONTEXT;
|
1210
|
+
}
|
904
1211
|
}
|
1212
|
+
if(msg) {
|
1213
|
+
this.error = msg;
|
1214
|
+
return false;
|
1215
|
+
}
|
1216
|
+
// Now `obj` refers to a model entity (ABCDELPQ).
|
1217
|
+
// This parseVariable(...) function must return a tuple like this:
|
1218
|
+
// [object or vector, anchor 1, offset 1, anchor 2, offset 2]
|
1219
|
+
// because this will be passed along with the VM instruction that
|
1220
|
+
// pushes the variable on the operand stack of this expression.
|
905
1221
|
if(obj === null) {
|
906
1222
|
msg = `Unknown entity "${name}"`;
|
907
|
-
} else if(obj === this.dataset && attr === this.selector) {
|
908
|
-
// Prevent cyclic reference
|
909
|
-
msg = (obj === MODEL.equations_dataset ?
|
910
|
-
'Equation' : 'Dataset modifier expression') +
|
911
|
-
' must not reference itself';
|
912
1223
|
} else if(obj.array &&
|
913
1224
|
(anchor1 && '#ijk'.indexOf(anchor1) < 0 ||
|
914
1225
|
anchor2 && '#ijk'.indexOf(anchor2) < 0)) {
|
1226
|
+
// Only indices (i, j, k) and the # number can index arrays, as arrays
|
1227
|
+
// have no time dimension, while all other anchors relate to time.
|
915
1228
|
msg = 'Invalid anchor(s) for array-type dataset ' + obj.displayName;
|
916
1229
|
} else {
|
917
|
-
//
|
918
|
-
//
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
// If "by reference", return the object itself plus its attribute
|
925
|
-
if(by_reference) {
|
926
|
-
return [{r: obj, a: attr}, anchor1, offset1, anchor2, offset2];
|
927
|
-
}
|
928
|
-
if(obj === this.dataset && attr === '' && !obj.array) {
|
929
|
-
// When dataset modifier expression refers to its dataset without
|
930
|
-
// selector, then this is equivalent to [.] (use the series data
|
931
|
-
// vector) unless it is an array, since then the series data is
|
932
|
-
// not a time-scaled vector => special case
|
933
|
-
args = obj.vector;
|
934
|
-
} else if(attr === '') {
|
935
|
-
// For all other variables, assume default attribute
|
936
|
-
attr = obj.defaultAttribute;
|
937
|
-
// For a dataset, check whether the VMI_push_dataset_modifier should be
|
938
|
-
// used. This is the case for array-type datasets, and for datasets
|
939
|
-
// having modifiers UNLESS the modeler used a vertical bar to indicate
|
940
|
-
// "use the data"
|
941
|
-
if(obj instanceof Dataset &&
|
942
|
-
(obj.array || (!use_data && obj.selectorList.length > 0))) {
|
943
|
-
if(obj.data.length > 1 || obj.data.length > 0 && !obj.periodic ||
|
944
|
-
!obj.allModifiersAreStatic) {
|
945
|
-
// No explicit selector => dynamic unless no time series data, and
|
946
|
-
// ALL modifier expressions are static
|
947
|
-
this.is_static = false;
|
948
|
-
this.log('dynamic because dataset without explicit selector is used');
|
949
|
-
}
|
950
|
-
// NOTE: also pass the "use data" flag so that experiment selectors
|
951
|
-
// will be ignored if the modeler coded the vertical bar
|
952
|
-
return [{d: obj, ud: use_data}, anchor1, offset1, anchor2, offset2];
|
953
|
-
}
|
1230
|
+
// If the variable denotes an equation or a dataset with a selector,
|
1231
|
+
// check whether this is the "owner" of the expression being parsed.
|
1232
|
+
let sel = '',
|
1233
|
+
xtype = '';
|
1234
|
+
if(obj instanceof DatasetModifier) {
|
1235
|
+
sel = obj.selector;
|
1236
|
+
xtype = 'Equation';
|
954
1237
|
} else if(obj instanceof Dataset) {
|
955
|
-
|
956
|
-
|
957
|
-
// expression for this wildcard AND the specified attribute
|
958
|
-
if(mm.length > 0) {
|
959
|
-
const nr = endsWithDigits(attr);
|
960
|
-
if(!mm[0].expression.isStatic) {
|
961
|
-
this.is_static = false;
|
962
|
-
this.log('dynamic because dataset modifier expression is dynamic');
|
963
|
-
}
|
964
|
-
// NOTE: the attribute is relevant only to set the context for #,
|
965
|
-
// so if it ends on digits, put the number in the VM instruction
|
966
|
-
// to save execution time
|
967
|
-
return [{d: obj, s: (nr ? parseInt(nr) : attr),
|
968
|
-
x: mm[0].expression}, anchor1, offset1, anchor2, offset2];
|
969
|
-
}
|
1238
|
+
sel = attr;
|
1239
|
+
xtype = 'Dataset modifier expression';
|
970
1240
|
}
|
971
|
-
//
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
1241
|
+
// In variable names, wildcards are denoted as #, so also check for
|
1242
|
+
// the (unlikely) case that [eq#x] is used in the expression for a
|
1243
|
+
// wildcard equation or dataset modifier with name "eq??x".
|
1244
|
+
if(sel && (sel === this.selector ||
|
1245
|
+
sel.replace('#', '??') === this.selector)) {
|
1246
|
+
// Match indicates a cyclic reference
|
1247
|
+
msg = `${xtype} must not reference itself`;
|
1248
|
+
}
|
1249
|
+
}
|
1250
|
+
if(msg) {
|
1251
|
+
this.error = msg;
|
1252
|
+
return false;
|
1253
|
+
}
|
1254
|
+
// If `obj` is a dataset *modifier*, it must be a "normal" equation...
|
1255
|
+
if(obj instanceof DatasetModifier) {
|
1256
|
+
if(this.TRACE) console.log('TRACE: Dataset modifier "' + obj.displayName +
|
1257
|
+
'" mapped to dataset:', obj.dataset.name,
|
1258
|
+
'and selector:', obj.selector);
|
1259
|
+
// ... so "map" it onto the equations dataset + selector...
|
1260
|
+
attr = obj.selector;
|
1261
|
+
obj = obj.dataset;
|
1262
|
+
}
|
1263
|
+
// ... so now it will be processed the same way dataset modifiers
|
1264
|
+
// are processed, especially when they have a tail number.
|
1265
|
+
|
1266
|
+
// Set default anchors in case no anchors are specified.
|
1267
|
+
// Except for array-type datasets, the default anchor is 't';
|
1268
|
+
// for array-type datasets in expressions for array-type datasets,
|
1269
|
+
// the SPECIAL anchor is '^' to indicate "use parent anchor"
|
1270
|
+
// (which will be the parent's context-sensitive number #)
|
1271
|
+
const default_anchor = (obj.array ?
|
1272
|
+
(this.dataset && this.dataset.array ? '^' : '') : 't');
|
1273
|
+
if(!anchor1) anchor1 = default_anchor;
|
1274
|
+
if(!anchor2) anchor2 = default_anchor;
|
1275
|
+
|
1276
|
+
// If "by reference", return the object itself plus its attribute
|
1277
|
+
if(by_reference) {
|
1278
|
+
if(this.TRACE) console.log('TRACE: Variable is a reference to',
|
1279
|
+
obj.displayName, '. Attribute:', attr);
|
1280
|
+
return [{r: obj, a: attr}, anchor1, offset1, anchor2, offset2];
|
1281
|
+
}
|
1282
|
+
if(obj === this.dataset && attr === '' && !obj.array) {
|
1283
|
+
// When dataset modifier expression refers to its dataset without
|
1284
|
+
// selector, then this is equivalent to [.] (use the series data
|
1285
|
+
// vector) unless it is an array, since then the series data is
|
1286
|
+
// not a time-scaled vector => special case.
|
1287
|
+
if(this.TRACE) console.log(
|
1288
|
+
'TRACE: Dataset without selector, no array:', obj.displayName,
|
1289
|
+
'Use vector:', obj.vector);
|
1290
|
+
arg0 = obj.vector;
|
1291
|
+
} else if(attr === '') {
|
1292
|
+
// For all other variables, assume default attribute if none specified
|
1293
|
+
attr = obj.defaultAttribute;
|
1294
|
+
// For a dataset, check whether the VMI_push_dataset_modifier should be
|
1295
|
+
// used. This is the case for array-type datasets, and for datasets
|
1296
|
+
// having modifiers UNLESS the modeler used a vertical bar to indicate
|
1297
|
+
// "use the data".
|
1298
|
+
if(obj instanceof Dataset &&
|
1299
|
+
(obj.array || (!use_data && obj.selectorList.length > 0))) {
|
1300
|
+
// No explicit selector means that this variable is dynamic if
|
1301
|
+
// the dataset has time series data, or if some of its modifier
|
1302
|
+
// expressions are dynamic.
|
1303
|
+
if(obj.data.length > 1 || (obj.data.length > 0 && !obj.periodic) ||
|
1304
|
+
!obj.allModifiersAreStatic) {
|
980
1305
|
this.is_static = false;
|
981
|
-
this.log('dynamic because
|
982
|
-
}
|
983
|
-
|
1306
|
+
this.log('dynamic because dataset without explicit selector is used');
|
1307
|
+
}
|
1308
|
+
if(this.TRACE) console.log(
|
1309
|
+
'TRACE: Dataset without explicit selector:',
|
1310
|
+
(obj.array ? 'array' : 'has modifiers'), obj.displayName,
|
1311
|
+
'\nTRACE: Use VMI_push_dataset_modifier; use-data flag:', use_data);
|
1312
|
+
// NOTE: Also pass the "use data" flag so that experiment selectors
|
1313
|
+
// will be ignored if the modeler coded the vertical bar.
|
1314
|
+
return [{d: obj, ud: use_data}, anchor1, offset1, anchor2, offset2];
|
1315
|
+
}
|
1316
|
+
} else if(obj instanceof Dataset) {
|
1317
|
+
// For datasets, the attribute must be a modifier selector, so first
|
1318
|
+
// check if this dataset has a modifier that matches `attr`.
|
1319
|
+
const mm = obj.matchingModifiers([attr]);
|
1320
|
+
if(mm.length === 0) {
|
1321
|
+
// No match indicates unknown attribute.
|
1322
|
+
this.error = `Dataset ${obj.displayName} has no modifier with selector "${attr}"`;
|
1323
|
+
return false;
|
1324
|
+
} else {
|
1325
|
+
// NOTE: Multiple matches are impossible because `attr` cannot
|
1326
|
+
// contain wildcards; hence this is a unique match, so the modifier
|
1327
|
+
// expression is known.
|
1328
|
+
const m = mm[0];
|
1329
|
+
if(!m.expression.isStatic) {
|
984
1330
|
this.is_static = false;
|
985
|
-
this.log('
|
986
|
-
|
1331
|
+
this.log('dynamic because dataset modifier expression is dynamic');
|
1332
|
+
}
|
1333
|
+
// NOTE: A single match may be due to wildcard(s) in the modifier,
|
1334
|
+
// e.g., a variable [dataset|abc] matches with a modifier having
|
1335
|
+
// wildcard selector "a?b", or [dataset|a12] matches with "a*".
|
1336
|
+
// In such cases, if the selector matches an integer like "a12"
|
1337
|
+
// in the example above, this number (12) should be used as
|
1338
|
+
// number context (overriding the number of the dataset, so
|
1339
|
+
// for [datset34|a12], the number context is '12' and not '34').
|
1340
|
+
let mcn = matchingNumber(attr, m.selector);
|
1341
|
+
if(mcn === false) {
|
1342
|
+
// NOTE: When no matching number is found, `attr` may still
|
1343
|
+
// contain a ?? wildcard. If it indeed identifies a wildcard
|
1344
|
+
// equation, then "?" should be passed to the VM instruction.
|
1345
|
+
if(obj === MODEL.equations_dataset && attr.indexOf('??') >= 0) {
|
1346
|
+
mcn = '?';
|
1347
|
+
} else {
|
1348
|
+
// Ensure that `mcn` is either an integer value or FALSE.
|
1349
|
+
mcn = parseInt(UI.tailNumber(obj.name)) || this.context_number;
|
1350
|
+
}
|
987
1351
|
}
|
1352
|
+
// Pass the dataset, the context number # (or FALSE) in place,
|
1353
|
+
// and the modifier expression.
|
1354
|
+
if(this.TRACE) console.log('TRACE: Variable is',
|
1355
|
+
(m.dataset === MODEL.equations_dataset ?
|
1356
|
+
'an equation: ' + m.selector :
|
1357
|
+
'a dataset with explicit selector: ' + m.displayName),
|
1358
|
+
'\nTRACE: Context number:', mcn, ' Expression:', m.expression.text);
|
1359
|
+
return [
|
1360
|
+
{d: m.dataset, s: mcn, x: m.expression},
|
1361
|
+
anchor1, offset1, anchor2, offset2];
|
988
1362
|
}
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
this.is_level_based = true;
|
996
|
-
// @TO DO: rethink whether this will indeed also make this expression
|
997
|
-
// dynamic
|
1363
|
+
}
|
1364
|
+
// NOTE: `arg0` can now be a single value, a vector, or NULL.
|
1365
|
+
if(arg0 === null) arg0 = obj.attributeValue(attr);
|
1366
|
+
if(Array.isArray(arg0)) {
|
1367
|
+
if(obj instanceof Dataset) {
|
1368
|
+
if(obj.data.length > 1 || obj.data.length > 0 && !obj.periodic) {
|
998
1369
|
this.is_static = false;
|
999
|
-
this.log('dynamic because
|
1000
|
-
// NOTE: VM instructions VMI_push_var will recognize this special case
|
1001
|
-
return [{c: obj, u: cluster_balance_unit},
|
1002
|
-
anchor1, offset1, anchor2, offset2];
|
1370
|
+
this.log('dynamic because dataset vector is used');
|
1003
1371
|
}
|
1004
|
-
|
1005
|
-
|
1372
|
+
} else if(VM.level_based_attr.indexOf(attr) >= 0) {
|
1373
|
+
this.is_static = false;
|
1374
|
+
this.log('dynamic because level-based attribute');
|
1006
1375
|
} else {
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1376
|
+
// Unusual (?) combi, so let's assume dynamic.
|
1377
|
+
this.is_static = false;
|
1378
|
+
this.log('probably dynamic -- check below:');
|
1379
|
+
console.log('ANOMALY: array for', obj.displayName, obj, attr, arg0);
|
1380
|
+
}
|
1381
|
+
if(this.TRACE) console.log('TRACE: arg[0] is a vector');
|
1382
|
+
}
|
1383
|
+
// If not a single value or vector, it must be an expression.
|
1384
|
+
if(arg0 === null) arg0 = obj.attributeExpression(attr);
|
1385
|
+
if(arg0 === null) {
|
1386
|
+
// Only NOW check whether unit balance for clusters is asked for.
|
1387
|
+
if(cluster_balance_unit !== false && obj instanceof Cluster) {
|
1388
|
+
// NOTE: Cluster balance ALWAYS makes expression level-based
|
1389
|
+
// and dynamic.
|
1390
|
+
this.is_level_based = true;
|
1391
|
+
this.is_static = false;
|
1392
|
+
this.log('dynamic because cluster balance is level-based');
|
1393
|
+
if(this.TRACE) console.log('TRACE: Variable is a balance:',
|
1394
|
+
cluster_balance_unit, 'for cluster', obj.displayName);
|
1395
|
+
// NOTE: VM instructions VMI_push_var will recognize this special case
|
1396
|
+
return [{c: obj, u: cluster_balance_unit},
|
1397
|
+
anchor1, offset1, anchor2, offset2];
|
1398
|
+
}
|
1399
|
+
// Fall-through: invalid attribute for this object
|
1400
|
+
msg = `${obj.type} entities have no attribute "${attr}"`;
|
1401
|
+
} else {
|
1402
|
+
if(arg0 instanceof Expression) {
|
1403
|
+
this.is_static = this.is_static && arg0.isStatic;
|
1011
1404
|
}
|
1405
|
+
if(this.TRACE) console.log('TRACE: arg[0] is the expression for',
|
1406
|
+
arg0.variableName, '\nTRACE: Expression:', arg0.text);
|
1407
|
+
args = [arg0, anchor1, offset1, anchor2, offset2];
|
1012
1408
|
}
|
1013
|
-
if(msg
|
1014
|
-
|
1015
|
-
|
1016
|
-
this.is_level_based = this.is_level_based ||
|
1017
|
-
// NOTE: dataset modifier expressions may be level_based
|
1018
|
-
obj instanceof Dataset && attr && args[0].is_level_based ||
|
1019
|
-
// NOTE: assume NOT level_based if anchor & offset are specified
|
1020
|
-
VM.level_based_attr.indexOf(attr) >= 0 &&
|
1021
|
-
anchor1 === 't' && offset1 === 0 &&
|
1022
|
-
anchor2 === 't' && offset2 === 0;
|
1023
|
-
return args;
|
1409
|
+
if(msg) {
|
1410
|
+
this.error = msg;
|
1411
|
+
return false;
|
1024
1412
|
}
|
1025
|
-
|
1026
|
-
|
1413
|
+
// Now `args` should be a valid argument for a VM instruction that
|
1414
|
+
// pushes an operand on the evaluation stack.
|
1415
|
+
// Check whether the attribute is level-based (i.e., can be computed
|
1416
|
+
// only after optimizing a block) while no offset is defined to use
|
1417
|
+
// prior data.
|
1418
|
+
this.is_level_based = this.is_level_based ||
|
1419
|
+
// NOTE: Dataset modifier expressions may be level-based.
|
1420
|
+
(obj instanceof Dataset && attr && arg0.is_level_based) ||
|
1421
|
+
// Assume NOT level-based if anchor & offset are specified.
|
1422
|
+
// NOTE: This is based on the assumption that advanced modelers
|
1423
|
+
// know what they are doing.
|
1424
|
+
(VM.level_based_attr.indexOf(attr) >= 0 &&
|
1425
|
+
anchor1 === 't' && offset1 === 0 &&
|
1426
|
+
anchor2 === 't' && offset2 === 0);
|
1427
|
+
return args;
|
1027
1428
|
}
|
1028
1429
|
|
1029
|
-
// Gets the next substring in the expression that is a valid symbol
|
1030
|
-
// while advancing the position-in-text (pit) and length-of-symbol (los),
|
1031
|
-
// which are used to highlight the position of a syntax error in the
|
1032
|
-
// expression editor
|
1033
1430
|
getSymbol() {
|
1431
|
+
// Gets the next substring in the expression that is a valid symbol
|
1432
|
+
// while advancing the position-in-text (`pit`) and length-of-symbol
|
1433
|
+
// (`los`), which are used to highlight the position of a syntax error
|
1434
|
+
// in the expression editor
|
1034
1435
|
let c, f, i, l, v;
|
1035
1436
|
this.prev_sym = this.sym;
|
1036
1437
|
this.sym = null;
|
@@ -1130,7 +1531,7 @@ class ExpressionParser {
|
|
1130
1531
|
if(this.selector.indexOf('*') >= 0 ||
|
1131
1532
|
this.selector.indexOf('?') >= 0 ||
|
1132
1533
|
this.owner.numberContext) {
|
1133
|
-
this.sym =
|
1534
|
+
this.sym = VMI_push_contextual_number;
|
1134
1535
|
} else {
|
1135
1536
|
this.error = '# is undefined in this context';
|
1136
1537
|
}
|
@@ -1241,6 +1642,8 @@ class ExpressionParser {
|
|
1241
1642
|
// Compiles expression into array of VM instructions `code`
|
1242
1643
|
// NOTE: always create a new code array instance, as it will typically
|
1243
1644
|
// become the code attribute of an expression object
|
1645
|
+
if(DEBUGGING) console.log('COMPILING', this.ownerName, ':\n',
|
1646
|
+
this.expr, '\ncontext number =', this.context_number);
|
1244
1647
|
this.code = [];
|
1245
1648
|
// Position in text
|
1246
1649
|
this.pit = 0;
|
@@ -1347,7 +1750,7 @@ class ExpressionParser {
|
|
1347
1750
|
// start by popping the FALSE result of the IF condition
|
1348
1751
|
this.code.push([VMI_pop_false, null]);
|
1349
1752
|
}
|
1350
|
-
// END of new code
|
1753
|
+
// END of new code for IF-THEN-ELSE
|
1351
1754
|
|
1352
1755
|
this.op_stack.push(this.sym);
|
1353
1756
|
} else if(this.sym !== null) {
|
@@ -1356,11 +1759,14 @@ class ExpressionParser {
|
|
1356
1759
|
this.code.push([this.sym, null]);
|
1357
1760
|
} else if(Array.isArray(this.sym)) {
|
1358
1761
|
// Either a statistic, a dataset (array-type or with modifier),
|
1359
|
-
// an experiment run result, or a variable
|
1360
|
-
if(this.sym.length
|
1762
|
+
// an experiment run result, or a variable.
|
1763
|
+
if(this.sym.length >= 6) {
|
1764
|
+
// 6 or 7 arguments indicates a statistic.
|
1361
1765
|
this.code.push([VMI_push_statistic, this.sym]);
|
1362
1766
|
} else if(this.sym[0].hasOwnProperty('d')) {
|
1363
1767
|
this.code.push([VMI_push_dataset_modifier, this.sym]);
|
1768
|
+
} else if(this.sym[0].hasOwnProperty('ee')) {
|
1769
|
+
this.code.push([VMI_push_wildcard_entity, this.sym]);
|
1364
1770
|
} else if(this.sym[0].hasOwnProperty('x')) {
|
1365
1771
|
this.code.push([VMI_push_run_result, this.sym]);
|
1366
1772
|
} else if(this.sym[0].hasOwnProperty('r')) {
|
@@ -1392,8 +1798,7 @@ class ExpressionParser {
|
|
1392
1798
|
this.error = 'Invalid parameter list';
|
1393
1799
|
}
|
1394
1800
|
}
|
1395
|
-
if(DEBUGGING) console.log('PARSED',
|
1396
|
-
this.owner.displayName + '|' + this.attribute, ':',
|
1801
|
+
if(this.TRACE || DEBUGGING) console.log('PARSED', this.ownerName, ':',
|
1397
1802
|
this.expr, this.code);
|
1398
1803
|
}
|
1399
1804
|
|
@@ -4261,9 +4666,8 @@ class VirtualMachine {
|
|
4261
4666
|
} else if(this.error_codes.indexOf(n) < 0) {
|
4262
4667
|
err += '? value = ' + n;
|
4263
4668
|
}
|
4264
|
-
|
4265
|
-
|
4266
|
-
UI.alert(msg);
|
4669
|
+
this.logMessage(this.block_count, err);
|
4670
|
+
UI.alert(err);
|
4267
4671
|
}
|
4268
4672
|
|
4269
4673
|
get actualBlockLength() {
|
@@ -4844,13 +5248,18 @@ Solver status = ${json.status}`);
|
|
4844
5248
|
// Show the reset button (GUI only)
|
4845
5249
|
UI.readyToReset();
|
4846
5250
|
// If receiver is active, report results
|
4847
|
-
if(RECEIVER.solving) RECEIVER.report();
|
5251
|
+
if(RECEIVER.solving || MODEL.report_results) RECEIVER.report();
|
4848
5252
|
// If experiment is active, signal the manager
|
4849
5253
|
if(MODEL.running_experiment) EXPERIMENT_MANAGER.processRun();
|
4850
5254
|
// Warn modeler if any issues occurred
|
4851
5255
|
if(this.block_issues) {
|
4852
|
-
|
4853
|
-
|
5256
|
+
let msg = 'Issues occurred in ' +
|
5257
|
+
pluralS(this.block_issues, 'block') +
|
5258
|
+
' -- details can be viewed in the monitor';
|
5259
|
+
if(VM.issue_list.length) {
|
5260
|
+
msg += ' and by using \u25C1 \u25B7';
|
5261
|
+
}
|
5262
|
+
UI.warn(msg);
|
4854
5263
|
UI.updateIssuePanel();
|
4855
5264
|
}
|
4856
5265
|
if(this.license_expired > 0) {
|
@@ -5230,11 +5639,11 @@ function VMI_push_second(x, empty) {
|
|
5230
5639
|
pushTimeStepsPerTimeUnit(x, 'second');
|
5231
5640
|
}
|
5232
5641
|
|
5233
|
-
function
|
5234
|
-
// Pushes the numeric value of the
|
5642
|
+
function VMI_push_contextual_number(x, empty) {
|
5643
|
+
// Pushes the numeric value of the context-sensitive number #
|
5235
5644
|
const n = valueOfNumberSign(x);
|
5236
5645
|
if(DEBUGGING) {
|
5237
|
-
console.log('push
|
5646
|
+
console.log('push contextual number: # = ' + VM.sig2Dig(n));
|
5238
5647
|
}
|
5239
5648
|
x.push(n);
|
5240
5649
|
}
|
@@ -5242,118 +5651,121 @@ function VMI_push_selector_wildcard(x, empty) {
|
|
5242
5651
|
/* VM instruction helper functions */
|
5243
5652
|
|
5244
5653
|
function valueOfNumberSign(x) {
|
5245
|
-
// Pushes the numeric value of the # sign
|
5654
|
+
// Pushes the numeric value of the # sign for the context of expression `x`
|
5246
5655
|
// NOTE: this can be a wildcard match, an active experiment run selector
|
5247
|
-
// ending on digits, or an entity
|
5656
|
+
// ending on digits, or tne number context of an entity. The latter typically
|
5657
|
+
// is the number its name or any of its prefixes ends on, but notes are
|
5658
|
+
// more "creative" and can return the number context of nearby entities.
|
5248
5659
|
let s = 'NO SELECTOR',
|
5249
5660
|
m = 'NO MATCH',
|
5250
|
-
n =
|
5251
|
-
// NOTE:
|
5252
|
-
// because wildcard selector is an immediate property of the dataset
|
5253
|
-
// modifier
|
5254
|
-
// experiment selectors that identify the run
|
5255
|
-
if(x.
|
5256
|
-
n = x.
|
5661
|
+
n = VM.UNDEFINED;
|
5662
|
+
// NOTE: Give wildcard selectors precedence over experiment selectors
|
5663
|
+
// because a wildcard selector is an immediate property of the dataset
|
5664
|
+
// modifier expression, and hence "closer" to the expression than the
|
5665
|
+
// experiment selectors that identify the run.
|
5666
|
+
if(x.wildcard_vector_index !== false) {
|
5667
|
+
n = x.wildcard_vector_index;
|
5257
5668
|
s = x.attribute;
|
5258
5669
|
m = 'wildcard';
|
5259
5670
|
} else {
|
5260
|
-
|
5261
|
-
|
5262
|
-
|
5263
|
-
|
5264
|
-
|
5265
|
-
|
5266
|
-
|
5671
|
+
// Check whether `x` is a dataset modifier expression.
|
5672
|
+
// NOTE: This includes equations.
|
5673
|
+
if(x.object instanceof Dataset) {
|
5674
|
+
if(x.attribute) {
|
5675
|
+
s = x.attribute;
|
5676
|
+
} else {
|
5677
|
+
// Selector may also be defined by a running experiment.
|
5678
|
+
if(MODEL.running_experiment) {
|
5679
|
+
// Let `m` be the primary selector for the current experiment run.
|
5680
|
+
const sl = MODEL.running_experiment.activeCombination;
|
5681
|
+
if(sl) {
|
5682
|
+
m = sl[0];
|
5683
|
+
s = 'experiment';
|
5684
|
+
}
|
5267
5685
|
}
|
5268
5686
|
}
|
5269
5687
|
}
|
5270
|
-
// If selector contains no wildcards,
|
5688
|
+
// If selector contains no wildcards, get number context (typically
|
5689
|
+
// inferred from a number in the name of the object)
|
5271
5690
|
if(s.indexOf('*') < 0 && s.indexOf('?') < 0) {
|
5272
5691
|
const d = x.object.numberContext;
|
5273
5692
|
if(d) {
|
5274
|
-
s =
|
5693
|
+
s = x.object.displayName;
|
5275
5694
|
m = d;
|
5695
|
+
n = parseInt(d);
|
5276
5696
|
}
|
5277
|
-
}
|
5278
|
-
|
5279
|
-
//
|
5280
|
-
|
5281
|
-
match = m.match(new RegExp(raw));
|
5282
|
-
if(match) {
|
5283
|
-
// Concatenate all matching characters (need not be digits)
|
5284
|
-
m = '';
|
5285
|
-
for(let i = 1; i < match.length; i++) m += match[i];
|
5286
|
-
// Try to convert to an integer
|
5287
|
-
n = parseInt(m);
|
5288
|
-
// Default to 0
|
5289
|
-
if(isNaN(n)) n = 0;
|
5290
|
-
}
|
5697
|
+
} else if(m !== 'NO MATCH') {
|
5698
|
+
// If matching `m` against `s` do not yield an integer string,
|
5699
|
+
// use UNDEFINED
|
5700
|
+
n = matchingNumber(m, s) || VM.UNDEFINED;
|
5291
5701
|
}
|
5292
5702
|
}
|
5703
|
+
// For datasets, set the parent anchor to be the context-sensitive number
|
5293
5704
|
if(x.object instanceof Dataset) x.object.parent_anchor = n;
|
5294
5705
|
if(DEBUGGING) {
|
5295
|
-
console.log(
|
5296
|
-
`${x.object.displayName}${x.attribute ? '|' + x.attribute : ''}
|
5706
|
+
console.log(`context for # in expression for ${x.variableName}
|
5297
5707
|
- expression: ${x.text}
|
5298
|
-
- inferred value of # ${s} => ${m} => ${
|
5708
|
+
- inferred value of # ${s} => ${m} => ${n}`, x.code);
|
5299
5709
|
}
|
5300
5710
|
return n;
|
5301
5711
|
}
|
5302
5712
|
|
5303
5713
|
function relativeTimeStep(t, anchor, offset, dtm, x) {
|
5304
|
-
// Returns the relative time step, given t, anchor, offset,
|
5305
|
-
// and the expression being evaluated (to provide
|
5306
|
-
//
|
5307
|
-
|
5308
|
-
// Anchors are checked for in order of *expected* frequency of occurrence
|
5714
|
+
// Returns the relative time step, given t, anchor, offset,
|
5715
|
+
// delta-t-multiplier and the expression being evaluated (to provide
|
5716
|
+
// context for anchor #).
|
5717
|
+
// NOTE: t = 1 corresponds with first time step of simulation period.
|
5718
|
+
// Anchors are checked for in order of *expected* frequency of occurrence.
|
5309
5719
|
if(anchor === 't') {
|
5310
|
-
// Offset relative to current time step (most likely to occur)
|
5720
|
+
// Offset relative to current time step (most likely to occur).
|
5311
5721
|
return Math.floor(t + offset);
|
5312
5722
|
}
|
5723
|
+
if(anchor === '#') {
|
5724
|
+
// Index: offset is added to the inferred value of the # symbol.
|
5725
|
+
return valueOfNumberSign(x) + offset;
|
5726
|
+
}
|
5727
|
+
if(anchor === '^') {
|
5728
|
+
// Inherited index (for dataset modifier expressions): offset is added
|
5729
|
+
// to the anchor of the modifier's dataset.
|
5730
|
+
if(x.object.array) {
|
5731
|
+
if(DEBUGGING) {
|
5732
|
+
console.log('Parent anchor', x.object.parent_anchor);
|
5733
|
+
}
|
5734
|
+
// NOTE: For not array-type datasets, ^ is equivalent to #
|
5735
|
+
return x.object.parent_anchor;
|
5736
|
+
}
|
5737
|
+
return valueOfNumberSign(x) + offset;
|
5738
|
+
}
|
5739
|
+
if('ijk'.indexOf(anchor) >= 0) {
|
5740
|
+
// Index: offset is added to the iterator index i, j or k.
|
5741
|
+
return valueOfIndexVariable(anchor) + offset;
|
5742
|
+
}
|
5313
5743
|
if(anchor === 'r') {
|
5314
|
-
// Offset relative to current time step, scaled to time unit of run
|
5744
|
+
// Offset relative to current time step, scaled to time unit of run.
|
5315
5745
|
return Math.floor((t + offset) * dtm);
|
5316
5746
|
}
|
5317
5747
|
if(anchor === 'c') {
|
5318
|
-
// Relative to start of current optimization block
|
5748
|
+
// Relative to start of current optimization block.
|
5319
5749
|
return Math.trunc(t / MODEL.block_length) * MODEL.block_length + offset;
|
5320
5750
|
}
|
5321
5751
|
if(anchor === 'p') {
|
5322
|
-
// Relative to start of previous optimization block
|
5752
|
+
// Relative to start of previous optimization block.
|
5323
5753
|
return (Math.trunc(t / MODEL.block_length) - 1) * MODEL.block_length + offset;
|
5324
5754
|
}
|
5325
5755
|
if(anchor === 'n') {
|
5326
|
-
// Relative to start of next optimization block
|
5756
|
+
// Relative to start of next optimization block.
|
5327
5757
|
return (Math.trunc(t / MODEL.block_length) + 1) * MODEL.block_length + offset;
|
5328
5758
|
}
|
5329
5759
|
if(anchor === 'l') {
|
5330
|
-
// Last: offset relative to the last index in the vector
|
5760
|
+
// Last: offset relative to the last index in the vector.
|
5331
5761
|
return MODEL.end_period - MODEL.start_period + 1 + offset;
|
5332
5762
|
}
|
5333
5763
|
if(anchor === 's') {
|
5334
|
-
// Scaled: offset is scaled to time unit of run
|
5764
|
+
// Scaled: offset is scaled to time unit of run.
|
5335
5765
|
return Math.floor(offset * dtm);
|
5336
5766
|
}
|
5337
|
-
|
5338
|
-
|
5339
|
-
return valueOfIndexVariable(anchor) + offset;
|
5340
|
-
}
|
5341
|
-
if(anchor === '#') {
|
5342
|
-
// Index: offset is added to the inferred value of the # symbol
|
5343
|
-
return valueOfNumberSign(x) + offset;
|
5344
|
-
}
|
5345
|
-
if(anchor === '^') {
|
5346
|
-
// Inherited index: offset is added to anchor of "parent" dataset
|
5347
|
-
if(x.object.array) {
|
5348
|
-
if(DEBUGGING) {
|
5349
|
-
console.log('Parent anchor', x.object.parent_anchor);
|
5350
|
-
}
|
5351
|
-
return x.object.parent_anchor;
|
5352
|
-
}
|
5353
|
-
return valueOfNumberSign(x) + offset;
|
5354
|
-
}
|
5355
|
-
// Fall-through: offset relative to the initial value index (0)
|
5356
|
-
// NOTE: this also applies to anchor f (First)
|
5767
|
+
// Fall-through: offset relative to the initial value index (0).
|
5768
|
+
// NOTE: this also applies to anchor f (First).
|
5357
5769
|
return offset;
|
5358
5770
|
}
|
5359
5771
|
|
@@ -5433,9 +5845,9 @@ function VMI_push_var(x, args) {
|
|
5433
5845
|
}
|
5434
5846
|
|
5435
5847
|
function VMI_push_entity(x, args) {
|
5436
|
-
// Pushes a special "entity reference" object based on `args`, being the
|
5437
|
-
// [obj, anchor1, offset1, anchor2, offset2] where `obj` has the
|
5438
|
-
// {r: entity object, a: attribute}
|
5848
|
+
// Pushes a special "entity reference" object based on `args`, being the
|
5849
|
+
// list [obj, anchor1, offset1, anchor2, offset2] where `obj` has the
|
5850
|
+
// format {r: entity object, a: attribute}
|
5439
5851
|
// The object that is pushed on the stack passes the entity, the attribute
|
5440
5852
|
// to use, and the time interval
|
5441
5853
|
const
|
@@ -5451,35 +5863,92 @@ function VMI_push_entity(x, args) {
|
|
5451
5863
|
x.push(er);
|
5452
5864
|
}
|
5453
5865
|
|
5866
|
+
function VMI_push_wildcard_entity(x, args) {
|
5867
|
+
// Pushes the value of (or reference to) an entity attribute, based on
|
5868
|
+
// `args`, being the list [obj, anchor1, offset1, anchor2, offset2]
|
5869
|
+
// where `obj` has the format {ee: list of eligible entities,
|
5870
|
+
// n: name (with wildcard #), a: attribute, br: by reference (boolean)}
|
5871
|
+
// First select the first entity in `ee` that matches the wildcard vector
|
5872
|
+
// index of the expression `x` being executed.
|
5873
|
+
const el = args[0].ee;
|
5874
|
+
let nn = args[0].n.replace('#', x.wildcard_vector_index),
|
5875
|
+
obj = null;
|
5876
|
+
for(let i = 0; !obj && i < el.length; i++) {
|
5877
|
+
if(el[i].name === nn) obj = el[i];
|
5878
|
+
}
|
5879
|
+
// If no match, then this indicates a bad reference.
|
5880
|
+
if(!obj) {
|
5881
|
+
console.log(`ERROR: no match for "${nn}" in eligible entity list`, el);
|
5882
|
+
x.push(VM.BAD_REF);
|
5883
|
+
return;
|
5884
|
+
}
|
5885
|
+
// Otherwise, if args[0] indicates "by reference", then VMI_push_entity
|
5886
|
+
// can be called with the appropriate parameters.
|
5887
|
+
const attr = args[0].a || obj.defaultAttribute;
|
5888
|
+
if(args[0].br) {
|
5889
|
+
VMI_push_entity(x, {r: obj, a: attr});
|
5890
|
+
return;
|
5891
|
+
}
|
5892
|
+
// Otherwise, if the entity is a dataset modifier, this must be an
|
5893
|
+
// equation (identified by its name, not by a modifier selector) so
|
5894
|
+
// push the result of this equation using the wildcard vector index
|
5895
|
+
// of the expression that is being computed.
|
5896
|
+
if(obj instanceof DatasetModifier) {
|
5897
|
+
VMI_push_dataset_modifier(x,
|
5898
|
+
[{d: obj.dataset, s: x.wildcard_vector_index, x: obj.expression},
|
5899
|
+
args[1], args[2], args[3], args[4]]);
|
5900
|
+
return;
|
5901
|
+
}
|
5902
|
+
// Otherwise, it can be a vector type attribute or an expression.
|
5903
|
+
let v = obj.attributeValue(attr);
|
5904
|
+
if(v === null) v = obj.attributeExpression(attr);
|
5905
|
+
// If no match, then this indicates a bad reference.
|
5906
|
+
if(v === null) {
|
5907
|
+
console.log(`ERROR: bad variable "${obj.displayName}" with attribute "${attr}"`);
|
5908
|
+
x.push(VM.BAD_REF);
|
5909
|
+
return;
|
5910
|
+
}
|
5911
|
+
// Otherwise, VMI_push_var can be called with `v` as first argument.
|
5912
|
+
VMI_push_var(x, [v, args[1], args[2], args[3], args[4]]);
|
5913
|
+
}
|
5914
|
+
|
5454
5915
|
function VMI_push_dataset_modifier(x, args) {
|
5455
|
-
// NOTE: the first argument specifies the dataset `d` and (optionally!)
|
5456
|
-
// modifier selector `s`, and expression `x
|
5457
|
-
//
|
5458
|
-
//
|
5459
|
-
//
|
5916
|
+
// NOTE: the first argument specifies the dataset `d` and (optionally!)
|
5917
|
+
// the modifier selector `s`, and expression `x`.
|
5918
|
+
// If `s` is a number, then the result of `x` must be computed with
|
5919
|
+
// this number als wildcard number.
|
5920
|
+
// If `s` is not specified, the modifier to be used must be inferred from
|
5921
|
+
// the running experiment UNLESS the field `ud` ("use data") is defined
|
5922
|
+
// for the first argument, and evaluates as TRUE.
|
5923
|
+
// NOTE: Ensure that number 0 is not interpreted as FALSE.
|
5924
|
+
let wcnr = (args[0].s === undefined ? false : args[0].s);
|
5460
5925
|
const
|
5461
5926
|
ds = args[0].d,
|
5462
|
-
|
5463
|
-
|
5464
|
-
|
5465
|
-
|
5466
|
-
// NOTE: use the "local" time step for expression x, i.e., the top value
|
5467
|
-
// of the expression's time step stack `x.step`
|
5927
|
+
ud = args[0].ud || false,
|
5928
|
+
mx = args[0].x || null,
|
5929
|
+
// NOTE: Use the "local" time step for expression x, i.e., the top
|
5930
|
+
// value of the expression's time step stack `x.step`.
|
5468
5931
|
tot = twoOffsetTimeStep(x.step[x.step.length - 1],
|
5469
5932
|
args[1], args[2], args[3], args[4], 1, x);
|
5933
|
+
// NOTE: Sanity check to facilitate debugging; if no dataset is provided,
|
5934
|
+
// the script will still break at the LET statement below.
|
5935
|
+
if(!ds) console.log('ERROR: VMI_push_dataset_modifier without dataset',
|
5936
|
+
x.variableName, x.code);
|
5470
5937
|
let t = tot[0],
|
5938
|
+
// By default, use the vector of the dataset to compute the value.
|
5471
5939
|
obj = ds.vector;
|
5940
|
+
|
5472
5941
|
if(ds.array) {
|
5473
|
-
// For array, do NOT adjust "index" t to model run period
|
5474
|
-
// NOTE:
|
5942
|
+
// For array-type datasets, do NOT adjust "index" t to model run period.
|
5943
|
+
// NOTE: Indices start at 1, but arrays are zero-based, so subtract 1.
|
5475
5944
|
t--;
|
5476
|
-
// When periodic, adjust t to fall within the vector length
|
5945
|
+
// When data is periodic, adjust `t` to fall within the vector length.
|
5477
5946
|
if(ds.periodic && obj.length > 0) {
|
5478
5947
|
t = t % obj.length;
|
5479
5948
|
if(t < 0) t += obj.length;
|
5480
5949
|
}
|
5481
5950
|
if(args[1] === '#' || args[3] === '#') {
|
5482
|
-
// NOTE:
|
5951
|
+
// NOTE: Add 1 because (parent) anchors are 1-based.
|
5483
5952
|
ds.parent_anchor = t + 1;
|
5484
5953
|
if(DEBUGGING) {
|
5485
5954
|
console.log('ANCHOR for:', ds.displayName, '=', ds.parent_anchor);
|
@@ -5487,52 +5956,67 @@ function VMI_push_dataset_modifier(x, args) {
|
|
5487
5956
|
}
|
5488
5957
|
} else {
|
5489
5958
|
// Negative time step is evaluated as t = 0 (initial value), t beyond
|
5490
|
-
// optimization period is evaluated as its last time step
|
5491
|
-
// NOTE: By default, use the dataset vector value for t
|
5959
|
+
// optimization period is evaluated as its last time step.
|
5960
|
+
// NOTE: By default, use the dataset vector value for `t`.
|
5492
5961
|
t = Math.max(0, Math.min(
|
5493
5962
|
MODEL.end_period - MODEL.start_period + MODEL.look_ahead + 1, t));
|
5494
5963
|
}
|
5495
|
-
if(
|
5496
|
-
// If
|
5964
|
+
if(wcnr !== false || ds === MODEL.equations_dataset) {
|
5965
|
+
// If a wildcard number is specified, or when a normal (not-wildcard)
|
5966
|
+
// equation is referenced, use the modifier expression to calculate
|
5967
|
+
// the value to push.
|
5497
5968
|
obj = mx;
|
5498
|
-
|
5969
|
+
// If '?' is passed as wildcard number, use the wildcard vector index
|
5970
|
+
// of the expression that is being computed (this may be FALSE).
|
5971
|
+
if(wcnr === '?') {
|
5972
|
+
wcnr = x.wildcard_vector_index;
|
5973
|
+
}
|
5974
|
+
} else if(!ud ) {
|
5975
|
+
// In no selector and not "use data", check whether a running experiment
|
5976
|
+
// defines the expression to use. If not, `obj` will be the dataset
|
5977
|
+
// vector (so same as when "use data" is set).
|
5499
5978
|
obj = ds.activeModifierExpression;
|
5500
5979
|
}
|
5501
|
-
|
5980
|
+
if(!obj) {
|
5981
|
+
console.log('ANOMALY: no object. obj, wcnr, args, x', obj, wcnr, args, x);
|
5982
|
+
}
|
5983
|
+
// Now determine what value `v` should be pushed onto the expression stack.
|
5984
|
+
// By default, use the dataset default value.
|
5502
5985
|
let v = ds.defaultValue,
|
5503
|
-
obstr
|
5504
|
-
|
5986
|
+
// NOTE: `obstr` is used only when debugging, to log `obj` in human-
|
5987
|
+
// readable format.
|
5988
|
+
obstr = (obj instanceof Expression ? obj.text : `[${obj.toString()}]`);
|
5505
5989
|
if(Array.isArray(obj)) {
|
5506
|
-
//
|
5990
|
+
// `obj` is a vector.
|
5507
5991
|
if(t >= 0 && t < obj.length) {
|
5508
5992
|
v = obj[t];
|
5509
5993
|
} else if(ds.array && t >= obj.length) {
|
5510
|
-
// Set error value if array index is out of bounds
|
5994
|
+
// Set error value if array index is out of bounds.
|
5511
5995
|
v = VM.ARRAY_INDEX;
|
5512
5996
|
VM.out_of_bounds_array = ds.displayName;
|
5513
|
-
VM.out_of_bounds_msg = `Index ${t + 1} not in array dataset ` +
|
5997
|
+
VM.out_of_bounds_msg = `Index ${VM.sig2Dig(t + 1)} not in array dataset ` +
|
5514
5998
|
`${ds.displayName}, which has length ${obj.length}`;
|
5515
5999
|
console.log(VM.out_of_bounds_msg);
|
5516
6000
|
}
|
5517
|
-
// Fall through: no change to `v` => dataset default value is pushed
|
6001
|
+
// Fall through: no change to `v` => dataset default value is pushed.
|
5518
6002
|
} else {
|
5519
|
-
//
|
5520
|
-
// NOTE:
|
6003
|
+
// `obj` is an expression.
|
6004
|
+
// NOTE: Readjust `t` when `obj` is an expression for an *array-type*
|
6005
|
+
// dataset modifier.
|
5521
6006
|
if(obj.object instanceof Dataset && obj.object.array) t++;
|
5522
|
-
// Pass modifier selector (if specified; may be
|
5523
|
-
// will be recomputed with this selector as context for
|
5524
|
-
|
5525
|
-
// these will be used as the actual values of the formal parameters
|
5526
|
-
v = obj.result(t, ms);
|
6007
|
+
// Pass modifier selector (if specified; may be FALSE) so that result
|
6008
|
+
// will be recomputed with this selector as context for #.
|
6009
|
+
v = obj.result(t, wcnr);
|
5527
6010
|
}
|
5528
|
-
// Trace only now that time step t has been computed
|
6011
|
+
// Trace only now that time step t has been computed.
|
5529
6012
|
if(DEBUGGING) {
|
5530
6013
|
console.log('push dataset modifier:', obstr,
|
5531
|
-
tot[1] + (tot[2] ? ':' + tot[2] : ''), '
|
5532
|
-
|
6014
|
+
tot[1] + (tot[2] ? ':' + tot[2] : ''), 'value =', VM.sig4Dig(v),
|
6015
|
+
'\nExpression: ', x.text, '\nVariable:', x.variableName,
|
6016
|
+
'Context number:', wcnr);
|
5533
6017
|
}
|
5534
|
-
// NOTE:
|
5535
|
-
if(v >= VM.PLUS_INFINITY) v = ds.defaultValue;
|
6018
|
+
// NOTE: If value is exceptional ("undefined", etc.), use default value.
|
6019
|
+
// DEPRECATED !! if(v >= VM.PLUS_INFINITY) v = ds.defaultValue;
|
5536
6020
|
// Finally, push the value onto the expression stack
|
5537
6021
|
x.push(v);
|
5538
6022
|
}
|
@@ -5654,8 +6138,8 @@ function VMI_push_statistic(x, args) {
|
|
5654
6138
|
// of MAX, MEAN, MIN, N, SD, SUM, and VAR, and `list` is a list of vectors
|
5655
6139
|
// NOTE: each statistic may also be "suffixed" by NZ to denote that only
|
5656
6140
|
// non-zero numbers should be considered
|
5657
|
-
let stat = args[0]
|
5658
|
-
|
6141
|
+
let stat = args[0],
|
6142
|
+
list = args[1];
|
5659
6143
|
if(!list) {
|
5660
6144
|
// Special case: null or empty list => push zero
|
5661
6145
|
if(DEBUGGING) {
|
@@ -5664,8 +6148,17 @@ function VMI_push_statistic(x, args) {
|
|
5664
6148
|
x.push(0);
|
5665
6149
|
return;
|
5666
6150
|
}
|
5667
|
-
const
|
5668
|
-
|
6151
|
+
const
|
6152
|
+
anchor1 = args[2],
|
6153
|
+
offset1 = args[3],
|
6154
|
+
anchor2 = args[4],
|
6155
|
+
offset2 = args[5],
|
6156
|
+
wdict = args[6] || false;
|
6157
|
+
// If defined, the wildcard dictionary provides subsets of `list`
|
6158
|
+
// to be used when the wildcard number of the expression is set.
|
6159
|
+
if(wdict && x.wildcard_vector_index !== false) {
|
6160
|
+
list = wdict[x.wildcard_vector_index] || [];
|
6161
|
+
}
|
5669
6162
|
// If no list specified, the result is undefined
|
5670
6163
|
if(!Array.isArray(list) || list.length === 0) {
|
5671
6164
|
x.push(VM.UNDEFINED);
|
@@ -5724,13 +6217,20 @@ function VMI_push_statistic(x, args) {
|
|
5724
6217
|
}
|
5725
6218
|
// Push value unless it is zero and NZ is TRUE, or if it is undefined
|
5726
6219
|
// (this will occur when a variable has been deleted)
|
5727
|
-
if(v <= VM.PLUS_INFINITY && (!nz || Math.abs(v)
|
6220
|
+
if(v <= VM.PLUS_INFINITY && (!nz || Math.abs(v) > VM.NEAR_ZERO)) {
|
5728
6221
|
vlist.push(v);
|
5729
6222
|
}
|
5730
6223
|
}
|
5731
6224
|
}
|
5732
|
-
|
5733
|
-
|
6225
|
+
const
|
6226
|
+
n = vlist.length,
|
6227
|
+
// NOTE: count is the number of values used in the statistic
|
6228
|
+
count = (nz ? n : list.length);
|
6229
|
+
if(stat === 'N') {
|
6230
|
+
x.push(count);
|
6231
|
+
return;
|
6232
|
+
}
|
6233
|
+
// If no non-zero values remain, all statistics are zero (as ALL values were zero)
|
5734
6234
|
if(n === 0) {
|
5735
6235
|
x.push(0);
|
5736
6236
|
return;
|
@@ -5744,10 +6244,6 @@ function VMI_push_statistic(x, args) {
|
|
5744
6244
|
x.push(Math.max(...vlist));
|
5745
6245
|
return;
|
5746
6246
|
}
|
5747
|
-
if(stat === 'N') {
|
5748
|
-
x.push(n);
|
5749
|
-
return;
|
5750
|
-
}
|
5751
6247
|
// For all remaining statistics, the sum must be calculated
|
5752
6248
|
let sum = 0;
|
5753
6249
|
for(let i = 0; i < n; i++) {
|
@@ -5759,7 +6255,7 @@ function VMI_push_statistic(x, args) {
|
|
5759
6255
|
}
|
5760
6256
|
// Now statistic must be either MEAN, SD or VAR, so start with the mean
|
5761
6257
|
// NOTE: no more need to check for division by zero
|
5762
|
-
const mean = sum /
|
6258
|
+
const mean = sum / count;
|
5763
6259
|
if(stat === 'MEAN') {
|
5764
6260
|
x.push(mean);
|
5765
6261
|
return;
|
@@ -5770,11 +6266,11 @@ function VMI_push_statistic(x, args) {
|
|
5770
6266
|
sumsq += Math.pow(vlist[i] - mean, 2);
|
5771
6267
|
}
|
5772
6268
|
if(stat === 'VAR') {
|
5773
|
-
x.push(sumsq /
|
6269
|
+
x.push(sumsq / count);
|
5774
6270
|
return;
|
5775
6271
|
}
|
5776
6272
|
if(stat === 'SD') {
|
5777
|
-
x.push(Math.sqrt(sumsq /
|
6273
|
+
x.push(Math.sqrt(sumsq / count));
|
5778
6274
|
return;
|
5779
6275
|
}
|
5780
6276
|
// Fall-through: unknown statistic
|
@@ -7145,7 +7641,7 @@ const
|
|
7145
7641
|
VMI_push_look_ahead, VMI_push_round, VMI_push_last_round,
|
7146
7642
|
VMI_push_number_of_rounds, VMI_push_run_number, VMI_push_number_of_runs,
|
7147
7643
|
VMI_push_random, VMI_push_delta_t, VMI_push_true, VMI_push_false,
|
7148
|
-
VMI_push_pi, VMI_push_infinity,
|
7644
|
+
VMI_push_pi, VMI_push_infinity, VMI_push_contextual_number,
|
7149
7645
|
VMI_push_i, VMI_push_j, VMI_push_k,
|
7150
7646
|
VMI_push_year, VMI_push_week, VMI_push_day, VMI_push_hour,
|
7151
7647
|
VMI_push_minute, VMI_push_second],
|