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.
- package/LICENSE +1 -1
- package/README.md +4 -4
- package/package.json +1 -1
- package/server.js +1 -1
- package/static/images/eq-negated.png +0 -0
- package/static/images/power.png +0 -0
- package/static/images/tex.png +0 -0
- package/static/index.html +225 -10
- package/static/linny-r.css +458 -8
- package/static/scripts/linny-r-ctrl.js +6 -4
- package/static/scripts/linny-r-gui-actor-manager.js +1 -1
- package/static/scripts/linny-r-gui-chart-manager.js +20 -13
- package/static/scripts/linny-r-gui-constraint-editor.js +410 -50
- package/static/scripts/linny-r-gui-controller.js +127 -12
- package/static/scripts/linny-r-gui-dataset-manager.js +28 -20
- package/static/scripts/linny-r-gui-documentation-manager.js +11 -3
- package/static/scripts/linny-r-gui-equation-manager.js +1 -1
- package/static/scripts/linny-r-gui-experiment-manager.js +1 -1
- package/static/scripts/linny-r-gui-expression-editor.js +7 -1
- package/static/scripts/linny-r-gui-file-manager.js +31 -13
- package/static/scripts/linny-r-gui-finder.js +1 -1
- package/static/scripts/linny-r-gui-model-autosaver.js +1 -1
- package/static/scripts/linny-r-gui-monitor.js +1 -1
- package/static/scripts/linny-r-gui-paper.js +108 -25
- package/static/scripts/linny-r-gui-power-grid-manager.js +529 -0
- package/static/scripts/linny-r-gui-receiver.js +1 -1
- package/static/scripts/linny-r-gui-repository-browser.js +1 -1
- package/static/scripts/linny-r-gui-scale-unit-manager.js +1 -1
- package/static/scripts/linny-r-gui-sensitivity-analysis.js +1 -1
- package/static/scripts/linny-r-gui-tex-manager.js +110 -0
- package/static/scripts/linny-r-gui-undo-redo.js +1 -1
- package/static/scripts/linny-r-milp.js +1 -1
- package/static/scripts/linny-r-model.js +1016 -155
- package/static/scripts/linny-r-utils.js +3 -3
- package/static/scripts/linny-r-vm.js +714 -248
- package/static/show-diff.html +1 -1
- 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-
|
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
|
1247
|
-
|
1248
|
-
|
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
|
-
|
1478
|
-
|
1479
|
-
|
1480
|
-
|
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
|
-
|
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
|
-
|
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(
|
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
|
-
|
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
|
-
//
|
2066
|
-
proc.
|
2067
|
-
|
2068
|
-
|
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) {
|