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
@@ -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.
|
@@ -173,6 +173,12 @@ class PowerGridManager {
|
|
173
173
|
MODEL.ignore_KVL = UI.boxChecked('power-grids-KVL');
|
174
174
|
MODEL.ignore_power_losses = UI.boxChecked('power-grids-losses');
|
175
175
|
this.dialog.hide();
|
176
|
+
const pg_btn = document.getElementById('settings-power-btn');
|
177
|
+
if(MODEL.ignore_grid_capacity || MODEL.ignore_KVL || MODEL.ignore_power_losses) {
|
178
|
+
pg_btn.classList.add('ignore');
|
179
|
+
} else {
|
180
|
+
pg_btn.classList.remove('ignore');
|
181
|
+
}
|
176
182
|
}
|
177
183
|
|
178
184
|
selectPowerGrid(event, id, focus) {
|
@@ -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,14 @@ module.exports = class MILPSolver {
|
|
578
580
|
x_values.push(0);
|
579
581
|
col++;
|
580
582
|
}
|
581
|
-
|
583
|
+
// Return near-zero values as 0.
|
584
|
+
let xv = x_dict[v];
|
585
|
+
const xfv = parseFloat(xv);
|
586
|
+
if(xfv && Math.abs(xfv) < this.near_zero) {
|
587
|
+
console.log('NOTE: Truncated ', xfv, ' to zero for variable', v);
|
588
|
+
xv = '0';
|
589
|
+
}
|
590
|
+
x_values.push(xv);
|
582
591
|
col++;
|
583
592
|
}
|
584
593
|
// Add zeros to vector for remaining columns.
|