linny-r 2.1.3 → 2.1.5
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 +15 -1
- package/static/linny-r.css +17 -9
- package/static/scripts/linny-r-ctrl.js +3 -0
- package/static/scripts/linny-r-gui-chart-manager.js +10 -8
- package/static/scripts/linny-r-gui-finder.js +6 -2
- package/static/scripts/linny-r-gui-paper.js +52 -51
- package/static/scripts/linny-r-gui-sensitivity-analysis.js +5 -6
- package/static/scripts/linny-r-milp.js +4 -1
- package/static/scripts/linny-r-model.js +57 -32
- package/static/scripts/linny-r-vm.js +33 -12
package/package.json
CHANGED
package/static/index.html
CHANGED
@@ -2381,6 +2381,14 @@ NOTE: * and ? will be interpreted as wildcards"
|
|
2381
2381
|
<div id="variable-color" title="Click to copy, Shift-click to paste"></div>
|
2382
2382
|
<div id="variable-paste-color"></div>
|
2383
2383
|
</div>
|
2384
|
+
<div id="variable-absolute-div"
|
2385
|
+
title="Plot the absolute value of this variable"
|
2386
|
+
style="margin: 2px -2px">
|
2387
|
+
<div id="variable-absolute" class="box clear"></div>
|
2388
|
+
<div style="display:inline-block; vertical-align:top; margin-top: 3px">
|
2389
|
+
Absolute
|
2390
|
+
</div>
|
2391
|
+
</div>
|
2384
2392
|
<div id="variable-scale-div" style="margin-top: 3px">
|
2385
2393
|
<div style="display:inline-block; vertical-align:top; margin-top: 2px">
|
2386
2394
|
Scale by:
|
@@ -3201,10 +3209,16 @@ where X can be one or several of these letters: ABCDELPQ">
|
|
3201
3209
|
<select id="confirm-add-chart-variables-attribute"></select>
|
3202
3210
|
of <span id="confirm-add-chart-variables-count"></span>
|
3203
3211
|
</div>
|
3212
|
+
<div>
|
3213
|
+
<div id="confirm-add-chart-variables-absolute" class="box clear"></div>
|
3214
|
+
<div style="display:inline-block; vertical-align:top; margin-top: 3px">
|
3215
|
+
Plot absolute values
|
3216
|
+
</div>
|
3217
|
+
</div>
|
3204
3218
|
<div>
|
3205
3219
|
<div id="confirm-add-chart-variables-stacked" class="box clear"></div>
|
3206
3220
|
<div style="display:inline-block; vertical-align:top; margin-top: 3px">
|
3207
|
-
|
3221
|
+
Plot as stacked areas
|
3208
3222
|
</div>
|
3209
3223
|
</div>
|
3210
3224
|
</div>
|
package/static/linny-r.css
CHANGED
@@ -1397,18 +1397,22 @@ td.a-weight {
|
|
1397
1397
|
}
|
1398
1398
|
|
1399
1399
|
td.import,
|
1400
|
-
td.export
|
1400
|
+
td.export,
|
1401
|
+
div.import,
|
1402
|
+
div.export {
|
1401
1403
|
font-weight: 600;
|
1402
1404
|
text-decoration-line: underline;
|
1403
1405
|
text-decoration-style: dotted;
|
1404
1406
|
}
|
1405
1407
|
|
1406
|
-
td.import
|
1408
|
+
td.import,
|
1409
|
+
div.import {
|
1407
1410
|
color: #b00000;
|
1408
1411
|
}
|
1409
1412
|
|
1410
|
-
td.export
|
1411
|
-
|
1413
|
+
td.export,
|
1414
|
+
div.export {
|
1415
|
+
color: #0000b0;
|
1412
1416
|
}
|
1413
1417
|
|
1414
1418
|
/* styles for the SERVER dialog */
|
@@ -2914,8 +2918,8 @@ td.dataset-selector {
|
|
2914
2918
|
}
|
2915
2919
|
|
2916
2920
|
td.equation-selector {
|
2917
|
-
|
2918
|
-
max-width:
|
2921
|
+
padding-right: 6px;
|
2922
|
+
max-width: 200px;
|
2919
2923
|
white-space: nowrap;
|
2920
2924
|
overflow: hidden;
|
2921
2925
|
text-overflow: ellipsis;
|
@@ -2960,11 +2964,12 @@ td.equation-expression {
|
|
2960
2964
|
}
|
2961
2965
|
|
2962
2966
|
td.equation-expression {
|
2963
|
-
|
2967
|
+
width: 85%;
|
2964
2968
|
}
|
2965
2969
|
|
2966
2970
|
td.equation-expression-multi {
|
2967
2971
|
border-left: solid 1px Silver;
|
2972
|
+
width: 85%;
|
2968
2973
|
white-space: normal;
|
2969
2974
|
overflow-x: hidden;
|
2970
2975
|
overflow-y: auto;
|
@@ -3424,6 +3429,9 @@ td.vbl-desc-lead::after {
|
|
3424
3429
|
color: #b00080;
|
3425
3430
|
}
|
3426
3431
|
|
3432
|
+
td.vbl-abs {
|
3433
|
+
color: #0050d0 !important;
|
3434
|
+
}
|
3427
3435
|
|
3428
3436
|
#chart-variables {
|
3429
3437
|
position: absolute;
|
@@ -3600,8 +3608,8 @@ img.v-disab {
|
|
3600
3608
|
}
|
3601
3609
|
|
3602
3610
|
#variable-dlg {
|
3603
|
-
width:
|
3604
|
-
height:
|
3611
|
+
width: 245px;
|
3612
|
+
height: 170px;
|
3605
3613
|
}
|
3606
3614
|
|
3607
3615
|
#variable-dlg-name {
|
@@ -986,11 +986,14 @@ class SensitivityAnalysis {
|
|
986
986
|
UI.alert(`Parameter ${p} is not a dataset or expression`);
|
987
987
|
}
|
988
988
|
}
|
989
|
+
// Create the SA chart having a variable for each SA outcome.
|
989
990
|
this.chart = new Chart(this.chart_title);
|
990
991
|
for(const o of MODEL.sensitivity_outcomes) {
|
991
992
|
this.chart.addVariable(...o.split(UI.OA_SEPARATOR));
|
992
993
|
}
|
994
|
+
// Create the SA experiment.
|
993
995
|
this.experiment = new Experiment(this.experiment_title);
|
996
|
+
// Add the SA chart so the outcomes become result variables.
|
994
997
|
this.experiment.charts = [this.chart];
|
995
998
|
this.experiment.inferVariables();
|
996
999
|
// This experiment always uses the same combination: the base selectors.
|
@@ -125,7 +125,7 @@ class GUIChartManager extends ChartManager {
|
|
125
125
|
document.getElementById('chart-copy-data-btn').addEventListener(
|
126
126
|
'click', () => CHART_MANAGER.copyData());
|
127
127
|
document.getElementById('chart-copy-table-btn').addEventListener(
|
128
|
-
'click', () => CHART_MANAGER.copyTable());
|
128
|
+
'click', (event) => CHART_MANAGER.copyTable(event.shiftKey));
|
129
129
|
document.getElementById('chart-save-btn').addEventListener(
|
130
130
|
'click', () => CHART_MANAGER.downloadChart(event.shiftKey));
|
131
131
|
document.getElementById('chart-widen-btn').addEventListener(
|
@@ -164,8 +164,8 @@ class GUIChartManager extends ChartManager {
|
|
164
164
|
'click', (event) => CHART_MANAGER.copyPasteColor(event));
|
165
165
|
// NOTE: Uses the color picker developed by James Daniel.
|
166
166
|
this.color_picker = new iro.ColorPicker("#color-picker", {
|
167
|
-
width:
|
168
|
-
height:
|
167
|
+
width: 115,
|
168
|
+
height: 115,
|
169
169
|
color: '#a00',
|
170
170
|
markerRadius: 10,
|
171
171
|
padding: 1,
|
@@ -391,7 +391,7 @@ class GUIChartManager extends ChartManager {
|
|
391
391
|
(cv.visible ? ' checked' : ' clear'),
|
392
392
|
'" onclick="CHART_MANAGER.toggleVariable(', i,
|
393
393
|
');"></div></td><td class="v-name vbl-', cv.sorted,
|
394
|
-
'">', cv.displayName,
|
394
|
+
(cv.absolute ? ' vbl-abs' : ''), '">', cv.displayName,
|
395
395
|
'</td></tr>'].join(''));
|
396
396
|
}
|
397
397
|
this.variables_table.innerHTML = ol.join('');
|
@@ -641,7 +641,7 @@ class GUIChartManager extends ChartManager {
|
|
641
641
|
for(const cv of c.variables) {
|
642
642
|
const nv = new ChartVariable(nc);
|
643
643
|
nv.setProperties(cv.object, cv.attribute, cv.stacked,
|
644
|
-
cv.color, cv.scale_factor, cv.line_width, cv.sorted);
|
644
|
+
cv.color, cv.scale_factor, cv.absolute, cv.line_width, cv.sorted);
|
645
645
|
nc.variables.push(nv);
|
646
646
|
}
|
647
647
|
this.chart_index = MODEL.indexOfChart(nc.title);
|
@@ -844,6 +844,7 @@ class GUIChartManager extends ChartManager {
|
|
844
844
|
const cv = MODEL.charts[this.chart_index].variables[this.variable_index];
|
845
845
|
document.getElementById('variable-dlg-name').innerHTML = cv.displayName;
|
846
846
|
UI.setBox('variable-stacked', cv.stacked);
|
847
|
+
UI.setBox('variable-absolute', cv.absolute);
|
847
848
|
// Pass TRUE tiny flag to permit very small scaling factors.
|
848
849
|
this.variable_modal.element('scale').value = VM.sig4Dig(cv.scale_factor, true);
|
849
850
|
this.variable_modal.element('width').value = VM.sig4Dig(cv.line_width);
|
@@ -928,6 +929,7 @@ class GUIChartManager extends ChartManager {
|
|
928
929
|
c = MODEL.charts[this.chart_index],
|
929
930
|
cv = c.variables[this.variable_index];
|
930
931
|
cv.stacked = UI.boxChecked('variable-stacked');
|
932
|
+
cv.absolute = UI.boxChecked('variable-absolute');
|
931
933
|
cv.scale_factor = s;
|
932
934
|
// Prevent negative or near-zero line width.
|
933
935
|
cv.line_width = Math.max(0.001, w);
|
@@ -1092,9 +1094,9 @@ class GUIChartManager extends ChartManager {
|
|
1092
1094
|
}
|
1093
1095
|
}
|
1094
1096
|
|
1095
|
-
copyTable() {
|
1096
|
-
UI.copyHtmlToClipboard(this.table_panel.innerHTML);
|
1097
|
-
UI.notify('Table copied to clipboard (as HTML)');
|
1097
|
+
copyTable(plain) {
|
1098
|
+
UI.copyHtmlToClipboard(this.table_panel.innerHTML, plain);
|
1099
|
+
UI.notify('Table copied to clipboard (as ', (plain ? 'text' : 'HTML'), ')');
|
1098
1100
|
}
|
1099
1101
|
|
1100
1102
|
copyStatistics() {
|
@@ -424,13 +424,17 @@ class Finder {
|
|
424
424
|
const
|
425
425
|
c = MODEL.charts[ci],
|
426
426
|
a = md.element('attribute').value,
|
427
|
-
|
427
|
+
abs = UI.boxChecked('confirm-add-chart-variables-absolute'),
|
428
|
+
stack = UI.boxChecked('confirm-add-chart-variables-stacked'),
|
428
429
|
enl = [];
|
429
430
|
for(const e of this.entities) enl.push(e.name);
|
430
431
|
enl.sort((a, b) => UI.compareFullNames(a, b, true));
|
431
432
|
for(const en of enl) {
|
432
433
|
const vi = c.addVariable(en, a);
|
433
|
-
if(vi !== null)
|
434
|
+
if(vi !== null) {
|
435
|
+
c.variables[vi].absolute = abs;
|
436
|
+
c.variables[vi].stacked = stack;
|
437
|
+
}
|
434
438
|
}
|
435
439
|
CHART_MANAGER.updateDialog();
|
436
440
|
md.hide();
|
@@ -395,6 +395,9 @@ class Paper {
|
|
395
395
|
id = 'i_n_a_c_t_i_v_e__t_r_i_a_n_g_l_e__t_i_p__ID';
|
396
396
|
this.inactive_triangle = `url(#${id})`;
|
397
397
|
this.addMarker(defs, id, tri, 8, 'silver');
|
398
|
+
id = 'i_g_n_o_r_e__t_r_i_a_n_g_l_e__t_i_p__ID';
|
399
|
+
this.ignore_triangle = `url(#${id})`;
|
400
|
+
this.addMarker(defs, id, tri, 8, this.palette.ignore);
|
398
401
|
id = 'o_p_e_n__t_r_i_a_n_g_l_e__t_i_p__ID*';
|
399
402
|
this.open_triangle = `url(#${id})`;
|
400
403
|
this.addMarker(defs, id, tri, 7.5, 'white');
|
@@ -1034,9 +1037,9 @@ class Paper {
|
|
1034
1037
|
|
1035
1038
|
// Arrows having both "from" and "to" are displayed as "real" arrows
|
1036
1039
|
// The hidden nodes list must contain the nodes that have no position
|
1037
|
-
// in the cluster being drawn
|
1038
|
-
// NOTE:
|
1039
|
-
// links, but also if it is a single link from a cluster to a process
|
1040
|
+
// in the cluster being drawn.
|
1041
|
+
// NOTE: Products are "hidden" typically when this arrow represents multiple
|
1042
|
+
// links, but also if it is a single link from a cluster to a process.
|
1040
1043
|
const
|
1041
1044
|
from_c = from_nb instanceof Cluster,
|
1042
1045
|
to_c = to_nb instanceof Cluster,
|
@@ -1049,12 +1052,12 @@ class Paper {
|
|
1049
1052
|
fn = lnk.from_node,
|
1050
1053
|
tn = lnk.to_node;
|
1051
1054
|
if(fn instanceof Product && fn != from_nb && fn != to_nb) {
|
1052
|
-
// Add node only if they not already shown at EITHER end of the arrow
|
1055
|
+
// Add node only if they not already shown at EITHER end of the arrow.
|
1053
1056
|
addDistinct(fn, arrw.hidden_nodes);
|
1054
|
-
// Count number of data flows represented by arrow
|
1057
|
+
// Count number of data flows represented by arrow.
|
1055
1058
|
if(tn.is_data) data_flows++;
|
1056
1059
|
}
|
1057
|
-
// NOTE:
|
1060
|
+
// NOTE: No ELSE IF, because BOTH link nodes can be products.
|
1058
1061
|
if(tn instanceof Product && tn != from_nb && tn != to_nb) {
|
1059
1062
|
addDistinct(tn, arrw.hidden_nodes);
|
1060
1063
|
// Count number of data flows represented by arrow
|
@@ -1063,7 +1066,7 @@ class Paper {
|
|
1063
1066
|
}
|
1064
1067
|
}
|
1065
1068
|
|
1066
|
-
// NEXT:
|
1069
|
+
// NEXT: Some more local variables.
|
1067
1070
|
fnx = from_nb.x + dx;
|
1068
1071
|
fny = from_nb.y + dy;
|
1069
1072
|
fnw = from_nb.width;
|
@@ -1088,19 +1091,19 @@ class Paper {
|
|
1088
1091
|
tnh = 24;
|
1089
1092
|
}
|
1090
1093
|
|
1091
|
-
// Do not draw arrow if so short that it is hidden by its FROM and TO nodes
|
1094
|
+
// Do not draw arrow if so short that it is hidden by its FROM and TO nodes.
|
1092
1095
|
if((Math.abs(fnx - tnx) < (fnw + tnw)/2) &&
|
1093
1096
|
(Math.abs(fny - tny) <= (fnh + tnh)/2)) {
|
1094
1097
|
return false;
|
1095
1098
|
}
|
1096
1099
|
|
1097
|
-
// Adjust node heights if nodes are thick-rimmed
|
1100
|
+
// Adjust node heights if nodes are thick-rimmed.
|
1098
1101
|
if((from_nb instanceof Product) && from_nb.is_buffer) fnh += 2;
|
1099
1102
|
if((to_nb instanceof Product) && to_nb.is_buffer) tnh += 2;
|
1100
|
-
// Get horizontal distance dx and vertical distance dy of the node centers
|
1103
|
+
// Get horizontal distance dx and vertical distance dy of the node centers.
|
1101
1104
|
dx = tnx - fnx;
|
1102
1105
|
dy = tny - fny;
|
1103
|
-
// If dx is less than half a pixel, draw a vertical line
|
1106
|
+
// If dx is less than half a pixel, draw a vertical line.
|
1104
1107
|
if(Math.abs(dx) < 0.5) {
|
1105
1108
|
arrw.from_x = fnx;
|
1106
1109
|
arrw.to_x = fnx;
|
@@ -1112,11 +1115,11 @@ class Paper {
|
|
1112
1115
|
arrw.to_y = tny + tnh/2;
|
1113
1116
|
}
|
1114
1117
|
} else {
|
1115
|
-
// Now dx > 0, so no division by zero can occur when calculating dy/dx
|
1116
|
-
// First compute X and Y of tail (FROM node)
|
1118
|
+
// Now dx > 0, so no division by zero can occur when calculating dy/dx.
|
1119
|
+
// First compute X and Y of tail (FROM node).
|
1117
1120
|
w = (from_nb instanceof Product ? from_nb.frame_width : fnw);
|
1118
1121
|
if(Math.abs(dy / dx) >= Math.abs(fnh / w)) {
|
1119
|
-
// Arrow connects to horizontal edge
|
1122
|
+
// Arrow connects to horizontal edge.
|
1120
1123
|
arrw.from_y = (dy > 0 ? fny + fnh/2 : fny - fnh/2);
|
1121
1124
|
arrw.from_x = fnx + fnh/2 * dx / Math.abs(dy);
|
1122
1125
|
} else if(from_nb instanceof Product) {
|
@@ -1127,7 +1130,7 @@ class Paper {
|
|
1127
1130
|
dd = fnw/2;
|
1128
1131
|
nn = (-dd - Math.sqrt(rr - aa * dd * dd + aa * rr)) / (1 + aa);
|
1129
1132
|
if(dx > 0) {
|
1130
|
-
// link points towards the right
|
1133
|
+
// link points towards the right.
|
1131
1134
|
arrw.from_x = fnx - nn;
|
1132
1135
|
arrw.from_y = fny - nn * dy / dx;
|
1133
1136
|
} else {
|
@@ -1135,28 +1138,28 @@ class Paper {
|
|
1135
1138
|
arrw.from_y = fny + nn * dy / dx;
|
1136
1139
|
}
|
1137
1140
|
} else {
|
1138
|
-
// Rectangular box
|
1141
|
+
// Rectangular box.
|
1139
1142
|
arrw.from_x = (dx > 0 ? fnx + w/2 : fnx - w/2);
|
1140
1143
|
arrw.from_y = fny + w/2 * dy / Math.abs(dx);
|
1141
1144
|
}
|
1142
|
-
// Then compute X and Y of head (TO node)
|
1145
|
+
// Then compute X and Y of head (TO node).
|
1143
1146
|
w = (to_nb instanceof Product ? to_nb.frame_width : tnw);
|
1144
1147
|
dx = arrw.from_x - tnx;
|
1145
1148
|
dy = arrw.from_y - tny;
|
1146
1149
|
if(Math.abs(dx) > 0) {
|
1147
1150
|
if(Math.abs(dy / dx) >= Math.abs(tnh / w)) {
|
1148
|
-
// Connects to horizontal edge
|
1151
|
+
// Connects to horizontal edge.
|
1149
1152
|
arrw.to_y = (dy > 0 ? tny + tnh/2 : tny - tnh/2);
|
1150
1153
|
arrw.to_x = tnx + tnh/2 * dx / Math.abs(dy);
|
1151
1154
|
} else if(to_nb instanceof Product) {
|
1152
|
-
// Node with semicircular sides
|
1155
|
+
// Node with semicircular sides.
|
1153
1156
|
tnw = to_nb.frame_width;
|
1154
1157
|
rr = (tnh/2) * (tnh/2); // R square
|
1155
1158
|
aa = (dy / dx) * (dy / dx); // A square
|
1156
1159
|
dd = tnw/2;
|
1157
1160
|
nn = (-dd - Math.sqrt(rr - aa*(dd*dd - rr))) / (1 + aa);
|
1158
1161
|
if(dx > 0) {
|
1159
|
-
// Link points towards the right
|
1162
|
+
// Link points towards the right.
|
1160
1163
|
arrw.to_x = tnx - nn;
|
1161
1164
|
arrw.to_y = tny - nn * dy / dx;
|
1162
1165
|
} else {
|
@@ -1164,30 +1167,30 @@ class Paper {
|
|
1164
1167
|
arrw.to_y = tny + nn * dy / dx;
|
1165
1168
|
}
|
1166
1169
|
} else {
|
1167
|
-
// Rectangular node
|
1170
|
+
// Rectangular node.
|
1168
1171
|
arrw.to_x = (dx > 0 ? tnx + w/2 : tnx - w/2);
|
1169
1172
|
arrw.to_y = tny + w/2 * dy / Math.abs(dx);
|
1170
1173
|
}
|
1171
1174
|
}
|
1172
1175
|
}
|
1173
1176
|
|
1174
|
-
// Assume default arrow properties
|
1177
|
+
// Assume default arrow properties.
|
1175
1178
|
sda = 'none';
|
1176
1179
|
stroke_color = (ignored ? this.palette.ignore : this.palette.node_rim);
|
1177
1180
|
stroke_width = 1.5;
|
1178
1181
|
arrow_start = 'none';
|
1179
|
-
arrow_end = this.triangle;
|
1180
|
-
// Default multi-flow values are: NO multiflow, NOT congested or reversed
|
1182
|
+
arrow_end = (ignored ? this.ignore_triangle : this.triangle);
|
1183
|
+
// Default multi-flow values are: NO multiflow, NOT congested or reversed.
|
1181
1184
|
let mf = [0, 0, 0, false, false],
|
1182
1185
|
reversed = false;
|
1183
1186
|
// These may need to be modified due to actual flow, etc.
|
1184
1187
|
if(arrw.links.length === 1) {
|
1185
|
-
// Display link properties of a specific link if arrow is plain
|
1188
|
+
// Display link properties of a specific link if arrow is plain.
|
1186
1189
|
luc = arrw.links[0];
|
1187
|
-
ignored = MODEL.ignored_entities[luc.identifier];
|
1190
|
+
ignored = ignored || MODEL.ignored_entities[luc.identifier];
|
1188
1191
|
if(MODEL.solved && !ignored) {
|
1189
1192
|
// Draw arrow in dark blue if a flow occurs, or in a lighter gray
|
1190
|
-
// if NO flow occurs
|
1193
|
+
// if NO flow occurs.
|
1191
1194
|
af = luc.actualFlow(MODEL.t);
|
1192
1195
|
if(Math.abs(af) > VM.SIG_DIF_FROM_ZERO) {
|
1193
1196
|
// NOTE: negative flow should affect arrow heads only when link has
|
@@ -1203,12 +1206,13 @@ class Paper {
|
|
1203
1206
|
arrow_end = this.active_triangle;
|
1204
1207
|
}
|
1205
1208
|
} else {
|
1206
|
-
stroke_color =
|
1207
|
-
this.palette.ignore : 'silver');
|
1209
|
+
stroke_color = 'silver';
|
1208
1210
|
arrow_end = this.inactive_triangle;
|
1209
1211
|
}
|
1210
|
-
} else {
|
1212
|
+
} else if(ignored) {
|
1211
1213
|
af = VM.UNDEFINED;
|
1214
|
+
stroke_color = this.palette.ignore;
|
1215
|
+
arrow_end = this.ignore_triangle;
|
1212
1216
|
}
|
1213
1217
|
if(luc.from_node instanceof Process) {
|
1214
1218
|
proc = luc.from_node;
|
@@ -1225,12 +1229,12 @@ class Paper {
|
|
1225
1229
|
arrow_end = this.feedback_triangle;
|
1226
1230
|
}
|
1227
1231
|
}
|
1228
|
-
// Data link => dotted line
|
1232
|
+
// Data link => dotted line.
|
1229
1233
|
if(luc.dataOnly) {
|
1230
1234
|
sda = UI.sda.dot;
|
1231
1235
|
}
|
1232
1236
|
if(luc.selected) {
|
1233
|
-
// Draw arrow line thick and in red
|
1237
|
+
// Draw arrow line thick and in red.
|
1234
1238
|
stroke_color = this.palette.select;
|
1235
1239
|
stroke_width = 2;
|
1236
1240
|
if(arrow_end == this.open_wedge) {
|
@@ -1239,7 +1243,6 @@ class Paper {
|
|
1239
1243
|
arrow_end = this.selected_triangle;
|
1240
1244
|
}
|
1241
1245
|
}
|
1242
|
-
if(ignored) stroke_color = this.palette.ignore;
|
1243
1246
|
} else {
|
1244
1247
|
// A composite arrow is visualized differently, depending on the number
|
1245
1248
|
// of related products and the direction of the underlying links:
|
@@ -1264,23 +1267,23 @@ class Paper {
|
|
1264
1267
|
if(arrw.bidirectional) arrow_start = arrow_end;
|
1265
1268
|
}
|
1266
1269
|
// Correct the start and end points of the shaft for the stroke width
|
1267
|
-
// and size and number of the arrow heads
|
1268
|
-
// NOTE:
|
1270
|
+
// and size and number of the arrow heads.
|
1271
|
+
// NOTE: Re-use of dx and dy for different purpose!
|
1269
1272
|
dx = arrw.to_x - arrw.from_x;
|
1270
1273
|
dy = arrw.to_y - arrw.from_y;
|
1271
1274
|
l = Math.sqrt(dx * dx + dy * dy);
|
1272
1275
|
let cdx = 0, cdy = 0;
|
1273
1276
|
if(l > 0) {
|
1274
|
-
// Amount to shorten the line to accommodate arrow head
|
1275
|
-
// NOTE:
|
1277
|
+
// Amount to shorten the line to accommodate arrow head.
|
1278
|
+
// NOTE: For thicker arrows, subtract a bit more.
|
1276
1279
|
cdx = (4 + 1.7 * (stroke_width - 1.5)) * dx / l;
|
1277
1280
|
cdy = (4 + 1.7 * (stroke_width - 1.5)) * dy / l;
|
1278
1281
|
}
|
1279
1282
|
if(reversed) {
|
1280
|
-
// Adjust end points by 1/2 px for rounded stroke end
|
1283
|
+
// Adjust end points by 1/2 px for rounded stroke end.
|
1281
1284
|
bpx = arrw.to_x - 0.5*dx / l;
|
1282
1285
|
bpy = arrw.to_y - 0.5*dy / l;
|
1283
|
-
// Adjust start points for arrow head(s)
|
1286
|
+
// Adjust start points for arrow head(s).
|
1284
1287
|
epx = arrw.from_x + cdx;
|
1285
1288
|
epy = arrw.from_y + cdy;
|
1286
1289
|
if(arrw.bidirectional) {
|
@@ -1288,10 +1291,10 @@ class Paper {
|
|
1288
1291
|
bpy -= cdy;
|
1289
1292
|
}
|
1290
1293
|
} else {
|
1291
|
-
// Adjust start points by 1/2 px for rounded stroke end
|
1294
|
+
// Adjust start points by 1/2 px for rounded stroke end.
|
1292
1295
|
bpx = arrw.from_x + 0.5*dx / l;
|
1293
1296
|
bpy = arrw.from_y + 0.5*dy / l;
|
1294
|
-
// Adjust end points for arrow head(s)
|
1297
|
+
// Adjust end points for arrow head(s).
|
1295
1298
|
epx = arrw.to_x - cdx;
|
1296
1299
|
epy = arrw.to_y - cdy;
|
1297
1300
|
if(arrw.bidirectional) {
|
@@ -1299,8 +1302,8 @@ class Paper {
|
|
1299
1302
|
bpy += cdy;
|
1300
1303
|
}
|
1301
1304
|
}
|
1302
|
-
// Calculate actual (multi)flow, as this co-determines the color of the arrow
|
1303
|
-
if(MODEL.solved) {
|
1305
|
+
// Calculate actual (multi)flow, as this co-determines the color of the arrow.
|
1306
|
+
if(MODEL.solved && !ignored) {
|
1304
1307
|
if(!luc) {
|
1305
1308
|
mf = arrw.multiFlows;
|
1306
1309
|
af = mf[1] + mf[2];
|
@@ -1315,22 +1318,20 @@ class Paper {
|
|
1315
1318
|
} else {
|
1316
1319
|
arrow_end = this.active_triangle;
|
1317
1320
|
}
|
1318
|
-
if(arrw.bidirectional) {
|
1319
|
-
arrow_start = arrow_end;
|
1320
|
-
}
|
1321
1321
|
} else {
|
1322
1322
|
if(stroke_color != this.palette.select) stroke_color = 'silver';
|
1323
1323
|
if(arrow_end === this.double_triangle) {
|
1324
1324
|
arrow_end = this.inactive_double_triangle;
|
1325
|
-
if(arrw.bidirectional) {
|
1326
|
-
arrow_start = this.inactive_double_triangle;
|
1327
|
-
}
|
1328
1325
|
}
|
1329
1326
|
}
|
1330
1327
|
} else {
|
1331
1328
|
af = VM.UNDEFINED;
|
1329
|
+
if(ignored && stroke_color != this.palette.select) {
|
1330
|
+
stroke_color = this.palette.ignore;
|
1331
|
+
arrow_end = this.ignore_triangle;
|
1332
|
+
}
|
1332
1333
|
}
|
1333
|
-
|
1334
|
+
if(arrw.bidirectional) arrow_start = arrow_end;
|
1334
1335
|
// Draw arrow shaft
|
1335
1336
|
if(stroke_width === 3 && data_flows) {
|
1336
1337
|
// Hollow shaft arrow: dotted when *all* represented links are
|
@@ -1509,7 +1510,7 @@ class Paper {
|
|
1509
1510
|
|
1510
1511
|
// Draw the actual flow
|
1511
1512
|
const absf = Math.abs(af);
|
1512
|
-
if(l > 0 && af < VM.UNDEFINED && absf > VM.SIG_DIF_FROM_ZERO) {
|
1513
|
+
if(!ignored && l > 0 && af < VM.UNDEFINED && absf > VM.SIG_DIF_FROM_ZERO) {
|
1513
1514
|
const ffill = {fill:'white', opacity:0.8};
|
1514
1515
|
if(luc || mf[0] == 1) {
|
1515
1516
|
// Draw flow data halfway the arrow only if calculated and non-zero.
|
@@ -124,7 +124,7 @@ class GUISensitivityAnalysis extends SensitivityAnalysis {
|
|
124
124
|
this.color_scales.rb.addEventListener('click', csf);
|
125
125
|
this.color_scales.no.addEventListener('click', csf);
|
126
126
|
document.getElementById('sa-copy-btn').addEventListener(
|
127
|
-
'click', () => SENSITIVITY_ANALYSIS.copyTableToClipboard());
|
127
|
+
'click', (event) => SENSITIVITY_ANALYSIS.copyTableToClipboard(event.shiftKey));
|
128
128
|
document.getElementById('sa-copy-data-btn').addEventListener(
|
129
129
|
'click', () => SENSITIVITY_ANALYSIS.copyDataToClipboard());
|
130
130
|
this.outcome_name = document.getElementById('sa-outcome-name');
|
@@ -255,7 +255,7 @@ class GUISensitivityAnalysis extends SensitivityAnalysis {
|
|
255
255
|
// Otherwise, display list of all dataset selectors in docu-viewer.
|
256
256
|
if(DOCUMENTATION_MANAGER.visible) {
|
257
257
|
const
|
258
|
-
ds_dict = MODEL.
|
258
|
+
ds_dict = MODEL.dictOfAllSelectors,
|
259
259
|
html = [],
|
260
260
|
sl = Object.keys(ds_dict).sort((a, b) => UI.compareFullNames(a, b, true));
|
261
261
|
for(const s of sl) {
|
@@ -346,7 +346,7 @@ class GUISensitivityAnalysis extends SensitivityAnalysis {
|
|
346
346
|
sl = this.base_selectors.value.replace(/[\;\,]/g, ' ').trim().replace(
|
347
347
|
/[^a-zA-Z0-9\+\-\%\_\s]/g, '').split(/\s+/),
|
348
348
|
bs = sl.join(' '),
|
349
|
-
sd = MODEL.
|
349
|
+
sd = MODEL.dictOfAllSelectors,
|
350
350
|
us = [];
|
351
351
|
for(const s of sl) if(s.length > 0 && !(s in sd)) us.push(s);
|
352
352
|
if(us.length > 0) {
|
@@ -760,9 +760,8 @@ class GUISensitivityAnalysis extends SensitivityAnalysis {
|
|
760
760
|
this.updateData();
|
761
761
|
}
|
762
762
|
|
763
|
-
copyTableToClipboard() {
|
764
|
-
UI.copyHtmlToClipboard(this.scroll_area.innerHTML);
|
765
|
-
UI.notify('Table copied to clipboard (as HTML)');
|
763
|
+
copyTableToClipboard(plain) {
|
764
|
+
UI.copyHtmlToClipboard(this.scroll_area.innerHTML, plain);
|
766
765
|
}
|
767
766
|
|
768
767
|
copyDataToClipboard() {
|
@@ -488,6 +488,8 @@ module.exports = class MILPSolver {
|
|
488
488
|
} else {
|
489
489
|
inttol = Math.max(1e-9, Math.min(0.1, inttol));
|
490
490
|
}
|
491
|
+
// Use integer tolerance setting as "near zero" threshold.
|
492
|
+
this.near_zero = inttol;
|
491
493
|
// Default relative MIP gap is 1e-4.
|
492
494
|
if(isNaN(mipgap)) {
|
493
495
|
mipgap = 1e-4;
|
@@ -578,7 +580,8 @@ module.exports = class MILPSolver {
|
|
578
580
|
x_values.push(0);
|
579
581
|
col++;
|
580
582
|
}
|
581
|
-
|
583
|
+
// Return near-zero values as 0.
|
584
|
+
x_values.push(Math.abs(x_dict[v]) < this.near_zero ? 0 : x_dict[v]);
|
582
585
|
col++;
|
583
586
|
}
|
584
587
|
// Add zeros to vector for remaining columns.
|
@@ -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)) {
|
@@ -3075,10 +3075,12 @@ class LinnyRModel {
|
|
3075
3075
|
if(n) {
|
3076
3076
|
// NOTE: Use a "dummy experiment object" as parent for SA runs.
|
3077
3077
|
const dummy = {title: SENSITIVITY_ANALYSIS.experiment_title};
|
3078
|
+
let r = 0;
|
3078
3079
|
for(const c of n.childNodes) if(c.nodeName === 'experiment-run') {
|
3079
|
-
const xr = new ExperimentRun(dummy,
|
3080
|
+
const xr = new ExperimentRun(dummy, r);
|
3080
3081
|
xr.initFromXML(c);
|
3081
3082
|
this.sensitivity_runs.push(xr);
|
3083
|
+
r++;
|
3082
3084
|
}
|
3083
3085
|
}
|
3084
3086
|
n = childNodeByTag(node, 'experiments');
|
@@ -3337,9 +3339,9 @@ class LinnyRModel {
|
|
3337
3339
|
return [c.dataAsString, c.statisticsAsString];
|
3338
3340
|
}
|
3339
3341
|
|
3340
|
-
get
|
3341
|
-
// Returns
|
3342
|
-
//
|
3342
|
+
get dictOfAllSelectors() {
|
3343
|
+
// Returns "dictionary" of all dataset modifier selectors like so:
|
3344
|
+
// {selector_1: [list of datasets], ...}
|
3343
3345
|
const ds_dict = {};
|
3344
3346
|
for(let k in this.datasets) if(this.datasets.hasOwnProperty(k)) {
|
3345
3347
|
const ds = this.datasets[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
|
+
v *= this.scale_factor;
|
9985
|
+
if(this.absolute) v = Math.abs(v);
|
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.
|
@@ -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
|
|
@@ -7269,12 +7283,12 @@ function VMI_push_run_result(x, args) {
|
|
7269
7283
|
rr = r.results[rri],
|
7270
7284
|
tsd = r.time_step_duration,
|
7271
7285
|
// Get the delta-t multiplier: divide model time step duration
|
7272
|
-
// by time step duration of the experiment run if they differ
|
7286
|
+
// by time step duration of the experiment run if they differ.
|
7273
7287
|
dtm = (Math.abs(tsd - model_dt) < VM.NEAR_ZERO ? 1 : model_dt / tsd);
|
7274
7288
|
let stat = rrspec.s;
|
7275
|
-
// For outcome datasets without specific statistic, default to LAST
|
7289
|
+
// For outcome datasets without specific statistic, default to LAST.
|
7276
7290
|
if(!(stat || rr.x_variable)) stat = 'LAST';
|
7277
|
-
// For a valid experiment variable, the default value is 0
|
7291
|
+
// For a valid experiment variable, the default value is 0.
|
7278
7292
|
v = 0;
|
7279
7293
|
if(stat) {
|
7280
7294
|
if(stat === 'LAST') {
|
@@ -7302,14 +7316,14 @@ function VMI_push_run_result(x, args) {
|
|
7302
7316
|
console.log(trc.join(''));
|
7303
7317
|
}
|
7304
7318
|
} else {
|
7305
|
-
// No statistic => return the vector for local time step
|
7319
|
+
// No statistic => return the vector for local time step,
|
7306
7320
|
// using here, too, the delta-time-modifier to adjust the offsets
|
7307
7321
|
// for different time steps per experiment.
|
7308
7322
|
const tot = twoOffsetTimeStep(x.step[x.step.length - 1],
|
7309
7323
|
args[1], args[2], args[3], args[4], dtm, x);
|
7310
7324
|
// Scale the (midpoint) time step (at current model run time scale)
|
7311
7325
|
// to the experiment run time scale and get the run result value.
|
7312
|
-
// NOTE:
|
7326
|
+
// NOTE: The .m property specifies the time scaling method, and
|
7313
7327
|
// the .p property whether the run result vector should be used as
|
7314
7328
|
// a periodic time series.
|
7315
7329
|
v = rr.valueAtModelTime(tot[0], model_dt, rrspec.m, rrspec.p);
|
@@ -7325,6 +7339,8 @@ function VMI_push_run_result(x, args) {
|
|
7325
7339
|
}
|
7326
7340
|
}
|
7327
7341
|
}
|
7342
|
+
// Truncate near-zero values.
|
7343
|
+
if(Math.abs(v) < VM.SIG_DIF_FROM_ZERO) v = 0;
|
7328
7344
|
x.push(v);
|
7329
7345
|
}
|
7330
7346
|
|
@@ -7706,7 +7722,7 @@ function VMI_sub(x) {
|
|
7706
7722
|
} else if(d[0] === VM.MINUS_INFINITY || d[1] === VM.PLUS_INFINITY) {
|
7707
7723
|
x.retop(VM.MINUS_INFINITY);
|
7708
7724
|
} else {
|
7709
|
-
x.retop(d[0]
|
7725
|
+
x.retop(d[0] - d[1]);
|
7710
7726
|
}
|
7711
7727
|
}
|
7712
7728
|
}
|
@@ -8413,11 +8429,16 @@ function VMI_set_bounds(args) {
|
|
8413
8429
|
VM.variables[vi - 1][0],'] t = ', VM.t, ' LB = ', VM.sig4Dig(l),
|
8414
8430
|
', UB = ', VM.sig4Dig(u), fixed].join(''), l, u, inf_val);
|
8415
8431
|
} else if(u < l) {
|
8416
|
-
//
|
8417
|
-
|
8418
|
-
|
8419
|
-
|
8420
|
-
|
8432
|
+
// Check the difference, as this may be negligible.
|
8433
|
+
if(u - l < VM.SIG_DIF_FROM_ZERO) {
|
8434
|
+
u = Math.round(u * 1e5) / 1e5;
|
8435
|
+
} else {
|
8436
|
+
// If substantial, warn that "impossible" bounds would have been set.
|
8437
|
+
const vk = vbl.displayName;
|
8438
|
+
if(!VM.bound_issues[vk]) VM.bound_issues[vk] = [];
|
8439
|
+
VM.bound_issues[vk].push(VM.t);
|
8440
|
+
}
|
8441
|
+
// Set LB to UB, so that lowest value is bounding.
|
8421
8442
|
l = u;
|
8422
8443
|
}
|
8423
8444
|
// NOTE: Since the VM vectors for lower bounds and upper bounds are
|