linny-r 2.0.8 → 2.0.9
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/README.md +3 -40
- package/package.json +1 -1
- package/server.js +19 -157
- package/static/index.html +58 -20
- package/static/linny-r.css +20 -16
- package/static/scripts/iro.min.js +7 -7
- package/static/scripts/linny-r-ctrl.js +50 -72
- package/static/scripts/linny-r-gui-actor-manager.js +23 -33
- package/static/scripts/linny-r-gui-chart-manager.js +43 -41
- package/static/scripts/linny-r-gui-constraint-editor.js +6 -10
- package/static/scripts/linny-r-gui-controller.js +254 -230
- package/static/scripts/linny-r-gui-dataset-manager.js +17 -36
- package/static/scripts/linny-r-gui-documentation-manager.js +11 -17
- package/static/scripts/linny-r-gui-equation-manager.js +22 -22
- package/static/scripts/linny-r-gui-experiment-manager.js +102 -129
- package/static/scripts/linny-r-gui-file-manager.js +42 -48
- package/static/scripts/linny-r-gui-finder.js +105 -51
- package/static/scripts/linny-r-gui-model-autosaver.js +2 -4
- package/static/scripts/linny-r-gui-monitor.js +35 -41
- package/static/scripts/linny-r-gui-paper.js +42 -70
- package/static/scripts/linny-r-gui-power-grid-manager.js +31 -34
- package/static/scripts/linny-r-gui-receiver.js +1 -2
- package/static/scripts/linny-r-gui-repository-browser.js +44 -46
- package/static/scripts/linny-r-gui-scale-unit-manager.js +32 -32
- package/static/scripts/linny-r-gui-sensitivity-analysis.js +61 -67
- package/static/scripts/linny-r-gui-undo-redo.js +94 -95
- package/static/scripts/linny-r-milp.js +20 -24
- package/static/scripts/linny-r-model.js +1832 -2248
- package/static/scripts/linny-r-utils.js +27 -27
- package/static/scripts/linny-r-vm.js +801 -905
- package/static/show-png.html +0 -113
@@ -128,7 +128,7 @@ class Shape {
|
|
128
128
|
el.setAttribute('x', x);
|
129
129
|
el.setAttribute('y', cy);
|
130
130
|
UI.paper.addSVGAttributes(el, attrs);
|
131
|
-
for(
|
131
|
+
for(const l of lines) {
|
132
132
|
const ts = UI.paper.newSVGElement('tspan');
|
133
133
|
ts.setAttribute('x', x);
|
134
134
|
ts.setAttribute('dy', fh);
|
@@ -136,7 +136,7 @@ class Shape {
|
|
136
136
|
// to normal spaces, or they will be rendered as ' ' and this
|
137
137
|
// will cause the SVG to break when it is inserted as picture into
|
138
138
|
// an MS Word document.
|
139
|
-
ts.textContent =
|
139
|
+
ts.textContent = l.replaceAll('\u00A0', ' ');
|
140
140
|
el.appendChild(ts);
|
141
141
|
}
|
142
142
|
this.element.appendChild(el);
|
@@ -614,42 +614,42 @@ class Paper {
|
|
614
614
|
fw = fh / 2;
|
615
615
|
let w = 0, m = 0;
|
616
616
|
// Approximate the width of the Unicode characters representing
|
617
|
-
// special values
|
617
|
+
// special values.
|
618
618
|
if(ns === '\u2047') {
|
619
619
|
w = 8; // undefined (??)
|
620
620
|
} else if(ns === '\u25A6' || ns === '\u2BBF' || ns === '\u26A0') {
|
621
621
|
w = 6; // computing, not computed, warning sign
|
622
622
|
} else {
|
623
623
|
// Assume that number has been rendered with fixed spacing
|
624
|
-
// (cf. addNumber method of class Shape)
|
624
|
+
// (cf. addNumber method of class Shape).
|
625
625
|
w = ns.length * fw;
|
626
|
-
// Decimal point and minus sign are narrower
|
626
|
+
// Decimal point and minus sign are narrower.
|
627
627
|
if(ns.indexOf('.') >= 0) w -= 0.6 * fw;
|
628
628
|
if(ns.startsWith('-')) w -= 0.55 * fw;
|
629
|
-
// Add approximate extra length for =, % and special Unicode characters
|
629
|
+
// Add approximate extra length for =, % and special Unicode characters.
|
630
630
|
if(ns.indexOf('=') >= 0) {
|
631
631
|
w += 0.2 * fw;
|
632
632
|
} else {
|
633
|
-
// LE, GE, undefined (??), or INF are a bit wider
|
633
|
+
// LE, GE, undefined (??), or INF are a bit wider.
|
634
634
|
m = ns.match(/%|\u2264|\u2265|\u2047|\u221E/g);
|
635
635
|
if(m) {
|
636
636
|
w += m.length * 0.25 * fw;
|
637
637
|
}
|
638
|
-
// Ellipsis (may occur between process bounds) is much wider
|
638
|
+
// Ellipsis (may occur between process bounds) is much wider.
|
639
639
|
m = ns.match(/\u2026/g);
|
640
640
|
if(m) w += m.length * 0.6 * fw;
|
641
641
|
}
|
642
642
|
}
|
643
|
-
//
|
643
|
+
// Adjust for font weight.
|
644
644
|
return {width: w * this.weight_factors[Math.round(fweight / 100)],
|
645
645
|
height: fh};
|
646
646
|
}
|
647
647
|
|
648
648
|
textSize(string, fsize=8, fweight=400) {
|
649
|
-
//
|
650
|
-
// NOTE:
|
651
|
-
// for text size computation
|
652
|
-
// NOTE:
|
649
|
+
// Return the boundingbox {width: ..., height: ...} of a string (in pixels).
|
650
|
+
// NOTE: Uses the invisible SVG element that is defined specifically
|
651
|
+
// for text size computation.
|
652
|
+
// NOTE: Text size calculation tends to slightly underestimate the
|
653
653
|
// length of the string as it is actually rendered, as font sizes
|
654
654
|
// appear to be rounded to the nearest available size.
|
655
655
|
const el = this.getSizingElement();
|
@@ -659,12 +659,11 @@ class Paper {
|
|
659
659
|
el.style.fontFamily = this.font_name;
|
660
660
|
let w = 0,
|
661
661
|
h = 0;
|
662
|
-
// Consider the separate lines of the string
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
el.textContent = lines[i];
|
662
|
+
// Consider the separate lines of the string.
|
663
|
+
// NOTE: Add '' to force conversion to string in case `string` is a number.
|
664
|
+
const lines = ('' + string).split('\n');
|
665
|
+
for(const l of lines) {
|
666
|
+
el.textContent = l;
|
668
667
|
const bb = el.getBBox();
|
669
668
|
w = Math.max(w, bb.width);
|
670
669
|
h += bb.height;
|
@@ -673,7 +672,7 @@ class Paper {
|
|
673
672
|
}
|
674
673
|
|
675
674
|
removeInvisibleSVG() {
|
676
|
-
//
|
675
|
+
// Remove SVG elements used by the user interface (not part of the model).
|
677
676
|
let el = document.getElementById(this.size_box);
|
678
677
|
if(el) this.svg.removeChild(el);
|
679
678
|
el = document.getElementById(this.drag_line);
|
@@ -921,35 +920,19 @@ class Paper {
|
|
921
920
|
// NOTE: Product positions must be updated before links are drawn, so
|
922
921
|
// that links arrows will be drawn over their shapes.
|
923
922
|
fc.positionProducts();
|
924
|
-
for(
|
925
|
-
|
926
|
-
}
|
927
|
-
for(let i = 0; i < fc.sub_clusters.length; i++) {
|
928
|
-
fc.sub_clusters[i].clearHiddenIO();
|
929
|
-
}
|
923
|
+
for(const p of fc.processes) p.clearHiddenIO();
|
924
|
+
for(const c of fc.sub_clusters) c.clearHiddenIO();
|
930
925
|
// NOTE: Also ensure that notes will update their fields.
|
931
926
|
fc.resetNoteFields();
|
932
927
|
// Draw link arrows and constraints first, as all other entities are
|
933
928
|
// slightly transparent so they cannot completely hide these lines.
|
934
|
-
for(
|
935
|
-
|
936
|
-
|
937
|
-
for(
|
938
|
-
|
939
|
-
}
|
940
|
-
for(let i = 0; i < fc.processes.length; i++) {
|
941
|
-
this.drawProcess(fc.processes[i]);
|
942
|
-
}
|
943
|
-
for(let i = 0; i < fc.product_positions.length; i++) {
|
944
|
-
this.drawProduct(fc.product_positions[i].product);
|
945
|
-
}
|
946
|
-
for(let i = 0; i < fc.sub_clusters.length; i++) {
|
947
|
-
this.drawCluster(fc.sub_clusters[i]);
|
948
|
-
}
|
929
|
+
for(const a of fc.arrows) this.drawArrow(a);
|
930
|
+
for(const c of fc.related_constraints) this.drawConstraint(c);
|
931
|
+
for(const p of fc.processes) this.drawProcess(p);
|
932
|
+
for(const pp of fc.product_positions) this.drawProduct(pp.product);
|
933
|
+
for(const c of fc.sub_clusters) this.drawCluster(c);
|
949
934
|
// Draw notes last, as they are semi-transparent (and can be quite small).
|
950
|
-
for(
|
951
|
-
this.drawNote(fc.notes[i]);
|
952
|
-
}
|
935
|
+
for(const n of fc.notes) this.drawNote(n);
|
953
936
|
// Resize paper if necessary.
|
954
937
|
this.extend();
|
955
938
|
// Display model name in browser.
|
@@ -961,8 +944,7 @@ class Paper {
|
|
961
944
|
// without a mouseout event.
|
962
945
|
this.constraint_under_cursor = null;
|
963
946
|
// Draw the selected entities and associated links, and also constraints.
|
964
|
-
for(
|
965
|
-
const obj = mdl.selection[i];
|
947
|
+
for(const obj of mdl.selection) {
|
966
948
|
// Links and constraints are drawn separately, so do not draw those
|
967
949
|
// contained in the selection.
|
968
950
|
if(!(obj instanceof Link || obj instanceof Constraint)) {
|
@@ -974,18 +956,13 @@ class Paper {
|
|
974
956
|
mdl.selection_related_arrows = mdl.focal_cluster.selectedArrows();
|
975
957
|
}
|
976
958
|
// Only draw the arrows that relate to the selection.
|
977
|
-
for(
|
978
|
-
this.drawArrow(mdl.selection_related_arrows[i]);
|
979
|
-
}
|
959
|
+
for(const a of mdl.selection_related_arrows) this.drawArrow(a);
|
980
960
|
// As they typically are few, simply redraw all constraints that relate to
|
981
961
|
// the focal cluster.
|
982
|
-
for(
|
983
|
-
this.drawConstraint(mdl.focal_cluster.related_constraints[i]);
|
984
|
-
}
|
962
|
+
for(const c of mdl.focal_cluster.related_constraints) this.drawConstraint(c);
|
985
963
|
this.extend();
|
986
964
|
}
|
987
965
|
|
988
|
-
|
989
966
|
//
|
990
967
|
// Shape-drawing methods for model entities
|
991
968
|
//
|
@@ -1026,8 +1003,7 @@ class Paper {
|
|
1026
1003
|
// Distinguish between input, output and io products.
|
1027
1004
|
let ip = [], op = [], iop = [];
|
1028
1005
|
if(cnb instanceof Cluster) {
|
1029
|
-
for(
|
1030
|
-
const lnk = arrw.links[i];
|
1006
|
+
for(const lnk of arrw.links) {
|
1031
1007
|
// Determine which product is involved.
|
1032
1008
|
prod = (lnk.from_node instanceof Product ? lnk.from_node : lnk.to_node);
|
1033
1009
|
// NOTE: Clusters "know" their input/output products.
|
@@ -1041,8 +1017,7 @@ class Paper {
|
|
1041
1017
|
}
|
1042
1018
|
} else {
|
1043
1019
|
// `cnb` is process or product => knows its inputs and outputs.
|
1044
|
-
for(
|
1045
|
-
const lnk = arrw.links[i];
|
1020
|
+
for(const lnk of arrw.links) {
|
1046
1021
|
if(lnk.from_node === cnb) {
|
1047
1022
|
addDistinct(lnk.to_node, op);
|
1048
1023
|
} else {
|
@@ -1069,9 +1044,8 @@ class Paper {
|
|
1069
1044
|
to_p = to_nb instanceof Process;
|
1070
1045
|
let data_flows = 0;
|
1071
1046
|
if(arrw.links.length > 1 || (from_c && to_p) || (from_p && to_c)) {
|
1072
|
-
for(
|
1047
|
+
for(const lnk of arrw.links) {
|
1073
1048
|
const
|
1074
|
-
lnk = arrw.links[i],
|
1075
1049
|
fn = lnk.from_node,
|
1076
1050
|
tn = lnk.to_node;
|
1077
1051
|
if(fn instanceof Product && fn != from_nb && fn != to_nb) {
|
@@ -1879,12 +1853,10 @@ class Paper {
|
|
1879
1853
|
stroke: stroke_color, 'stroke-width': stroke_width});
|
1880
1854
|
svg.appendChild(el);
|
1881
1855
|
// Add the bound line contours
|
1882
|
-
for(
|
1883
|
-
|
1884
|
-
|
1885
|
-
|
1886
|
-
// for regular color or the filled areas turn out too light.
|
1887
|
-
clr = (stroke_color === this.palette.node_rim ? 'black' : stroke_color);
|
1856
|
+
for(const bl of c.bound_lines) {
|
1857
|
+
// Draw thumbnail in shades of the arrow color, but use black
|
1858
|
+
// for regular color or the filled areas turn out too light.
|
1859
|
+
const clr = (stroke_color === this.palette.node_rim ? 'black' : stroke_color);
|
1888
1860
|
// Set the boundline point coordinates (TRUE indicates: also compute
|
1889
1861
|
// the thumbnail SVG).
|
1890
1862
|
bl.setDynamicPoints(MODEL.t, true);
|
@@ -2811,7 +2783,7 @@ class Paper {
|
|
2811
2783
|
'v', h - shadow_width, 'h-', w - shadow_width, 'z'],
|
2812
2784
|
{fill: fill_color, stroke: stroke_color, 'stroke-width': stroke_width});
|
2813
2785
|
if(clstr.ignore) {
|
2814
|
-
// Draw diagonal cross
|
2786
|
+
// Draw diagonal cross.
|
2815
2787
|
clstr.shape.addPath(['m', x - hw + 6, ',', y - hh + 6,
|
2816
2788
|
'l', w - 12 - shadow_width, ',', h - 12 - shadow_width,
|
2817
2789
|
'm', 12 - w + shadow_width, ',0',
|
@@ -2820,7 +2792,7 @@ class Paper {
|
|
2820
2792
|
'stroke-linecap': 'round'});
|
2821
2793
|
}
|
2822
2794
|
if(!clstr.collapsed) {
|
2823
|
-
// Draw text
|
2795
|
+
// Draw text.
|
2824
2796
|
const
|
2825
2797
|
lcnt = clstr.name_lines.split('\n').length,
|
2826
2798
|
cy = (clstr.hasActor ? y - 12 / (lcnt + 1) : y);
|
@@ -2835,14 +2807,14 @@ class Paper {
|
|
2835
2807
|
{'font-size': 12, fill: this.palette.actor_font,
|
2836
2808
|
'font-style': 'italic'});
|
2837
2809
|
let any = cy + th/2 + 7;
|
2838
|
-
for(
|
2839
|
-
clstr.shape.addText(x, any,
|
2810
|
+
for(const an of anl) {
|
2811
|
+
clstr.shape.addText(x, any, an, format);
|
2840
2812
|
any += 12;
|
2841
2813
|
}
|
2842
2814
|
}
|
2843
2815
|
}
|
2844
2816
|
if(MODEL.show_block_arrows && !ignored) {
|
2845
|
-
// Add block arrows for hidden IO links
|
2817
|
+
// Add block arrows for hidden IO links.
|
2846
2818
|
clstr.shape.addBlockArrow(x - hw + 3, y - hh + 15, UI.BLOCK_IN,
|
2847
2819
|
clstr.hidden_inputs.length);
|
2848
2820
|
clstr.shape.addBlockArrow(x + hw - 4, y - hh + 15, UI.BLOCK_OUT,
|
@@ -38,7 +38,7 @@ class PowerGridManager {
|
|
38
38
|
// Add the power grids modal
|
39
39
|
this.dialog = new ModalDialog('power-grids');
|
40
40
|
this.dialog.close.addEventListener('click',
|
41
|
-
() => POWER_GRID_MANAGER.
|
41
|
+
() => POWER_GRID_MANAGER.closeDialog());
|
42
42
|
// Make the add, edit and delete buttons of this modal responsive
|
43
43
|
this.dialog.element('new-btn').addEventListener('click',
|
44
44
|
() => POWER_GRID_MANAGER.promptForPowerGrid());
|
@@ -96,8 +96,8 @@ class PowerGridManager {
|
|
96
96
|
html.push('<div id="', modal, '-gm-none" class="no-grid-plate" ',
|
97
97
|
'title="No grid element" onclick="UI.setGridPlate(event.target)">',
|
98
98
|
'(↯)</div>');
|
99
|
-
for(
|
100
|
-
const pg = MODEL.power_grids[
|
99
|
+
for(const g of grids) {
|
100
|
+
const pg = MODEL.power_grids[g];
|
101
101
|
html.push('<div id="', modal, '-gm-', pg.id,
|
102
102
|
'"class="menu-plate" style="background-color: ', pg.color,
|
103
103
|
'" title="Element of grid “', pg.name,
|
@@ -147,7 +147,7 @@ class PowerGridManager {
|
|
147
147
|
pg.color, '">', pg.voltage, '</div>',
|
148
148
|
'<div class="grid-watts">', pg.power_unit, '</div>',
|
149
149
|
(pg.kirchhoff ?
|
150
|
-
'<div class="grid-
|
150
|
+
'<div class="grid-kvl-symbol">⟳</div>': ''),
|
151
151
|
(pg.loss_approximation ?
|
152
152
|
'<div class="grid-loss-symbol">L&sup' +
|
153
153
|
pg.loss_approximation + ';</div>' : ''),
|
@@ -155,6 +155,9 @@ class PowerGridManager {
|
|
155
155
|
}
|
156
156
|
}
|
157
157
|
this.table.innerHTML = sl.join('');
|
158
|
+
UI.setBox('power-grids-capacity', MODEL.ignore_grid_capacity);
|
159
|
+
UI.setBox('power-grids-KVL', MODEL.ignore_KVL);
|
160
|
+
UI.setBox('power-grids-losses', MODEL.ignore_power_losses);
|
158
161
|
if(ss) UI.scrollIntoView(document.getElementById(ssid));
|
159
162
|
const btns = 'power-grids-edit power-grids-delete';
|
160
163
|
if(ss) {
|
@@ -163,6 +166,14 @@ class PowerGridManager {
|
|
163
166
|
UI.disableButtons(btns);
|
164
167
|
}
|
165
168
|
}
|
169
|
+
|
170
|
+
closeDialog() {
|
171
|
+
// Save checkbox status and hide the dialog.
|
172
|
+
MODEL.ignore_grid_capacity = UI.boxChecked('power-grids-capacity');
|
173
|
+
MODEL.ignore_KVL = UI.boxChecked('power-grids-KVL');
|
174
|
+
MODEL.ignore_power_losses = UI.boxChecked('power-grids-losses');
|
175
|
+
this.dialog.hide();
|
176
|
+
}
|
166
177
|
|
167
178
|
selectPowerGrid(event, id, focus) {
|
168
179
|
// Select power grid, and when double-clicked, allow to edit it.
|
@@ -261,7 +272,7 @@ class PowerGridManager {
|
|
261
272
|
}
|
262
273
|
|
263
274
|
checkLengths() {
|
264
|
-
// Calculate
|
275
|
+
// Calculate length statistics for all grid processes.
|
265
276
|
this.min_length = 1e+10;
|
266
277
|
this.max_length = 0;
|
267
278
|
this.total_length = 0;
|
@@ -294,8 +305,7 @@ class PowerGridManager {
|
|
294
305
|
const mlmsg = [];
|
295
306
|
let fn = null,
|
296
307
|
tn = null;
|
297
|
-
for(
|
298
|
-
const l = p.inputs[i];
|
308
|
+
for(const l of p.inputs) {
|
299
309
|
if(l.multiplier === VM.LM_LEVEL &&
|
300
310
|
!MODEL.ignored_entities[l.identifier]) {
|
301
311
|
if(fn) {
|
@@ -306,8 +316,7 @@ class PowerGridManager {
|
|
306
316
|
}
|
307
317
|
}
|
308
318
|
if(!fn) mlmsg.push('no inputs');
|
309
|
-
for(
|
310
|
-
const l = p.outputs[i];
|
319
|
+
for(const l of p.outputs) {
|
311
320
|
if(l.multiplier === VM.LM_LEVEL &&
|
312
321
|
!MODEL.ignored_entities[l.identifier]) {
|
313
322
|
if(tn) {
|
@@ -405,9 +414,7 @@ class PowerGridManager {
|
|
405
414
|
// from `fn` to `tn` in the spanning tree of this grid.
|
406
415
|
// If edge connects path with TO node, `path` is complete.
|
407
416
|
if(fn === tn) return true;
|
408
|
-
const
|
409
|
-
for(let i = 0; i < elist.length; i++) {
|
410
|
-
const e = elist[i];
|
417
|
+
for(const e of this.tree_incidence[fn]) {
|
411
418
|
// Ignore edges already in the path.
|
412
419
|
if(path.indexOf(e) < 0) {
|
413
420
|
// NOTE: Edges are directed, but should not be considered as such.
|
@@ -426,10 +433,8 @@ class PowerGridManager {
|
|
426
433
|
if(!(MODEL.with_power_flow && MODEL.powerGridsWithKVL.length)) return;
|
427
434
|
this.inferNodesAndEdges();
|
428
435
|
this.inferSpanningTree();
|
429
|
-
for(
|
430
|
-
const
|
431
|
-
edge = this.cycle_edges[i],
|
432
|
-
path = [];
|
436
|
+
for(const edge of this.cycle_edges) {
|
437
|
+
const path = [];
|
433
438
|
if(this.pathInSpanningTree(edge.from_node, edge.to_node, path)) {
|
434
439
|
// Add flags that indicate whether the edge on the path is reversed.
|
435
440
|
// The closing edge determines the orientation.
|
@@ -460,11 +465,10 @@ class PowerGridManager {
|
|
460
465
|
const
|
461
466
|
c = this.cycle_basis[i],
|
462
467
|
l = [];
|
463
|
-
for(
|
464
|
-
l.push(
|
465
|
-
` [${c[j].orientation > 0 ? '+' : '-'}]`);
|
468
|
+
for(const e of c) {
|
469
|
+
l.push(`${e.process.displayName} [${e.orientation > 0 ? '+' : '-'}]`);
|
466
470
|
}
|
467
|
-
ll.push(`(${i+1}) ${l.join(', ')}`);
|
471
|
+
ll.push(`(${i + 1}) ${l.join(', ')}`);
|
468
472
|
}
|
469
473
|
return ll.join('\n');
|
470
474
|
}
|
@@ -477,9 +481,8 @@ class PowerGridManager {
|
|
477
481
|
'<th title="Reactance"><em>x</em></th><th title="Power">P</th>' +
|
478
482
|
'<th><em>x</em>P</th></tr>'];
|
479
483
|
let sum = 0;
|
480
|
-
for(
|
484
|
+
for(const edge of c) {
|
481
485
|
const
|
482
|
-
edge = c[i],
|
483
486
|
p = edge.process,
|
484
487
|
x = p.length_in_km * p.grid.reactancePerKm,
|
485
488
|
l = p.actualLevel(MODEL.t);
|
@@ -498,16 +501,12 @@ class PowerGridManager {
|
|
498
501
|
|
499
502
|
inCycle(p) {
|
500
503
|
// Return TRUE if process `p` is an edge in some cycle in the cycle basis.
|
501
|
-
for(
|
502
|
-
const c
|
503
|
-
for(let j = 0; j < c.length; j++) {
|
504
|
-
if(c[j].process === p) return true;
|
505
|
-
}
|
504
|
+
for(const c of this.cycle_basis) {
|
505
|
+
for(const e of c) if(e.process === p) return true;
|
506
506
|
}
|
507
507
|
return false;
|
508
508
|
}
|
509
509
|
|
510
|
-
|
511
510
|
allCycleFlows(p) {
|
512
511
|
// Return power flows for each cycle that `p` is part of as an HTML
|
513
512
|
// table (so it can be displayed in the documentation dialog).
|
@@ -515,12 +514,10 @@ class PowerGridManager {
|
|
515
514
|
const flows = [];
|
516
515
|
for(let i = 0; i < this.cycle_basis.length; i++) {
|
517
516
|
const c = this.cycle_basis[i];
|
518
|
-
for(
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
break;
|
523
|
-
}
|
517
|
+
for(const e of c) if(e.process === p) {
|
518
|
+
flows.push(`<h3>Flows through cycle (${i}):</h3>`,
|
519
|
+
this.cycleFlowTable(c));
|
520
|
+
break;
|
524
521
|
}
|
525
522
|
}
|
526
523
|
return flows.join('\n');
|
@@ -241,8 +241,7 @@ class GUIReceiver {
|
|
241
241
|
// NOTE: The @ will be replaced by the run number, so that that
|
242
242
|
// number precedes the clock time. The @ will be unique because
|
243
243
|
// `asFileName()` replaces special characters by underscores.
|
244
|
-
file =
|
245
|
-
'@-' + compactClockTime();
|
244
|
+
file = asFileName(MODEL.name || 'model') + '@-' + compactClockTime();
|
246
245
|
}
|
247
246
|
if(MODEL.solved && !VM.halted) {
|
248
247
|
// Normal execution termination => report results
|