linny-r 2.0.8 → 2.0.10
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 +6 -2
- package/server.js +19 -157
- package/static/images/solve-not-same-changed.png +0 -0
- package/static/images/solve-not-same-not-changed.png +0 -0
- package/static/images/solve-same-changed.png +0 -0
- package/static/images/solve-same-not-changed.png +0 -0
- package/static/index.html +137 -20
- package/static/linny-r.css +260 -23
- package/static/scripts/iro.min.js +7 -7
- package/static/scripts/linny-r-ctrl.js +126 -85
- package/static/scripts/linny-r-gui-actor-manager.js +23 -33
- package/static/scripts/linny-r-gui-chart-manager.js +56 -53
- package/static/scripts/linny-r-gui-constraint-editor.js +10 -14
- package/static/scripts/linny-r-gui-controller.js +644 -260
- package/static/scripts/linny-r-gui-dataset-manager.js +64 -66
- 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 +124 -141
- package/static/scripts/linny-r-gui-expression-editor.js +26 -12
- package/static/scripts/linny-r-gui-file-manager.js +42 -48
- package/static/scripts/linny-r-gui-finder.js +294 -55
- 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 +1932 -2274
- package/static/scripts/linny-r-utils.js +27 -27
- package/static/scripts/linny-r-vm.js +900 -998
- package/static/show-png.html +0 -113
@@ -158,10 +158,8 @@ class ModelAutoSaver {
|
|
158
158
|
document.getElementById('load-modal').style.display = 'none';
|
159
159
|
// Contruct the table to select from.
|
160
160
|
let html = '';
|
161
|
-
for(
|
162
|
-
const
|
163
|
-
m = this.model_list[i],
|
164
|
-
bytes = UI.sizeInBytes(m.size).split(' ');
|
161
|
+
for(const m of this.model_list) {
|
162
|
+
const bytes = UI.sizeInBytes(m.size).split(' ');
|
165
163
|
html += ['<tr class="dataset" style="color: gray" ',
|
166
164
|
'onclick="FILE_MANAGER.loadAutoSavedModel(\'',
|
167
165
|
m.name,'\');"><td class="restore-name">', m.name, '</td><td>',
|
@@ -122,9 +122,7 @@ class GUIMonitor {
|
|
122
122
|
addProgressBlock(b, err, time) {
|
123
123
|
// Adds a block to the progress bar, and updates the relative block lengths
|
124
124
|
let total_time = 0;
|
125
|
-
for(let i = 0; i < b; i++)
|
126
|
-
total_time += VM.solver_times[i];
|
127
|
-
}
|
125
|
+
for(let i = 0; i < b; i++) total_time += VM.solver_times[i];
|
128
126
|
const
|
129
127
|
n = document.createElement('div'),
|
130
128
|
ssecs = VM.solver_secs[b - 1];
|
@@ -158,15 +156,13 @@ class GUIMonitor {
|
|
158
156
|
showBlock(b) {
|
159
157
|
this.shown_block = b;
|
160
158
|
const cn = this.progress_bar.childNodes;
|
161
|
-
for(
|
162
|
-
cn[i].classList.remove('sel-pb');
|
163
|
-
}
|
159
|
+
for(const n of cn) n.classList.remove('sel-pb');
|
164
160
|
cn[b - 1].classList.add('sel-pb');
|
165
161
|
this.updateContent(this.tab);
|
166
162
|
}
|
167
163
|
|
168
164
|
updateDialog() {
|
169
|
-
// Implements default behavior for a draggable/resizable dialog
|
165
|
+
// Implements default behavior for a draggable/resizable dialog.
|
170
166
|
this.updateContent(this.tab);
|
171
167
|
}
|
172
168
|
|
@@ -199,33 +195,31 @@ class GUIMonitor {
|
|
199
195
|
}
|
200
196
|
|
201
197
|
showCallStack(t) {
|
202
|
-
// Show the error message in the dialog header
|
203
|
-
// NOTE:
|
198
|
+
// Show the error message in the dialog header.
|
199
|
+
// NOTE: Prevent showing again when VM detects multiple errors.
|
204
200
|
if(this.call_stack_shown) return;
|
205
201
|
const
|
206
202
|
csl = VM.call_stack.length,
|
207
203
|
top = VM.call_stack[csl - 1],
|
208
204
|
err = top.vector[t],
|
209
|
-
// Make separate lists of variable names and their expressions
|
205
|
+
// Make separate lists of variable names and their expressions.
|
210
206
|
vlist = [],
|
211
207
|
xlist = [];
|
212
208
|
document.getElementById('call-stack-error').innerHTML =
|
213
209
|
`ERROR at t=${t}: ` + VM.errorMessage(err);
|
214
|
-
for(
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
ons = (x.object === MODEL.equations_dataset ? '' :
|
219
|
-
x.object.displayName + '|');
|
210
|
+
for(const x of VM.call_stack) {
|
211
|
+
// For equations, only show the attribute.
|
212
|
+
const ons = (x.object === MODEL.equations_dataset ? '' :
|
213
|
+
x.object.displayName + '|');
|
220
214
|
vlist.push(ons + x.attribute);
|
221
|
-
// Trim spaces around all object-attribute separators in the expression
|
215
|
+
// Trim spaces around all object-attribute separators in the expression.
|
222
216
|
xlist.push(x.text.replace(/\s*\|\s*/g, '|'));
|
223
217
|
}
|
224
|
-
// Highlight variables where they are used in the expressions
|
218
|
+
// Highlight variables where they are used in the expressions.
|
225
219
|
const vcc = UI.chart_colors.length;
|
226
220
|
for(let i = 0; i < xlist.length; i++) {
|
227
221
|
for(let j = 0; j < vlist.length; j++) {
|
228
|
-
// Ignore selectors, as these may be different per experiment
|
222
|
+
// Ignore selectors, as these may be different per experiment.
|
229
223
|
const
|
230
224
|
vnl = vlist[j].split('|'),
|
231
225
|
sel = (vnl.length > 1 ? vnl.pop() : ''),
|
@@ -236,26 +230,26 @@ class GUIMonitor {
|
|
236
230
|
xlist[i] = xlist[i].split(vn).join(vnc);
|
237
231
|
}
|
238
232
|
}
|
239
|
-
// Then also color the variables
|
233
|
+
// Then also color the variables.
|
240
234
|
for(let i = 0; i < vlist.length; i++) {
|
241
235
|
vlist[i] = '<span style="font-weight: 600; color: ' +
|
242
236
|
`${UI.chart_colors[i % vcc]}">${vlist[i]}</span>`;
|
243
237
|
}
|
244
|
-
// Start without indentation
|
238
|
+
// Start without indentation.
|
245
239
|
let pad = 0;
|
246
|
-
// First show the variable being computed
|
240
|
+
// First show the variable being computed.
|
247
241
|
const tbl = ['<div>', vlist[0], '</div>'];
|
248
|
-
// Then iterate upwards over the call stack
|
242
|
+
// Then iterate upwards over the call stack.
|
249
243
|
for(let i = 0; i < vlist.length - 1; i++) {
|
250
|
-
// Show the expression, followed by the next computed variable
|
244
|
+
// Show the expression, followed by the next computed variable.
|
251
245
|
tbl.push(['<div class="call-stack-row" style="padding-left: ',
|
252
246
|
pad, 'px"><div class="call-stack-expr">', xlist[i],
|
253
|
-
'</div><div class="call-stack-vbl"> \u2937', vlist[i+1],
|
247
|
+
'</div><div class="call-stack-vbl"> \u2937', vlist[i + 1],
|
254
248
|
'</div></div>'].join(''));
|
255
249
|
// Increase indentation
|
256
250
|
pad += 8;
|
257
251
|
}
|
258
|
-
// Show the last expression, highlighting the array-out-of-bounds (if any)
|
252
|
+
// Show the last expression, highlighting the array-out-of-bounds (if any).
|
259
253
|
let last_x = xlist[xlist.length - 1],
|
260
254
|
anc = '';
|
261
255
|
if(VM.out_of_bounds_array) {
|
@@ -265,14 +259,14 @@ class GUIMonitor {
|
|
265
259
|
}
|
266
260
|
tbl.push('<div class="call-stack-expr" style="padding-left: ' +
|
267
261
|
`${pad}px">${last_x}</div>`);
|
268
|
-
// Add index-out-of-bounds message if appropriate
|
262
|
+
// Add index-out-of-bounds message if appropriate.
|
269
263
|
if(anc) {
|
270
264
|
tbl.push('<div style="color: gray; margin-top: 8px; font-size: 10px">',
|
271
265
|
VM.out_of_bounds_msg.replace(VM.out_of_bounds_array, anc), '</div>');
|
272
266
|
}
|
273
|
-
// Dump the code for the last expression to the console
|
267
|
+
// Dump the code for the last expression to the console.
|
274
268
|
console.log('Code for', top.text, top.code);
|
275
|
-
// Show the call stack dialog
|
269
|
+
// Show the call stack dialog.
|
276
270
|
document.getElementById('call-stack-table').innerHTML = tbl.join('');
|
277
271
|
document.getElementById('call-stack-modal').style.display = 'block';
|
278
272
|
this.call_stack_shown = true;
|
@@ -284,19 +278,19 @@ class GUIMonitor {
|
|
284
278
|
}
|
285
279
|
|
286
280
|
logMessage(block, msg) {
|
287
|
-
//
|
281
|
+
// Append a solver message to the monitor's messages textarea
|
288
282
|
if(this.messages_text.value === VM.no_messages) {
|
289
|
-
// Erase the "(no messages)" if still showing
|
283
|
+
// Erase the "(no messages)" if still showing.
|
290
284
|
this.messages_text.value = '';
|
291
285
|
}
|
292
286
|
if(this.shown_block === 0 && block !== this.last_message_block) {
|
293
|
-
// Clear text area when starting with new block while no block selected
|
287
|
+
// Clear text area when starting with new block while no block selected.
|
294
288
|
this.last_message_block = block;
|
295
289
|
this.messages_text.value = '';
|
296
290
|
}
|
297
291
|
// NOTE: `msg` is appended only if no block has been selected by
|
298
292
|
// clicking on the progress bar, or if the message belongs to the
|
299
|
-
// selected block
|
293
|
+
// selected block.
|
300
294
|
if(this.shown_block === 0 || this.shown_block === block) {
|
301
295
|
this.messages_text.value += msg + '\n';
|
302
296
|
}
|
@@ -347,8 +341,8 @@ class GUIMonitor {
|
|
347
341
|
}
|
348
342
|
|
349
343
|
connectToServer() {
|
350
|
-
//
|
351
|
-
// NOTE: No authentication prompt if SOLVER.user_id in `linny-r-config.js
|
344
|
+
// Prompt for credentials if not connected yet.
|
345
|
+
// NOTE: No authentication prompt if SOLVER.user_id in `linny-r-config.js`.
|
352
346
|
// is left blank.
|
353
347
|
if(!VM.solver_user) {
|
354
348
|
VM.connected = false;
|
@@ -414,7 +408,7 @@ UI.logHeapSize(`BEFORE creating post data`);
|
|
414
408
|
mipgap: MODEL.MIP_gap
|
415
409
|
});
|
416
410
|
UI.logHeapSize(`AFTER creating post data`);
|
417
|
-
// Immediately free the memory taken up by VM.lines
|
411
|
+
// Immediately free the memory taken up by VM.lines.
|
418
412
|
VM.lines = '';
|
419
413
|
UI.logHeapSize(`BEFORE submitting block #${bwr} to solver`);
|
420
414
|
fetch('solver/', pd)
|
@@ -430,15 +424,15 @@ UI.logHeapSize(`AFTER creating post data`);
|
|
430
424
|
try {
|
431
425
|
VM.processServerResponse(JSON.parse(data));
|
432
426
|
UI.logHeapSize('After processing results for block #' + this.block_count);
|
433
|
-
// If no errors, solve next block (if any)
|
434
|
-
// NOTE:
|
435
|
-
// and browser can update its DOM to display progress
|
427
|
+
// If no errors, solve next block (if any).
|
428
|
+
// NOTE: Use setTimeout so that this calling function returns,
|
429
|
+
// and browser can update its DOM to display progress.
|
436
430
|
setTimeout(() => VM.solveBlocks(), 1);
|
437
431
|
} catch(err) {
|
438
|
-
// Log details on the console
|
432
|
+
// Log details on the console.
|
439
433
|
console.log('ERROR while parsing JSON:', err);
|
440
434
|
console.log(data);
|
441
|
-
// Pass summary on to the browser
|
435
|
+
// Pass summary on to the browser.
|
442
436
|
const msg = 'ERROR: Unexpected data from server: ' +
|
443
437
|
ellipsedText(data);
|
444
438
|
this.logMessage(this.block_count, msg);
|
@@ -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
|