linny-r 1.4.0 → 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 +8 -0
- package/static/scripts/linny-r-ctrl.js +11 -0
- package/static/scripts/linny-r-gui.js +26 -10
- package/static/scripts/linny-r-model.js +12 -30
- package/static/scripts/linny-r-utils.js +13 -6
- package/static/scripts/linny-r-vm.js +235 -155
package/package.json
CHANGED
package/server.js
CHANGED
@@ -910,12 +910,12 @@ function loadData(res, url) {
|
|
910
910
|
// the call-back Python script specified for the channel
|
911
911
|
|
912
912
|
function receiver(res, sp) {
|
913
|
-
//This function processes all receiver actions
|
913
|
+
// This function processes all receiver actions.
|
914
914
|
let
|
915
915
|
rpath = anyOSpath(sp.get('path') || ''),
|
916
916
|
rfile = anyOSpath(sp.get('file') || '');
|
917
|
-
// Assume that path is relative to
|
918
|
-
// a (back)slash or
|
917
|
+
// Assume that path is relative to working directory unless it starts
|
918
|
+
// with a (back)slash or specifies drive or volume.
|
919
919
|
if(!(rpath.startsWith(path.sep) || rpath.indexOf(':') >= 0 ||
|
920
920
|
rpath.startsWith(WORKING_DIRECTORY))) {
|
921
921
|
rpath = path.join(WORKING_DIRECTORY, rpath);
|
@@ -1038,8 +1038,49 @@ function rcvrAbort(res, rpath, rfile, log) {
|
|
1038
1038
|
}
|
1039
1039
|
|
1040
1040
|
function rcvrReport(res, rpath, rfile, run, data, stats, log) {
|
1041
|
+
// Purge reports older than 24 hours.
|
1041
1042
|
try {
|
1042
|
-
|
1043
|
+
const
|
1044
|
+
now = new Date(),
|
1045
|
+
flist = fs.readdirSync(WORKSPACE.reports);
|
1046
|
+
let n = 0;
|
1047
|
+
for(let i = 0; i < flist.length; i++) {
|
1048
|
+
const
|
1049
|
+
pp = path.parse(flist[i]),
|
1050
|
+
fp = path.join(WORKSPACE.reports, flist[i]);
|
1051
|
+
// NOTE: Only consider text files (extension .txt)
|
1052
|
+
if(pp.ext === '.txt') {
|
1053
|
+
// Delete only if file is older than 24 hours.
|
1054
|
+
const fstat = fs.statSync(fp);
|
1055
|
+
if(now - fstat.mtimeMs > 24 * 3600000) {
|
1056
|
+
// Delete text file
|
1057
|
+
try {
|
1058
|
+
fs.unlinkSync(fp);
|
1059
|
+
n++;
|
1060
|
+
} catch(err) {
|
1061
|
+
console.log('WARNING: Failed to delete', fp);
|
1062
|
+
console.log(err);
|
1063
|
+
}
|
1064
|
+
}
|
1065
|
+
}
|
1066
|
+
}
|
1067
|
+
if(n) console.log(n + 'report file' + (n > 1 ? 's' : '') + 'purged');
|
1068
|
+
} catch(err) {
|
1069
|
+
// Log error, but do not abort.
|
1070
|
+
console.log(err);
|
1071
|
+
}
|
1072
|
+
// Now save the reports.
|
1073
|
+
// NOTE: The optional @ indicates where the run number must be inserted.
|
1074
|
+
// If not specified, append run number to the base report file name.
|
1075
|
+
if(rfile.indexOf('@') < 0) {
|
1076
|
+
rfile += run;
|
1077
|
+
} else {
|
1078
|
+
rfile = rfile.replace('@', run);
|
1079
|
+
}
|
1080
|
+
const base = path.join(rpath, rfile);
|
1081
|
+
let fp;
|
1082
|
+
try {
|
1083
|
+
fp = path.join(base + '-data.txt');
|
1043
1084
|
fs.writeFileSync(fp, data);
|
1044
1085
|
} catch(err) {
|
1045
1086
|
console.log(err);
|
@@ -1048,7 +1089,7 @@ function rcvrReport(res, rpath, rfile, run, data, stats, log) {
|
|
1048
1089
|
return;
|
1049
1090
|
}
|
1050
1091
|
try {
|
1051
|
-
fp = path.join(
|
1092
|
+
fp = path.join(base + '-stats.txt');
|
1052
1093
|
fs.writeFileSync(fp, stats);
|
1053
1094
|
} catch(err) {
|
1054
1095
|
console.log(err);
|
@@ -1057,7 +1098,7 @@ function rcvrReport(res, rpath, rfile, run, data, stats, log) {
|
|
1057
1098
|
return;
|
1058
1099
|
}
|
1059
1100
|
try {
|
1060
|
-
fp = path.join(
|
1101
|
+
fp = path.join(base + '-log.txt');
|
1061
1102
|
fs.writeFileSync(fp, log);
|
1062
1103
|
} catch(err) {
|
1063
1104
|
console.log(err);
|
@@ -1630,6 +1671,7 @@ function createWorkspace() {
|
|
1630
1671
|
data: path.join(SETTINGS.user_dir, 'data'),
|
1631
1672
|
diagrams: path.join(SETTINGS.user_dir, 'diagrams'),
|
1632
1673
|
modules: path.join(SETTINGS.user_dir, 'modules'),
|
1674
|
+
reports: path.join(SETTINGS.user_dir, 'reports'),
|
1633
1675
|
solver_output: path.join(SETTINGS.user_dir, 'solver')
|
1634
1676
|
};
|
1635
1677
|
// Create these sub-directories if not aready there
|
package/static/index.html
CHANGED
@@ -601,6 +601,14 @@ and move the cursor over the status bar">
|
|
601
601
|
</td>
|
602
602
|
<td style="padding-bottom:4px">Infer and display cost prices</td>
|
603
603
|
</tr>
|
604
|
+
<tr title="Reports will be saved in user/reports/, and removed after 24 h">
|
605
|
+
<td style="padding:0px">
|
606
|
+
<div id="settings-report-results" class="box clear"></div>
|
607
|
+
</td>
|
608
|
+
<td style="padding-bottom:4px">
|
609
|
+
Report results after each run
|
610
|
+
</td>
|
611
|
+
</tr>
|
604
612
|
<tr>
|
605
613
|
<td style="padding:0px">
|
606
614
|
<div id="settings-block-arrows" class="box clear"></div>
|
@@ -355,6 +355,17 @@ class Controller {
|
|
355
355
|
return nodes.join(arrow);
|
356
356
|
}
|
357
357
|
|
358
|
+
tailNumber(name) {
|
359
|
+
// Returns the string of digits at the end of `name`. If not there,
|
360
|
+
// check prefixes (if any) from right to left for a tail number.
|
361
|
+
const pan = UI.prefixesAndName(name);
|
362
|
+
let n = endsWithDigits(pan.pop());
|
363
|
+
while(!n && pan.length > 0) {
|
364
|
+
n = endsWithDigits(pan.pop());
|
365
|
+
}
|
366
|
+
return n;
|
367
|
+
}
|
368
|
+
|
358
369
|
nameToID(name) {
|
359
370
|
// Returns a name in lower case with link arrow replaced by three
|
360
371
|
// underscores, constraint link arrow by four underscores, and spaces
|
@@ -4901,7 +4901,7 @@ class GUIController extends Controller {
|
|
4901
4901
|
// Logs MB's of used heap memory to console (to detect memory leaks)
|
4902
4902
|
// NOTE: this feature is supported only by Chrome
|
4903
4903
|
if(msg) msg += ' -- ';
|
4904
|
-
if(
|
4904
|
+
if(performance.memory !== undefined) {
|
4905
4905
|
console.log(msg + 'Allocated memory: ' + Math.round(
|
4906
4906
|
performance.memory.usedJSHeapSize/1048576.0).toFixed(1) + ' MB');
|
4907
4907
|
}
|
@@ -5701,6 +5701,7 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
5701
5701
|
this.setBox('settings-decimal-comma', model.decimal_comma);
|
5702
5702
|
this.setBox('settings-align-to-grid', model.align_to_grid);
|
5703
5703
|
this.setBox('settings-cost-prices', model.infer_cost_prices);
|
5704
|
+
this.setBox('settings-report-results', model.report_results);
|
5704
5705
|
this.setBox('settings-block-arrows', model.show_block_arrows);
|
5705
5706
|
md.show('name');
|
5706
5707
|
}
|
@@ -5753,6 +5754,7 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
5753
5754
|
if(!model.scale_units.hasOwnProperty(dsu)) model.addScaleUnit(dsu);
|
5754
5755
|
model.default_unit = dsu;
|
5755
5756
|
model.currency_unit = md.element('currency-unit').value.trim();
|
5757
|
+
model.report_results = UI.boxChecked('settings-report-results');
|
5756
5758
|
model.encrypt = UI.boxChecked('settings-encrypt');
|
5757
5759
|
model.decimal_comma = UI.boxChecked('settings-decimal-comma');
|
5758
5760
|
// Some changes may necessitate redrawing the diagram
|
@@ -15991,8 +15993,8 @@ class GUIReceiver {
|
|
15991
15993
|
}
|
15992
15994
|
|
15993
15995
|
log(msg) {
|
15994
|
-
// Logs a message displayed on the status line while solving
|
15995
|
-
if(this.active) {
|
15996
|
+
// Logs a message displayed on the status line while solving.
|
15997
|
+
if(this.active || MODEL.report_results) {
|
15996
15998
|
if(!msg.startsWith('[')) {
|
15997
15999
|
const
|
15998
16000
|
d = new Date(),
|
@@ -16135,21 +16137,34 @@ class GUIReceiver {
|
|
16135
16137
|
report() {
|
16136
16138
|
// Posts the run results to the local server, or signals an error
|
16137
16139
|
let form,
|
16138
|
-
run = ''
|
16140
|
+
run = '',
|
16141
|
+
path = this.channel,
|
16142
|
+
file = this.file_name;
|
16139
16143
|
// NOTE: Always set `solving` to FALSE
|
16140
16144
|
this.solving = false;
|
16141
|
-
|
16145
|
+
// NOTE: When reporting receiver while is not active, report the
|
16146
|
+
// results of the running experiment.
|
16147
|
+
if(this.experiment || !this.active) {
|
16142
16148
|
if(MODEL.running_experiment) {
|
16143
16149
|
run = MODEL.running_experiment.active_combination_index;
|
16144
16150
|
this.log(`Reporting: ${this.file_name} (run #${run})`);
|
16145
16151
|
}
|
16146
16152
|
}
|
16153
|
+
// NOTE: If receiver is not active, path and file must be set.
|
16154
|
+
if(!this.active) {
|
16155
|
+
path = 'user/reports';
|
16156
|
+
// NOTE: The @ will be replaced by the run number, so that that
|
16157
|
+
// number precedes the clock time. The @ will be unique because
|
16158
|
+
// `asFileName()` replaces special characters by underscores.
|
16159
|
+
file = REPOSITORY_BROWSER.asFileName(MODEL.name || 'model') +
|
16160
|
+
'@-' + compactClockTime();
|
16161
|
+
}
|
16147
16162
|
if(MODEL.solved && !VM.halted) {
|
16148
16163
|
// Normal execution termination => report results
|
16149
16164
|
const od = MODEL.outputData;
|
16150
16165
|
form = {
|
16151
|
-
path:
|
16152
|
-
file:
|
16166
|
+
path: path,
|
16167
|
+
file: file,
|
16153
16168
|
action: 'report',
|
16154
16169
|
run: run,
|
16155
16170
|
data: od[0],
|
@@ -16178,10 +16193,11 @@ class GUIReceiver {
|
|
16178
16193
|
.then((data) => {
|
16179
16194
|
// For experiments, only display server response if warning or error
|
16180
16195
|
UI.postResponseOK(data, !RECEIVER.experiment);
|
16181
|
-
// If execution completed, perform the call-back action
|
16196
|
+
// If execution completed, perform the call-back action if the
|
16197
|
+
// receiver is active (so not when auto-reporting a run).
|
16182
16198
|
// NOTE: for experiments, call-back is performed upon completion by
|
16183
|
-
// the Experiment Manager
|
16184
|
-
if(!RECEIVER.experiment) RECEIVER.callBack();
|
16199
|
+
// the Experiment Manager.
|
16200
|
+
if(RECEIVER.active && !RECEIVER.experiment) RECEIVER.callBack();
|
16185
16201
|
})
|
16186
16202
|
.catch(() => UI.warn(UI.WARNING.NO_CONNECTION, err));
|
16187
16203
|
}
|
@@ -96,6 +96,7 @@ class LinnyRModel {
|
|
96
96
|
this.grid_pixels = 20;
|
97
97
|
this.align_to_grid = true;
|
98
98
|
this.infer_cost_prices = false;
|
99
|
+
this.report_results = false;
|
99
100
|
this.show_block_arrows = true;
|
100
101
|
this.last_zoom_factor = 1;
|
101
102
|
|
@@ -2486,6 +2487,7 @@ class LinnyRModel {
|
|
2486
2487
|
this.decimal_comma = nodeParameterValue(node, 'decimal-comma') === '1';
|
2487
2488
|
this.align_to_grid = nodeParameterValue(node, 'align-to-grid') === '1';
|
2488
2489
|
this.infer_cost_prices = nodeParameterValue(node, 'cost-prices') === '1';
|
2490
|
+
this.report_results = nodeParameterValue(node, 'report-results') === '1';
|
2489
2491
|
this.show_block_arrows = nodeParameterValue(node, 'block-arrows') === '1';
|
2490
2492
|
this.name = xmlDecoded(nodeContentByTag(node, 'name'));
|
2491
2493
|
this.author = xmlDecoded(nodeContentByTag(node, 'author'));
|
@@ -2828,6 +2830,7 @@ class LinnyRModel {
|
|
2828
2830
|
if(this.decimal_comma) p += ' decimal-comma="1"';
|
2829
2831
|
if(this.align_to_grid) p += ' align-to-grid="1"';
|
2830
2832
|
if(this.infer_cost_prices) p += ' cost-prices="1"';
|
2833
|
+
if(this.report_results) p += ' report-results="1"';
|
2831
2834
|
if(this.show_block_arrows) p += ' block-arrows="1"';
|
2832
2835
|
let xml = this.xml_header + ['<model', p, '><name>', xmlEncoded(this.name),
|
2833
2836
|
'</name><author>', xmlEncoded(this.author),
|
@@ -4738,7 +4741,7 @@ class Note extends ObjectWithXYWH {
|
|
4738
4741
|
get numberContext() {
|
4739
4742
|
// Returns the string to be used to evaluate #. For notes this is
|
4740
4743
|
// their note number if specified, otherwise the number context of a
|
4741
|
-
// nearby
|
4744
|
+
// nearby node, and otherwise the number context of their cluster.
|
4742
4745
|
let n = this.number;
|
4743
4746
|
if(n) return n;
|
4744
4747
|
n = this.nearbyNode;
|
@@ -5203,16 +5206,8 @@ class NodeBox extends ObjectWithXYWH {
|
|
5203
5206
|
|
5204
5207
|
get numberContext() {
|
5205
5208
|
// Returns the string to be used to evaluate #, so for clusters,
|
5206
|
-
// processes and products this is
|
5207
|
-
|
5208
|
-
// a number, the trailing digits of the first prefix (from right to
|
5209
|
-
// left) that does end on a number
|
5210
|
-
const sn = UI.prefixesAndName(this.name);
|
5211
|
-
let nc = endsWithDigits(sn.pop());
|
5212
|
-
while(!nc && sn.length > 0) {
|
5213
|
-
nc = endsWithDigits(sn.pop());
|
5214
|
-
}
|
5215
|
-
return nc;
|
5209
|
+
// processes and products this is their "tail number".
|
5210
|
+
return UI.tailNumber(this.name);
|
5216
5211
|
}
|
5217
5212
|
|
5218
5213
|
get similarNumberedEntities() {
|
@@ -8271,17 +8266,10 @@ class DatasetModifier {
|
|
8271
8266
|
// NOTE: If the selector contains wildcards, return "?" to indicate
|
8272
8267
|
// that the value of # cannot be inferred at compile time.
|
8273
8268
|
if(this.hasWildcards) return '?';
|
8274
|
-
// Otherwise,
|
8275
|
-
//
|
8276
|
-
//
|
8277
|
-
|
8278
|
-
let nc = endsWithDigits(sn.pop());
|
8279
|
-
while(!nc && sn.length > 0) {
|
8280
|
-
nc = endsWithDigits(sn.pop());
|
8281
|
-
}
|
8282
|
-
// NOTE: if the selector has no tail number, return the number context
|
8283
|
-
// of the dataset of this modifier.
|
8284
|
-
return nc || this.dataset.numberContext;
|
8269
|
+
// Otherwise, return the "tail number" of the selector, or if the
|
8270
|
+
// selector has no tail number, return the number context of the
|
8271
|
+
// dataset of this modifier.
|
8272
|
+
return UI.tailnumber(this.name) || this.dataset.numberContext;
|
8285
8273
|
}
|
8286
8274
|
|
8287
8275
|
match(s) {
|
@@ -8366,14 +8354,8 @@ class Dataset {
|
|
8366
8354
|
|
8367
8355
|
get numberContext() {
|
8368
8356
|
// Returns the string to be used to evaluate #
|
8369
|
-
// Like for nodes, this is the
|
8370
|
-
|
8371
|
-
const sn = UI.prefixesAndName(this.name);
|
8372
|
-
let nc = endsWithDigits(sn.pop());
|
8373
|
-
while(!nc && sn.length > 0) {
|
8374
|
-
nc = endsWithDigits(sn.pop());
|
8375
|
-
}
|
8376
|
-
return nc;
|
8357
|
+
// Like for nodes, this is the "tail number" of the dataset name.
|
8358
|
+
return UI.tailNumber(this.name);
|
8377
8359
|
}
|
8378
8360
|
|
8379
8361
|
get selectorList() {
|
@@ -135,6 +135,14 @@ function msecToTime(msec) {
|
|
135
135
|
return hms + '.' + ms.slice(0, 1) + ' sec';
|
136
136
|
}
|
137
137
|
|
138
|
+
function compactClockTime() {
|
139
|
+
// Returns current time (no date) in 6 digits hhmmss.
|
140
|
+
const d = new Date();
|
141
|
+
return d.getHours().toString().padStart(2, '0') +
|
142
|
+
d.getMinutes().toString().padStart(2, '0') +
|
143
|
+
d.getSeconds().toString().padStart(2, '0');
|
144
|
+
}
|
145
|
+
|
138
146
|
function uniformDecimals(data) {
|
139
147
|
// Formats the numbers in the array `data` so that they have uniform decimals
|
140
148
|
// NOTE: (1) this routine assumes that all number strings have sig4Dig format;
|
@@ -627,13 +635,11 @@ function customizeXML(str) {
|
|
627
635
|
// for example to rename entities in one go -- USE WITH CARE!
|
628
636
|
// First modify `str` -- by default, do nothing
|
629
637
|
|
630
|
-
|
631
|
-
if(str.indexOf('<author>
|
632
|
-
str = str.replace(
|
633
|
-
'is-information="1" no-slack="1"><name>Line $1: switch<');
|
634
|
-
str = str.replace(/: zon/g, ': solar');
|
638
|
+
/*
|
639
|
+
if(str.indexOf('<author>XXX</author>') >= 0) {
|
640
|
+
str = str.replace(/<url>NL\/(\w+)\.csv<\/url>/g, '<url></url>');
|
635
641
|
}
|
636
|
-
|
642
|
+
*/
|
637
643
|
|
638
644
|
// Finally, return the modified string
|
639
645
|
return str;
|
@@ -911,6 +917,7 @@ if(NODE) module.exports = {
|
|
911
917
|
rangeToList: rangeToList,
|
912
918
|
dateToString: dateToString,
|
913
919
|
msecToTime: msecToTime,
|
920
|
+
compactClockTime: compactClockTime,
|
914
921
|
uniformDecimals: uniformDecimals,
|
915
922
|
ellipsedText: ellipsedText,
|
916
923
|
earlierVersion: earlierVersion,
|
@@ -270,53 +270,61 @@ class Expression {
|
|
270
270
|
}
|
271
271
|
|
272
272
|
chooseVector(number) {
|
273
|
-
// Return the vector to use for computation (defaults to "own" vector)
|
274
|
-
|
275
|
-
if(number
|
276
|
-
|
277
|
-
|
278
|
-
|
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 {
|
279
283
|
MODEL.cleanVector(this.wildcard_vectors[number], VM.NOT_COMPUTED);
|
280
284
|
}
|
281
|
-
return this.wildcard_vectors[number];
|
282
285
|
}
|
286
|
+
return this.wildcard_vectors[number];
|
283
287
|
}
|
284
288
|
|
285
289
|
compute(t, number=false) {
|
286
|
-
// Executes the VM code for this expression for time step t
|
287
|
-
// 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.
|
288
292
|
if(!this.compiled) this.compile();
|
289
|
-
// Return FALSE if compilation resulted in error
|
293
|
+
// Return FALSE if compilation resulted in error.
|
290
294
|
if(!this.compiled) return false;
|
291
|
-
// Compute static expressions as if t = 0
|
295
|
+
// Compute static expressions as if t = 0.
|
292
296
|
if(t < 0 || this.isStatic) t = 0;
|
293
|
-
// Select the vector to use
|
297
|
+
// Select the vector to use.
|
294
298
|
const v = this.chooseVector(number);
|
295
|
-
// Check for potential error (that should NOT occur)
|
296
|
-
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) {
|
297
301
|
const msg = 'ERROR: Undefined value during expression evaluation';
|
298
302
|
UI.alert(msg);
|
299
303
|
console.log(this.variableName, ':', this.text, '#', number, '@', t, v);
|
300
|
-
// Throw exception to permit viewing the function call stack
|
304
|
+
// Throw exception to permit viewing the function call stack.
|
301
305
|
throw msg;
|
302
306
|
}
|
303
307
|
// When called while already computing for time step t, signal this
|
304
308
|
// as an error value.
|
305
309
|
if(v[t] === VM.COMPUTING) v[t] = VM.CYCLIC;
|
306
|
-
// Compute a value only once
|
307
|
-
if(v[t] !== VM.NOT_COMPUTED)
|
308
|
-
|
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).
|
309
317
|
this.wildcard_vector_index = number;
|
310
|
-
// Push this expression onto the call stack
|
318
|
+
// Push this expression onto the call stack.
|
311
319
|
VM.call_stack.push(this);
|
312
|
-
// Push time step in case a
|
313
|
-
// this same variable
|
314
|
-
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})`);
|
315
323
|
this.step.push(t);
|
316
|
-
// NOTE:
|
324
|
+
// NOTE: Trace expression AFTER pushing the time step.
|
317
325
|
this.trace(`"${this.text}"`);
|
318
326
|
v[t] = VM.COMPUTING;
|
319
|
-
// Execute the instructions
|
327
|
+
// Execute the instructions.
|
320
328
|
let vmi = null,
|
321
329
|
ok = true,
|
322
330
|
cl = this.code.length;
|
@@ -325,13 +333,13 @@ class Expression {
|
|
325
333
|
this.stack.length = 0;
|
326
334
|
while(ok && this.program_counter < cl && v[t] === VM.COMPUTING) {
|
327
335
|
vmi = this.code[this.program_counter];
|
328
|
-
// Instructions are 2-element arrays [function, [arguments]]
|
329
|
-
//
|
330
|
-
// 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.
|
331
339
|
vmi[0](this, vmi[1]);
|
332
340
|
this.program_counter++;
|
333
341
|
}
|
334
|
-
// Stack should now have length 1
|
342
|
+
// Stack should now have length 1.
|
335
343
|
if(this.stack.length > 1) {
|
336
344
|
v[t] = VM.OVERFLOW;
|
337
345
|
} else if(this.stack.length < 1) {
|
@@ -340,19 +348,20 @@ class Expression {
|
|
340
348
|
v[t] = this.stack.pop();
|
341
349
|
}
|
342
350
|
this.trace('RESULT = ' + VM.sig4Dig(v[t]));
|
343
|
-
// Pop the time step
|
351
|
+
// Pop the time step.
|
344
352
|
this.step.pop();
|
345
353
|
this.trace('--STOP: ' + this.variableName);
|
346
|
-
// Clear context for #
|
354
|
+
// Clear context for # for this expression (no stack needed, as
|
355
|
+
// wildcard expressions cannot reference themselves).
|
347
356
|
this.wildcard_vector_index = false;
|
348
|
-
// If error, display the call stack (only once)
|
357
|
+
// If error, display the call stack (only once).
|
349
358
|
// NOTE: "undefined", "not computed" and "still computing" are NOT
|
350
359
|
// problematic unless they result in an error (stack over/underflow)
|
351
360
|
if(v[t] <= VM.ERROR) {
|
352
361
|
MONITOR.showCallStack(t);
|
353
362
|
VM.logCallStack(t);
|
354
363
|
}
|
355
|
-
// Always pop the expression from the call stack
|
364
|
+
// Always pop the expression from the call stack.
|
356
365
|
VM.call_stack.pop(this);
|
357
366
|
return true;
|
358
367
|
}
|
@@ -365,7 +374,10 @@ class Expression {
|
|
365
374
|
// "initial value" (these follow from the variables used in the expression)
|
366
375
|
// Select the vector to use
|
367
376
|
const v = this.chooseVector(number);
|
368
|
-
if(!v)
|
377
|
+
if(!Array.isArray(v)) {
|
378
|
+
console.log('ANOMALY: No vector for result(t)');
|
379
|
+
return VM.UNDEFINED;
|
380
|
+
}
|
369
381
|
if(t < 0 || this.isStatic) t = 0;
|
370
382
|
if(t >= v.length) return VM.UNDEFINED;
|
371
383
|
if(v[t] === VM.NOT_COMPUTED || v[t] === VM.COMPUTING) {
|
@@ -532,12 +544,9 @@ class Expression {
|
|
532
544
|
// using the variable [partial load 1] to compute the partial load for
|
533
545
|
// process P1.
|
534
546
|
// NOTES:
|
535
|
-
// (1) This
|
536
|
-
//
|
537
|
-
//
|
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.
|
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 #]".
|
541
550
|
// (2) The # may be used in patterns, so when a model comprises processes
|
542
551
|
// P1 and P2, and products Q2 and Q3, and a wildcard equation "total level ??"
|
543
552
|
// with expression "[SUM$#|L]", then [total level 1] will evaluate as the level
|
@@ -545,6 +554,8 @@ class Expression {
|
|
545
554
|
|
546
555
|
class ExpressionParser {
|
547
556
|
constructor(text, owner=null, attribute='') {
|
557
|
+
// Setting TRACE to TRUE will log parsing information to the console.
|
558
|
+
this.TRACE = false;
|
548
559
|
// `text` is the expression string to be parsed.
|
549
560
|
this.expr = text;
|
550
561
|
// NOTE: When expressions for dataset modifiers or equations are
|
@@ -557,11 +568,12 @@ class ExpressionParser {
|
|
557
568
|
this.selector = '';
|
558
569
|
this.context_number = '';
|
559
570
|
this.wildcard_selector = false;
|
560
|
-
this.wildcard_equation = false;
|
561
571
|
// Always infer the value for the context-sensitive number #.
|
562
|
-
// NOTE: this will be
|
563
|
-
//
|
564
|
-
// cannot be inferred at
|
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
|
+
//
|
565
577
|
if(owner) {
|
566
578
|
this.context_number = owner.numberContext;
|
567
579
|
// NOTE: The owner prefix includes the trailing colon+space.
|
@@ -578,19 +590,34 @@ class ExpressionParser {
|
|
578
590
|
if(owner instanceof Dataset) {
|
579
591
|
this.dataset = owner;
|
580
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.
|
581
595
|
this.selector = attribute;
|
582
|
-
// Record whether this selector contains wildcards
|
596
|
+
// Record whether this selector contains wildcards (? and/or *
|
597
|
+
// for selectors, ?? for equations).
|
583
598
|
this.wildcard_selector = owner.isWildcardSelector(attribute);
|
584
|
-
if(
|
585
|
-
//
|
586
|
-
|
587
|
-
|
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) {
|
588
610
|
// For "normal" modifier expressions, the "dot" (.) can be used
|
589
611
|
// to refer to the dataset of the modifier.
|
590
612
|
this.dot = this.dataset;
|
591
613
|
}
|
592
614
|
}
|
593
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
|
+
}
|
594
621
|
// Immediately compile; this may generate warnings
|
595
622
|
this.compile();
|
596
623
|
}
|
@@ -601,14 +628,18 @@ class ExpressionParser {
|
|
601
628
|
let n = this.owner.displayName;
|
602
629
|
if(this.attribute) n += '|' + this.attribute;
|
603
630
|
if(this.wildcard_selector) {
|
604
|
-
n
|
605
|
-
(this.
|
631
|
+
n = [n, ' [wildcard ',
|
632
|
+
(this.dataset === MODEL.equations_dataset ?
|
633
|
+
'equation' : 'modifier'),
|
634
|
+
(this.context_number !== false ?
|
635
|
+
' # = ' + this.context_number : ''),
|
636
|
+
']'].join('');
|
606
637
|
}
|
607
638
|
return n;
|
608
639
|
}
|
609
640
|
|
610
641
|
log(msg) {
|
611
|
-
//
|
642
|
+
// NOTE: This method is used only to profile dynamic expressions.
|
612
643
|
if(true) return;
|
613
644
|
// Set the above IF condition to FALSE to profile dynamic expressions.
|
614
645
|
console.log(`Expression for ${this.ownerName}: ${this.expr}\n${msg}`);
|
@@ -630,6 +661,14 @@ class ExpressionParser {
|
|
630
661
|
parseVariable(name) {
|
631
662
|
// Reduce whitespace to single space.
|
632
663
|
name = name.replace(/\s+/g, ' ');
|
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
|
+
|
633
672
|
// Initialize possible components.
|
634
673
|
let obj = null,
|
635
674
|
attr = '',
|
@@ -640,6 +679,7 @@ class ExpressionParser {
|
|
640
679
|
anchor2 = '',
|
641
680
|
offset2 = 0,
|
642
681
|
msg = '',
|
682
|
+
arg0 = null,
|
643
683
|
args = null,
|
644
684
|
s = name.split('@');
|
645
685
|
if(s.length > 1) {
|
@@ -691,7 +731,7 @@ class ExpressionParser {
|
|
691
731
|
}
|
692
732
|
// Check whether # anchor is meaningful for this expression
|
693
733
|
if((anchor1 === '#' || anchor2 === '#') &&
|
694
|
-
!(this.wildcard_selector || this.context_number)) {
|
734
|
+
!(this.wildcard_selector || this.context_number !== false)) {
|
695
735
|
// Log debugging information for this error
|
696
736
|
console.log(this.owner.displayName, this.owner.type, this.selector);
|
697
737
|
this.error = 'Anchor # is undefined in this context';
|
@@ -805,7 +845,8 @@ class ExpressionParser {
|
|
805
845
|
if(x.x) {
|
806
846
|
// Look up name in experiment outcomes list
|
807
847
|
x.v = x.x.resultIndex(name);
|
808
|
-
if(x.v < 0 && name.indexOf('#') >= 0 &&
|
848
|
+
if(x.v < 0 && name.indexOf('#') >= 0 &&
|
849
|
+
typeof this.context_number === 'number') {
|
809
850
|
// Variable name may be parametrized with #, but not in
|
810
851
|
// expressions for wildcard selectors
|
811
852
|
name = name.replace('#', this.context_number);
|
@@ -819,9 +860,10 @@ class ExpressionParser {
|
|
819
860
|
// Check outcome list of ALL experiments
|
820
861
|
for(let i = 0; i < MODEL.experiments.length; i++) {
|
821
862
|
let xri = MODEL.experiments[i].resultIndex(name);
|
822
|
-
if(xri < 0 && name.indexOf('#') >= 0 &&
|
863
|
+
if(xri < 0 && name.indexOf('#') >= 0 &&
|
864
|
+
typeof this.context_number === 'number') {
|
823
865
|
// Variable name may be parametrized with #, but not in
|
824
|
-
// expressions for wildcard selectors
|
866
|
+
// expressions for wildcard selectors.
|
825
867
|
name = name.replace('#', this.context_number);
|
826
868
|
xri = MODEL.experiments[i].resultIndex(name);
|
827
869
|
}
|
@@ -859,6 +901,7 @@ class ExpressionParser {
|
|
859
901
|
// For experiment run results, default anchor is 't'.
|
860
902
|
if(!anchor1) anchor1 = 't';
|
861
903
|
if(!anchor2) anchor2 = 't';
|
904
|
+
if(this.TRACE) console.log('TRACE: Variable is run result. x =', x);
|
862
905
|
// NOTE: compiler will recognize `x` to indicate "push run results".
|
863
906
|
return [x, anchor1, offset1, anchor2, offset2];
|
864
907
|
}
|
@@ -899,7 +942,7 @@ class ExpressionParser {
|
|
899
942
|
let pat = name.split('$');
|
900
943
|
if(pat.length > 1 &&
|
901
944
|
VM.statistic_operators.indexOf(pat[0].toUpperCase()) >= 0) {
|
902
|
-
// For statistics, default anchor is 't'.
|
945
|
+
// For statistics, the default anchor is 't'.
|
903
946
|
if(!anchor1) anchor1 = 't';
|
904
947
|
if(!anchor2) anchor2 = 't';
|
905
948
|
// Check whether unit balance for clusters is asked for.
|
@@ -916,17 +959,20 @@ class ExpressionParser {
|
|
916
959
|
// NOTE: The "dot" dataset is not level-dependent, and statistics
|
917
960
|
// over its vector do NOT make the expression dynamic.
|
918
961
|
if(this.dot) {
|
919
|
-
|
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;
|
920
965
|
} else {
|
921
966
|
this.error = UI.ERROR.NO_DATASET_DOT;
|
922
967
|
return false;
|
923
968
|
}
|
924
969
|
}
|
925
970
|
// Deal with "prefix inheritance" when pattern starts with a colon.
|
926
|
-
if(pat.startsWith(':')) {
|
971
|
+
if(pat.startsWith(':') && this.owner_prefix) {
|
927
972
|
// Add a "must start with" AND condition to all OR clauses of the
|
928
973
|
// pattern.
|
929
|
-
// NOTE: Issues may occur when prefix contains &, ^ or
|
974
|
+
// NOTE: Issues may occur when prefix contains &, ^ or #.
|
975
|
+
// @@TO DO: See if this can be easily prohibited.
|
930
976
|
const oc = pat.substring(1).split('|');
|
931
977
|
for(let i = 0; i < oc.length; i++) {
|
932
978
|
oc[i] = `~${this.owner_prefix}&${oc[i]}`;
|
@@ -936,7 +982,9 @@ class ExpressionParser {
|
|
936
982
|
// NOTE: For patterns, assume that # *always* denotes the context-
|
937
983
|
// sensitive number #, because if modelers wishes to include
|
938
984
|
// ANY number, they can make their pattern less selective.
|
939
|
-
if(this.context_number
|
985
|
+
if(typeof this.context_number === 'number') {
|
986
|
+
pat = pat.replace('#', this.context_number);
|
987
|
+
}
|
940
988
|
// By default, consider all entity types.
|
941
989
|
let et = VM.entity_letters,
|
942
990
|
patstr = pat;
|
@@ -956,7 +1004,7 @@ class ExpressionParser {
|
|
956
1004
|
// returned list to the specified entity types.
|
957
1005
|
ewa = MODEL.entitiesWithAttribute(attr, et);
|
958
1006
|
// Create list of expression objects for the matching entities.
|
959
|
-
// Also create a "dict" with for each matching wildcard number
|
1007
|
+
// Also create a "dict" with, for each matching wildcard number,
|
960
1008
|
// the matching entities as a separate list. This will permit
|
961
1009
|
// narrowing the selection at run time, based on the expression's
|
962
1010
|
// wildcard number.
|
@@ -1002,16 +1050,18 @@ class ExpressionParser {
|
|
1002
1050
|
}
|
1003
1051
|
}
|
1004
1052
|
if(list.length > 0) {
|
1005
|
-
// NOTE:
|
1006
|
-
//
|
1007
|
-
// 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.
|
1008
1056
|
this.is_level_based = this.is_level_based ||
|
1009
1057
|
VM.level_based_attr.indexOf(attr) >= 0 &&
|
1010
1058
|
anchor1 === 't' && offset1 === 0 &&
|
1011
1059
|
anchor2 === 't' && offset2 === 0;
|
1012
|
-
|
1060
|
+
args = [stat, list, anchor1, offset1, anchor2, offset2];
|
1013
1061
|
if(Object.keys(wdict).length > 0) args.push(wdict);
|
1014
|
-
|
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.
|
1015
1065
|
return args;
|
1016
1066
|
}
|
1017
1067
|
this.error = `No entities that match pattern "${patstr}"` +
|
@@ -1045,6 +1095,7 @@ class ExpressionParser {
|
|
1045
1095
|
this.log('dynamic because of self-reference');
|
1046
1096
|
if(('cips'.indexOf(anchor1) >= 0 || anchor1 === 't' && offset1 < 0) &&
|
1047
1097
|
('cips'.indexOf(anchor2) >= 0 ||anchor2 === 't' && offset2 < 0)) {
|
1098
|
+
if(this.TRACE) console.log('TRACE: Variable is a self-reference.');
|
1048
1099
|
// The `xv` attribute will be recognized by VMI_push_var to denote
|
1049
1100
|
// "use the vector of the expression for which this VMI is code".
|
1050
1101
|
return [{xv: true, dv: this.dataset.defaultValue},
|
@@ -1080,6 +1131,9 @@ class ExpressionParser {
|
|
1080
1131
|
id = UI.nameToID(name),
|
1081
1132
|
w = MODEL.wildcardEquationByID(id);
|
1082
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);
|
1083
1137
|
// Variable matches wildcard equation w[0] with number w[1],
|
1084
1138
|
// so this equation must be evaluated for that number.
|
1085
1139
|
return [
|
@@ -1089,11 +1143,13 @@ class ExpressionParser {
|
|
1089
1143
|
// If no match, try to match the object ID with any type of entity.
|
1090
1144
|
obj = MODEL.objectByID(id);
|
1091
1145
|
}
|
1092
|
-
// If not,
|
1146
|
+
// If not, try whether wildcards can be substituted.
|
1093
1147
|
if(!obj && name.indexOf('#') >= 0) {
|
1094
|
-
if(this.context_number) {
|
1148
|
+
if(typeof this.context_number === 'number') {
|
1095
1149
|
obj = MODEL.objectByName(name.replace('#', this.context_number));
|
1096
1150
|
}
|
1151
|
+
if(obj && TRACE) console.log('TRACE: Matched ', name,
|
1152
|
+
'with entity:', obj.displayName);
|
1097
1153
|
if(!obj) {
|
1098
1154
|
// If immediate substitution of # does not identify an entity,
|
1099
1155
|
// then name may still refer to a wildcard equation.
|
@@ -1104,9 +1160,9 @@ class ExpressionParser {
|
|
1104
1160
|
} else {
|
1105
1161
|
obj = MODEL.equationByID(UI.nameToID(wcname));
|
1106
1162
|
if(obj) {
|
1107
|
-
// Special case: parsed variable references a wildcard
|
1108
|
-
// equation
|
1109
|
-
if(!(this.
|
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)) {
|
1110
1166
|
msg = UI.ERROR.NO_NUMBER_CONTEXT;
|
1111
1167
|
} else {
|
1112
1168
|
// Acceptable reference to a wildcard equation.
|
@@ -1117,23 +1173,13 @@ class ExpressionParser {
|
|
1117
1173
|
// NOTE: The referenced expression may be level-dependent.
|
1118
1174
|
this.is_level_based = this.is_level_based ||
|
1119
1175
|
obj.expression.is_level_based;
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
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
|
-
}
|
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);
|
1134
1180
|
// Use the context number as "selector" parameter of the VMI.
|
1135
1181
|
return [
|
1136
|
-
{d: obj.dataset, s:
|
1182
|
+
{d: obj.dataset, s: this.context_number, x: obj.expression},
|
1137
1183
|
anchor1, offset1, anchor2, offset2];
|
1138
1184
|
}
|
1139
1185
|
}
|
@@ -1153,8 +1199,10 @@ class ExpressionParser {
|
|
1153
1199
|
// at run time the VM can match with the value of #.
|
1154
1200
|
// NOTE: Also pass whether the entity should be pushed
|
1155
1201
|
// "by reference".
|
1156
|
-
|
1157
|
-
|
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},
|
1158
1206
|
anchor1, offset1, anchor2, offset2];
|
1159
1207
|
}
|
1160
1208
|
// Wildcard selector, but no number context for #.
|
@@ -1205,9 +1253,12 @@ class ExpressionParser {
|
|
1205
1253
|
}
|
1206
1254
|
// If `obj` is a dataset *modifier*, it must be a "normal" equation...
|
1207
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);
|
1208
1259
|
// ... so "map" it onto the equations dataset + selector...
|
1209
1260
|
attr = obj.selector;
|
1210
|
-
obj =
|
1261
|
+
obj = obj.dataset;
|
1211
1262
|
}
|
1212
1263
|
// ... so now it will be processed the same way dataset modifiers
|
1213
1264
|
// are processed, especially when they have a tail number.
|
@@ -1224,30 +1275,40 @@ class ExpressionParser {
|
|
1224
1275
|
|
1225
1276
|
// If "by reference", return the object itself plus its attribute
|
1226
1277
|
if(by_reference) {
|
1278
|
+
if(this.TRACE) console.log('TRACE: Variable is a reference to',
|
1279
|
+
obj.displayName, '. Attribute:', attr);
|
1227
1280
|
return [{r: obj, a: attr}, anchor1, offset1, anchor2, offset2];
|
1228
1281
|
}
|
1229
1282
|
if(obj === this.dataset && attr === '' && !obj.array) {
|
1230
1283
|
// When dataset modifier expression refers to its dataset without
|
1231
1284
|
// selector, then this is equivalent to [.] (use the series data
|
1232
1285
|
// vector) unless it is an array, since then the series data is
|
1233
|
-
// not a time-scaled vector => special case
|
1234
|
-
|
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;
|
1235
1291
|
} else if(attr === '') {
|
1236
1292
|
// For all other variables, assume default attribute if none specified
|
1237
1293
|
attr = obj.defaultAttribute;
|
1238
1294
|
// For a dataset, check whether the VMI_push_dataset_modifier should be
|
1239
1295
|
// used. This is the case for array-type datasets, and for datasets
|
1240
1296
|
// having modifiers UNLESS the modeler used a vertical bar to indicate
|
1241
|
-
// "use the data"
|
1297
|
+
// "use the data".
|
1242
1298
|
if(obj instanceof Dataset &&
|
1243
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.
|
1244
1303
|
if(obj.data.length > 1 || (obj.data.length > 0 && !obj.periodic) ||
|
1245
1304
|
!obj.allModifiersAreStatic) {
|
1246
|
-
// No explicit selector => dynamic unless no time series data, and
|
1247
|
-
// ALL modifier expressions are static
|
1248
1305
|
this.is_static = false;
|
1249
1306
|
this.log('dynamic because dataset without explicit selector is used');
|
1250
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);
|
1251
1312
|
// NOTE: Also pass the "use data" flag so that experiment selectors
|
1252
1313
|
// will be ignored if the modeler coded the vertical bar.
|
1253
1314
|
return [{d: obj, ud: use_data}, anchor1, offset1, anchor2, offset2];
|
@@ -1269,7 +1330,7 @@ class ExpressionParser {
|
|
1269
1330
|
this.is_static = false;
|
1270
1331
|
this.log('dynamic because dataset modifier expression is dynamic');
|
1271
1332
|
}
|
1272
|
-
// NOTE:
|
1333
|
+
// NOTE: A single match may be due to wildcard(s) in the modifier,
|
1273
1334
|
// e.g., a variable [dataset|abc] matches with a modifier having
|
1274
1335
|
// wildcard selector "a?b", or [dataset|a12] matches with "a*".
|
1275
1336
|
// In such cases, if the selector matches an integer like "a12"
|
@@ -1278,26 +1339,31 @@ class ExpressionParser {
|
|
1278
1339
|
// for [datset34|a12], the number context is '12' and not '34').
|
1279
1340
|
let mcn = matchingNumber(attr, m.selector);
|
1280
1341
|
if(mcn === false) {
|
1281
|
-
// NOTE: When no matching number
|
1342
|
+
// NOTE: When no matching number is found, `attr` may still
|
1282
1343
|
// contain a ?? wildcard. If it indeed identifies a wildcard
|
1283
1344
|
// equation, then "?" should be passed to the VM instruction.
|
1284
1345
|
if(obj === MODEL.equations_dataset && attr.indexOf('??') >= 0) {
|
1285
1346
|
mcn = '?';
|
1286
1347
|
} else {
|
1287
1348
|
// Ensure that `mcn` is either an integer value or FALSE.
|
1288
|
-
mcn =
|
1349
|
+
mcn = parseInt(UI.tailNumber(obj.name)) || this.context_number;
|
1289
1350
|
}
|
1290
1351
|
}
|
1291
|
-
// Pass the dataset, the context number # (or FALSE) in place,
|
1292
|
-
// modifier expression
|
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);
|
1293
1359
|
return [
|
1294
1360
|
{d: m.dataset, s: mcn, x: m.expression},
|
1295
1361
|
anchor1, offset1, anchor2, offset2];
|
1296
1362
|
}
|
1297
1363
|
}
|
1298
|
-
// NOTE: `
|
1299
|
-
if(
|
1300
|
-
if(Array.isArray(
|
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)) {
|
1301
1367
|
if(obj instanceof Dataset) {
|
1302
1368
|
if(obj.data.length > 1 || obj.data.length > 0 && !obj.periodic) {
|
1303
1369
|
this.is_static = false;
|
@@ -1307,23 +1373,25 @@ class ExpressionParser {
|
|
1307
1373
|
this.is_static = false;
|
1308
1374
|
this.log('dynamic because level-based attribute');
|
1309
1375
|
} else {
|
1310
|
-
// Unusual (?) combi, so let's assume dynamic
|
1376
|
+
// Unusual (?) combi, so let's assume dynamic.
|
1311
1377
|
this.is_static = false;
|
1312
1378
|
this.log('probably dynamic -- check below:');
|
1313
|
-
console.log(obj.displayName, obj, attr,
|
1379
|
+
console.log('ANOMALY: array for', obj.displayName, obj, attr, arg0);
|
1314
1380
|
}
|
1381
|
+
if(this.TRACE) console.log('TRACE: arg[0] is a vector');
|
1315
1382
|
}
|
1316
|
-
// If not a single value or vector, it must be an expression
|
1317
|
-
if(
|
1318
|
-
if(
|
1319
|
-
// Only NOW check whether unit balance for clusters is asked for
|
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.
|
1320
1387
|
if(cluster_balance_unit !== false && obj instanceof Cluster) {
|
1321
|
-
// NOTE:
|
1388
|
+
// NOTE: Cluster balance ALWAYS makes expression level-based
|
1389
|
+
// and dynamic.
|
1322
1390
|
this.is_level_based = true;
|
1323
|
-
// @TO DO: rethink whether this will indeed also make this expression
|
1324
|
-
// dynamic
|
1325
1391
|
this.is_static = false;
|
1326
|
-
this.log('dynamic because cluster balance is
|
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);
|
1327
1395
|
// NOTE: VM instructions VMI_push_var will recognize this special case
|
1328
1396
|
return [{c: obj, u: cluster_balance_unit},
|
1329
1397
|
anchor1, offset1, anchor2, offset2];
|
@@ -1331,26 +1399,31 @@ class ExpressionParser {
|
|
1331
1399
|
// Fall-through: invalid attribute for this object
|
1332
1400
|
msg = `${obj.type} entities have no attribute "${attr}"`;
|
1333
1401
|
} else {
|
1334
|
-
if(
|
1335
|
-
this.is_static = this.is_static &&
|
1402
|
+
if(arg0 instanceof Expression) {
|
1403
|
+
this.is_static = this.is_static && arg0.isStatic;
|
1336
1404
|
}
|
1337
|
-
|
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];
|
1338
1408
|
}
|
1339
1409
|
if(msg) {
|
1340
1410
|
this.error = msg;
|
1341
1411
|
return false;
|
1342
1412
|
}
|
1343
1413
|
// 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
|
1346
|
-
// after optimizing a block) while no offset is defined to use
|
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.
|
1347
1418
|
this.is_level_based = this.is_level_based ||
|
1348
|
-
// NOTE:
|
1349
|
-
obj instanceof Dataset && attr &&
|
1350
|
-
//
|
1351
|
-
|
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 &&
|
1352
1425
|
anchor1 === 't' && offset1 === 0 &&
|
1353
|
-
anchor2 === 't' && offset2 === 0;
|
1426
|
+
anchor2 === 't' && offset2 === 0);
|
1354
1427
|
return args;
|
1355
1428
|
}
|
1356
1429
|
|
@@ -1688,6 +1761,7 @@ class ExpressionParser {
|
|
1688
1761
|
// Either a statistic, a dataset (array-type or with modifier),
|
1689
1762
|
// an experiment run result, or a variable.
|
1690
1763
|
if(this.sym.length >= 6) {
|
1764
|
+
// 6 or 7 arguments indicates a statistic.
|
1691
1765
|
this.code.push([VMI_push_statistic, this.sym]);
|
1692
1766
|
} else if(this.sym[0].hasOwnProperty('d')) {
|
1693
1767
|
this.code.push([VMI_push_dataset_modifier, this.sym]);
|
@@ -1724,7 +1798,7 @@ class ExpressionParser {
|
|
1724
1798
|
this.error = 'Invalid parameter list';
|
1725
1799
|
}
|
1726
1800
|
}
|
1727
|
-
if(DEBUGGING) console.log('PARSED', this.ownerName, ':',
|
1801
|
+
if(this.TRACE || DEBUGGING) console.log('PARSED', this.ownerName, ':',
|
1728
1802
|
this.expr, this.code);
|
1729
1803
|
}
|
1730
1804
|
|
@@ -5174,7 +5248,7 @@ Solver status = ${json.status}`);
|
|
5174
5248
|
// Show the reset button (GUI only)
|
5175
5249
|
UI.readyToReset();
|
5176
5250
|
// If receiver is active, report results
|
5177
|
-
if(RECEIVER.solving) RECEIVER.report();
|
5251
|
+
if(RECEIVER.solving || MODEL.report_results) RECEIVER.report();
|
5178
5252
|
// If experiment is active, signal the manager
|
5179
5253
|
if(MODEL.running_experiment) EXPERIMENT_MANAGER.processRun();
|
5180
5254
|
// Warn modeler if any issues occurred
|
@@ -5796,10 +5870,9 @@ function VMI_push_wildcard_entity(x, args) {
|
|
5796
5870
|
// n: name (with wildcard #), a: attribute, br: by reference (boolean)}
|
5797
5871
|
// First select the first entity in `ee` that matches the wildcard vector
|
5798
5872
|
// index of the expression `x` being executed.
|
5799
|
-
const
|
5800
|
-
|
5801
|
-
|
5802
|
-
let obj = null;
|
5873
|
+
const el = args[0].ee;
|
5874
|
+
let nn = args[0].n.replace('#', x.wildcard_vector_index),
|
5875
|
+
obj = null;
|
5803
5876
|
for(let i = 0; !obj && i < el.length; i++) {
|
5804
5877
|
if(el[i].name === nn) obj = el[i];
|
5805
5878
|
}
|
@@ -5809,12 +5882,11 @@ function VMI_push_wildcard_entity(x, args) {
|
|
5809
5882
|
x.push(VM.BAD_REF);
|
5810
5883
|
return;
|
5811
5884
|
}
|
5812
|
-
// Otherwise, if
|
5813
|
-
// be called with the appropriate parameters.
|
5885
|
+
// Otherwise, if args[0] indicates "by reference", then VMI_push_entity
|
5886
|
+
// can be called with the appropriate parameters.
|
5814
5887
|
const attr = args[0].a || obj.defaultAttribute;
|
5815
5888
|
if(args[0].br) {
|
5816
|
-
|
5817
|
-
VMI_push_entity(x, args);
|
5889
|
+
VMI_push_entity(x, {r: obj, a: attr});
|
5818
5890
|
return;
|
5819
5891
|
}
|
5820
5892
|
// Otherwise, if the entity is a dataset modifier, this must be an
|
@@ -5822,8 +5894,9 @@ function VMI_push_wildcard_entity(x, args) {
|
|
5822
5894
|
// push the result of this equation using the wildcard vector index
|
5823
5895
|
// of the expression that is being computed.
|
5824
5896
|
if(obj instanceof DatasetModifier) {
|
5825
|
-
|
5826
|
-
|
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]]);
|
5827
5900
|
return;
|
5828
5901
|
}
|
5829
5902
|
// Otherwise, it can be a vector type attribute or an expression.
|
@@ -5836,8 +5909,7 @@ function VMI_push_wildcard_entity(x, args) {
|
|
5836
5909
|
return;
|
5837
5910
|
}
|
5838
5911
|
// Otherwise, VMI_push_var can be called with `v` as first argument.
|
5839
|
-
args[
|
5840
|
-
VMI_push_var(x, args);
|
5912
|
+
VMI_push_var(x, [v, args[1], args[2], args[3], args[4]]);
|
5841
5913
|
}
|
5842
5914
|
|
5843
5915
|
function VMI_push_dataset_modifier(x, args) {
|
@@ -5849,7 +5921,7 @@ function VMI_push_dataset_modifier(x, args) {
|
|
5849
5921
|
// the running experiment UNLESS the field `ud` ("use data") is defined
|
5850
5922
|
// for the first argument, and evaluates as TRUE.
|
5851
5923
|
// NOTE: Ensure that number 0 is not interpreted as FALSE.
|
5852
|
-
let
|
5924
|
+
let wcnr = (args[0].s === undefined ? false : args[0].s);
|
5853
5925
|
const
|
5854
5926
|
ds = args[0].d,
|
5855
5927
|
ud = args[0].ud || false,
|
@@ -5863,6 +5935,7 @@ function VMI_push_dataset_modifier(x, args) {
|
|
5863
5935
|
if(!ds) console.log('ERROR: VMI_push_dataset_modifier without dataset',
|
5864
5936
|
x.variableName, x.code);
|
5865
5937
|
let t = tot[0],
|
5938
|
+
// By default, use the vector of the dataset to compute the value.
|
5866
5939
|
obj = ds.vector;
|
5867
5940
|
|
5868
5941
|
if(ds.array) {
|
@@ -5888,27 +5961,33 @@ function VMI_push_dataset_modifier(x, args) {
|
|
5888
5961
|
t = Math.max(0, Math.min(
|
5889
5962
|
MODEL.end_period - MODEL.start_period + MODEL.look_ahead + 1, t));
|
5890
5963
|
}
|
5891
|
-
if(
|
5892
|
-
// If a
|
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.
|
5893
5968
|
obj = mx;
|
5894
|
-
// If '?' is passed as
|
5895
|
-
// expression that is being computed.
|
5896
|
-
if(
|
5897
|
-
|
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 ) {
|
5898
5975
|
// In no selector and not "use data", check whether a running experiment
|
5899
5976
|
// defines the expression to use. If not, `obj` will be the dataset
|
5900
5977
|
// vector (so same as when "use data" is set).
|
5901
5978
|
obj = ds.activeModifierExpression;
|
5902
5979
|
}
|
5980
|
+
if(!obj) {
|
5981
|
+
console.log('ANOMALY: no object. obj, wcnr, args, x', obj, wcnr, args, x);
|
5982
|
+
}
|
5903
5983
|
// Now determine what value `v` should be pushed onto the expression stack.
|
5904
5984
|
// By default, use the dataset default value.
|
5905
5985
|
let v = ds.defaultValue,
|
5906
5986
|
// NOTE: `obstr` is used only when debugging, to log `obj` in human-
|
5907
5987
|
// readable format.
|
5908
|
-
obstr = (obj instanceof Expression ?
|
5909
|
-
obj.text : '[' + obj.toString() + ']');
|
5988
|
+
obstr = (obj instanceof Expression ? obj.text : `[${obj.toString()}]`);
|
5910
5989
|
if(Array.isArray(obj)) {
|
5911
|
-
//
|
5990
|
+
// `obj` is a vector.
|
5912
5991
|
if(t >= 0 && t < obj.length) {
|
5913
5992
|
v = obj[t];
|
5914
5993
|
} else if(ds.array && t >= obj.length) {
|
@@ -5921,22 +6000,23 @@ function VMI_push_dataset_modifier(x, args) {
|
|
5921
6000
|
}
|
5922
6001
|
// Fall through: no change to `v` => dataset default value is pushed.
|
5923
6002
|
} else {
|
5924
|
-
//
|
6003
|
+
// `obj` is an expression.
|
5925
6004
|
// NOTE: Readjust `t` when `obj` is an expression for an *array-type*
|
5926
6005
|
// dataset modifier.
|
5927
6006
|
if(obj.object instanceof Dataset && obj.object.array) t++;
|
5928
6007
|
// Pass modifier selector (if specified; may be FALSE) so that result
|
5929
|
-
// will be recomputed with this selector as context for
|
5930
|
-
v = obj.result(t,
|
6008
|
+
// will be recomputed with this selector as context for #.
|
6009
|
+
v = obj.result(t, wcnr);
|
5931
6010
|
}
|
5932
|
-
// Trace only now that time step t has been computed
|
6011
|
+
// Trace only now that time step t has been computed.
|
5933
6012
|
if(DEBUGGING) {
|
5934
6013
|
console.log('push dataset modifier:', obstr,
|
5935
|
-
tot[1] + (tot[2] ? ':' + tot[2] : ''), 'value =', VM.sig4Dig(v)
|
5936
|
-
|
6014
|
+
tot[1] + (tot[2] ? ':' + tot[2] : ''), 'value =', VM.sig4Dig(v),
|
6015
|
+
'\nExpression: ', x.text, '\nVariable:', x.variableName,
|
6016
|
+
'Context number:', wcnr);
|
5937
6017
|
}
|
5938
|
-
// NOTE:
|
5939
|
-
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;
|
5940
6020
|
// Finally, push the value onto the expression stack
|
5941
6021
|
x.push(v);
|
5942
6022
|
}
|