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
@@ -47,13 +47,11 @@ class GUIFileManager {
|
|
47
47
|
// buttons on the top menu.
|
48
48
|
|
49
49
|
getRemoteData(dataset, url) {
|
50
|
-
// Gets data from a URL, or from a file on the local host
|
50
|
+
// Gets data from a URL, or from a file on the local host.
|
51
51
|
if(url === '') return;
|
52
52
|
if(url.indexOf('%') >= 0) {
|
53
53
|
// Expand %i, %j and %k if used in the URL.
|
54
|
-
const
|
55
|
-
for(let i = 0; i < letters.length; i++) {
|
56
|
-
const l = letters[i];
|
54
|
+
for(const l of ['i', 'j', 'k']) {
|
57
55
|
url = url.replaceAll('%' + l, valueOfIndexVariable(l));
|
58
56
|
}
|
59
57
|
}
|
@@ -361,9 +359,8 @@ class GUIFileManager {
|
|
361
359
|
}
|
362
360
|
fetch('autosave/', postData({
|
363
361
|
action: 'store',
|
364
|
-
file:
|
365
|
-
(MODEL.
|
366
|
-
(MODEL.author || 'no-author')),
|
362
|
+
file: asFileName((MODEL.name || 'no-name') +
|
363
|
+
'_by_' + (MODEL.author || 'no-author')),
|
367
364
|
xml: MODEL.asXML,
|
368
365
|
wsd: workspace
|
369
366
|
}))
|
@@ -404,10 +401,9 @@ class GUIFileManager {
|
|
404
401
|
}
|
405
402
|
}
|
406
403
|
|
407
|
-
|
408
|
-
//
|
409
|
-
|
410
|
-
if(tight) {
|
404
|
+
saveDiagramAsSVG(event) {
|
405
|
+
// Output SVG as string with nodes and arrows 100% opaque.
|
406
|
+
if(event.altKey) {
|
411
407
|
// First align to grid and then fit to size.
|
412
408
|
MODEL.alignToGrid();
|
413
409
|
UI.paper.fitToSize(1);
|
@@ -415,48 +411,15 @@ class GUIFileManager {
|
|
415
411
|
UI.paper.fitToSize();
|
416
412
|
MODEL.alignToGrid();
|
417
413
|
}
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
renderSVGAsPNG(svg) {
|
422
|
-
// Sends SVG to the server, which will convert it to PNG using Inkscape;
|
423
|
-
// if successful, the server will return the URL to the PNG file location;
|
424
|
-
// this URL is passed via the browser's local storage to the newly opened
|
425
|
-
// browser tab that awaits this URL and then loads it
|
426
|
-
const form = {
|
427
|
-
action: 'png',
|
428
|
-
user: VM.solver_user,
|
429
|
-
token: VM.solver_token,
|
430
|
-
data: btoa(encodeURI(svg))
|
431
|
-
};
|
432
|
-
fetch('solver/', postData(form))
|
433
|
-
.then((response) => {
|
434
|
-
if(!response.ok) {
|
435
|
-
UI.alert(`ERROR ${response.status}: ${response.statusText}`);
|
436
|
-
}
|
437
|
-
return response.text();
|
438
|
-
})
|
439
|
-
.then((data) => {
|
440
|
-
// Pass URL of image to the newly opened browser window
|
441
|
-
window.localStorage.setItem('png-url', data);
|
442
|
-
})
|
443
|
-
.catch((err) => UI.warn(UI.WARNING.NO_CONNECTION, err));
|
444
|
-
}
|
445
|
-
|
446
|
-
saveDiagramAsSVG(tight) {
|
447
|
-
// Output SVG as string with nodes and arrows 100% opaque.
|
448
|
-
if(tight) {
|
449
|
-
// First align to grid and then fit to size.
|
450
|
-
MODEL.alignToGrid();
|
451
|
-
UI.paper.fitToSize(1);
|
414
|
+
if(event.shiftKey) {
|
415
|
+
this.pushOutSVG(UI.paper.opaqueSVG);
|
452
416
|
} else {
|
453
|
-
UI.paper.
|
454
|
-
MODEL.alignToGrid();
|
417
|
+
this.pushOutPNG(UI.paper.opaqueSVG);
|
455
418
|
}
|
456
|
-
this.pushOutSVG(UI.paper.opaqueSVG);
|
457
419
|
}
|
458
420
|
|
459
421
|
pushOutSVG(svg) {
|
422
|
+
// Output SVG to browser as SVG image file download.
|
460
423
|
const blob = new Blob([svg], {'type': 'image/svg+xml'});
|
461
424
|
const e = document.getElementById('svg-saver');
|
462
425
|
e.download = 'model.svg';
|
@@ -464,5 +427,36 @@ class GUIFileManager {
|
|
464
427
|
e.href = (window.URL || webkitURL).createObjectURL(blob);
|
465
428
|
e.click();
|
466
429
|
}
|
430
|
+
|
431
|
+
pushOutPNG(svg) {
|
432
|
+
// Output SVG to browser as PNG image file download.
|
433
|
+
const
|
434
|
+
bytes = new TextEncoder().encode(svg),
|
435
|
+
binstr = Array.from(bytes, (b) => String.fromCodePoint(b)).join(''),
|
436
|
+
uri = 'data:image/svg+xml;base64,' + window.btoa(binstr),
|
437
|
+
img = new Image();
|
438
|
+
img.onload = () => {
|
439
|
+
const
|
440
|
+
cvs = document.createElement('canvas'),
|
441
|
+
ctx = cvs.getContext('2d');
|
442
|
+
cvs.width = img.width * 4;
|
443
|
+
cvs.height = img.height * 4;
|
444
|
+
ctx.scale(4, 4);
|
445
|
+
ctx.drawImage(img, 0, 0);
|
446
|
+
cvs.toBlob(blob => {
|
447
|
+
const
|
448
|
+
e = document.getElementById('svg-saver'),
|
449
|
+
url = (window.URL || webkitURL).createObjectURL(blob),
|
450
|
+
name = asFileName(MODEL.focal_cluster.parent ?
|
451
|
+
MODEL.focal_cluster.displayName : MODEL.name) ||
|
452
|
+
'Linny-R-model';
|
453
|
+
e.download = name + '.png';
|
454
|
+
e.type = 'image/png';
|
455
|
+
e.href = url;
|
456
|
+
e.click();
|
457
|
+
});
|
458
|
+
};
|
459
|
+
img.src = uri;
|
460
|
+
}
|
467
461
|
|
468
462
|
} // END of class GUIFileManager
|
@@ -46,7 +46,10 @@ class Finder {
|
|
46
46
|
this.filter_input.addEventListener('input', () => FINDER.changeFilter());
|
47
47
|
this.edit_btn = document.getElementById('finder-edit-btn');
|
48
48
|
this.edit_btn.addEventListener(
|
49
|
-
'click', (
|
49
|
+
'click', () => FINDER.editAttributes());
|
50
|
+
this.chart_btn = document.getElementById('finder-chart-btn');
|
51
|
+
this.chart_btn.addEventListener(
|
52
|
+
'click', () => FINDER.confirmAddChartVariables());
|
50
53
|
this.copy_btn = document.getElementById('finder-copy-btn');
|
51
54
|
this.copy_btn.addEventListener(
|
52
55
|
'click', (event) => FINDER.copyAttributesToClipboard(event.shiftKey));
|
@@ -54,6 +57,13 @@ class Finder {
|
|
54
57
|
this.item_table = document.getElementById('finder-item-table');
|
55
58
|
this.expression_table = document.getElementById('finder-expression-table');
|
56
59
|
|
60
|
+
// The Confirm add chart variables modal.
|
61
|
+
this.add_chart_variables_modal = new ModalDialog('confirm-add-chart-variables');
|
62
|
+
this.add_chart_variables_modal.ok.addEventListener(
|
63
|
+
'click', () => FINDER.addVariablesToChart());
|
64
|
+
this.add_chart_variables_modal.cancel.addEventListener(
|
65
|
+
'click', () => FINDER.add_chart_variables_modal.hide());
|
66
|
+
|
57
67
|
// Attribute headers are used by Finder to output entity attribute values.
|
58
68
|
this.attribute_headers = {
|
59
69
|
A: 'ACTORS:\tWeight\tCash IN\tCash OUT\tCash FLOW',
|
@@ -301,41 +311,100 @@ class Finder {
|
|
301
311
|
this.edit_btn.style.display = 'none';
|
302
312
|
this.copy_btn.style.display = 'none';
|
303
313
|
if(n > 0) {
|
304
|
-
this.copy_btn.style.display = 'block';
|
314
|
+
this.copy_btn.style.display = 'inline-block';
|
315
|
+
if(CHART_MANAGER.visible && CHART_MANAGER.chart_index >= 0) {
|
316
|
+
const ca = this.commonAttributes;
|
317
|
+
if(ca.length) {
|
318
|
+
this.chart_btn.title = 'Add ' + pluralS(n, 'variable') +
|
319
|
+
' to selected chart';
|
320
|
+
this.chart_btn.style.display = 'inline-block';
|
321
|
+
}
|
322
|
+
}
|
305
323
|
n = this.entityGroup.length;
|
306
324
|
if(n > 0) {
|
307
325
|
this.edit_btn.title = 'Edit attributes of ' +
|
308
326
|
pluralS(n, this.entities[0].type.toLowerCase());
|
309
|
-
this.edit_btn.style.display = 'block';
|
327
|
+
this.edit_btn.style.display = 'inline-block';
|
310
328
|
}
|
311
329
|
}
|
312
330
|
this.updateRightPane();
|
313
331
|
}
|
314
332
|
|
333
|
+
get commonAttributes() {
|
334
|
+
// Returns list of attributes that all filtered entities have in common.
|
335
|
+
let ca = Object.keys(VM.attribute_names);
|
336
|
+
for(const et of this.filtered_types) {
|
337
|
+
ca = intersection(ca, VM.attribute_codes[et]);
|
338
|
+
}
|
339
|
+
return ca;
|
340
|
+
}
|
341
|
+
|
315
342
|
get entityGroup() {
|
316
343
|
// Returns the list of filtered entities if all are of the same type,
|
317
344
|
// while excluding (no actor), (top cluster), datasets and equations.
|
318
345
|
const
|
319
346
|
eg = [],
|
320
|
-
|
321
|
-
if(
|
322
|
-
const
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
// many of their properties should not be changed.
|
330
|
-
!e.name.startsWith('$')) {
|
331
|
-
eg.push(e);
|
332
|
-
}
|
347
|
+
ft = this.filtered_types[0];
|
348
|
+
if(this.filtered_types.length === 1 && 'DE'.indexOf(ft) < 0) {
|
349
|
+
for(const e of this.entities) {
|
350
|
+
// Exclude "no actor" and top cluster.
|
351
|
+
if(e.name && e.name !== '(no_actor)' && e.name !== '(top_cluster)' &&
|
352
|
+
// Also exclude actor cash flow data products because
|
353
|
+
// many of their properties should not be changed.
|
354
|
+
!e.name.startsWith('$')) {
|
355
|
+
eg.push(e);
|
333
356
|
}
|
334
357
|
}
|
335
358
|
}
|
336
359
|
return eg;
|
337
360
|
}
|
338
361
|
|
362
|
+
confirmAddChartVariables() {
|
363
|
+
// Show confirmation dialog to add variables to chart.
|
364
|
+
const
|
365
|
+
md = this.add_chart_variables_modal,
|
366
|
+
n = this.entities.length,
|
367
|
+
ca = this.commonAttributes;
|
368
|
+
let html,
|
369
|
+
et = '1 entity';
|
370
|
+
if(this.filtered_types.length === 1) {
|
371
|
+
et = pluralS(n, this.entities[0].type.toLowerCase());
|
372
|
+
} else if(n !== 1) {
|
373
|
+
et = `${n} entities`;
|
374
|
+
}
|
375
|
+
for(const a of ca) {
|
376
|
+
html += `<option value="${a}">${VM.attribute_names[a]}</option>`;
|
377
|
+
}
|
378
|
+
md.element('attribute').innerHTML = html;
|
379
|
+
md.element('count').innerText = et;
|
380
|
+
md.show();
|
381
|
+
}
|
382
|
+
|
383
|
+
addVariablesToChart() {
|
384
|
+
// Add selected attribute for each filtered entity as chart variable
|
385
|
+
// to the selected chart.
|
386
|
+
const
|
387
|
+
md = this.add_chart_variables_modal,
|
388
|
+
ci = CHART_MANAGER.chart_index;
|
389
|
+
// Double-check whether chart exists.
|
390
|
+
if(ci < 0 || ci >= MODEL.charts.length) {
|
391
|
+
console.log('ANOMALY: No chart for index', ci);
|
392
|
+
}
|
393
|
+
const
|
394
|
+
c = MODEL.charts[ci],
|
395
|
+
a = md.element('attribute').value,
|
396
|
+
s = UI.boxChecked('confirm-add-chart-variables-stacked'),
|
397
|
+
enl = [];
|
398
|
+
for(const e of this.entities) enl.push(e.name);
|
399
|
+
enl.sort((a, b) => UI.compareFullNames(a, b, true));
|
400
|
+
for(const en of enl) {
|
401
|
+
const vi = c.addVariable(en, a);
|
402
|
+
if(vi !== null) c.variables[vi].stacked = s;
|
403
|
+
}
|
404
|
+
CHART_MANAGER.updateDialog();
|
405
|
+
md.hide();
|
406
|
+
}
|
407
|
+
|
339
408
|
updateRightPane() {
|
340
409
|
const
|
341
410
|
se = this.selected_entity,
|
@@ -352,10 +421,7 @@ class Finder {
|
|
352
421
|
if(se.cluster) occ.push(se.cluster.identifier);
|
353
422
|
} else if(se instanceof Product) {
|
354
423
|
// Products "occur" in clusters where they have a position.
|
355
|
-
const
|
356
|
-
for(let i = 0; i < cl.length; i++) {
|
357
|
-
occ.push(cl[i].identifier);
|
358
|
-
}
|
424
|
+
for(const c of se.productPositionClusters) occ.push(c.identifier);
|
359
425
|
} else if(se instanceof Actor) {
|
360
426
|
// Actors "occur" in clusters where they "own" processes or clusters.
|
361
427
|
for(let k in MODEL.processes) if(MODEL.processes.hasOwnProperty(k)) {
|
@@ -374,13 +440,12 @@ class Finder {
|
|
374
440
|
// NOTE: No "occurrence" of datasets or equations.
|
375
441
|
// @@TO DO: identify MODULES (?)
|
376
442
|
// All entities can also occur as chart variables.
|
377
|
-
for(let
|
378
|
-
const c = MODEL.charts[
|
379
|
-
for(
|
380
|
-
const v = c.variables[k];
|
443
|
+
for(let ci = 0; ci < MODEL.charts.length; ci++) {
|
444
|
+
const c = MODEL.charts[ci];
|
445
|
+
for(const v of c.variables) {
|
381
446
|
if(v.object === se || (se instanceof DatasetModifier &&
|
382
447
|
se.identifier === UI.nameToID(v.attribute))) {
|
383
|
-
occ.push(MODEL.chart_id_prefix +
|
448
|
+
occ.push(MODEL.chart_id_prefix + ci);
|
384
449
|
break;
|
385
450
|
}
|
386
451
|
}
|
@@ -437,9 +502,8 @@ class Finder {
|
|
437
502
|
// Check all notes in clusters for their color expressions and field.
|
438
503
|
for(let k in MODEL.clusters) if(MODEL.clusters.hasOwnProperty(k)) {
|
439
504
|
const c = MODEL.clusters[k];
|
440
|
-
for(
|
441
|
-
|
442
|
-
// Look for entity in both note contents and note color expression
|
505
|
+
for(const n of c.notes) {
|
506
|
+
// Look for entity in both note contents and note color expression.
|
443
507
|
if(re.test(n.color.text) || re.test(n.contents)) {
|
444
508
|
xal.push('NOTE');
|
445
509
|
xol.push(n.identifier);
|
@@ -463,11 +527,9 @@ class Finder {
|
|
463
527
|
const c = MODEL.constraints[k];
|
464
528
|
for(let i = 0; i < c.bound_lines.length; i++) {
|
465
529
|
const bl = c.bound_lines[i];
|
466
|
-
for(
|
467
|
-
|
468
|
-
|
469
|
-
xol.push(c.identifier);
|
470
|
-
}
|
530
|
+
for(const sel of bl.selectors) if(re.test(sel.expression.text)) {
|
531
|
+
xal.push('I' + (i + 1));
|
532
|
+
xol.push(c.identifier);
|
471
533
|
}
|
472
534
|
}
|
473
535
|
}
|
@@ -720,28 +782,25 @@ class Finder {
|
|
720
782
|
|
721
783
|
copyAttributesToClipboard(shift) {
|
722
784
|
// Copy relevant entity attributes as tab-separated text to clipboard.
|
723
|
-
// NOTE: All entity types have "get" `attributes` that returns an
|
785
|
+
// NOTE: All entity types have "get" method `attributes` that returns an
|
724
786
|
// object that for each defined attribute (and if model has been
|
725
787
|
// solved also each inferred attribute) has a property with its value.
|
726
|
-
// For dynamic expressions, the expression text is used
|
788
|
+
// For dynamic expressions, the expression text is used.
|
727
789
|
const ea_dict = {A: [], B: [], C: [], D: [], E: [], L: [], P: [], Q: []};
|
728
|
-
|
790
|
+
const e = this.selected_entity;
|
729
791
|
if(shift && e) {
|
730
792
|
ea_dict[e.typeLetter].push(e.attributes);
|
731
793
|
} else {
|
732
|
-
for(
|
733
|
-
e = this.entities[i];
|
734
|
-
ea_dict[e.typeLetter].push(e.attributes);
|
735
|
-
}
|
794
|
+
for(const e of this.entities) ea_dict[e.typeLetter].push(e.attributes);
|
736
795
|
}
|
737
796
|
const
|
738
797
|
seq = ['A', 'B', 'C', 'D', 'E', 'P', 'Q', 'L'],
|
739
798
|
text = [],
|
740
799
|
attr = [];
|
741
|
-
for(
|
800
|
+
for(const etl of seq) {
|
742
801
|
const
|
743
|
-
|
744
|
-
|
802
|
+
ead = ea_dict[etl],
|
803
|
+
atcodes = VM.attribute_codes[etl];
|
745
804
|
if(ead && ead.length > 0) {
|
746
805
|
// No blank line before first entity type.
|
747
806
|
if(text.length > 0) text.push('');
|
@@ -755,14 +814,9 @@ class Finder {
|
|
755
814
|
}
|
756
815
|
text.push(ah);
|
757
816
|
attr.length = 0;
|
758
|
-
for(
|
759
|
-
const
|
760
|
-
|
761
|
-
ac = VM.attribute_codes[etl],
|
762
|
-
al = [ea.name];
|
763
|
-
for(let j = 0; j < ac.length; j++) {
|
764
|
-
if(ea.hasOwnProperty(ac[j])) al.push(ea[ac[j]]);
|
765
|
-
}
|
817
|
+
for(const ea of ead) {
|
818
|
+
const al = [ea.name];
|
819
|
+
for(const ac of atcodes) if(ea.hasOwnProperty(ac)) al.push(ea[ac]);
|
766
820
|
attr.push(al.join('\t'));
|
767
821
|
}
|
768
822
|
attr.sort();
|
@@ -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);
|