linny-r 1.9.2 → 2.0.1

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 (37) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +4 -4
  3. package/package.json +1 -1
  4. package/server.js +1 -1
  5. package/static/images/eq-negated.png +0 -0
  6. package/static/images/power.png +0 -0
  7. package/static/images/tex.png +0 -0
  8. package/static/index.html +225 -10
  9. package/static/linny-r.css +458 -8
  10. package/static/scripts/linny-r-ctrl.js +6 -4
  11. package/static/scripts/linny-r-gui-actor-manager.js +1 -1
  12. package/static/scripts/linny-r-gui-chart-manager.js +20 -13
  13. package/static/scripts/linny-r-gui-constraint-editor.js +410 -50
  14. package/static/scripts/linny-r-gui-controller.js +127 -12
  15. package/static/scripts/linny-r-gui-dataset-manager.js +28 -20
  16. package/static/scripts/linny-r-gui-documentation-manager.js +11 -3
  17. package/static/scripts/linny-r-gui-equation-manager.js +1 -1
  18. package/static/scripts/linny-r-gui-experiment-manager.js +1 -1
  19. package/static/scripts/linny-r-gui-expression-editor.js +7 -1
  20. package/static/scripts/linny-r-gui-file-manager.js +31 -13
  21. package/static/scripts/linny-r-gui-finder.js +1 -1
  22. package/static/scripts/linny-r-gui-model-autosaver.js +1 -1
  23. package/static/scripts/linny-r-gui-monitor.js +1 -1
  24. package/static/scripts/linny-r-gui-paper.js +108 -25
  25. package/static/scripts/linny-r-gui-power-grid-manager.js +529 -0
  26. package/static/scripts/linny-r-gui-receiver.js +1 -1
  27. package/static/scripts/linny-r-gui-repository-browser.js +1 -1
  28. package/static/scripts/linny-r-gui-scale-unit-manager.js +1 -1
  29. package/static/scripts/linny-r-gui-sensitivity-analysis.js +1 -1
  30. package/static/scripts/linny-r-gui-tex-manager.js +110 -0
  31. package/static/scripts/linny-r-gui-undo-redo.js +1 -1
  32. package/static/scripts/linny-r-milp.js +1 -1
  33. package/static/scripts/linny-r-model.js +1016 -155
  34. package/static/scripts/linny-r-utils.js +3 -3
  35. package/static/scripts/linny-r-vm.js +714 -248
  36. package/static/show-diff.html +1 -1
  37. package/static/show-png.html +1 -1
