linny-r 1.3.3 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/console.js +17 -0
- package/package.json +1 -1
- package/server.js +17 -2
- package/static/index.html +6 -12
- package/static/linny-r.css +28 -2
- package/static/scripts/linny-r-ctrl.js +40 -8
- package/static/scripts/linny-r-gui.js +174 -73
- package/static/scripts/linny-r-model.js +491 -154
- package/static/scripts/linny-r-utils.js +134 -12
- package/static/scripts/linny-r-vm.js +815 -396
@@ -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;
|
@@ -287,7 +306,7 @@ class Expression {
|
|
287
306
|
// Compute a value only once
|
288
307
|
if(v[t] !== VM.NOT_COMPUTED) return true;
|
289
308
|
// Provide selector context for # (number = FALSE => no wildcard match)
|
290
|
-
this.
|
309
|
+
this.wildcard_vector_index = number;
|
291
310
|
// Push this expression onto the call stack
|
292
311
|
VM.call_stack.push(this);
|
293
312
|
// Push time step in case a VMI_push_var instruction references
|
@@ -325,7 +344,7 @@ class Expression {
|
|
325
344
|
this.step.pop();
|
326
345
|
this.trace('--STOP: ' + this.variableName);
|
327
346
|
// Clear context for #
|
328
|
-
this.
|
347
|
+
this.wildcard_vector_index = false;
|
329
348
|
// If error, display the call stack (only once)
|
330
349
|
// NOTE: "undefined", "not computed" and "still computing" are NOT
|
331
350
|
// problematic unless they result in an error (stack over/underflow)
|
@@ -346,6 +365,7 @@ class Expression {
|
|
346
365
|
// "initial value" (these follow from the variables used in the expression)
|
347
366
|
// Select the vector to use
|
348
367
|
const v = this.chooseVector(number);
|
368
|
+
if(!v) return VM.UNDEFINED;
|
349
369
|
if(t < 0 || this.isStatic) t = 0;
|
350
370
|
if(t >= v.length) return VM.UNDEFINED;
|
351
371
|
if(v[t] === VM.NOT_COMPUTED || v[t] === VM.COMPUTING) {
|
@@ -487,46 +507,130 @@ class Expression {
|
|
487
507
|
|
488
508
|
// CLASS ExpressionParser
|
489
509
|
// 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
|
510
|
+
// an array of VM instructions. The optional parameters `owner` and
|
511
|
+
// `attribute` are used to prefix "local" entities, and also to implement
|
512
|
+
// modifier expressions that contain the "dot" that (when used within
|
513
|
+
// brackets) denotes the data value of the dataset.
|
514
|
+
|
515
|
+
// Since version 1.4.0, a leading colon indicates that the variable
|
516
|
+
// "inherits" the prefixes of its owner. Thus, for example, in the expression
|
517
|
+
// for the upper bound of proces "Storage: Li-ion: battery 1", the variable
|
518
|
+
// [:capacity] will be interpreted as [Storage: Li-ion: capacity].
|
519
|
+
// The prefixed name will be parsed normally, so if "Storage: Li-ion: capacity"
|
520
|
+
// identifies an array-type dataset, [:capacity@#] will work, since the
|
521
|
+
// value of # can be inferred from the expression owner's name
|
522
|
+
// "Storage: Li-ion: battery 1".
|
523
|
+
|
524
|
+
// Also since version 1.4.0, the context sensitive number # can also be used
|
525
|
+
// as a "wildcard" in an entity name. This is useful mainly when combined with
|
526
|
+
// wildcard equations with names like "eq??ion" which can then be referred to
|
527
|
+
// in expressions not only as "eq12ion" (then # in the expression for the
|
528
|
+
// wildcard equation evaluates as 12), but also as "eq#ion" (then # in the
|
529
|
+
// expression for the wildcard equation will have the value of # in the
|
530
|
+
// "calling" expression. This permits, for example, defining as single
|
531
|
+
// equation "partial load ??" with expression "[P#|L] / [P#|UB]", and then
|
532
|
+
// using the variable [partial load 1] to compute the partial load for
|
533
|
+
// process P1.
|
534
|
+
// NOTES:
|
535
|
+
// (1) This will (for now) NOT apply recursively, so [partial load #] cannot
|
536
|
+
// be used in some other wildcard equation like, for example, "percent load ??"
|
537
|
+
// having expression "100 * [partial load #]". This restriction follows from
|
538
|
+
// the present limitation that only ONE context-sensitive number exists, which
|
539
|
+
// makes that "nested" wildcard expressions can always be rewritten as a single
|
540
|
+
// expression.
|
541
|
+
// (2) The # may be used in patterns, so when a model comprises processes
|
542
|
+
// P1 and P2, and products Q2 and Q3, and a wildcard equation "total level ??"
|
543
|
+
// with expression "[SUM$#|L]", then [total level 1] will evaluate as the level
|
544
|
+
// of P1, and [total level 2] as the level of P2 plus the level of Q3.
|
545
|
+
|
494
546
|
class ExpressionParser {
|
495
547
|
constructor(text, owner=null, attribute='') {
|
496
|
-
// `text` is the expression string to be parsed
|
548
|
+
// `text` is the expression string to be parsed.
|
497
549
|
this.expr = text;
|
550
|
+
// NOTE: When expressions for dataset modifiers or equations are
|
551
|
+
// parsed, `owner` is their dataset, and `attribute` is their name.
|
498
552
|
this.owner = owner;
|
553
|
+
this.owner_prefix = '';
|
499
554
|
this.attribute = attribute;
|
500
|
-
|
501
|
-
this.
|
502
|
-
this.selector =
|
555
|
+
this.dataset = null;
|
556
|
+
this.dot = null;
|
557
|
+
this.selector = '';
|
558
|
+
this.context_number = '';
|
559
|
+
this.wildcard_selector = false;
|
560
|
+
this.wildcard_equation = false;
|
561
|
+
// Always infer the value for the context-sensitive number #.
|
562
|
+
// NOTE: this will be "?" if `owner` is a dataset modifier with
|
563
|
+
// wildcards in its selector; this indicates that the value of #
|
564
|
+
// cannot be inferred at compile time.
|
565
|
+
if(owner) {
|
566
|
+
this.context_number = owner.numberContext;
|
567
|
+
// NOTE: The owner prefix includes the trailing colon+space.
|
568
|
+
if(owner instanceof Link || owner instanceof Constraint) {
|
569
|
+
// For links and constraints, use the longest prefix that
|
570
|
+
// their nodes have in common.
|
571
|
+
this.owner_prefix = UI.sharedPrefix(owner.from_node.displayName,
|
572
|
+
owner.to_node.displayName) + UI.PREFIXER;
|
573
|
+
} else if(owner === MODEL.equations_dataset) {
|
574
|
+
this.owner_prefix = UI.completePrefix(attribute);
|
575
|
+
} else {
|
576
|
+
this.owner_prefix = UI.completePrefix(owner.displayName);
|
577
|
+
}
|
578
|
+
if(owner instanceof Dataset) {
|
579
|
+
this.dataset = owner;
|
580
|
+
// The attribute (if specified) is a dataset modifier selector.
|
581
|
+
this.selector = attribute;
|
582
|
+
// Record whether this selector contains wildcards.
|
583
|
+
this.wildcard_selector = owner.isWildcardSelector(attribute);
|
584
|
+
if(owner === MODEL.equations_dataset) {
|
585
|
+
// Reocrd that the owner is a wildcard equation.
|
586
|
+
this.wildcard_equation = this.wildcard_selector;
|
587
|
+
} else {
|
588
|
+
// For "normal" modifier expressions, the "dot" (.) can be used
|
589
|
+
// to refer to the dataset of the modifier.
|
590
|
+
this.dot = this.dataset;
|
591
|
+
}
|
592
|
+
}
|
593
|
+
}
|
594
|
+
// Immediately compile; this may generate warnings
|
503
595
|
this.compile();
|
504
596
|
}
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
597
|
+
|
598
|
+
get ownerName() {
|
599
|
+
// FOR TRACING & DEBUGGING: Returns the owner of this equation (if any).
|
600
|
+
if(!this.owner) return '(no owner)';
|
601
|
+
let n = this.owner.displayName;
|
602
|
+
if(this.attribute) n += '|' + this.attribute;
|
603
|
+
if(this.wildcard_selector) {
|
604
|
+
n += ' [wildcard ' + (this.wildcard_equation ? 'equation' : 'modifier') +
|
605
|
+
(this.context_number ? ' # = ' + this.context_number : '') + ']';
|
510
606
|
}
|
607
|
+
return n;
|
511
608
|
}
|
512
609
|
|
513
|
-
|
610
|
+
log(msg) {
|
611
|
+
// FOR TRACING & DEBUGGING: Logs a message to the console.
|
612
|
+
if(true) return;
|
613
|
+
// Set the above IF condition to FALSE to profile dynamic expressions.
|
614
|
+
console.log(`Expression for ${this.ownerName}: ${this.expr}\n${msg}`);
|
615
|
+
}
|
616
|
+
|
617
|
+
// The method parseVariable(name) checks whether `name` fits this pattern:
|
514
618
|
// {run}statistic$entity|attribute@offset_1:offset_2
|
515
619
|
// allowing spaces within {run} and around | and @ and :
|
516
620
|
// The entity is mandatory, but {run} and statistic$ are optional, and
|
517
621
|
// attribute and offset have defaults.
|
518
|
-
//
|
519
|
-
// pattern matches and no statistic, or the 6-element array
|
622
|
+
// It returns array [object, anchor_1, offset_1, anchor_2, offset_2] if
|
623
|
+
// the pattern matches and no statistic, or the 6-element array
|
520
624
|
// [statistic, object list, anchor, offset, anchor_2, offset_2]
|
521
|
-
// if a valid statistic is specified; otherwise FALSE.
|
625
|
+
// if a valid statistic is specified; otherwise it returns FALSE.
|
522
626
|
// The object is either a vector or an expression, or a special object
|
523
627
|
// (dataset specifier, experiment run specifier or unit balance specifier)
|
524
628
|
// NOTE: this array is used as argument for the virtual machine instructions
|
525
|
-
// VMI_push_var, VMI_push_statistic and VMI_push_run_result
|
629
|
+
// VMI_push_var, VMI_push_statistic and VMI_push_run_result.
|
526
630
|
parseVariable(name) {
|
527
|
-
// Reduce whitespace to single space
|
631
|
+
// Reduce whitespace to single space.
|
528
632
|
name = name.replace(/\s+/g, ' ');
|
529
|
-
// Initialize possible components
|
633
|
+
// Initialize possible components.
|
530
634
|
let obj = null,
|
531
635
|
attr = '',
|
532
636
|
use_data = false,
|
@@ -539,6 +643,7 @@ class ExpressionParser {
|
|
539
643
|
args = null,
|
540
644
|
s = name.split('@');
|
541
645
|
if(s.length > 1) {
|
646
|
+
// [variable@offset] where offset has form (anchor1)number1(:(anchor2)number 2)
|
542
647
|
// Offsets make expression dynamic (for now, ignore exceptional cases)
|
543
648
|
this.is_static = false;
|
544
649
|
this.log('dynamic because of offset');
|
@@ -586,9 +691,7 @@ class ExpressionParser {
|
|
586
691
|
}
|
587
692
|
// Check whether # anchor is meaningful for this expression
|
588
693
|
if((anchor1 === '#' || anchor2 === '#') &&
|
589
|
-
!(this.
|
590
|
-
this.selector.indexOf('?') >= 0 ||
|
591
|
-
this.owner.numberContext)) {
|
694
|
+
!(this.wildcard_selector || this.context_number)) {
|
592
695
|
// Log debugging information for this error
|
593
696
|
console.log(this.owner.displayName, this.owner.type, this.selector);
|
594
697
|
this.error = 'Anchor # is undefined in this context';
|
@@ -597,7 +700,8 @@ class ExpressionParser {
|
|
597
700
|
}
|
598
701
|
}
|
599
702
|
// Run specifier (optional) must be leading and braced
|
600
|
-
// Specifier format: {method$title|run} where method and title are
|
703
|
+
// Specifier format: {method$title|run} where method and title are
|
704
|
+
// optional -- NOTE: # in title or run is NOT seen as a wildcard
|
601
705
|
if(name.startsWith('{')) {
|
602
706
|
s = name.split('}');
|
603
707
|
if(s.length > 1) {
|
@@ -631,20 +735,29 @@ class ExpressionParser {
|
|
631
735
|
}
|
632
736
|
s = s.split('#');
|
633
737
|
let rn = (s.length > 1 ? s[1].trim() : false);
|
634
|
-
// Experiment specifier may contain selectors
|
738
|
+
// Experiment specifier may contain modifier selectors.
|
635
739
|
s = s[0].trim().split(UI.OA_SEPARATOR);
|
636
740
|
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
|
741
|
+
// If so, the selector list may indicate the run number.
|
742
|
+
// NOTE: permit selectors to be separated by various characters.
|
639
743
|
x.r = s.slice(1).join('|').split(/[\|\,\.\:\;\/\s]+/g);
|
640
744
|
}
|
641
745
|
if(rn) {
|
746
|
+
// NOTE: Special notation for run numbers to permit modelers
|
747
|
+
// to chart results as if run numbers are on the time axis
|
748
|
+
// (with a given step size). The chart will be made as usual,
|
749
|
+
// i.e., plot a point for each time step t, but the value v[t]
|
750
|
+
// will then stay the same for the time interval that corresponds
|
751
|
+
// to simulation period length / number of runs.
|
752
|
+
// NOTE: This will fail to produce a meaningful chart when the
|
753
|
+
// simulation period is small compared to the number of runs.
|
642
754
|
if(rn.startsWith('n')) {
|
643
|
-
// #n may be followed by a range, or defaults to
|
644
|
-
// Of this range, the i-th number will
|
755
|
+
// #n may be followed by a range, or this range defaults to
|
756
|
+
// 0 - last run number. Of this range, the i-th number will
|
757
|
+
// be used, where i is computes as:
|
645
758
|
// floor(current time step * number of runs / period length)
|
646
759
|
const range = rn.substring(1);
|
647
|
-
// Call rangeToList only to validate the range syntax
|
760
|
+
// Call rangeToList only to validate the range syntax.
|
648
761
|
if(rangeToList(range)) {
|
649
762
|
x.nr = range;
|
650
763
|
this.is_static = false;
|
@@ -653,12 +766,12 @@ class ExpressionParser {
|
|
653
766
|
msg = `Invalid experiment run number range "${range}"`;
|
654
767
|
}
|
655
768
|
} else {
|
656
|
-
// Explicit run number
|
769
|
+
// Explicit run number is specified.
|
657
770
|
const n = parseInt(rn);
|
658
771
|
if(isNaN(n)) {
|
659
772
|
msg = `Invalid experiment run number "${rn}"`;
|
660
773
|
} else {
|
661
|
-
// Explicit run number overrules selector list
|
774
|
+
// Explicit run number overrules selector list.
|
662
775
|
x.r = n;
|
663
776
|
}
|
664
777
|
}
|
@@ -666,7 +779,7 @@ class ExpressionParser {
|
|
666
779
|
// NOTE: s[0] still holds the experiment title
|
667
780
|
s = s[0].trim();
|
668
781
|
if(s) {
|
669
|
-
// NOTE: title cannot be parametrized
|
782
|
+
// NOTE: title cannot be parametrized with a # wildcard
|
670
783
|
const n = MODEL.indexOfExperiment(s);
|
671
784
|
if(n < 0) {
|
672
785
|
msg = `Unknown experiment "${s}"`;
|
@@ -674,24 +787,30 @@ class ExpressionParser {
|
|
674
787
|
x.x = MODEL.experiments[n];
|
675
788
|
}
|
676
789
|
}
|
677
|
-
// Variable name may start with a (case insensitive) statistic
|
790
|
+
// Variable name may start with a (case insensitive) statistic
|
791
|
+
// specifier such as SUM or MEAN
|
678
792
|
s = name.split('$');
|
679
793
|
if(s.length > 1) {
|
680
794
|
const stat = s[0].trim().toUpperCase();
|
681
|
-
// NOTE: simply ignore $
|
795
|
+
// NOTE: simply ignore $ (i.e., consider it as part of the
|
796
|
+
// variable name) unless it is preceded by a valid statistic
|
682
797
|
if(VM.outcome_statistics.indexOf(stat) >= 0) {
|
683
798
|
x.s = stat;
|
684
799
|
name = s[1].trim();
|
685
800
|
}
|
686
801
|
}
|
802
|
+
// Variable name may start with a colon to denote that the owner
|
803
|
+
// prefix should be added.
|
804
|
+
name = UI.colonPrefixedName(name, this.owner_prefix);
|
687
805
|
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
806
|
// Look up name in experiment outcomes list
|
694
807
|
x.v = x.x.resultIndex(name);
|
808
|
+
if(x.v < 0 && name.indexOf('#') >= 0 && this.context_number) {
|
809
|
+
// Variable name may be parametrized with #, but not in
|
810
|
+
// expressions for wildcard selectors
|
811
|
+
name = name.replace('#', this.context_number);
|
812
|
+
x.v = x.x.resultIndex(name);
|
813
|
+
}
|
695
814
|
if(x.v < 0) {
|
696
815
|
msg = ['Variable "', name, '" is not a result of experiment "',
|
697
816
|
x.x.displayName, '"'].join('');
|
@@ -699,15 +818,22 @@ class ExpressionParser {
|
|
699
818
|
} else {
|
700
819
|
// Check outcome list of ALL experiments
|
701
820
|
for(let i = 0; i < MODEL.experiments.length; i++) {
|
702
|
-
|
821
|
+
let xri = MODEL.experiments[i].resultIndex(name);
|
822
|
+
if(xri < 0 && name.indexOf('#') >= 0 && this.context_number) {
|
823
|
+
// Variable name may be parametrized with #, but not in
|
824
|
+
// expressions for wildcard selectors
|
825
|
+
name = name.replace('#', this.context_number);
|
826
|
+
xri = MODEL.experiments[i].resultIndex(name);
|
827
|
+
}
|
828
|
+
if(xri >= 0) {
|
703
829
|
// If some match is found, the name specifies a variable
|
704
|
-
x.v =
|
830
|
+
x.v = xri;
|
705
831
|
break;
|
706
832
|
}
|
707
833
|
}
|
708
834
|
}
|
709
|
-
// NOTE: experiment may still be FALSE, as this will be interpreted
|
710
|
-
// "use current experiment", but run number should be specified
|
835
|
+
// NOTE: experiment may still be FALSE, as this will be interpreted
|
836
|
+
// as "use current experiment", but run number should be specified.
|
711
837
|
if(!msg) {
|
712
838
|
if(x.r === false && x.t === false) {
|
713
839
|
msg = 'Experiment run not specified';
|
@@ -719,107 +845,139 @@ class ExpressionParser {
|
|
719
845
|
this.error = msg;
|
720
846
|
return false;
|
721
847
|
}
|
722
|
-
// Notify modeler when two statistics are used
|
848
|
+
// Notify modeler when two statistics are used.
|
723
849
|
if(x.s && x.m) {
|
724
850
|
UI.notify(`Method statistic (${x.m}) does not apply to ` +
|
725
851
|
`run result statistic (${x.s})`);
|
726
852
|
}
|
727
|
-
// NOTE:
|
728
|
-
// dynamic, so only set is_static to FALSE if NO statistic or method
|
853
|
+
// NOTE: Using AGGREGATED run results does NOT make the expression
|
854
|
+
// dynamic, so only set is_static to FALSE if NO statistic or method.
|
729
855
|
if(!x.s && !x.m) {
|
730
856
|
this.is_static = false;
|
731
857
|
this.log('dynamic because UNaggregated experiment result');
|
732
858
|
}
|
733
|
-
// For experiment run results, default anchor is 't'
|
859
|
+
// For experiment run results, default anchor is 't'.
|
734
860
|
if(!anchor1) anchor1 = 't';
|
735
861
|
if(!anchor2) anchor2 = 't';
|
736
|
-
// NOTE: compiler will recognize `x` to indicate "push run results"
|
862
|
+
// NOTE: compiler will recognize `x` to indicate "push run results".
|
737
863
|
return [x, anchor1, offset1, anchor2, offset2];
|
738
864
|
}
|
739
865
|
}
|
740
866
|
|
741
867
|
//
|
742
|
-
// NOTE:
|
743
|
-
// a result, so what follows does not apply to experiment results
|
868
|
+
// NOTE: For experiment results, the method will ALWAYS have returned
|
869
|
+
// a result, so what follows does not apply to experiment results.
|
744
870
|
//
|
745
871
|
|
746
|
-
//
|
872
|
+
// If reached this stage, variable must be like this:
|
873
|
+
// [(statistic$)entity name pattern(|attribute)]
|
874
|
+
// Attribute name (optional) follows the object-attribute separator |
|
747
875
|
s = name.split(UI.OA_SEPARATOR);
|
748
876
|
if(s.length > 1) {
|
749
|
-
// Attribute is string after LAST separator
|
877
|
+
// Attribute is string after the LAST separator...
|
750
878
|
attr = s.pop().trim();
|
751
|
-
// ... so restore name
|
879
|
+
// ... so restore `name` in case itself contains other separators.
|
752
880
|
name = s.join(UI.OA_SEPARATOR).trim();
|
753
881
|
if(!attr) {
|
754
882
|
// Explicit *empty* attribute, e.g., [name|]
|
755
|
-
// NOTE:
|
883
|
+
// NOTE: This matters for datasets having specifiers: the vertical
|
756
884
|
// bar indicates "do not infer a modifier from a running experiment,
|
757
|
-
// but use the data"
|
885
|
+
// but use the data".
|
758
886
|
use_data = true;
|
759
887
|
} else if(attr.startsWith('=')) {
|
760
888
|
// Attribute starting with = indicates cluster balance
|
761
|
-
// NOTE: empty string is considered as "any unit"
|
889
|
+
// NOTE: empty string is considered as "any unit".
|
762
890
|
cluster_balance_unit = attr.substring(1).trim();
|
891
|
+
} else if(attr.indexOf('?') >= 0 || attr.indexOf('#') >= 0) {
|
892
|
+
// Wildcard selectors of dataset modifiers cannot be used.
|
893
|
+
this.error = `Invalid attribute "${attr}"`;
|
894
|
+
return false;
|
763
895
|
}
|
764
896
|
}
|
765
897
|
|
766
|
-
// Check whether statistic is specified
|
898
|
+
// Check whether a statistic is specified.
|
767
899
|
let pat = name.split('$');
|
768
900
|
if(pat.length > 1 &&
|
769
901
|
VM.statistic_operators.indexOf(pat[0].toUpperCase()) >= 0) {
|
770
|
-
// For statistics, default anchor is 't'
|
902
|
+
// For statistics, default anchor is 't'.
|
771
903
|
if(!anchor1) anchor1 = 't';
|
772
904
|
if(!anchor2) anchor2 = 't';
|
773
|
-
// Check whether unit balance for clusters is asked for
|
905
|
+
// Check whether unit balance for clusters is asked for.
|
774
906
|
if(cluster_balance_unit !== false) {
|
775
907
|
this.error = 'Aggregation of unit balance over clusters is not supported';
|
776
908
|
return false;
|
777
909
|
}
|
778
|
-
// Consider only the first $ as statistic separator
|
910
|
+
// Consider only the first $ as statistic separator.
|
779
911
|
const stat = pat.shift().toUpperCase();
|
780
|
-
// Reassemble pattern string, which may itself contain
|
912
|
+
// Reassemble pattern string, which may itself contain $.
|
781
913
|
pat = pat.join('$');
|
782
|
-
// Special case: dataset "dot" is NOT a pattern
|
914
|
+
// Special case: dataset "dot" is NOT a pattern.
|
783
915
|
if(pat === '.') {
|
784
|
-
// NOTE: "dot" dataset is not level-dependent, and statistics
|
785
|
-
// vector do NOT make the expression dynamic
|
786
|
-
if(this.
|
787
|
-
return [stat, [this.
|
916
|
+
// NOTE: The "dot" dataset is not level-dependent, and statistics
|
917
|
+
// over its vector do NOT make the expression dynamic.
|
918
|
+
if(this.dot) {
|
919
|
+
return [stat, [this.dot.vector], anchor1, offset1, anchor2, offset2];
|
788
920
|
} else {
|
789
|
-
|
921
|
+
this.error = UI.ERROR.NO_DATASET_DOT;
|
790
922
|
return false;
|
791
923
|
}
|
792
924
|
}
|
793
|
-
//
|
794
|
-
|
925
|
+
// Deal with "prefix inheritance" when pattern starts with a colon.
|
926
|
+
if(pat.startsWith(':')) {
|
927
|
+
// Add a "must start with" AND condition to all OR clauses of the
|
928
|
+
// pattern.
|
929
|
+
// NOTE: Issues may occur when prefix contains &, ^ or # (@@TO DO?)
|
930
|
+
const oc = pat.substring(1).split('|');
|
931
|
+
for(let i = 0; i < oc.length; i++) {
|
932
|
+
oc[i] = `~${this.owner_prefix}&${oc[i]}`;
|
933
|
+
}
|
934
|
+
pat = oc.join('|');
|
935
|
+
}
|
936
|
+
// NOTE: For patterns, assume that # *always* denotes the context-
|
937
|
+
// sensitive number #, because if modelers wishes to include
|
938
|
+
// ANY number, they can make their pattern less selective.
|
939
|
+
if(this.context_number) pat = pat.replace('#', this.context_number);
|
940
|
+
// By default, consider all entity types.
|
795
941
|
let et = VM.entity_letters,
|
796
942
|
patstr = pat;
|
797
|
-
// Selection may be limited to specific entity types by
|
943
|
+
// Selection may be limited to specific entity types by prefix "...?"
|
944
|
+
// where ... is one or more entity letters (A for actor, etc.).
|
798
945
|
if(/^[ABCDELPQ]+\?/i.test(pat)) {
|
799
946
|
pat = pat.split('?');
|
800
947
|
et = pat[0].toUpperCase();
|
801
948
|
pat = pat.slice(1).join('=');
|
802
949
|
}
|
803
|
-
// Get the name pattern
|
950
|
+
// Get the name pattern.
|
804
951
|
pat = patternList(pat);
|
805
|
-
// Infer the entity type(s) from the attribute (if defined)
|
952
|
+
// Infer the entity type(s) from the attribute (if defined).
|
806
953
|
const
|
807
954
|
list = [],
|
808
|
-
// NOTE: optional second parameter `et`
|
955
|
+
// NOTE: The optional second parameter `et` will limit the
|
956
|
+
// returned list to the specified entity types.
|
809
957
|
ewa = MODEL.entitiesWithAttribute(attr, et);
|
810
|
-
// Create list of expression objects for the matching entities
|
958
|
+
// Create list of expression objects for the matching entities.
|
959
|
+
// Also create a "dict" with for each matching wildcard number
|
960
|
+
// the matching entities as a separate list. This will permit
|
961
|
+
// narrowing the selection at run time, based on the expression's
|
962
|
+
// wildcard number.
|
963
|
+
const wdict = {};
|
811
964
|
for(let i = 0; i < ewa.length; i++) {
|
812
965
|
const e = ewa[i];
|
813
966
|
if(patternMatch(e.displayName, pat)) {
|
814
|
-
|
967
|
+
const mnr = matchingWildcardNumber(e.displayName, pat);
|
968
|
+
// NOTE: Attribute may be a single value, a vector, or an expression.
|
815
969
|
obj = e.attributeValue(attr);
|
816
|
-
//
|
970
|
+
// If neither a single value nor a vector, it must be an expression.
|
817
971
|
if(obj === null) obj = e.attributeExpression(attr);
|
818
|
-
// Double-check: only add it if it is not NULL
|
972
|
+
// Double-check: only add it if it is not NULL.
|
819
973
|
if(obj) {
|
820
974
|
list.push(obj);
|
975
|
+
if(mnr) {
|
976
|
+
if(!wdict[mnr]) wdict[mnr] = [];
|
977
|
+
wdict[mnr].push(obj);
|
978
|
+
}
|
821
979
|
// Expression becomes dynamic if any element that is added is
|
822
|
-
// neither a single value nor a static expression
|
980
|
+
// neither a single value nor a static expression.
|
823
981
|
if(Array.isArray(obj) ||
|
824
982
|
(obj instanceof Expression && !obj.isStatic)) {
|
825
983
|
this.is_static = false;
|
@@ -828,15 +986,18 @@ class ExpressionParser {
|
|
828
986
|
}
|
829
987
|
}
|
830
988
|
}
|
831
|
-
// NOTE: also add expressions for
|
832
|
-
|
833
|
-
|
834
|
-
const
|
835
|
-
if(
|
836
|
-
|
837
|
-
if(
|
838
|
-
|
839
|
-
|
989
|
+
// NOTE: If no attribute is specified, also add expressions for
|
990
|
+
// equations that match UNLESS entity type specifier excludes them.
|
991
|
+
if(!attr && (!et || et.indexOf('E') >= 0)) {
|
992
|
+
const edm = MODEL.equations_dataset.modifiers;
|
993
|
+
for(let k in edm) if(edm.hasOwnProperty(k)) {
|
994
|
+
const m = edm[k];
|
995
|
+
if(patternMatch(m.selector, pat)) {
|
996
|
+
list.push(m.expression);
|
997
|
+
if(!m.expression.isStatic) {
|
998
|
+
this.is_static = false;
|
999
|
+
this.log('dynamic because matching equation is dynamic');
|
1000
|
+
}
|
840
1001
|
}
|
841
1002
|
}
|
842
1003
|
}
|
@@ -848,8 +1009,10 @@ class ExpressionParser {
|
|
848
1009
|
VM.level_based_attr.indexOf(attr) >= 0 &&
|
849
1010
|
anchor1 === 't' && offset1 === 0 &&
|
850
1011
|
anchor2 === 't' && offset2 === 0;
|
851
|
-
|
852
|
-
|
1012
|
+
const args = [stat, list, anchor1, offset1, anchor2, offset2];
|
1013
|
+
if(Object.keys(wdict).length > 0) args.push(wdict);
|
1014
|
+
// NOTE: Compiler will recognize 6- or 7-element list as a "statistic"
|
1015
|
+
return args;
|
853
1016
|
}
|
854
1017
|
this.error = `No entities that match pattern "${patstr}"` +
|
855
1018
|
(attr ? ' and have attribute ' + attr : ' when no attribute is specified');
|
@@ -857,180 +1020,345 @@ class ExpressionParser {
|
|
857
1020
|
}
|
858
1021
|
|
859
1022
|
//
|
860
|
-
// NOTE:
|
861
|
-
// so what follows does not apply to statistics results
|
1023
|
+
// NOTE: For statistics, the method will ALWAYS have returned a result,
|
1024
|
+
// so what follows does not apply to statistics results, but only to
|
1025
|
+
// "plain" variables like [entity name(|attribute)].
|
862
1026
|
//
|
863
1027
|
|
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');
|
1028
|
+
// For all entity types except array-type datasets, the default anchor
|
1029
|
+
// for offsets is the current time step `t`.
|
1030
|
+
if(!(this.dataset && this.dataset.array)) {
|
876
1031
|
if(!anchor1) anchor1 = 't';
|
877
1032
|
if(!anchor2) anchor2 = 't';
|
1033
|
+
}
|
1034
|
+
// First handle this special case: no name or attribute. This is valid
|
1035
|
+
// only for dataset modifier expressions (and hence also equations).
|
1036
|
+
// Variables like [@t-1] are interpreted as a self-reference. This is
|
1037
|
+
// meaningful when a *negative* offset is specified to denote "use the
|
1038
|
+
// value of this expression for some earlier time step".
|
1039
|
+
// NOTES:
|
1040
|
+
// (1) This makes the expression dynamic.
|
1041
|
+
// (2) It does not apply to array-type datasets, as these have no
|
1042
|
+
// time dimension.
|
1043
|
+
if(!name && !attr && this.dataset && !this.dataset.array) {
|
1044
|
+
this.is_static = false;
|
1045
|
+
this.log('dynamic because of self-reference');
|
878
1046
|
if(('cips'.indexOf(anchor1) >= 0 || anchor1 === 't' && offset1 < 0) &&
|
879
1047
|
('cips'.indexOf(anchor2) >= 0 ||anchor2 === 't' && offset2 < 0)) {
|
880
1048
|
// 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"
|
1049
|
+
// "use the vector of the expression for which this VMI is code".
|
882
1050
|
return [{xv: true, dv: this.dataset.defaultValue},
|
883
1051
|
anchor1, offset1, anchor2, offset2];
|
884
|
-
} else {
|
885
|
-
this.error = 'Expression can reference only previous values of itself';
|
886
|
-
return false;
|
887
1052
|
}
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
1053
|
+
msg = 'Expression can reference only previous values of itself';
|
1054
|
+
}
|
1055
|
+
// A leading "!" denotes: pass variable reference instead of its value.
|
1056
|
+
// NOTE: This also applies to the "dot", so [!.] is a valid variable.
|
1057
|
+
let by_reference = name.startsWith('!');
|
1058
|
+
if(by_reference) name = name.substring(1);
|
1059
|
+
// When `name` is a single dot, it refers to the dataset for which the
|
1060
|
+
// modifier expression is being parsed. Like all datasets, the "dot"
|
1061
|
+
// may also have an attribute.
|
1062
|
+
if(name === '.') {
|
1063
|
+
obj = this.dot;
|
1064
|
+
if(!obj) msg = UI.ERROR.NO_DATASET_DOT;
|
1065
|
+
} else if(name.indexOf('??') >= 0) {
|
1066
|
+
msg = 'Use # as wildcard, not ??';
|
1067
|
+
}
|
1068
|
+
if(msg) {
|
1069
|
+
this.error = msg;
|
1070
|
+
return false;
|
1071
|
+
}
|
1072
|
+
// Check whether name refers to a Linny-R entity defined by the model.
|
1073
|
+
if(!obj) {
|
1074
|
+
// Variable name may start with a colon to denote that the owner
|
1075
|
+
// prefix should be added.
|
1076
|
+
name = UI.colonPrefixedName(name, this.owner_prefix);
|
1077
|
+
// Start with wildcard equations, as these are likely to be few
|
1078
|
+
// (so a quick scan) and constitute a special case.
|
1079
|
+
const
|
1080
|
+
id = UI.nameToID(name),
|
1081
|
+
w = MODEL.wildcardEquationByID(id);
|
1082
|
+
if(w) {
|
1083
|
+
// Variable matches wildcard equation w[0] with number w[1],
|
1084
|
+
// so this equation must be evaluated for that number.
|
1085
|
+
return [
|
1086
|
+
{d: w[0].dataset, s: w[1], x: w[0].expression},
|
1087
|
+
anchor1, offset1, anchor2, offset2];
|
1088
|
+
}
|
1089
|
+
// If no match, try to match the object ID with any type of entity.
|
1090
|
+
obj = MODEL.objectByID(id);
|
1091
|
+
}
|
1092
|
+
// If not, retry if wildcards can be substituted.
|
1093
|
+
if(!obj && name.indexOf('#') >= 0) {
|
1094
|
+
if(this.context_number) {
|
1095
|
+
obj = MODEL.objectByName(name.replace('#', this.context_number));
|
1096
|
+
}
|
1097
|
+
if(!obj) {
|
1098
|
+
// If immediate substitution of # does not identify an entity,
|
1099
|
+
// then name may still refer to a wildcard equation.
|
1100
|
+
const wcname = name.replace('#', '??');
|
1101
|
+
// Check for self-reference.
|
1102
|
+
if(wcname === this.attribute) {
|
1103
|
+
msg = 'Equation cannot reference itself';
|
1104
|
+
} else {
|
1105
|
+
obj = MODEL.equationByID(UI.nameToID(wcname));
|
1106
|
+
if(obj) {
|
1107
|
+
// Special case: parsed variable references a wildcard
|
1108
|
+
// equation (so `obj` is an instance of DatasetModifier).
|
1109
|
+
if(!(this.wildcard_equation || this.context_number)) {
|
1110
|
+
msg = UI.ERROR.NO_NUMBER_CONTEXT;
|
1111
|
+
} else {
|
1112
|
+
// Acceptable reference to a wildcard equation.
|
1113
|
+
if(!obj.expression.isStatic) {
|
1114
|
+
this.is_static = false;
|
1115
|
+
this.log('dynamic because wildcard equation is dynamic');
|
1116
|
+
}
|
1117
|
+
// NOTE: The referenced expression may be level-dependent.
|
1118
|
+
this.is_level_based = this.is_level_based ||
|
1119
|
+
obj.expression.is_level_based;
|
1120
|
+
// NOTE: Pass a question mark "?" as selector when one
|
1121
|
+
// wildcard equation references another; otherwise, pass
|
1122
|
+
// the context number as integer.
|
1123
|
+
let cnr;
|
1124
|
+
if(this.wildcard_equation) {
|
1125
|
+
cnr = '?';
|
1126
|
+
} else {
|
1127
|
+
cnr = parseInt(this.context_number);
|
1128
|
+
// NOTE: Let script break if context number is not numeric.
|
1129
|
+
if(isNaN(cnr) ) throw ['FATAL ERROR in expression for ',
|
1130
|
+
this.ownerName, ' while parsing variable "', name,
|
1131
|
+
'"\nContext number "', this.context_number,
|
1132
|
+
'" is not a number\nExpression: ', this.expr].join('');
|
1133
|
+
}
|
1134
|
+
// Use the context number as "selector" parameter of the VMI.
|
1135
|
+
return [
|
1136
|
+
{d: obj.dataset, s: cnr, x: obj.expression},
|
1137
|
+
anchor1, offset1, anchor2, offset2];
|
1138
|
+
}
|
1139
|
+
}
|
902
1140
|
}
|
903
1141
|
}
|
1142
|
+
if(!obj) {
|
1143
|
+
// Final possibility is a match with a tail-numbered entity name.
|
1144
|
+
// NOTE: also pass `attr` so that only entities having this
|
1145
|
+
// attribute will match.
|
1146
|
+
const ame = MODEL.allMatchingEntities(wildcardMatchRegex(name), attr);
|
1147
|
+
if(ame.length > 0) {
|
1148
|
+
// NOTE: Some attributes make this expression level-dependent.
|
1149
|
+
const uca = attr.toUpperCase();
|
1150
|
+
this.is_level_based = this.is_level_based ||
|
1151
|
+
VM.level_based_attr.indexOf(uca) >= 0;
|
1152
|
+
// Pass the eligible entities along with the selector, so that
|
1153
|
+
// at run time the VM can match with the value of #.
|
1154
|
+
// NOTE: Also pass whether the entity should be pushed
|
1155
|
+
// "by reference".
|
1156
|
+
return [
|
1157
|
+
{ee: ame, n: name, a: uca, br: by_reference},
|
1158
|
+
anchor1, offset1, anchor2, offset2];
|
1159
|
+
}
|
1160
|
+
// Wildcard selector, but no number context for #.
|
1161
|
+
msg = UI.ERROR.NO_NUMBER_CONTEXT;
|
1162
|
+
}
|
904
1163
|
}
|
1164
|
+
if(msg) {
|
1165
|
+
this.error = msg;
|
1166
|
+
return false;
|
1167
|
+
}
|
1168
|
+
// Now `obj` refers to a model entity (ABCDELPQ).
|
1169
|
+
// This parseVariable(...) function must return a tuple like this:
|
1170
|
+
// [object or vector, anchor 1, offset 1, anchor 2, offset 2]
|
1171
|
+
// because this will be passed along with the VM instruction that
|
1172
|
+
// pushes the variable on the operand stack of this expression.
|
905
1173
|
if(obj === null) {
|
906
1174
|
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
1175
|
} else if(obj.array &&
|
913
1176
|
(anchor1 && '#ijk'.indexOf(anchor1) < 0 ||
|
914
1177
|
anchor2 && '#ijk'.indexOf(anchor2) < 0)) {
|
1178
|
+
// Only indices (i, j, k) and the # number can index arrays, as arrays
|
1179
|
+
// have no time dimension, while all other anchors relate to time.
|
915
1180
|
msg = 'Invalid anchor(s) for array-type dataset ' + obj.displayName;
|
916
1181
|
} 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
|
-
}
|
1182
|
+
// If the variable denotes an equation or a dataset with a selector,
|
1183
|
+
// check whether this is the "owner" of the expression being parsed.
|
1184
|
+
let sel = '',
|
1185
|
+
xtype = '';
|
1186
|
+
if(obj instanceof DatasetModifier) {
|
1187
|
+
sel = obj.selector;
|
1188
|
+
xtype = 'Equation';
|
954
1189
|
} 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
|
-
}
|
1190
|
+
sel = attr;
|
1191
|
+
xtype = 'Dataset modifier expression';
|
970
1192
|
}
|
971
|
-
//
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
1193
|
+
// In variable names, wildcards are denoted as #, so also check for
|
1194
|
+
// the (unlikely) case that [eq#x] is used in the expression for a
|
1195
|
+
// wildcard equation or dataset modifier with name "eq??x".
|
1196
|
+
if(sel && (sel === this.selector ||
|
1197
|
+
sel.replace('#', '??') === this.selector)) {
|
1198
|
+
// Match indicates a cyclic reference
|
1199
|
+
msg = `${xtype} must not reference itself`;
|
1200
|
+
}
|
1201
|
+
}
|
1202
|
+
if(msg) {
|
1203
|
+
this.error = msg;
|
1204
|
+
return false;
|
1205
|
+
}
|
1206
|
+
// If `obj` is a dataset *modifier*, it must be a "normal" equation...
|
1207
|
+
if(obj instanceof DatasetModifier) {
|
1208
|
+
// ... so "map" it onto the equations dataset + selector...
|
1209
|
+
attr = obj.selector;
|
1210
|
+
obj = MODEL.equations_dataset;
|
1211
|
+
}
|
1212
|
+
// ... so now it will be processed the same way dataset modifiers
|
1213
|
+
// are processed, especially when they have a tail number.
|
1214
|
+
|
1215
|
+
// Set default anchors in case no anchors are specified.
|
1216
|
+
// Except for array-type datasets, the default anchor is 't';
|
1217
|
+
// for array-type datasets in expressions for array-type datasets,
|
1218
|
+
// the SPECIAL anchor is '^' to indicate "use parent anchor"
|
1219
|
+
// (which will be the parent's context-sensitive number #)
|
1220
|
+
const default_anchor = (obj.array ?
|
1221
|
+
(this.dataset && this.dataset.array ? '^' : '') : 't');
|
1222
|
+
if(!anchor1) anchor1 = default_anchor;
|
1223
|
+
if(!anchor2) anchor2 = default_anchor;
|
1224
|
+
|
1225
|
+
// If "by reference", return the object itself plus its attribute
|
1226
|
+
if(by_reference) {
|
1227
|
+
return [{r: obj, a: attr}, anchor1, offset1, anchor2, offset2];
|
1228
|
+
}
|
1229
|
+
if(obj === this.dataset && attr === '' && !obj.array) {
|
1230
|
+
// When dataset modifier expression refers to its dataset without
|
1231
|
+
// selector, then this is equivalent to [.] (use the series data
|
1232
|
+
// vector) unless it is an array, since then the series data is
|
1233
|
+
// not a time-scaled vector => special case
|
1234
|
+
args = obj.vector;
|
1235
|
+
} else if(attr === '') {
|
1236
|
+
// For all other variables, assume default attribute if none specified
|
1237
|
+
attr = obj.defaultAttribute;
|
1238
|
+
// For a dataset, check whether the VMI_push_dataset_modifier should be
|
1239
|
+
// used. This is the case for array-type datasets, and for datasets
|
1240
|
+
// having modifiers UNLESS the modeler used a vertical bar to indicate
|
1241
|
+
// "use the data"
|
1242
|
+
if(obj instanceof Dataset &&
|
1243
|
+
(obj.array || (!use_data && obj.selectorList.length > 0))) {
|
1244
|
+
if(obj.data.length > 1 || (obj.data.length > 0 && !obj.periodic) ||
|
1245
|
+
!obj.allModifiersAreStatic) {
|
1246
|
+
// No explicit selector => dynamic unless no time series data, and
|
1247
|
+
// ALL modifier expressions are static
|
980
1248
|
this.is_static = false;
|
981
|
-
this.log('dynamic because
|
982
|
-
}
|
983
|
-
|
1249
|
+
this.log('dynamic because dataset without explicit selector is used');
|
1250
|
+
}
|
1251
|
+
// NOTE: Also pass the "use data" flag so that experiment selectors
|
1252
|
+
// will be ignored if the modeler coded the vertical bar.
|
1253
|
+
return [{d: obj, ud: use_data}, anchor1, offset1, anchor2, offset2];
|
1254
|
+
}
|
1255
|
+
} else if(obj instanceof Dataset) {
|
1256
|
+
// For datasets, the attribute must be a modifier selector, so first
|
1257
|
+
// check if this dataset has a modifier that matches `attr`.
|
1258
|
+
const mm = obj.matchingModifiers([attr]);
|
1259
|
+
if(mm.length === 0) {
|
1260
|
+
// No match indicates unknown attribute.
|
1261
|
+
this.error = `Dataset ${obj.displayName} has no modifier with selector "${attr}"`;
|
1262
|
+
return false;
|
1263
|
+
} else {
|
1264
|
+
// NOTE: Multiple matches are impossible because `attr` cannot
|
1265
|
+
// contain wildcards; hence this is a unique match, so the modifier
|
1266
|
+
// expression is known.
|
1267
|
+
const m = mm[0];
|
1268
|
+
if(!m.expression.isStatic) {
|
984
1269
|
this.is_static = false;
|
985
|
-
this.log('
|
986
|
-
|
1270
|
+
this.log('dynamic because dataset modifier expression is dynamic');
|
1271
|
+
}
|
1272
|
+
// NOTE: a single match may be due to wildcard(s) in the modifier,
|
1273
|
+
// e.g., a variable [dataset|abc] matches with a modifier having
|
1274
|
+
// wildcard selector "a?b", or [dataset|a12] matches with "a*".
|
1275
|
+
// In such cases, if the selector matches an integer like "a12"
|
1276
|
+
// in the example above, this number (12) should be used as
|
1277
|
+
// number context (overriding the number of the dataset, so
|
1278
|
+
// for [datset34|a12], the number context is '12' and not '34').
|
1279
|
+
let mcn = matchingNumber(attr, m.selector);
|
1280
|
+
if(mcn === false) {
|
1281
|
+
// NOTE: When no matching number if found, `attr` may still
|
1282
|
+
// contain a ?? wildcard. If it indeed identifies a wildcard
|
1283
|
+
// equation, then "?" should be passed to the VM instruction.
|
1284
|
+
if(obj === MODEL.equations_dataset && attr.indexOf('??') >= 0) {
|
1285
|
+
mcn = '?';
|
1286
|
+
} else {
|
1287
|
+
// Ensure that `mcn` is either an integer value or FALSE.
|
1288
|
+
mcn = endsWithDigits(obj.name) || this.context_number || false;
|
1289
|
+
}
|
987
1290
|
}
|
1291
|
+
// Pass the dataset, the context number # (or FALSE) in place, and the
|
1292
|
+
// modifier expression
|
1293
|
+
return [
|
1294
|
+
{d: m.dataset, s: mcn, x: m.expression},
|
1295
|
+
anchor1, offset1, anchor2, offset2];
|
988
1296
|
}
|
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
|
1297
|
+
}
|
1298
|
+
// NOTE: `args` can now be a single value, a vector, or NULL.
|
1299
|
+
if(args === null) args = obj.attributeValue(attr);
|
1300
|
+
if(Array.isArray(args)) {
|
1301
|
+
if(obj instanceof Dataset) {
|
1302
|
+
if(obj.data.length > 1 || obj.data.length > 0 && !obj.periodic) {
|
998
1303
|
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];
|
1304
|
+
this.log('dynamic because dataset vector is used');
|
1003
1305
|
}
|
1004
|
-
|
1005
|
-
|
1306
|
+
} else if(VM.level_based_attr.indexOf(attr) >= 0) {
|
1307
|
+
this.is_static = false;
|
1308
|
+
this.log('dynamic because level-based attribute');
|
1006
1309
|
} else {
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1310
|
+
// Unusual (?) combi, so let's assume dynamic
|
1311
|
+
this.is_static = false;
|
1312
|
+
this.log('probably dynamic -- check below:');
|
1313
|
+
console.log(obj.displayName, obj, attr, args);
|
1314
|
+
}
|
1315
|
+
}
|
1316
|
+
// If not a single value or vector, it must be an expression
|
1317
|
+
if(args === null) args = obj.attributeExpression(attr);
|
1318
|
+
if(args === null) {
|
1319
|
+
// Only NOW check whether unit balance for clusters is asked for
|
1320
|
+
if(cluster_balance_unit !== false && obj instanceof Cluster) {
|
1321
|
+
// NOTE: cluster balance ALWAYS makes expression level-based
|
1322
|
+
this.is_level_based = true;
|
1323
|
+
// @TO DO: rethink whether this will indeed also make this expression
|
1324
|
+
// dynamic
|
1325
|
+
this.is_static = false;
|
1326
|
+
this.log('dynamic because cluster balance is believed to be dynamic');
|
1327
|
+
// NOTE: VM instructions VMI_push_var will recognize this special case
|
1328
|
+
return [{c: obj, u: cluster_balance_unit},
|
1329
|
+
anchor1, offset1, anchor2, offset2];
|
1330
|
+
}
|
1331
|
+
// Fall-through: invalid attribute for this object
|
1332
|
+
msg = `${obj.type} entities have no attribute "${attr}"`;
|
1333
|
+
} else {
|
1334
|
+
if(args instanceof Expression) {
|
1335
|
+
this.is_static = this.is_static && args.isStatic;
|
1011
1336
|
}
|
1337
|
+
args = [args, anchor1, offset1, anchor2, offset2];
|
1012
1338
|
}
|
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;
|
1339
|
+
if(msg) {
|
1340
|
+
this.error = msg;
|
1341
|
+
return false;
|
1024
1342
|
}
|
1025
|
-
|
1026
|
-
|
1343
|
+
// Now `args` should be a valid argument for a VM instruction that
|
1344
|
+
// pushes an operand on the evaluation stack
|
1345
|
+
// Check whether the attribute is level-based (i.e., can be computed only
|
1346
|
+
// after optimizing a block) while no offset is defined to use prior data
|
1347
|
+
this.is_level_based = this.is_level_based ||
|
1348
|
+
// NOTE: dataset modifier expressions may be level_based
|
1349
|
+
obj instanceof Dataset && attr && args[0].is_level_based ||
|
1350
|
+
// NOTE: assume NOT level_based if anchor & offset are specified
|
1351
|
+
VM.level_based_attr.indexOf(attr) >= 0 &&
|
1352
|
+
anchor1 === 't' && offset1 === 0 &&
|
1353
|
+
anchor2 === 't' && offset2 === 0;
|
1354
|
+
return args;
|
1027
1355
|
}
|
1028
1356
|
|
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
1357
|
getSymbol() {
|
1358
|
+
// Gets the next substring in the expression that is a valid symbol
|
1359
|
+
// while advancing the position-in-text (`pit`) and length-of-symbol
|
1360
|
+
// (`los`), which are used to highlight the position of a syntax error
|
1361
|
+
// in the expression editor
|
1034
1362
|
let c, f, i, l, v;
|
1035
1363
|
this.prev_sym = this.sym;
|
1036
1364
|
this.sym = null;
|
@@ -1130,7 +1458,7 @@ class ExpressionParser {
|
|
1130
1458
|
if(this.selector.indexOf('*') >= 0 ||
|
1131
1459
|
this.selector.indexOf('?') >= 0 ||
|
1132
1460
|
this.owner.numberContext) {
|
1133
|
-
this.sym =
|
1461
|
+
this.sym = VMI_push_contextual_number;
|
1134
1462
|
} else {
|
1135
1463
|
this.error = '# is undefined in this context';
|
1136
1464
|
}
|
@@ -1241,6 +1569,8 @@ class ExpressionParser {
|
|
1241
1569
|
// Compiles expression into array of VM instructions `code`
|
1242
1570
|
// NOTE: always create a new code array instance, as it will typically
|
1243
1571
|
// become the code attribute of an expression object
|
1572
|
+
if(DEBUGGING) console.log('COMPILING', this.ownerName, ':\n',
|
1573
|
+
this.expr, '\ncontext number =', this.context_number);
|
1244
1574
|
this.code = [];
|
1245
1575
|
// Position in text
|
1246
1576
|
this.pit = 0;
|
@@ -1347,7 +1677,7 @@ class ExpressionParser {
|
|
1347
1677
|
// start by popping the FALSE result of the IF condition
|
1348
1678
|
this.code.push([VMI_pop_false, null]);
|
1349
1679
|
}
|
1350
|
-
// END of new code
|
1680
|
+
// END of new code for IF-THEN-ELSE
|
1351
1681
|
|
1352
1682
|
this.op_stack.push(this.sym);
|
1353
1683
|
} else if(this.sym !== null) {
|
@@ -1356,11 +1686,13 @@ class ExpressionParser {
|
|
1356
1686
|
this.code.push([this.sym, null]);
|
1357
1687
|
} else if(Array.isArray(this.sym)) {
|
1358
1688
|
// Either a statistic, a dataset (array-type or with modifier),
|
1359
|
-
// an experiment run result, or a variable
|
1360
|
-
if(this.sym.length
|
1689
|
+
// an experiment run result, or a variable.
|
1690
|
+
if(this.sym.length >= 6) {
|
1361
1691
|
this.code.push([VMI_push_statistic, this.sym]);
|
1362
1692
|
} else if(this.sym[0].hasOwnProperty('d')) {
|
1363
1693
|
this.code.push([VMI_push_dataset_modifier, this.sym]);
|
1694
|
+
} else if(this.sym[0].hasOwnProperty('ee')) {
|
1695
|
+
this.code.push([VMI_push_wildcard_entity, this.sym]);
|
1364
1696
|
} else if(this.sym[0].hasOwnProperty('x')) {
|
1365
1697
|
this.code.push([VMI_push_run_result, this.sym]);
|
1366
1698
|
} else if(this.sym[0].hasOwnProperty('r')) {
|
@@ -1392,8 +1724,7 @@ class ExpressionParser {
|
|
1392
1724
|
this.error = 'Invalid parameter list';
|
1393
1725
|
}
|
1394
1726
|
}
|
1395
|
-
if(DEBUGGING) console.log('PARSED',
|
1396
|
-
this.owner.displayName + '|' + this.attribute, ':',
|
1727
|
+
if(DEBUGGING) console.log('PARSED', this.ownerName, ':',
|
1397
1728
|
this.expr, this.code);
|
1398
1729
|
}
|
1399
1730
|
|
@@ -3255,19 +3586,22 @@ class VirtualMachine {
|
|
3255
3586
|
if(ubx.isStatic) {
|
3256
3587
|
// If UB is very high (typically: undefined, so +INF), try to infer
|
3257
3588
|
// a lower value for UB to use for the ON/OFF binary
|
3258
|
-
let ub = ubx.result(0)
|
3589
|
+
let ub = ubx.result(0),
|
3590
|
+
hub = ub;
|
3259
3591
|
if(ub > VM.MEGA_UPPER_BOUND) {
|
3260
|
-
|
3592
|
+
hub = p.highestUpperBound([]);
|
3261
3593
|
// If UB still very high, warn modeler on infoline and in monitor
|
3262
|
-
if(
|
3263
|
-
const msg = 'High upper bound (' + this.sig4Dig(
|
3594
|
+
if(hub > VM.MEGA_UPPER_BOUND) {
|
3595
|
+
const msg = 'High upper bound (' + this.sig4Dig(hub) +
|
3264
3596
|
') for <strong>' + p.displayName + '</strong>' +
|
3265
3597
|
' will compromise computation of its binary variables';
|
3266
3598
|
UI.warn(msg);
|
3267
3599
|
this.logMessage(this.block_count,
|
3268
3600
|
'WARNING: ' + msg.replace(/<\/?strong>/g, '"'));
|
3269
3601
|
}
|
3270
|
-
}
|
3602
|
+
}
|
3603
|
+
if(hub !== ub) {
|
3604
|
+
ub = hub;
|
3271
3605
|
this.logMessage(this.block_count,
|
3272
3606
|
`Inferred upper bound for ${p.displayName}: ${this.sig4Dig(ub)}`);
|
3273
3607
|
}
|
@@ -3771,7 +4105,7 @@ class VirtualMachine {
|
|
3771
4105
|
for(let k = 0; k < l; k++) {
|
3772
4106
|
const
|
3773
4107
|
vi = svl[k],
|
3774
|
-
slack = x[vi + j],
|
4108
|
+
slack = parseFloat(x[vi + j]),
|
3775
4109
|
absl = Math.abs(slack);
|
3776
4110
|
if(absl > VM.NEAR_ZERO) {
|
3777
4111
|
const v = this.variables[vi - 1];
|
@@ -4258,9 +4592,8 @@ class VirtualMachine {
|
|
4258
4592
|
} else if(this.error_codes.indexOf(n) < 0) {
|
4259
4593
|
err += '? value = ' + n;
|
4260
4594
|
}
|
4261
|
-
|
4262
|
-
|
4263
|
-
UI.alert(msg);
|
4595
|
+
this.logMessage(this.block_count, err);
|
4596
|
+
UI.alert(err);
|
4264
4597
|
}
|
4265
4598
|
|
4266
4599
|
get actualBlockLength() {
|
@@ -4846,8 +5179,13 @@ Solver status = ${json.status}`);
|
|
4846
5179
|
if(MODEL.running_experiment) EXPERIMENT_MANAGER.processRun();
|
4847
5180
|
// Warn modeler if any issues occurred
|
4848
5181
|
if(this.block_issues) {
|
4849
|
-
|
4850
|
-
|
5182
|
+
let msg = 'Issues occurred in ' +
|
5183
|
+
pluralS(this.block_issues, 'block') +
|
5184
|
+
' -- details can be viewed in the monitor';
|
5185
|
+
if(VM.issue_list.length) {
|
5186
|
+
msg += ' and by using \u25C1 \u25B7';
|
5187
|
+
}
|
5188
|
+
UI.warn(msg);
|
4851
5189
|
UI.updateIssuePanel();
|
4852
5190
|
}
|
4853
5191
|
if(this.license_expired > 0) {
|
@@ -5227,11 +5565,11 @@ function VMI_push_second(x, empty) {
|
|
5227
5565
|
pushTimeStepsPerTimeUnit(x, 'second');
|
5228
5566
|
}
|
5229
5567
|
|
5230
|
-
function
|
5231
|
-
// Pushes the numeric value of the
|
5568
|
+
function VMI_push_contextual_number(x, empty) {
|
5569
|
+
// Pushes the numeric value of the context-sensitive number #
|
5232
5570
|
const n = valueOfNumberSign(x);
|
5233
5571
|
if(DEBUGGING) {
|
5234
|
-
console.log('push
|
5572
|
+
console.log('push contextual number: # = ' + VM.sig2Dig(n));
|
5235
5573
|
}
|
5236
5574
|
x.push(n);
|
5237
5575
|
}
|
@@ -5239,118 +5577,121 @@ function VMI_push_selector_wildcard(x, empty) {
|
|
5239
5577
|
/* VM instruction helper functions */
|
5240
5578
|
|
5241
5579
|
function valueOfNumberSign(x) {
|
5242
|
-
// Pushes the numeric value of the # sign
|
5580
|
+
// Pushes the numeric value of the # sign for the context of expression `x`
|
5243
5581
|
// NOTE: this can be a wildcard match, an active experiment run selector
|
5244
|
-
// ending on digits, or an entity
|
5582
|
+
// ending on digits, or tne number context of an entity. The latter typically
|
5583
|
+
// is the number its name or any of its prefixes ends on, but notes are
|
5584
|
+
// more "creative" and can return the number context of nearby entities.
|
5245
5585
|
let s = 'NO SELECTOR',
|
5246
5586
|
m = 'NO MATCH',
|
5247
|
-
n =
|
5248
|
-
// NOTE:
|
5249
|
-
// because wildcard selector is an immediate property of the dataset
|
5250
|
-
// modifier
|
5251
|
-
// experiment selectors that identify the run
|
5252
|
-
if(x.
|
5253
|
-
n = x.
|
5587
|
+
n = VM.UNDEFINED;
|
5588
|
+
// NOTE: Give wildcard selectors precedence over experiment selectors
|
5589
|
+
// because a wildcard selector is an immediate property of the dataset
|
5590
|
+
// modifier expression, and hence "closer" to the expression than the
|
5591
|
+
// experiment selectors that identify the run.
|
5592
|
+
if(x.wildcard_vector_index !== false) {
|
5593
|
+
n = x.wildcard_vector_index;
|
5254
5594
|
s = x.attribute;
|
5255
5595
|
m = 'wildcard';
|
5256
5596
|
} else {
|
5257
|
-
|
5258
|
-
|
5259
|
-
|
5260
|
-
|
5261
|
-
|
5262
|
-
|
5263
|
-
|
5597
|
+
// Check whether `x` is a dataset modifier expression.
|
5598
|
+
// NOTE: This includes equations.
|
5599
|
+
if(x.object instanceof Dataset) {
|
5600
|
+
if(x.attribute) {
|
5601
|
+
s = x.attribute;
|
5602
|
+
} else {
|
5603
|
+
// Selector may also be defined by a running experiment.
|
5604
|
+
if(MODEL.running_experiment) {
|
5605
|
+
// Let `m` be the primary selector for the current experiment run.
|
5606
|
+
const sl = MODEL.running_experiment.activeCombination;
|
5607
|
+
if(sl) {
|
5608
|
+
m = sl[0];
|
5609
|
+
s = 'experiment';
|
5610
|
+
}
|
5264
5611
|
}
|
5265
5612
|
}
|
5266
5613
|
}
|
5267
|
-
// If selector contains no wildcards,
|
5614
|
+
// If selector contains no wildcards, get number context (typically
|
5615
|
+
// inferred from a number in the name of the object)
|
5268
5616
|
if(s.indexOf('*') < 0 && s.indexOf('?') < 0) {
|
5269
5617
|
const d = x.object.numberContext;
|
5270
5618
|
if(d) {
|
5271
|
-
s =
|
5619
|
+
s = x.object.displayName;
|
5272
5620
|
m = d;
|
5621
|
+
n = parseInt(d);
|
5273
5622
|
}
|
5274
|
-
}
|
5275
|
-
|
5276
|
-
//
|
5277
|
-
|
5278
|
-
match = m.match(new RegExp(raw));
|
5279
|
-
if(match) {
|
5280
|
-
// Concatenate all matching characters (need not be digits)
|
5281
|
-
m = '';
|
5282
|
-
for(let i = 1; i < match.length; i++) m += match[i];
|
5283
|
-
// Try to convert to an integer
|
5284
|
-
n = parseInt(m);
|
5285
|
-
// Default to 0
|
5286
|
-
if(isNaN(n)) n = 0;
|
5287
|
-
}
|
5623
|
+
} else if(m !== 'NO MATCH') {
|
5624
|
+
// If matching `m` against `s` do not yield an integer string,
|
5625
|
+
// use UNDEFINED
|
5626
|
+
n = matchingNumber(m, s) || VM.UNDEFINED;
|
5288
5627
|
}
|
5289
5628
|
}
|
5629
|
+
// For datasets, set the parent anchor to be the context-sensitive number
|
5290
5630
|
if(x.object instanceof Dataset) x.object.parent_anchor = n;
|
5291
5631
|
if(DEBUGGING) {
|
5292
|
-
console.log(
|
5293
|
-
`${x.object.displayName}${x.attribute ? '|' + x.attribute : ''}
|
5632
|
+
console.log(`context for # in expression for ${x.variableName}
|
5294
5633
|
- expression: ${x.text}
|
5295
|
-
- inferred value of # ${s} => ${m} => ${
|
5634
|
+
- inferred value of # ${s} => ${m} => ${n}`, x.code);
|
5296
5635
|
}
|
5297
5636
|
return n;
|
5298
5637
|
}
|
5299
5638
|
|
5300
5639
|
function relativeTimeStep(t, anchor, offset, dtm, x) {
|
5301
|
-
// Returns the relative time step, given t, anchor, offset,
|
5302
|
-
// and the expression being evaluated (to provide
|
5303
|
-
//
|
5304
|
-
|
5305
|
-
// Anchors are checked for in order of *expected* frequency of occurrence
|
5640
|
+
// Returns the relative time step, given t, anchor, offset,
|
5641
|
+
// delta-t-multiplier and the expression being evaluated (to provide
|
5642
|
+
// context for anchor #).
|
5643
|
+
// NOTE: t = 1 corresponds with first time step of simulation period.
|
5644
|
+
// Anchors are checked for in order of *expected* frequency of occurrence.
|
5306
5645
|
if(anchor === 't') {
|
5307
|
-
// Offset relative to current time step (most likely to occur)
|
5646
|
+
// Offset relative to current time step (most likely to occur).
|
5308
5647
|
return Math.floor(t + offset);
|
5309
5648
|
}
|
5649
|
+
if(anchor === '#') {
|
5650
|
+
// Index: offset is added to the inferred value of the # symbol.
|
5651
|
+
return valueOfNumberSign(x) + offset;
|
5652
|
+
}
|
5653
|
+
if(anchor === '^') {
|
5654
|
+
// Inherited index (for dataset modifier expressions): offset is added
|
5655
|
+
// to the anchor of the modifier's dataset.
|
5656
|
+
if(x.object.array) {
|
5657
|
+
if(DEBUGGING) {
|
5658
|
+
console.log('Parent anchor', x.object.parent_anchor);
|
5659
|
+
}
|
5660
|
+
// NOTE: For not array-type datasets, ^ is equivalent to #
|
5661
|
+
return x.object.parent_anchor;
|
5662
|
+
}
|
5663
|
+
return valueOfNumberSign(x) + offset;
|
5664
|
+
}
|
5665
|
+
if('ijk'.indexOf(anchor) >= 0) {
|
5666
|
+
// Index: offset is added to the iterator index i, j or k.
|
5667
|
+
return valueOfIndexVariable(anchor) + offset;
|
5668
|
+
}
|
5310
5669
|
if(anchor === 'r') {
|
5311
|
-
// Offset relative to current time step, scaled to time unit of run
|
5670
|
+
// Offset relative to current time step, scaled to time unit of run.
|
5312
5671
|
return Math.floor((t + offset) * dtm);
|
5313
5672
|
}
|
5314
5673
|
if(anchor === 'c') {
|
5315
|
-
// Relative to start of current optimization block
|
5674
|
+
// Relative to start of current optimization block.
|
5316
5675
|
return Math.trunc(t / MODEL.block_length) * MODEL.block_length + offset;
|
5317
5676
|
}
|
5318
5677
|
if(anchor === 'p') {
|
5319
|
-
// Relative to start of previous optimization block
|
5678
|
+
// Relative to start of previous optimization block.
|
5320
5679
|
return (Math.trunc(t / MODEL.block_length) - 1) * MODEL.block_length + offset;
|
5321
5680
|
}
|
5322
5681
|
if(anchor === 'n') {
|
5323
|
-
// Relative to start of next optimization block
|
5682
|
+
// Relative to start of next optimization block.
|
5324
5683
|
return (Math.trunc(t / MODEL.block_length) + 1) * MODEL.block_length + offset;
|
5325
5684
|
}
|
5326
5685
|
if(anchor === 'l') {
|
5327
|
-
// Last: offset relative to the last index in the vector
|
5686
|
+
// Last: offset relative to the last index in the vector.
|
5328
5687
|
return MODEL.end_period - MODEL.start_period + 1 + offset;
|
5329
5688
|
}
|
5330
5689
|
if(anchor === 's') {
|
5331
|
-
// Scaled: offset is scaled to time unit of run
|
5690
|
+
// Scaled: offset is scaled to time unit of run.
|
5332
5691
|
return Math.floor(offset * dtm);
|
5333
5692
|
}
|
5334
|
-
|
5335
|
-
|
5336
|
-
return valueOfIndexVariable(anchor) + offset;
|
5337
|
-
}
|
5338
|
-
if(anchor === '#') {
|
5339
|
-
// Index: offset is added to the inferred value of the # symbol
|
5340
|
-
return valueOfNumberSign(x) + offset;
|
5341
|
-
}
|
5342
|
-
if(anchor === '^') {
|
5343
|
-
// Inherited index: offset is added to anchor of "parent" dataset
|
5344
|
-
if(x.object.array) {
|
5345
|
-
if(DEBUGGING) {
|
5346
|
-
console.log('Parent anchor', x.object.parent_anchor);
|
5347
|
-
}
|
5348
|
-
return x.object.parent_anchor;
|
5349
|
-
}
|
5350
|
-
return valueOfNumberSign(x) + offset;
|
5351
|
-
}
|
5352
|
-
// Fall-through: offset relative to the initial value index (0)
|
5353
|
-
// NOTE: this also applies to anchor f (First)
|
5693
|
+
// Fall-through: offset relative to the initial value index (0).
|
5694
|
+
// NOTE: this also applies to anchor f (First).
|
5354
5695
|
return offset;
|
5355
5696
|
}
|
5356
5697
|
|
@@ -5430,9 +5771,9 @@ function VMI_push_var(x, args) {
|
|
5430
5771
|
}
|
5431
5772
|
|
5432
5773
|
function VMI_push_entity(x, args) {
|
5433
|
-
// Pushes a special "entity reference" object based on `args`, being the
|
5434
|
-
// [obj, anchor1, offset1, anchor2, offset2] where `obj` has the
|
5435
|
-
// {r: entity object, a: attribute}
|
5774
|
+
// Pushes a special "entity reference" object based on `args`, being the
|
5775
|
+
// list [obj, anchor1, offset1, anchor2, offset2] where `obj` has the
|
5776
|
+
// format {r: entity object, a: attribute}
|
5436
5777
|
// The object that is pushed on the stack passes the entity, the attribute
|
5437
5778
|
// to use, and the time interval
|
5438
5779
|
const
|
@@ -5448,35 +5789,93 @@ function VMI_push_entity(x, args) {
|
|
5448
5789
|
x.push(er);
|
5449
5790
|
}
|
5450
5791
|
|
5792
|
+
function VMI_push_wildcard_entity(x, args) {
|
5793
|
+
// Pushes the value of (or reference to) an entity attribute, based on
|
5794
|
+
// `args`, being the list [obj, anchor1, offset1, anchor2, offset2]
|
5795
|
+
// where `obj` has the format {ee: list of eligible entities,
|
5796
|
+
// n: name (with wildcard #), a: attribute, br: by reference (boolean)}
|
5797
|
+
// First select the first entity in `ee` that matches the wildcard vector
|
5798
|
+
// index of the expression `x` being executed.
|
5799
|
+
const
|
5800
|
+
el = args[0].ee,
|
5801
|
+
nn = args[0].n.replace('#', x.wildcard_vector_index);
|
5802
|
+
let obj = null;
|
5803
|
+
for(let i = 0; !obj && i < el.length; i++) {
|
5804
|
+
if(el[i].name === nn) obj = el[i];
|
5805
|
+
}
|
5806
|
+
// If no match, then this indicates a bad reference.
|
5807
|
+
if(!obj) {
|
5808
|
+
console.log(`ERROR: no match for "${nn}" in eligible entity list`, el);
|
5809
|
+
x.push(VM.BAD_REF);
|
5810
|
+
return;
|
5811
|
+
}
|
5812
|
+
// Otherwise, if `br` indicates "by reference", then VMI_push_entity can
|
5813
|
+
// be called with the appropriate parameters.
|
5814
|
+
const attr = args[0].a || obj.defaultAttribute;
|
5815
|
+
if(args[0].br) {
|
5816
|
+
args[0] = {r: obj, a: attr};
|
5817
|
+
VMI_push_entity(x, args);
|
5818
|
+
return;
|
5819
|
+
}
|
5820
|
+
// Otherwise, if the entity is a dataset modifier, this must be an
|
5821
|
+
// equation (identified by its name, not by a modifier selector) so
|
5822
|
+
// push the result of this equation using the wildcard vector index
|
5823
|
+
// of the expression that is being computed.
|
5824
|
+
if(obj instanceof DatasetModifier) {
|
5825
|
+
args[0] = {d: obj.dataset, s: x.wildcard_vector_index, x: obj.expression};
|
5826
|
+
VMI_push_dataset_modifier(x, args);
|
5827
|
+
return;
|
5828
|
+
}
|
5829
|
+
// Otherwise, it can be a vector type attribute or an expression.
|
5830
|
+
let v = obj.attributeValue(attr);
|
5831
|
+
if(v === null) v = obj.attributeExpression(attr);
|
5832
|
+
// If no match, then this indicates a bad reference.
|
5833
|
+
if(v === null) {
|
5834
|
+
console.log(`ERROR: bad variable "${obj.displayName}" with attribute "${attr}"`);
|
5835
|
+
x.push(VM.BAD_REF);
|
5836
|
+
return;
|
5837
|
+
}
|
5838
|
+
// Otherwise, VMI_push_var can be called with `v` as first argument.
|
5839
|
+
args[0] = v;
|
5840
|
+
VMI_push_var(x, args);
|
5841
|
+
}
|
5842
|
+
|
5451
5843
|
function VMI_push_dataset_modifier(x, args) {
|
5452
|
-
// NOTE: the first argument specifies the dataset `d` and (optionally!)
|
5453
|
-
// modifier selector `s`, and expression `x
|
5454
|
-
//
|
5455
|
-
//
|
5456
|
-
//
|
5844
|
+
// NOTE: the first argument specifies the dataset `d` and (optionally!)
|
5845
|
+
// the modifier selector `s`, and expression `x`.
|
5846
|
+
// If `s` is a number, then the result of `x` must be computed with
|
5847
|
+
// this number als wildcard number.
|
5848
|
+
// If `s` is not specified, the modifier to be used must be inferred from
|
5849
|
+
// the running experiment UNLESS the field `ud` ("use data") is defined
|
5850
|
+
// for the first argument, and evaluates as TRUE.
|
5851
|
+
// NOTE: Ensure that number 0 is not interpreted as FALSE.
|
5852
|
+
let ms = (args[0].s === 0 || args[0].s) ? args[0].s : false;
|
5457
5853
|
const
|
5458
5854
|
ds = args[0].d,
|
5459
|
-
|
5460
|
-
|
5461
|
-
|
5462
|
-
|
5463
|
-
// NOTE: use the "local" time step for expression x, i.e., the top value
|
5464
|
-
// of the expression's time step stack `x.step`
|
5855
|
+
ud = args[0].ud || false,
|
5856
|
+
mx = args[0].x || null,
|
5857
|
+
// NOTE: Use the "local" time step for expression x, i.e., the top
|
5858
|
+
// value of the expression's time step stack `x.step`.
|
5465
5859
|
tot = twoOffsetTimeStep(x.step[x.step.length - 1],
|
5466
5860
|
args[1], args[2], args[3], args[4], 1, x);
|
5861
|
+
// NOTE: Sanity check to facilitate debugging; if no dataset is provided,
|
5862
|
+
// the script will still break at the LET statement below.
|
5863
|
+
if(!ds) console.log('ERROR: VMI_push_dataset_modifier without dataset',
|
5864
|
+
x.variableName, x.code);
|
5467
5865
|
let t = tot[0],
|
5468
5866
|
obj = ds.vector;
|
5867
|
+
|
5469
5868
|
if(ds.array) {
|
5470
|
-
// For array, do NOT adjust "index" t to model run period
|
5471
|
-
// NOTE:
|
5869
|
+
// For array-type datasets, do NOT adjust "index" t to model run period.
|
5870
|
+
// NOTE: Indices start at 1, but arrays are zero-based, so subtract 1.
|
5472
5871
|
t--;
|
5473
|
-
// When periodic, adjust t to fall within the vector length
|
5872
|
+
// When data is periodic, adjust `t` to fall within the vector length.
|
5474
5873
|
if(ds.periodic && obj.length > 0) {
|
5475
5874
|
t = t % obj.length;
|
5476
5875
|
if(t < 0) t += obj.length;
|
5477
5876
|
}
|
5478
5877
|
if(args[1] === '#' || args[3] === '#') {
|
5479
|
-
// NOTE:
|
5878
|
+
// NOTE: Add 1 because (parent) anchors are 1-based.
|
5480
5879
|
ds.parent_anchor = t + 1;
|
5481
5880
|
if(DEBUGGING) {
|
5482
5881
|
console.log('ANCHOR for:', ds.displayName, '=', ds.parent_anchor);
|
@@ -5484,48 +5883,56 @@ function VMI_push_dataset_modifier(x, args) {
|
|
5484
5883
|
}
|
5485
5884
|
} else {
|
5486
5885
|
// Negative time step is evaluated as t = 0 (initial value), t beyond
|
5487
|
-
// optimization period is evaluated as its last time step
|
5488
|
-
// NOTE: By default, use the dataset vector value for t
|
5886
|
+
// optimization period is evaluated as its last time step.
|
5887
|
+
// NOTE: By default, use the dataset vector value for `t`.
|
5489
5888
|
t = Math.max(0, Math.min(
|
5490
5889
|
MODEL.end_period - MODEL.start_period + MODEL.look_ahead + 1, t));
|
5491
5890
|
}
|
5492
|
-
if(ms) {
|
5493
|
-
// If modifier selector is specified, use the associated expression
|
5891
|
+
if(ms !== false) {
|
5892
|
+
// If a modifier selector is specified, use the associated expression.
|
5494
5893
|
obj = mx;
|
5894
|
+
// If '?' is passed as selector, use the wildcard vector index of the
|
5895
|
+
// expression that is being computed.
|
5896
|
+
if(ms === '?') ms = x.wildcard_vector_index;
|
5495
5897
|
} else if(!ud) {
|
5898
|
+
// In no selector and not "use data", check whether a running experiment
|
5899
|
+
// defines the expression to use. If not, `obj` will be the dataset
|
5900
|
+
// vector (so same as when "use data" is set).
|
5496
5901
|
obj = ds.activeModifierExpression;
|
5497
5902
|
}
|
5498
|
-
//
|
5903
|
+
// Now determine what value `v` should be pushed onto the expression stack.
|
5904
|
+
// By default, use the dataset default value.
|
5499
5905
|
let v = ds.defaultValue,
|
5906
|
+
// NOTE: `obstr` is used only when debugging, to log `obj` in human-
|
5907
|
+
// readable format.
|
5500
5908
|
obstr = (obj instanceof Expression ?
|
5501
5909
|
obj.text : '[' + obj.toString() + ']');
|
5502
5910
|
if(Array.isArray(obj)) {
|
5503
|
-
// Object is a vector
|
5911
|
+
// Object is a vector.
|
5504
5912
|
if(t >= 0 && t < obj.length) {
|
5505
5913
|
v = obj[t];
|
5506
5914
|
} else if(ds.array && t >= obj.length) {
|
5507
|
-
// Set error value if array index is out of bounds
|
5915
|
+
// Set error value if array index is out of bounds.
|
5508
5916
|
v = VM.ARRAY_INDEX;
|
5509
5917
|
VM.out_of_bounds_array = ds.displayName;
|
5510
|
-
VM.out_of_bounds_msg = `Index ${t + 1} not in array dataset ` +
|
5918
|
+
VM.out_of_bounds_msg = `Index ${VM.sig2Dig(t + 1)} not in array dataset ` +
|
5511
5919
|
`${ds.displayName}, which has length ${obj.length}`;
|
5512
5920
|
console.log(VM.out_of_bounds_msg);
|
5513
5921
|
}
|
5514
|
-
// Fall through: no change to `v` => dataset default value is pushed
|
5922
|
+
// Fall through: no change to `v` => dataset default value is pushed.
|
5515
5923
|
} else {
|
5516
|
-
// Object is an expression
|
5517
|
-
// NOTE:
|
5924
|
+
// Object is an expression.
|
5925
|
+
// NOTE: Readjust `t` when `obj` is an expression for an *array-type*
|
5926
|
+
// dataset modifier.
|
5518
5927
|
if(obj.object instanceof Dataset && obj.object.array) t++;
|
5519
|
-
// Pass modifier selector (if specified; may be
|
5928
|
+
// Pass modifier selector (if specified; may be FALSE) so that result
|
5520
5929
|
// will be recomputed with this selector as context for #
|
5521
|
-
// Also pass equation parameters (if specified; may be undefined) so that
|
5522
|
-
// these will be used as the actual values of the formal parameters
|
5523
5930
|
v = obj.result(t, ms);
|
5524
5931
|
}
|
5525
5932
|
// Trace only now that time step t has been computed
|
5526
5933
|
if(DEBUGGING) {
|
5527
5934
|
console.log('push dataset modifier:', obstr,
|
5528
|
-
tot[1] + (tot[2] ? ':' + tot[2] : ''), '
|
5935
|
+
tot[1] + (tot[2] ? ':' + tot[2] : ''), 'value =', VM.sig4Dig(v));
|
5529
5936
|
console.log(' --', x.text, ' for owner ', x.object.displayName, x.attribute);
|
5530
5937
|
}
|
5531
5938
|
// NOTE: if value is exceptional ("undefined", etc.), use default value
|
@@ -5651,8 +6058,8 @@ function VMI_push_statistic(x, args) {
|
|
5651
6058
|
// of MAX, MEAN, MIN, N, SD, SUM, and VAR, and `list` is a list of vectors
|
5652
6059
|
// NOTE: each statistic may also be "suffixed" by NZ to denote that only
|
5653
6060
|
// non-zero numbers should be considered
|
5654
|
-
let stat = args[0]
|
5655
|
-
|
6061
|
+
let stat = args[0],
|
6062
|
+
list = args[1];
|
5656
6063
|
if(!list) {
|
5657
6064
|
// Special case: null or empty list => push zero
|
5658
6065
|
if(DEBUGGING) {
|
@@ -5661,8 +6068,17 @@ function VMI_push_statistic(x, args) {
|
|
5661
6068
|
x.push(0);
|
5662
6069
|
return;
|
5663
6070
|
}
|
5664
|
-
const
|
5665
|
-
|
6071
|
+
const
|
6072
|
+
anchor1 = args[2],
|
6073
|
+
offset1 = args[3],
|
6074
|
+
anchor2 = args[4],
|
6075
|
+
offset2 = args[5],
|
6076
|
+
wdict = args[6] || false;
|
6077
|
+
// If defined, the wildcard dictionary provides subsets of `list`
|
6078
|
+
// to be used when the wildcard number of the expression is set.
|
6079
|
+
if(wdict && x.wildcard_vector_index !== false) {
|
6080
|
+
list = wdict[x.wildcard_vector_index] || [];
|
6081
|
+
}
|
5666
6082
|
// If no list specified, the result is undefined
|
5667
6083
|
if(!Array.isArray(list) || list.length === 0) {
|
5668
6084
|
x.push(VM.UNDEFINED);
|
@@ -5721,13 +6137,20 @@ function VMI_push_statistic(x, args) {
|
|
5721
6137
|
}
|
5722
6138
|
// Push value unless it is zero and NZ is TRUE, or if it is undefined
|
5723
6139
|
// (this will occur when a variable has been deleted)
|
5724
|
-
if(v <= VM.PLUS_INFINITY && (!nz || Math.abs(v)
|
6140
|
+
if(v <= VM.PLUS_INFINITY && (!nz || Math.abs(v) > VM.NEAR_ZERO)) {
|
5725
6141
|
vlist.push(v);
|
5726
6142
|
}
|
5727
6143
|
}
|
5728
6144
|
}
|
5729
|
-
|
5730
|
-
|
6145
|
+
const
|
6146
|
+
n = vlist.length,
|
6147
|
+
// NOTE: count is the number of values used in the statistic
|
6148
|
+
count = (nz ? n : list.length);
|
6149
|
+
if(stat === 'N') {
|
6150
|
+
x.push(count);
|
6151
|
+
return;
|
6152
|
+
}
|
6153
|
+
// If no non-zero values remain, all statistics are zero (as ALL values were zero)
|
5731
6154
|
if(n === 0) {
|
5732
6155
|
x.push(0);
|
5733
6156
|
return;
|
@@ -5741,10 +6164,6 @@ function VMI_push_statistic(x, args) {
|
|
5741
6164
|
x.push(Math.max(...vlist));
|
5742
6165
|
return;
|
5743
6166
|
}
|
5744
|
-
if(stat === 'N') {
|
5745
|
-
x.push(n);
|
5746
|
-
return;
|
5747
|
-
}
|
5748
6167
|
// For all remaining statistics, the sum must be calculated
|
5749
6168
|
let sum = 0;
|
5750
6169
|
for(let i = 0; i < n; i++) {
|
@@ -5756,7 +6175,7 @@ function VMI_push_statistic(x, args) {
|
|
5756
6175
|
}
|
5757
6176
|
// Now statistic must be either MEAN, SD or VAR, so start with the mean
|
5758
6177
|
// NOTE: no more need to check for division by zero
|
5759
|
-
const mean = sum /
|
6178
|
+
const mean = sum / count;
|
5760
6179
|
if(stat === 'MEAN') {
|
5761
6180
|
x.push(mean);
|
5762
6181
|
return;
|
@@ -5767,11 +6186,11 @@ function VMI_push_statistic(x, args) {
|
|
5767
6186
|
sumsq += Math.pow(vlist[i] - mean, 2);
|
5768
6187
|
}
|
5769
6188
|
if(stat === 'VAR') {
|
5770
|
-
x.push(sumsq /
|
6189
|
+
x.push(sumsq / count);
|
5771
6190
|
return;
|
5772
6191
|
}
|
5773
6192
|
if(stat === 'SD') {
|
5774
|
-
x.push(Math.sqrt(sumsq /
|
6193
|
+
x.push(Math.sqrt(sumsq / count));
|
5775
6194
|
return;
|
5776
6195
|
}
|
5777
6196
|
// Fall-through: unknown statistic
|
@@ -7142,7 +7561,7 @@ const
|
|
7142
7561
|
VMI_push_look_ahead, VMI_push_round, VMI_push_last_round,
|
7143
7562
|
VMI_push_number_of_rounds, VMI_push_run_number, VMI_push_number_of_runs,
|
7144
7563
|
VMI_push_random, VMI_push_delta_t, VMI_push_true, VMI_push_false,
|
7145
|
-
VMI_push_pi, VMI_push_infinity,
|
7564
|
+
VMI_push_pi, VMI_push_infinity, VMI_push_contextual_number,
|
7146
7565
|
VMI_push_i, VMI_push_j, VMI_push_k,
|
7147
7566
|
VMI_push_year, VMI_push_week, VMI_push_day, VMI_push_hour,
|
7148
7567
|
VMI_push_minute, VMI_push_second],
|