linny-r 2.1.4 → 2.1.6
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/static/index.html +33 -3
- package/static/linny-r.css +53 -5
- package/static/scripts/linny-r-ctrl.js +3 -0
- package/static/scripts/linny-r-gui-chart-manager.js +12 -9
- package/static/scripts/linny-r-gui-controller.js +32 -25
- package/static/scripts/linny-r-gui-dataset-manager.js +8 -1
- package/static/scripts/linny-r-gui-experiment-manager.js +28 -16
- package/static/scripts/linny-r-gui-finder.js +266 -24
- package/static/scripts/linny-r-gui-paper.js +52 -51
- package/static/scripts/linny-r-gui-power-grid-manager.js +6 -0
- package/static/scripts/linny-r-gui-sensitivity-analysis.js +5 -6
- package/static/scripts/linny-r-milp.js +10 -1
- package/static/scripts/linny-r-model.js +69 -44
- package/static/scripts/linny-r-utils.js +42 -30
- package/static/scripts/linny-r-vm.js +50 -15
@@ -542,7 +542,7 @@ class LinnyRModel {
|
|
542
542
|
e = this.objectByName(en);
|
543
543
|
if(!e) return `Unknown model entity "${en}"`;
|
544
544
|
const
|
545
|
-
ao = ea[1].split('@'),
|
545
|
+
ao = (ea.length > 1 ? ea[1].split('@') : ['']),
|
546
546
|
a = ao[0].trim();
|
547
547
|
// Valid if no attribute, as all entity types have a default attribute.
|
548
548
|
if(!a) return true;
|
@@ -1037,11 +1037,11 @@ class LinnyRModel {
|
|
1037
1037
|
// NOTE: A dimension is a list of one or more relevant selectors.
|
1038
1038
|
this.dimensions.length = 0;
|
1039
1039
|
// NOTE: Ignore the equations dataset.
|
1040
|
-
for(let
|
1041
|
-
this.datasets[
|
1040
|
+
for(let k in this.datasets) if(this.datasets.hasOwnProperty(k) &&
|
1041
|
+
this.datasets[k] !== this.equations_dataset) {
|
1042
1042
|
// Get the selector list for this dataset.
|
1043
1043
|
// NOTE: Ignore wildcard selectors!
|
1044
|
-
this.processSelectorList(this.datasets[
|
1044
|
+
this.processSelectorList(this.datasets[k].plainSelectors);
|
1045
1045
|
}
|
1046
1046
|
// Analyze constraint bound lines in the same way.
|
1047
1047
|
for(let k in this.constraints) if(this.constraints.hasOwnProperty(k)) {
|
@@ -1636,8 +1636,7 @@ class LinnyRModel {
|
|
1636
1636
|
const ci = this.indexOfChart(title);
|
1637
1637
|
if(ci >= 0) return this.charts[ci];
|
1638
1638
|
// Otherwise, add it. NOTE: Unlike datasets, charts are not "entities".
|
1639
|
-
let c = new Chart();
|
1640
|
-
c.title = title;
|
1639
|
+
let c = new Chart(title);
|
1641
1640
|
if(node) c.initFromXML(node);
|
1642
1641
|
this.charts.push(c);
|
1643
1642
|
// Sort the chart titles alphabetically...
|
@@ -3075,10 +3074,12 @@ class LinnyRModel {
|
|
3075
3074
|
if(n) {
|
3076
3075
|
// NOTE: Use a "dummy experiment object" as parent for SA runs.
|
3077
3076
|
const dummy = {title: SENSITIVITY_ANALYSIS.experiment_title};
|
3077
|
+
let r = 0;
|
3078
3078
|
for(const c of n.childNodes) if(c.nodeName === 'experiment-run') {
|
3079
|
-
const xr = new ExperimentRun(dummy,
|
3079
|
+
const xr = new ExperimentRun(dummy, r);
|
3080
3080
|
xr.initFromXML(c);
|
3081
3081
|
this.sensitivity_runs.push(xr);
|
3082
|
+
r++;
|
3082
3083
|
}
|
3083
3084
|
}
|
3084
3085
|
n = childNodeByTag(node, 'experiments');
|
@@ -3321,7 +3322,7 @@ class LinnyRModel {
|
|
3321
3322
|
vbls.sort((a, b) => UI.compareFullNames(a.displayName, b.displayName));
|
3322
3323
|
// Create a new chart as dummy, so without adding it to this model.
|
3323
3324
|
const
|
3324
|
-
c = new Chart(),
|
3325
|
+
c = new Chart('__d_u_m_m_y__c_h_a_r_t__'),
|
3325
3326
|
wcdm = [];
|
3326
3327
|
for(const v of vbls) {
|
3327
3328
|
// NOTE: Prevent adding wildcard dataset modifiers more than once.
|
@@ -3337,9 +3338,9 @@ class LinnyRModel {
|
|
3337
3338
|
return [c.dataAsString, c.statisticsAsString];
|
3338
3339
|
}
|
3339
3340
|
|
3340
|
-
get
|
3341
|
-
// Returns
|
3342
|
-
//
|
3341
|
+
get dictOfAllSelectors() {
|
3342
|
+
// Returns "dictionary" of all dataset modifier selectors like so:
|
3343
|
+
// {selector_1: [list of datasets], ...}
|
3343
3344
|
const ds_dict = {};
|
3344
3345
|
for(let k in this.datasets) if(this.datasets.hasOwnProperty(k)) {
|
3345
3346
|
const ds = this.datasets[k];
|
@@ -6240,19 +6241,19 @@ class Cluster extends NodeBox {
|
|
6240
6241
|
}
|
6241
6242
|
|
6242
6243
|
get nestingLevel() {
|
6243
|
-
// Return the "depth" of this cluster in the cluster hierarchy
|
6244
|
+
// Return the "depth" of this cluster in the cluster hierarchy.
|
6244
6245
|
if(this.cluster) return this.cluster.nestingLevel + 1; // recursion!
|
6245
6246
|
return 0;
|
6246
6247
|
}
|
6247
6248
|
|
6248
6249
|
get toBeIgnored() {
|
6249
|
-
// Return TRUE if this cluster or some parent cluster is set to be ignored
|
6250
|
+
// Return TRUE if this cluster or some parent cluster is set to be ignored.
|
6250
6251
|
return this.ignore || MODEL.ignoreClusterInThisRun(this) ||
|
6251
6252
|
(this.cluster && this.cluster.toBeIgnored); // recursion!
|
6252
6253
|
}
|
6253
6254
|
|
6254
6255
|
get blackBoxed() {
|
6255
|
-
// Return TRUE if this cluster or some parent cluster is marked as black box
|
6256
|
+
// Return TRUE if this cluster or some parent cluster is marked as black box.
|
6256
6257
|
return this.black_box ||
|
6257
6258
|
(this.cluster && this.cluster.blackBoxed); // recursion!
|
6258
6259
|
}
|
@@ -7928,6 +7929,7 @@ class Process extends Node {
|
|
7928
7929
|
const a = {name: this.displayName};
|
7929
7930
|
a.LB = this.lower_bound.asAttribute;
|
7930
7931
|
a.UB = (this.equal_bounds ? a.LB : this.upper_bound.asAttribute);
|
7932
|
+
if(this.grid) a.LB = -a.UB;
|
7931
7933
|
a.IL = this.initial_level.asAttribute;
|
7932
7934
|
a.LCF = this.pace_expression.asAttribute;
|
7933
7935
|
if(MODEL.solved) {
|
@@ -8861,13 +8863,13 @@ class Link {
|
|
8861
8863
|
}
|
8862
8864
|
|
8863
8865
|
get identifier() {
|
8864
|
-
// NOTE:
|
8865
|
-
// prevents problems when nodes are renamed
|
8866
|
+
// NOTE: Link IDs are based on the node codes rather than IDs, as this
|
8867
|
+
// prevents problems when nodes are renamed.
|
8866
8868
|
return this.from_node.code + '___' + this.to_node.code;
|
8867
8869
|
}
|
8868
8870
|
|
8869
8871
|
get attributes() {
|
8870
|
-
// NOTE:
|
8872
|
+
// NOTE: Link is named by its tab-separated node names.
|
8871
8873
|
const a = {name: this.from_node.displayName + '\t' + this.to_node.displayName};
|
8872
8874
|
a.R = this.relative_rate.asAttribute;
|
8873
8875
|
if(MODEL.infer_cost_prices) a.SOC = this.share_of_cost;
|
@@ -9220,7 +9222,7 @@ class Dataset {
|
|
9220
9222
|
}
|
9221
9223
|
|
9222
9224
|
get attributes() {
|
9223
|
-
// NOTE:
|
9225
|
+
// NOTE: Modifiers are appended as additional lines of text.
|
9224
9226
|
const a = {name: this.displayName};
|
9225
9227
|
a.D = '\t' + (this.vector ? this.vector[MODEL.t] : this.default_value);
|
9226
9228
|
for(let k in this.modifiers) if(this.modifiers.hasOwnProperty(k)) {
|
@@ -9755,13 +9757,14 @@ class ChartVariable {
|
|
9755
9757
|
this.wildcard_index = false;
|
9756
9758
|
}
|
9757
9759
|
|
9758
|
-
setProperties(obj, attr, stck, clr, sf=1, lw=1, vis=true, sort='not') {
|
9760
|
+
setProperties(obj, attr, stck, clr, sf=1, abs=false, lw=1, vis=true, sort='not') {
|
9759
9761
|
// Sets the defining properties for this chart variable.
|
9760
9762
|
this.object = obj;
|
9761
9763
|
this.attribute = attr;
|
9762
9764
|
this.stacked = stck;
|
9763
9765
|
this.color = clr;
|
9764
9766
|
this.scale_factor = sf;
|
9767
|
+
this.absolute = abs;
|
9765
9768
|
this.line_width = lw;
|
9766
9769
|
this.visible = vis;
|
9767
9770
|
this.sorted = sort;
|
@@ -9777,9 +9780,11 @@ class ChartVariable {
|
|
9777
9780
|
// Returns the display name for this variable. This is the name of
|
9778
9781
|
// the Linny-R entity and its attribute, followed by its scale factor
|
9779
9782
|
// unless it equals 1 (no scaling).
|
9780
|
-
const
|
9781
|
-
|
9782
|
-
|
9783
|
+
const
|
9784
|
+
bar = (this.absolute ? '\u2503' : ''),
|
9785
|
+
sf = (this.scale_factor === 1 ? '' :
|
9786
|
+
// NOTE: Pass tiny = TRUE to permit very small scaling factors.
|
9787
|
+
` (x${VM.sig4Dig(this.scale_factor, true)})`);
|
9783
9788
|
// Display name of equation is just the equations dataset selector.
|
9784
9789
|
if(this.object instanceof DatasetModifier) {
|
9785
9790
|
let eqn = this.object.selector;
|
@@ -9795,7 +9800,7 @@ class ChartVariable {
|
|
9795
9800
|
// method name (leading colon replaced by the prefixer ": ").
|
9796
9801
|
eqn = this.chart.prefix + UI.PREFIXER + eqn.substring(1);
|
9797
9802
|
}
|
9798
|
-
return eqn + sf;
|
9803
|
+
return bar + eqn + bar + sf;
|
9799
9804
|
}
|
9800
9805
|
// NOTE: Same holds for "dummy variables" added for wildcard
|
9801
9806
|
// dataset selectors.
|
@@ -9804,11 +9809,13 @@ class ChartVariable {
|
|
9804
9809
|
if(this.wildcard_index !== false) {
|
9805
9810
|
eqn = eqn.replace('??', this.wildcard_index);
|
9806
9811
|
}
|
9807
|
-
return eqn + sf;
|
9812
|
+
return bar + eqn + bar + sf;
|
9808
9813
|
}
|
9809
9814
|
// NOTE: Do not display the vertical bar if no attribute is specified.
|
9810
|
-
if(!this.attribute)
|
9811
|
-
|
9815
|
+
if(!this.attribute) {
|
9816
|
+
return bar + this.object.displayName + bar + sf;
|
9817
|
+
}
|
9818
|
+
return bar + this.object.displayName + '|' + this.attribute + bar + sf;
|
9812
9819
|
}
|
9813
9820
|
|
9814
9821
|
get asXML() {
|
@@ -9818,7 +9825,9 @@ class ChartVariable {
|
|
9818
9825
|
if(MODEL.black_box_entities.hasOwnProperty(id)) {
|
9819
9826
|
id = UI.nameToID(MODEL.black_box_entities[id]);
|
9820
9827
|
}
|
9821
|
-
const xml = ['<chart-variable',
|
9828
|
+
const xml = ['<chart-variable',
|
9829
|
+
(this.stacked ? ' stacked="1"' : ''),
|
9830
|
+
(this.absolute ? ' absolute="1"' : ''),
|
9822
9831
|
(this.visible ? ' visible="1"' : ''),
|
9823
9832
|
(this.wildcard_index !== false ?
|
9824
9833
|
` wildcard-index="${this.wildcard_index}"` : ''),
|
@@ -9876,6 +9885,7 @@ class ChartVariable {
|
|
9876
9885
|
nodeParameterValue(node, 'stacked') === '1',
|
9877
9886
|
nodeContentByTag(node, 'color'),
|
9878
9887
|
safeStrToFloat(nodeContentByTag(node, 'scale-factor')),
|
9888
|
+
nodeParameterValue(node, 'absolute') === '1',
|
9879
9889
|
safeStrToFloat(nodeContentByTag(node, 'line-width')),
|
9880
9890
|
nodeParameterValue(node, 'visible') === '1',
|
9881
9891
|
nodeParameterValue(node, 'sorted') || 'not');
|
@@ -9970,7 +9980,10 @@ class ChartVariable {
|
|
9970
9980
|
v = 0;
|
9971
9981
|
}
|
9972
9982
|
// Scale the value unless run result (these are already scaled!).
|
9973
|
-
if(!rr)
|
9983
|
+
if(!rr) {
|
9984
|
+
if(this.absolute) v = Math.abs(v);
|
9985
|
+
v *= this.scale_factor;
|
9986
|
+
}
|
9974
9987
|
this.vector.push(v);
|
9975
9988
|
// Do not include values for t = 0 in statistics.
|
9976
9989
|
if(t > 0) {
|
@@ -10184,7 +10197,7 @@ class Chart {
|
|
10184
10197
|
return '#c00000';
|
10185
10198
|
}
|
10186
10199
|
|
10187
|
-
addVariable(n, a) {
|
10200
|
+
addVariable(n, a='') {
|
10188
10201
|
// Add variable [entity name `n`|attribute `a`] to the chart unless
|
10189
10202
|
// it is already in the variable list.
|
10190
10203
|
let dn = n + UI.OA_SEPARATOR + a;
|
@@ -10220,7 +10233,7 @@ class Chart {
|
|
10220
10233
|
}
|
10221
10234
|
} else {
|
10222
10235
|
const v = new ChartVariable(this);
|
10223
|
-
v.setProperties(obj, a, false, this.nextAvailableDefaultColor
|
10236
|
+
v.setProperties(obj, a, false, this.nextAvailableDefaultColor);
|
10224
10237
|
this.variables.push(v);
|
10225
10238
|
}
|
10226
10239
|
return this.variables.length - 1;
|
@@ -11197,9 +11210,9 @@ class ExperimentRunResult {
|
|
11197
11210
|
this.x_variable = true;
|
11198
11211
|
this.object_id = v.object.identifier;
|
11199
11212
|
this.attribute = v.attribute;
|
11200
|
-
this.was_ignored = MODEL.ignored_entities[this.object_id];
|
11213
|
+
this.was_ignored = MODEL.ignored_entities[this.object_id] || false;
|
11201
11214
|
if(this.was_ignored) {
|
11202
|
-
// Chart variable entity was ignored => all results are undefined
|
11215
|
+
// Chart variable entity was ignored => all results are undefined.
|
11203
11216
|
this.vector = [];
|
11204
11217
|
this.N = VM.UNDEFINED;
|
11205
11218
|
this.sum = VM.UNDEFINED;
|
@@ -11236,6 +11249,11 @@ class ExperimentRunResult {
|
|
11236
11249
|
// statistic.
|
11237
11250
|
this.last = (this.vector.length > 0 ?
|
11238
11251
|
this.vector[this.vector.length - 1] : VM.UNDEFINED);
|
11252
|
+
// NOTE: For sensitivity analyses, the vector is NOT stored because
|
11253
|
+
// the SA reports only the descriptive statistics.
|
11254
|
+
if(this.run.experiment === SENSITIVITY_ANALYSIS.experiment) {
|
11255
|
+
this.vector.length = 0;
|
11256
|
+
}
|
11239
11257
|
}
|
11240
11258
|
} else if(v instanceof Dataset) {
|
11241
11259
|
// This dataset will be an "outcome" dataset => store statistics only
|
@@ -11329,12 +11347,12 @@ class ExperimentRunResult {
|
|
11329
11347
|
}
|
11330
11348
|
// The vector MAY need to be scaled to model time by different methods,
|
11331
11349
|
// but since this is likely to be rare, such scaling is performed
|
11332
|
-
// "lazily", so the method-specific vectors are initially
|
11350
|
+
// "lazily", so the method-specific vectors are initially empty.
|
11333
11351
|
this.resetScaledVectors();
|
11334
11352
|
}
|
11335
11353
|
|
11336
11354
|
resetScaledVectors() {
|
11337
|
-
//
|
11355
|
+
// Clear the special vectors, so they will be recalculated.
|
11338
11356
|
this.scaled_vectors = {'NEAREST': [], 'MEAN': [], 'SUM': [], 'MAX': []};
|
11339
11357
|
}
|
11340
11358
|
|
@@ -11612,8 +11630,12 @@ class ExperimentRun {
|
|
11612
11630
|
// NOTE: All equations are also considered to be outcomes EXCEPT
|
11613
11631
|
// methods (selectors starting with a colon).
|
11614
11632
|
this.eq_list = [];
|
11615
|
-
|
11616
|
-
|
11633
|
+
// NOTE: For sensitivity analyses, equations are NOT outcomes, as all
|
11634
|
+
// SA outcomes must be specified explicitly.
|
11635
|
+
if(this.experiment !== SENSITIVITY_ANALYSIS.experiment) {
|
11636
|
+
const eml = Object.keys(MODEL.equations_dataset.modifiers);
|
11637
|
+
for(const em of eml) if(!em.startsWith(':')) this.eq_list.push(em);
|
11638
|
+
}
|
11617
11639
|
const
|
11618
11640
|
cv = this.experiment.variables.length,
|
11619
11641
|
oc = this.oc_list.length,
|
@@ -11918,7 +11940,7 @@ class Experiment {
|
|
11918
11940
|
}
|
11919
11941
|
return index;
|
11920
11942
|
}
|
11921
|
-
|
11943
|
+
|
11922
11944
|
isDimensionSelector(s) {
|
11923
11945
|
// Return TRUE if `s` is a dimension selector in this experiment.
|
11924
11946
|
for(const dim of this.dimensions) if(dim.indexOf(s) >= 0) return true;
|
@@ -11928,6 +11950,15 @@ class Experiment {
|
|
11928
11950
|
return false;
|
11929
11951
|
}
|
11930
11952
|
|
11953
|
+
get allDimensionSelectors() {
|
11954
|
+
// Return list of all dimension selectors in this experiment.
|
11955
|
+
const
|
11956
|
+
dict = MODEL.dictOfAllSelectors,
|
11957
|
+
dims = [];
|
11958
|
+
for(let s in dict) if(this.isDimensionSelector(s)) dims.push(s);
|
11959
|
+
return dims;
|
11960
|
+
}
|
11961
|
+
|
11931
11962
|
get asXML() {
|
11932
11963
|
let d = '';
|
11933
11964
|
for(const dim of this.dimensions) {
|
@@ -12152,12 +12183,6 @@ class Experiment {
|
|
12152
12183
|
}
|
12153
12184
|
}
|
12154
12185
|
|
12155
|
-
get allDimensionSelectors() {
|
12156
|
-
const sl = Object.keys(MODEL.listOfAllSelectors);
|
12157
|
-
// Add selectors of actor, iterator and settings dimensions.
|
12158
|
-
return sl;
|
12159
|
-
}
|
12160
|
-
|
12161
12186
|
orthogonalSelectors(c) {
|
12162
12187
|
// Return TRUE iff the selectors in set `c` all are elements of
|
12163
12188
|
// different experiment dimensions.
|
@@ -13026,7 +13051,7 @@ class Constraint {
|
|
13026
13051
|
}
|
13027
13052
|
|
13028
13053
|
get typeLetter() {
|
13029
|
-
return '
|
13054
|
+
return 'B';
|
13030
13055
|
}
|
13031
13056
|
|
13032
13057
|
get identifier() {
|
@@ -189,11 +189,16 @@ function uniformDecimals(data) {
|
|
189
189
|
}
|
190
190
|
maxi = Math.max(maxi, ss[0].length);
|
191
191
|
}
|
192
|
-
// STEP 2: Convert the data to a uniform format
|
192
|
+
// STEP 2: Convert the data to a uniform format.
|
193
|
+
const special = ['\u221E', '-\u221E', '\u2047', '\u00A2'];
|
193
194
|
for(let i = 0; i < data.length; i++) {
|
194
|
-
const
|
195
|
+
const
|
196
|
+
v = data[i],
|
197
|
+
f = parseFloat(v);
|
195
198
|
if(isNaN(f)) {
|
196
|
-
|
199
|
+
// Keep special values such as infinity, and replace error values
|
200
|
+
// by Unicode warning sign.
|
201
|
+
if(special.indexOf(v) < 0) data[i] = '\u26A0';
|
197
202
|
} else if(maxe > 0) {
|
198
203
|
// Convert ALL numbers to exponential notation with two decimals (1.23e+7)
|
199
204
|
const v = f.toExponential(2);
|
@@ -405,51 +410,59 @@ function patternList(str) {
|
|
405
410
|
|
406
411
|
function patternMatch(str, patterns) {
|
407
412
|
// Returns TRUE when `str` matches the &|^-pattern.
|
408
|
-
// NOTE: If a pattern starts with
|
409
|
-
//
|
410
|
-
//
|
413
|
+
// NOTE: If a pattern starts with an opening bracket [ then `str` must
|
414
|
+
// start with the rest of the pattern to match. If it ends with a closing
|
415
|
+
// bracket ] then `str` must end with the first part of the pattern.
|
416
|
+
// In this way, [pattern] denotes that `str` should exactly match
|
411
417
|
for(let i = 0; i < patterns.length; i++) {
|
412
418
|
const p = patterns[i];
|
413
419
|
// NOTE: `p` is an OR sub-pattern that tests for a set of "plus"
|
414
420
|
// sub-sub-patterns (all of which should match) and a set of "min"
|
415
421
|
// sub-sub-patters (all should NOT match)
|
416
422
|
let pm,
|
423
|
+
swob,
|
424
|
+
ewcb,
|
417
425
|
re,
|
418
426
|
match = true;
|
419
427
|
for(let j = 0; match && j < p.plus.length; j++) {
|
420
428
|
pm = p.plus[j];
|
421
|
-
|
422
|
-
|
423
|
-
|
429
|
+
swob = pm.startsWith('[');
|
430
|
+
ewcb = pm.endsWith(']');
|
431
|
+
if(swob && ewcb) {
|
432
|
+
match = (str === pm.slice(1, -1));
|
433
|
+
} else if(swob) {
|
424
434
|
match = str.startsWith(pm.substring(1));
|
435
|
+
} else if(ewcb) {
|
436
|
+
match = str.endsWith(pm.slice(0, -1));
|
425
437
|
} else {
|
426
438
|
match = (str.indexOf(pm) >= 0);
|
427
439
|
}
|
428
|
-
// If no match, check whether pattern contains wildcards
|
440
|
+
// If no match, check whether pattern contains wildcards.
|
429
441
|
if(!match && pm.indexOf('#') >= 0) {
|
430
442
|
// If so, rematch using regular expression that tests for a
|
431
|
-
// number or a ?? wildcard
|
443
|
+
// number or a ?? wildcard.
|
432
444
|
let res = pm.split('#');
|
433
445
|
for(let i = 0; i < res.length; i++) {
|
434
446
|
res[i] = escapeRegex(res[i]);
|
435
447
|
}
|
436
448
|
res = res.join('(\\d+|\\?\\?)');
|
437
|
-
if(
|
438
|
-
|
439
|
-
} else if(pm.startsWith('~')) {
|
440
|
-
res = '^' + res;
|
441
|
-
}
|
449
|
+
if(swob) res = '^' + res;
|
450
|
+
if(ewcb) res += '$';
|
442
451
|
re = new RegExp(res, 'g');
|
443
452
|
match = re.test(str);
|
444
453
|
}
|
445
454
|
}
|
446
|
-
// Any "min" match indicates NO match for this sub-pattern
|
455
|
+
// Any "min" match indicates NO match for this sub-pattern.
|
447
456
|
for(let j = 0; match && j < p.min.length; j++) {
|
448
457
|
pm = p.min[j];
|
449
|
-
|
450
|
-
|
451
|
-
|
458
|
+
swob = pm.startsWith('[');
|
459
|
+
ewcb = pm.endsWith(']');
|
460
|
+
if(swob && ewcb) {
|
461
|
+
match = (str !== pm.slice(1, -1));
|
462
|
+
} else if(swob) {
|
452
463
|
match = !str.startsWith(pm.substring(1));
|
464
|
+
} else if(ewcb) {
|
465
|
+
match = !str.endsWith(pm.slice(0, -1));
|
453
466
|
} else {
|
454
467
|
match = (str.indexOf(pm) < 0);
|
455
468
|
}
|
@@ -461,11 +474,8 @@ function patternMatch(str, patterns) {
|
|
461
474
|
res[i] = escapeRegex(res[i]);
|
462
475
|
}
|
463
476
|
res = res.join('(\\d+|\\?\\?)');
|
464
|
-
if(
|
465
|
-
|
466
|
-
} else if(pm.startsWith('~')) {
|
467
|
-
res = '^' + res;
|
468
|
-
}
|
477
|
+
if(swob) res = '^' + res;
|
478
|
+
if(ewcb) res += '$';
|
469
479
|
re = new RegExp(res, 'g');
|
470
480
|
match = !re.test(str);
|
471
481
|
}
|
@@ -973,18 +983,20 @@ function nameToLines(name, actor_name = '') {
|
|
973
983
|
// the node box.
|
974
984
|
let m = actor_name.length;
|
975
985
|
const
|
976
|
-
d = Math.floor(Math.sqrt(0.
|
986
|
+
d = Math.floor(Math.sqrt(0.25 * name.length)),
|
977
987
|
// Do not wrap strings shorter than 13 characters (about 50 pixels).
|
978
988
|
limit = Math.max(Math.ceil(name.length / d), m, 13),
|
979
|
-
|
980
|
-
|
989
|
+
// NOTE: Do not split on spaces followed by a number or a single
|
990
|
+
// capital letter.
|
991
|
+
a = name.split(/\s(?!\d+:|\d+$|[A-Z]\W)/);
|
992
|
+
// Split words at '-' when wider than limit.
|
981
993
|
for(let j = 0; j < a.length; j++) {
|
982
994
|
if(a[j].length > limit) {
|
983
995
|
const sw = a[j].split('-');
|
984
996
|
if(sw.length > 1) {
|
985
|
-
// Replace j-th word by last fragment of split string
|
997
|
+
// Replace j-th word by last fragment of split string.
|
986
998
|
a[j] = sw.pop();
|
987
|
-
// Insert remaining fragments before
|
999
|
+
// Insert remaining fragments before.
|
988
1000
|
while(sw.length > 0) a.splice(j, 0, sw.pop() + '-');
|
989
1001
|
}
|
990
1002
|
}
|
@@ -885,7 +885,7 @@ class ExpressionParser {
|
|
885
885
|
// of commas, semicolons and spaces.
|
886
886
|
x.r = run_spec.split(/[\,\;\/\s]+/g);
|
887
887
|
// NOTE: The VMI instruction accepts `x.r` to be a list of selectors
|
888
|
-
// or an integer number.
|
888
|
+
// or an integer number.
|
889
889
|
} else {
|
890
890
|
// If the specifier does start with a #, trim it...
|
891
891
|
run_spec = run_spec.substring(1);
|
@@ -933,6 +933,20 @@ class ExpressionParser {
|
|
933
933
|
x.x = MODEL.experiments[n];
|
934
934
|
}
|
935
935
|
}
|
936
|
+
// If run specifier `x.r` is a list, check whether all elements in the
|
937
|
+
// list are selectors in a dimension of experiment `x.x` (if specified).
|
938
|
+
// If experiment is unknown, check against the list of all selectors
|
939
|
+
// defined in the model.
|
940
|
+
if(Array.isArray(x.r)) {
|
941
|
+
const
|
942
|
+
sl = (x.x instanceof Experiment ? x.x.allDimensionSelectors :
|
943
|
+
Object.keys(MODEL.dictOfAllSelectors)),
|
944
|
+
unknown = complement(x.r, sl);
|
945
|
+
if(unknown.length) {
|
946
|
+
msg = pluralS(unknown.length, 'unknown selector') + ': <tt>' +
|
947
|
+
unknown.join(' ') + '</tt>';
|
948
|
+
}
|
949
|
+
}
|
936
950
|
// END of code for parsing an experiment result specifier.
|
937
951
|
// Now proceed with parsing the variable name.
|
938
952
|
|
@@ -1350,7 +1364,10 @@ class ExpressionParser {
|
|
1350
1364
|
const
|
1351
1365
|
parts = name.split(UI.PREFIXER),
|
1352
1366
|
tail = parts.pop();
|
1353
|
-
if(parts.length
|
1367
|
+
if(!tail && parts.length) {
|
1368
|
+
// Prefix without its trailing colon+space could identify an entity.
|
1369
|
+
obj = MODEL.objectByID(UI.nameToID(parts.join(UI.PREFIXER)));
|
1370
|
+
} else if(parts.length > 0) {
|
1354
1371
|
// Name contains at least one prefix => last part *could* be a
|
1355
1372
|
// method name, so look it up after adding a leading colon.
|
1356
1373
|
const method = MODEL.equationByID(UI.nameToID(':' + tail));
|
@@ -3377,13 +3394,19 @@ class VirtualMachine {
|
|
3377
3394
|
// Infer cycle basis for combined power grids for which Kirchhoff's
|
3378
3395
|
// voltage law must be enforced.
|
3379
3396
|
if(MODEL.with_power_flow) {
|
3380
|
-
|
3397
|
+
this.logMessage(1, 'POWER FLOW: ' +
|
3381
3398
|
pluralS(Object.keys(MODEL.power_grids).length, 'grid'));
|
3399
|
+
if(MODEL.ignore_grid_capacity) this.logMessage(1,
|
3400
|
+
'NOTE: Assuming infinite grid line cacity');
|
3401
|
+
if(MODEL.ignore_KVL) this.logMessage(1,
|
3402
|
+
'NOTE: Disregarding Kirchhoff\'s Voltage Law');
|
3403
|
+
if(MODEL.ignore_power_losses) this.logMessage(1,
|
3404
|
+
'NOTE: Disregarding transmission losses');
|
3382
3405
|
POWER_GRID_MANAGER.inferCycleBasis();
|
3383
3406
|
if(POWER_GRID_MANAGER.messages.length > 1) {
|
3384
3407
|
UI.warn('Check monitor for power grid warnings');
|
3385
3408
|
}
|
3386
|
-
|
3409
|
+
this.logMessage(1, POWER_GRID_MANAGER.messages.join('\n'));
|
3387
3410
|
if(POWER_GRID_MANAGER.cycle_basis.length) this.logMessage(1,
|
3388
3411
|
'Enforcing Kirchhoff\'s voltage law for ' +
|
3389
3412
|
POWER_GRID_MANAGER.cycleBasisAsString);
|
@@ -6190,7 +6213,7 @@ Solver status = ${json.status}`);
|
|
6190
6213
|
} catch(err) {
|
6191
6214
|
const msg = `ERROR while processing solver data for block ${bnr}: ${err}`;
|
6192
6215
|
console.log(msg);
|
6193
|
-
|
6216
|
+
this.logMessage(bnr, msg);
|
6194
6217
|
UI.alert(msg);
|
6195
6218
|
this.stopSolving();
|
6196
6219
|
this.halted = true;
|
@@ -7269,12 +7292,12 @@ function VMI_push_run_result(x, args) {
|
|
7269
7292
|
rr = r.results[rri],
|
7270
7293
|
tsd = r.time_step_duration,
|
7271
7294
|
// Get the delta-t multiplier: divide model time step duration
|
7272
|
-
// by time step duration of the experiment run if they differ
|
7295
|
+
// by time step duration of the experiment run if they differ.
|
7273
7296
|
dtm = (Math.abs(tsd - model_dt) < VM.NEAR_ZERO ? 1 : model_dt / tsd);
|
7274
7297
|
let stat = rrspec.s;
|
7275
|
-
// For outcome datasets without specific statistic, default to LAST
|
7298
|
+
// For outcome datasets without specific statistic, default to LAST.
|
7276
7299
|
if(!(stat || rr.x_variable)) stat = 'LAST';
|
7277
|
-
// For a valid experiment variable, the default value is 0
|
7300
|
+
// For a valid experiment variable, the default value is 0.
|
7278
7301
|
v = 0;
|
7279
7302
|
if(stat) {
|
7280
7303
|
if(stat === 'LAST') {
|
@@ -7302,14 +7325,14 @@ function VMI_push_run_result(x, args) {
|
|
7302
7325
|
console.log(trc.join(''));
|
7303
7326
|
}
|
7304
7327
|
} else {
|
7305
|
-
// No statistic => return the vector for local time step
|
7328
|
+
// No statistic => return the vector for local time step,
|
7306
7329
|
// using here, too, the delta-time-modifier to adjust the offsets
|
7307
7330
|
// for different time steps per experiment.
|
7308
7331
|
const tot = twoOffsetTimeStep(x.step[x.step.length - 1],
|
7309
7332
|
args[1], args[2], args[3], args[4], dtm, x);
|
7310
7333
|
// Scale the (midpoint) time step (at current model run time scale)
|
7311
7334
|
// to the experiment run time scale and get the run result value.
|
7312
|
-
// NOTE:
|
7335
|
+
// NOTE: The .m property specifies the time scaling method, and
|
7313
7336
|
// the .p property whether the run result vector should be used as
|
7314
7337
|
// a periodic time series.
|
7315
7338
|
v = rr.valueAtModelTime(tot[0], model_dt, rrspec.m, rrspec.p);
|
@@ -7325,6 +7348,11 @@ function VMI_push_run_result(x, args) {
|
|
7325
7348
|
}
|
7326
7349
|
}
|
7327
7350
|
}
|
7351
|
+
// Truncate near-zero values.
|
7352
|
+
if(v && Math.abs(v) < VM.SIG_DIF_FROM_ZERO) {
|
7353
|
+
console.log('NOTE: Truncated experiment run result', v, 'to zero');
|
7354
|
+
v = 0;
|
7355
|
+
}
|
7328
7356
|
x.push(v);
|
7329
7357
|
}
|
7330
7358
|
|
@@ -8413,11 +8441,18 @@ function VMI_set_bounds(args) {
|
|
8413
8441
|
VM.variables[vi - 1][0],'] t = ', VM.t, ' LB = ', VM.sig4Dig(l),
|
8414
8442
|
', UB = ', VM.sig4Dig(u), fixed].join(''), l, u, inf_val);
|
8415
8443
|
} else if(u < l) {
|
8416
|
-
//
|
8417
|
-
|
8418
|
-
|
8419
|
-
|
8420
|
-
|
8444
|
+
// Check the difference, as this may be negligible.
|
8445
|
+
if(u - l < VM.SIG_DIF_FROM_ZERO) {
|
8446
|
+
u = Math.round(u * 1e5) / 1e5;
|
8447
|
+
// NOTE: This may result in -0 (minus zero) => then set to 0.
|
8448
|
+
if(u < 0 && u > -VM.NEAR_ZERO) u = 0;
|
8449
|
+
} else {
|
8450
|
+
// If substantial, warn that "impossible" bounds would have been set.
|
8451
|
+
const vk = vbl.displayName;
|
8452
|
+
if(!VM.bound_issues[vk]) VM.bound_issues[vk] = [];
|
8453
|
+
VM.bound_issues[vk].push(VM.t);
|
8454
|
+
}
|
8455
|
+
// Set LB to UB, so that lowest value is bounding.
|
8421
8456
|
l = u;
|
8422
8457
|
}
|
8423
8458
|
// NOTE: Since the VM vectors for lower bounds and upper bounds are
|