@@ -11,7 +11,7 @@ functionality for the Linny-R model editor.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2023 Delft University of Technology
14
+ Copyright (c) 2017-2024 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -998,7 +998,7 @@ class Paper {
998
998
  let cnb, proc, prod, fnx, fny, fnw, fnh, tnx, tny, tnw, tnh,
999
999
  cp, rr, aa, bb, dd, nn, af, l, s, w, tw, th, bpx, bpy, epx, epy,
1000
1000
  sda, stroke_color, stroke_width, arrow_start, arrow_end,
1001
- font_color, font_weight, luc = null;
1001
+ font_color, font_weight, luc = null, grid = null;
1002
1002
  // Get the main arrow attributes
1003
1003
  const
1004
1004
  from_nb = arrw.from_node,
@@ -1243,9 +1243,12 @@ class Paper {
1243
1243
  prod = luc.from_node;
1244
1244
  }
1245
1245
  // NOTE: `luc` may also be a constraint!
1246
- if(luc instanceof Link && luc.is_feedback) {
1247
- sda = UI.sda.long_dash_dot;
1248
- arrow_end = this.feedback_triangle;
1246
+ if(luc instanceof Link) {
1247
+ grid = proc.grid;
1248
+ if(luc.is_feedback) {
1249
+ sda = UI.sda.long_dash_dot;
1250
+ arrow_end = this.feedback_triangle;
1251
+ }
1249
1252
  }
1250
1253
  // Data link => dotted line
1251
1254
  if(luc.dataOnly) {
@@ -1474,11 +1477,27 @@ class Paper {
1474
1477
  epy = arrw.from_y + (shift + bi) * dy / l;
1475
1478
  font_color = this.palette.produced;
1476
1479
  }
1477
- // Draw the rate in a semi-transparent white roundbox.
1478
- arrw.shape.addRect(epx, epy, tw, th,
1479
- {fill: 'white', opacity: 0.8, rx: 2, ry: 2});
1480
- arrw.shape.addNumber(epx, epy, s, {fill: font_color, 'font-style': rrfs});
1481
-
1480
+ let with_rate = true;
1481
+ if(grid) {
1482
+ // For power links, only draw the rate when the model has been run
1483
+ // and the actual flow is less than the process level (so the rate
1484
+ // then reflects the loss).
1485
+ const
1486
+ absf = Math.abs(af),
1487
+ apl = Math.abs(proc.actualLevel(MODEL.t));
1488
+ with_rate = MODEL.solved && apl - absf > VM.SIG_DIF_FROM_ZERO;
1489
+ font_color = 'gray';
1490
+ s = VM.sig4Dig(absf / apl);
1491
+ bb = this.numberSize(s);
1492
+ th = bb.height;
1493
+ tw = Math.max(th, bb.width);
1494
+ }
1495
+ if(with_rate) {
1496
+ // Draw the rate in a semi-transparent white roundbox.
1497
+ arrw.shape.addRect(epx, epy, tw, th,
1498
+ {fill: 'white', opacity: 0.8, rx: 2, ry: 2});
1499
+ arrw.shape.addNumber(epx, epy, s, {fill: font_color, 'font-style': rrfs});
1500
+ }
1482
1501
  // Draw the share of cost (only if relevant and > 0) behind the rate
1483
1502
  // in a pale yellow filled box.
1484
1503
  if(MODEL.infer_cost_prices && luc.share_of_cost > 0) {
@@ -1514,11 +1533,13 @@ class Paper {
1514
1533
  }
1515
1534
 
1516
1535
  // Draw the actual flow
1517
- if(l > 0 && af < VM.UNDEFINED && Math.abs(af) > VM.SIG_DIF_FROM_ZERO) {
1536
+ const absf = Math.abs(af);
1537
+ if(l > 0 && af < VM.UNDEFINED && absf > VM.SIG_DIF_FROM_ZERO) {
1518
1538
  const ffill = {fill:'white', opacity:0.8};
1519
1539
  if(luc || mf[0] == 1) {
1520
- // Draw flow data halfway the arrow only if calculated and non-zero
1521
- s = VM.sig4Dig(af);
1540
+ // Draw flow data halfway the arrow only if calculated and non-zero.
1541
+ // NOTE: Power flows are always absolute flows.
1542
+ s = VM.sig4Dig(grid ? absf : af);
1522
1543
  bb = this.numberSize(s, 10, 700);
1523
1544
  tw = bb.width/2;
1524
1545
  th = bb.height/2;
@@ -1861,11 +1882,14 @@ class Paper {
1861
1882
  const
1862
1883
  bl = c.bound_lines[i],
1863
1884
  // Draw thumbnail in shades of the arrow color, but use black
1864
- // for regular color or the filled areas turn out too light
1885
+ // for regular color or the filled areas turn out too light.
1865
1886
  clr = (stroke_color === this.palette.node_rim ? 'black' : stroke_color);
1887
+ // Set the boundline point coordinates (TRUE indicates: also compute
1888
+ // the thumbnail SVG).
1889
+ bl.setDynamicPoints(MODEL.t, true);
1866
1890
  el = this.newSVGElement('path');
1867
1891
  if(bl.type === VM.EQ) {
1868
- // For EQ bound lines, draw crisp line on silver background
1892
+ // For EQ bound lines, draw crisp line on silver background.
1869
1893
  this.addSVGAttributes(el,
1870
1894
  {d: bl.contour_path, fill: 'none', stroke: clr, 'stroke-width': 30});
1871
1895
  } else {
@@ -1875,7 +1899,7 @@ class Paper {
1875
1899
  svg.appendChild(el);
1876
1900
  }
1877
1901
  // Draw the share of cost (only if relevant and non-zero) near tail
1878
- // (or head if Y->X) of arrow in a pale yellow filled box
1902
+ // (or head if Y->X) of arrow in a pale yellow filled box.
1879
1903
  if(MODEL.infer_cost_prices && c.share_of_cost) {
1880
1904
  let s = VM.sig4Dig(c.share_of_cost * 100) + '%',
1881
1905
  bb = this.numberSize(s, 7),
@@ -2017,7 +2041,11 @@ class Paper {
2017
2041
  // Negative level => more reddish stroke and font
2018
2042
  font_color = this.palette.compound_flow;
2019
2043
  stroke_color = font_color;
2020
- if(lb < -VM.NEAR_ZERO) bar_ratio = l / lb;
2044
+ if(proc.grid) {
2045
+ bar_ratio = l / -ub;
2046
+ } else if(lb < -VM.NEAR_ZERO) {
2047
+ bar_ratio = l / lb;
2048
+ }
2021
2049
  stroke_width = 1.25;
2022
2050
  } else {
2023
2051
  font_color = this.palette.active_process;
@@ -2027,7 +2055,9 @@ class Paper {
2027
2055
  }
2028
2056
  // For options, set longer-dashed rim if committed at time <= t
2029
2057
  const fcn = (is_fc_option ? proc : fc_option_node);
2030
- if(fcn && fcn.start_ups.length > 0 && MODEL.t >= fcn.start_ups[0]) {
2058
+ // NOTE: When initial level > 0, option is already committed at t=0.
2059
+ if(fcn && (fcn.actualLevel(0) > 0 ||
2060
+ (fcn.start_ups.length > 0 && MODEL.t >= fcn.start_ups[0]))) {
2031
2061
  sda = UI.sda.longer_dash;
2032
2062
  }
2033
2063
  } else if(il) {
@@ -2052,7 +2082,7 @@ class Paper {
2052
2082
  hw = proc.width / 2;
2053
2083
  hh = proc.height / 2;
2054
2084
  }
2055
- // Draw frame using colors as defined above
2085
+ // Draw frame using colors as defined above.
2056
2086
  proc.shape.addRect(x, y, 2 * hw, 2 * hh,
2057
2087
  {fill: fill_color, stroke: stroke_color, 'stroke-width': stroke_width,
2058
2088
  'stroke-dasharray': sda, 'stroke-linecap': 'round'});
@@ -2062,10 +2092,19 @@ class Paper {
2062
2092
  const
2063
2093
  hsw = stroke_width / 2,
2064
2094
  hbl = hh * bar_ratio - hsw;
2065
- // NOTE: when level < 0, bar drops down from top
2066
- proc.shape.addRect(x + hw - 4 - hsw,
2067
- (l < 0 ? y - hh + hbl + hsw : y + hh - hbl - hsw),
2068
- 8, 2 * hbl, {fill: bar_color, stroke: 'none'});
2095
+ // Collapesed grid processes display a "wire" instead of a bar.
2096
+ if(proc.grid && proc.collapsed) {
2097
+ proc.shape.addPath(
2098
+ ['M', x - hw + 0.5, ',', y - hh/2, 'L', x + hw - 0.5, ',', y - hh/2],
2099
+ // NOTE: Use *squared* bar ratio to reflect quadratic losses.
2100
+ {fill: 'none', stroke: proc.grid.color,
2101
+ 'stroke-width': hh * bar_ratio * bar_ratio});
2102
+ } else {
2103
+ // NOTE: when level < 0, bar drops down from top
2104
+ proc.shape.addRect(x + hw - 4 - hsw,
2105
+ (l < 0 ? y - hh + hbl + hsw : y + hh - hbl - hsw),
2106
+ 8, 2 * hbl, {fill: bar_color, stroke: 'none'});
2107
+ }
2069
2108
  }
2070
2109
  // If semi-continuous, add a double rim 2 px above the bottom line
2071
2110
  if(proc.level_to_zero) {
@@ -2073,6 +2112,42 @@ class Paper {
2073
2112
  proc.shape.addPath(['M', x - hw, ',', bly, 'L', x + hw, ',', bly],
2074
2113
  {'fill': 'none', stroke: stroke_color, 'stroke-width': 0.6});
2075
2114
  }
2115
+ // If grid element, add colored strip at bottom.
2116
+ if(proc.grid) {
2117
+ proc.shape.addRect(x, y + hh - 3.3, 2*hw - 1.5, 6,
2118
+ {'fill': proc.grid.color, stroke: 'none'});
2119
+ // If grid enforces Kirchhoff's voltage law and/or losses, length
2120
+ // matters, so draw a white horizontal line through the strip that
2121
+ // is proportional to the length property of the process.
2122
+ if(MODEL.solved &&
2123
+ (proc.grid.kirchhoff || proc.grid.loss_approximation)) {
2124
+ const
2125
+ maxl = Math.max(1, POWER_GRID_MANAGER.max_length),
2126
+ w = (2 * hw - 8) * proc.length_in_km / maxl,
2127
+ bly = y + hh - 3.3;
2128
+ proc.shape.addPath(
2129
+ ['M', x - w/2, ',', bly, 'L', x + w/2, ',', bly],
2130
+ {'fill': 'none', stroke: 'white', 'stroke-width': 1.5,
2131
+ 'stroke-linecap': 'round'});
2132
+ }
2133
+ // If process has no capacity, cross it out.
2134
+ if(ub <= VM.NEAR_ZERO) {
2135
+ proc.shape.addPath(
2136
+ ['M', x - hw + 0.8, ',', y - hh + 0.5,
2137
+ 'L', x + hw - 0.5, ',', y + hh - 0.5,
2138
+ 'M', x - hw + 0.8, ',', y + hh - 0.5,
2139
+ 'L', x + hw - 0.5, ',', y - hh + 0.5],
2140
+ {fill: 'none', stroke: 'white', 'stroke-width': 2,
2141
+ 'stroke-linecap': 'round'});
2142
+ proc.shape.addPath(
2143
+ ['M', x - hw + 0.8, ',', y - hh + 0.5,
2144
+ 'L', x + hw - 0.5, ',', y + hh - 0.5,
2145
+ 'M', x - hw + 0.8, ',', y + hh - 0.5,
2146
+ 'L', x + hw - 0.5, ',', y - hh + 0.5],
2147
+ {fill: 'none', stroke: proc.grid.color, 'stroke-width': 1,
2148
+ 'stroke-linecap': 'round'});
2149
+ }
2150
+ }
2076
2151
  if(!proc.collapsed) {
2077
2152
  // If model has been computed or initial level is non-zero, draw
2078
2153
  // production level in upper right corner
@@ -2102,6 +2177,10 @@ class Paper {
2102
2177
  proc.shape.addNumber(cx, cy, s,
2103
2178
  {'font-size': 9, 'fill': font_color, 'font-weight': 700});
2104
2179
  }
2180
+ if(proc.grid && POWER_GRID_MANAGER.inCycle(proc)) {
2181
+ proc.shape.addText(x + hw - 2, y - hh + bh + 3, '\u27F3',
2182
+ {'font-size': 9, fill: 'black', 'text-anchor':'end'});
2183
+ }
2105
2184
  }
2106
2185
  // Draw boundaries in upper left corner
2107
2186
  // NOTE: their expressions should have been computed
@@ -2118,11 +2197,11 @@ class Paper {
2118
2197
  } else {
2119
2198
  const ubs = (ub >= VM.PLUS_INFINITY && !proc.upper_bound.defined ?
2120
2199
  '\u221E' : VM.sig4Dig(ub));
2121
- if(Math.abs(lb) > VM.NEAR_ZERO) {
2200
+ if(Math.abs(lb) > VM.NEAR_ZERO && !proc.grid) {
2122
2201
  // If lb <> 0 then lb...ub (with ellipsis).
2123
2202
  s += '\u2026' + ubs;
2124
2203
  } else {
2125
- // If lb = 0 show only the upper bound.
2204
+ // If gid process or lb = 0, show only the upper bound.
2126
2205
  s = ubs;
2127
2206
  lbw = 0;
2128
2207
  }
@@ -2137,6 +2216,10 @@ class Paper {
2137
2216
  ty = y - hh + sh/2 + 1;
2138
2217
  proc.shape.addNumber(tx + btw/2, ty, s,
2139
2218
  {fill: 'black', 'font-style': bfs});
2219
+ if(proc.grid) {
2220
+ proc.shape.addText(tx + 1, ty + 8, proc.grid.power_unit,
2221
+ {'font-size': 6, fill: 'black', 'text-anchor':'start'});
2222
+ }
2140
2223
  // Show start/stop-related status right of the process boundaries.
2141
2224
  // NOTE: lb must be > 0 for start/stop to work.
2142
2225
  if(proc.level_to_zero && lbw) {