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.
Files changed (31) hide show
  1. package/README.md +3 -40
  2. package/package.json +1 -1
  3. package/server.js +19 -157
  4. package/static/index.html +58 -20
  5. package/static/linny-r.css +20 -16
  6. package/static/scripts/iro.min.js +7 -7
  7. package/static/scripts/linny-r-ctrl.js +50 -72
  8. package/static/scripts/linny-r-gui-actor-manager.js +23 -33
  9. package/static/scripts/linny-r-gui-chart-manager.js +43 -41
  10. package/static/scripts/linny-r-gui-constraint-editor.js +6 -10
  11. package/static/scripts/linny-r-gui-controller.js +254 -230
  12. package/static/scripts/linny-r-gui-dataset-manager.js +17 -36
  13. package/static/scripts/linny-r-gui-documentation-manager.js +11 -17
  14. package/static/scripts/linny-r-gui-equation-manager.js +22 -22
  15. package/static/scripts/linny-r-gui-experiment-manager.js +102 -129
  16. package/static/scripts/linny-r-gui-file-manager.js +42 -48
  17. package/static/scripts/linny-r-gui-finder.js +105 -51
  18. package/static/scripts/linny-r-gui-model-autosaver.js +2 -4
  19. package/static/scripts/linny-r-gui-monitor.js +35 -41
  20. package/static/scripts/linny-r-gui-paper.js +42 -70
  21. package/static/scripts/linny-r-gui-power-grid-manager.js +31 -34
  22. package/static/scripts/linny-r-gui-receiver.js +1 -2
  23. package/static/scripts/linny-r-gui-repository-browser.js +44 -46
  24. package/static/scripts/linny-r-gui-scale-unit-manager.js +32 -32
  25. package/static/scripts/linny-r-gui-sensitivity-analysis.js +61 -67
  26. package/static/scripts/linny-r-gui-undo-redo.js +94 -95
  27. package/static/scripts/linny-r-milp.js +20 -24
  28. package/static/scripts/linny-r-model.js +1832 -2248
  29. package/static/scripts/linny-r-utils.js +27 -27
  30. package/static/scripts/linny-r-vm.js +801 -905
  31. 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(let i = 0; i < lines.length; i++) {
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 '&nbsp;' 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 = lines[i].replaceAll('\u00A0', ' ');
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
- // adjust for font weight
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
- // Returns 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
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
- const
664
- lines = ('' + string).split('\n'), // Add '' in case string is a number
665
- ll = lines.length;
666
- for(let i = 0; i < ll; i++) {
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
- // Removes SVG elements used by the user interface (not part of the model)
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(let i = 0; i < fc.processes.length; i++) {
925
- fc.processes[i].clearHiddenIO();
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(let i = 0; i < fc.arrows.length; i++) {
935
- this.drawArrow(fc.arrows[i]);
936
- }
937
- for(let i = 0; i < fc.related_constraints.length; i++) {
938
- this.drawConstraint(fc.related_constraints[i]);
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(let i = 0; i < fc.notes.length; i++) {
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(let i = 0; i < mdl.selection.length; i++) {
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(let i = 0; i < mdl.selection_related_arrows.length; i++) {
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(let i = 0; i < mdl.focal_cluster.related_constraints.length; i++) {
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(let i = 0; i < arrw.links.length; i++) {
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(let i = 0; i < arrw.links.length; i++) {
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(let i = 0; i < arrw.links.length; i++) {
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(let i = 0; i < c.bound_lines.length; i++) {
1883
- const
1884
- bl = c.bound_lines[i],
1885
- // Draw thumbnail in shades of the arrow color, but use black
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(let i = 0; i < anl.length; i++) {
2839
- clstr.shape.addText(x, any, anl[i], format);
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.dialog.hide());
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
  '(&#x21AF;)</div>');
99
- for(let i = 0; i < grids.length; i++) {
100
- const pg = MODEL.power_grids[grids[i]];
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 &ldquo;', 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-kcl-symbol">&#x27F3;</div>': ''),
150
+ '<div class="grid-kvl-symbol">&#x27F3;</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 lengt statistics for all grid processes.
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(let i = 0; i < p.inputs.length; i++) {
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(let i = 0; i < p.outputs.length; i++) {
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 elist = this.tree_incidence[fn];
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(let i = 0; i < this.cycle_edges.length; i++) {
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(let j = 0; j < c.length; j++) {
464
- l.push(c[j].process.displayName +
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(let i = 0; i < c.length; i++) {
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(let i = 0; i < this.cycle_basis.length; i++) {
502
- const c = this.cycle_basis[i];
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(let j = 0; j < c.length; j++) {
519
- if(c[j].process === p) {
520
- flows.push(`<h3>Flows through cycle (${i}):</h3>`,
521
- this.cycleFlowTable(c));
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 = REPOSITORY_BROWSER.asFileName(MODEL.name || 'model') +
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