linny-r 2.0.7 → 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 +74 -21
- package/static/linny-r.css +22 -16
- package/static/scripts/iro.min.js +7 -7
- package/static/scripts/linny-r-ctrl.js +51 -72
- package/static/scripts/linny-r-gui-actor-manager.js +23 -33
- package/static/scripts/linny-r-gui-chart-manager.js +50 -45
- 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 +143 -32
- 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 +53 -46
- 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 +35 -27
- package/static/scripts/linny-r-vm.js +807 -905
- package/static/show-png.html +0 -113
@@ -52,6 +52,8 @@ class GUIDatasetManager extends DatasetManager {
|
|
52
52
|
'click', () => DATASET_MANAGER.promptForName());
|
53
53
|
document.getElementById('ds-clone-btn').addEventListener(
|
54
54
|
'click', () => DATASET_MANAGER.cloneDataset());
|
55
|
+
document.getElementById('ds-load-btn').addEventListener(
|
56
|
+
'click', () => DATASET_MANAGER.load_csv_modal.show());
|
55
57
|
document.getElementById('ds-delete-btn').addEventListener(
|
56
58
|
'click', () => DATASET_MANAGER.deleteDataset());
|
57
59
|
document.getElementById('ds-filter-btn').addEventListener(
|
@@ -97,6 +99,11 @@ class GUIDatasetManager extends DatasetManager {
|
|
97
99
|
'click', () => DATASET_MANAGER.renameDataset());
|
98
100
|
this.rename_modal.cancel.addEventListener(
|
99
101
|
'click', () => DATASET_MANAGER.rename_modal.hide());
|
102
|
+
this.load_csv_modal = new ModalDialog('load-csv');
|
103
|
+
this.load_csv_modal.ok.addEventListener(
|
104
|
+
'click', () => FILE_MANAGER.loadCSVFile());
|
105
|
+
this.load_csv_modal.cancel.addEventListener(
|
106
|
+
'click', () => DATASET_MANAGER.load_csv_modal.hide());
|
100
107
|
this.conversion_modal = new ModalDialog('convert-modifiers');
|
101
108
|
this.conversion_modal.ok.addEventListener(
|
102
109
|
'click', () => DATASET_MANAGER.convertModifiers());
|
@@ -200,10 +207,9 @@ class GUIDatasetManager extends DatasetManager {
|
|
200
207
|
}
|
201
208
|
|
202
209
|
hideCollapsedRows() {
|
203
|
-
// Hides all rows except top level and immediate children of expanded
|
204
|
-
for(
|
210
|
+
// Hides all rows except top level and immediate children of expanded.
|
211
|
+
for(const row of this.dataset_table.rows) {
|
205
212
|
const
|
206
|
-
row = this.dataset_table.rows[i],
|
207
213
|
// Get the first DIV in the first TD of this row
|
208
214
|
first_div = row.firstChild.firstElementChild,
|
209
215
|
btn = first_div.dataset.prefix === 'x';
|
@@ -267,10 +273,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
267
273
|
pl.pop();
|
268
274
|
}
|
269
275
|
this.hideCollapsedRows();
|
270
|
-
for(
|
271
|
-
const r = this.dataset_table.rows[i];
|
272
|
-
if(r.dataset.prefix === lcp) return r;
|
273
|
-
}
|
276
|
+
for(const r of this.dataset_table.rows) if(r.dataset.prefix === lcp) return r;
|
274
277
|
return null;
|
275
278
|
}
|
276
279
|
|
@@ -321,8 +324,8 @@ class GUIDatasetManager extends DatasetManager {
|
|
321
324
|
names = [],
|
322
325
|
pref_names = {},
|
323
326
|
xids = [];
|
324
|
-
for(
|
325
|
-
const pref = UI.prefixesAndName(MODEL.datasets[
|
327
|
+
for(const dn of dnl) {
|
328
|
+
const pref = UI.prefixesAndName(MODEL.datasets[dn].name);
|
326
329
|
// NOTE: only the name part (so no prefixes at all) will be shown
|
327
330
|
names.push(pref.pop());
|
328
331
|
indent.push(pref.length);
|
@@ -844,8 +847,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
844
847
|
// Update all chartvariables referencing this dataset + old selector.
|
845
848
|
const vl = MODEL.datasetVariables;
|
846
849
|
let cv_cnt = 0;
|
847
|
-
for(
|
848
|
-
const v = vl[i];
|
850
|
+
for(const v of vl) {
|
849
851
|
if(v.object === this.selected_dataset && v.attribute === oldm.selector) {
|
850
852
|
v.attribute = m.selector;
|
851
853
|
cv_cnt++;
|
@@ -942,16 +944,10 @@ class GUIDatasetManager extends DatasetManager {
|
|
942
944
|
return;
|
943
945
|
}
|
944
946
|
prefix += UI.PREFIXER;
|
945
|
-
const
|
946
|
-
|
947
|
-
pml = ds.inferPrefixableModifiers,
|
948
|
-
xl = MODEL.allExpressions,
|
949
|
-
vl = MODEL.datasetVariables,
|
950
|
-
nl = MODEL.notesWithTags;
|
951
|
-
for(let i = 0; i < pml.length; i++) {
|
947
|
+
const dsn = ds.displayName;
|
948
|
+
for(const m of ds.inferPrefixableModifiers) {
|
952
949
|
// Create prefixed dataset with correct default value
|
953
950
|
const
|
954
|
-
m = pml[i],
|
955
951
|
sel = m.selector,
|
956
952
|
newds = MODEL.addDataset(prefix + sel);
|
957
953
|
if(newds) {
|
@@ -969,17 +965,16 @@ class GUIDatasetManager extends DatasetManager {
|
|
969
965
|
const
|
970
966
|
from = dsn + UI.OA_SEPARATOR + sel,
|
971
967
|
to = newds.displayName;
|
972
|
-
for(
|
973
|
-
|
974
|
-
// NOTE: variable should match original dataset + selector
|
968
|
+
for(const v of MODEL.datasetVariables) {
|
969
|
+
// NOTE: variable should match original dataset + selector.
|
975
970
|
if(v.displayName === from) {
|
976
|
-
// Change to new dataset WITHOUT selector
|
971
|
+
// Change to new dataset WITHOUT selector.
|
977
972
|
v.object = newds;
|
978
973
|
v.attribute = '';
|
979
974
|
vcount++;
|
980
975
|
}
|
981
976
|
}
|
982
|
-
// Rename variable in the Sensitivity Analysis
|
977
|
+
// Rename variable in the Sensitivity Analysis.
|
983
978
|
for(let j = 0; j < MODEL.sensitivity_parameters.length; j++) {
|
984
979
|
if(MODEL.sensitivity_parameters[j] === from) {
|
985
980
|
MODEL.sensitivity_parameters[j] = to;
|
@@ -1001,10 +996,8 @@ class GUIDatasetManager extends DatasetManager {
|
|
1001
996
|
// Pattern ends at any character that is invalid for a
|
1002
997
|
// dataset modifier selector (unlike equation names)
|
1003
998
|
'\\s*[^a-zA-Z0-9\\+\\-\\%\\_]', 'gi');
|
1004
|
-
for(
|
1005
|
-
const
|
1006
|
-
x = xl[j],
|
1007
|
-
matches = x.text.match(re);
|
999
|
+
for(const x of MODEL.allExpressions) {
|
1000
|
+
const matches = x.text.match(re);
|
1008
1001
|
if(matches) {
|
1009
1002
|
for(let k = 0; k < matches.length; k++) {
|
1010
1003
|
// NOTE: each match will start with the opening bracket,
|
@@ -1018,10 +1011,8 @@ class GUIDatasetManager extends DatasetManager {
|
|
1018
1011
|
x.code = null;
|
1019
1012
|
}
|
1020
1013
|
}
|
1021
|
-
for(
|
1022
|
-
const
|
1023
|
-
n = nl[j],
|
1024
|
-
matches = n.contents.match(re);
|
1014
|
+
for(const n of MODEL.notesWithTags) {
|
1015
|
+
const matches = n.contents.match(re);
|
1025
1016
|
if(matches) {
|
1026
1017
|
for(let k = 0; k < matches.length; k++) {
|
1027
1018
|
// See NOTE above for the use of `slice` here
|
@@ -1149,4 +1140,124 @@ class GUIDatasetManager extends DatasetManager {
|
|
1149
1140
|
this.updateDialog();
|
1150
1141
|
}
|
1151
1142
|
|
1143
|
+
readCSVData(text) {
|
1144
|
+
// Parse text from uploaded file and create or overwrite datasets.
|
1145
|
+
const
|
1146
|
+
lines = text.trim().split(/\n/),
|
1147
|
+
n = lines.length;
|
1148
|
+
if(n < 2) {
|
1149
|
+
UI.warn('Data must have at least 2 lines: dataset names and default values');
|
1150
|
+
return false;
|
1151
|
+
}
|
1152
|
+
// Infer most likely delimiter.
|
1153
|
+
const
|
1154
|
+
tabs = text.split('\t').length - 1,
|
1155
|
+
tab0 = lines[0].split('\t').length - 1,
|
1156
|
+
tab1 = lines[1].split('\t').length - 1,
|
1157
|
+
semic0 = lines[0].split(';').length - 1,
|
1158
|
+
semics = text.split(';').length - 1 - semic0;
|
1159
|
+
let sep = '\t';
|
1160
|
+
if(!tabs || tab0 !== tab1) {
|
1161
|
+
// Tab is most likely NOT the separator.
|
1162
|
+
sep = (semics ? ';' : ',');
|
1163
|
+
}
|
1164
|
+
const
|
1165
|
+
parts = lines[0].split(sep),
|
1166
|
+
dsn = [];
|
1167
|
+
let quoted = false;
|
1168
|
+
for(const p of parts) {
|
1169
|
+
const
|
1170
|
+
swq = /^\"(\"\")*($|[^\"])/.test(p),
|
1171
|
+
ewq = p.endsWith('"');
|
1172
|
+
if(!quoted && swq && !ewq) {
|
1173
|
+
dsn.push(p);
|
1174
|
+
quoted = true;
|
1175
|
+
} else if(quoted) {
|
1176
|
+
dsn[dsn.length - 1] += sep + p;
|
1177
|
+
quoted = !ewq;
|
1178
|
+
} else {
|
1179
|
+
dsn.push(p);
|
1180
|
+
}
|
1181
|
+
}
|
1182
|
+
for(let i = 0; i < dsn.length; i++) {
|
1183
|
+
const n = unquoteCSV(dsn[i].trim());
|
1184
|
+
if(!UI.validName(n)) {
|
1185
|
+
UI.warn(`Invalid dataset name "${n}" in column ${i}`);
|
1186
|
+
return false;
|
1187
|
+
}
|
1188
|
+
dsn[i] = n;
|
1189
|
+
}
|
1190
|
+
const
|
1191
|
+
ncol = dsn.length,
|
1192
|
+
dsa = [],
|
1193
|
+
dsdv = lines[1].split(sep);
|
1194
|
+
if(dsdv.length !== ncol) {
|
1195
|
+
UI.warn(`Number of default values (${dsdv.length}) does not match number of dataset names (${ncol})`);
|
1196
|
+
return false;
|
1197
|
+
}
|
1198
|
+
for(let i = 0; i < dsdv.length; i++) {
|
1199
|
+
const
|
1200
|
+
v = dsdv[i].trim(),
|
1201
|
+
sf = safeStrToFloat(v, NaN);
|
1202
|
+
if(isNaN(sf)) {
|
1203
|
+
UI.warn(`Invalid default value "${v}" in column ${i}`);
|
1204
|
+
return false;
|
1205
|
+
} else {
|
1206
|
+
dsa.push([sf]);
|
1207
|
+
}
|
1208
|
+
}
|
1209
|
+
for(let i = 2; i < n; i++) {
|
1210
|
+
const dsv = lines[i].trim().split(sep);
|
1211
|
+
if(dsv.length !== ncol) {
|
1212
|
+
UI.warn(`Number of values (${dsv.length}) on line ${i} does not match number of dataset names (${ncol})`);
|
1213
|
+
return false;
|
1214
|
+
}
|
1215
|
+
for(let j = 0; j < dsv.length; j++) {
|
1216
|
+
const
|
1217
|
+
v = dsv[j].trim(),
|
1218
|
+
sf = safeStrToFloat(v, '');
|
1219
|
+
if(sf === '' && v !== '') {
|
1220
|
+
UI.warn(`Invalid numerical value "${v}" for <strong>${dsn[j]}</strong> on line ${i}`);
|
1221
|
+
return false;
|
1222
|
+
}
|
1223
|
+
}
|
1224
|
+
}
|
1225
|
+
// Add or update datasets.
|
1226
|
+
let added = 0,
|
1227
|
+
updated = 0;
|
1228
|
+
for(let i = 0; i < dsn.length; i++) {
|
1229
|
+
const
|
1230
|
+
n = dsn[i],
|
1231
|
+
id = UI.nameToID(n),
|
1232
|
+
ods = MODEL.namedObjectByID(id),
|
1233
|
+
ds = ods || MODEL.addDataset(n);
|
1234
|
+
// NOTE: `ds` will now be either a new dataset or an existing one.
|
1235
|
+
if(ds) {
|
1236
|
+
// Keep track of added/updated datasets.
|
1237
|
+
const
|
1238
|
+
odv = ds.default_value,
|
1239
|
+
odata = ds.dataString;
|
1240
|
+
ds.default_value = safeStrToFloat(dsdv[i], 0);
|
1241
|
+
ds.data = dsa[i];
|
1242
|
+
if(ods) {
|
1243
|
+
if(ds.default_value !== odv || odata !== ds.dataString) updated++;
|
1244
|
+
} else {
|
1245
|
+
added++;
|
1246
|
+
}
|
1247
|
+
ds.computeStatistics();
|
1248
|
+
}
|
1249
|
+
}
|
1250
|
+
// Notify modeler of changes (if any).
|
1251
|
+
let msg = 'No datasets added or updated';
|
1252
|
+
if(added) {
|
1253
|
+
msg = pluralS(added, 'dataset') + ' added';
|
1254
|
+
if(updated) msg += ', ' + pluralS(updated, 'dataset') + ' updated';
|
1255
|
+
} else if(updated) {
|
1256
|
+
msg = pluralS(updated, 'dataset') + ' updated';
|
1257
|
+
}
|
1258
|
+
UI.notify(msg);
|
1259
|
+
this.updateDialog();
|
1260
|
+
return true;
|
1261
|
+
}
|
1262
|
+
|
1152
1263
|
} // END of class GUIDatasetManager
|
@@ -280,10 +280,10 @@ class DocumentationManager {
|
|
280
280
|
|
281
281
|
clearEntity(list) {
|
282
282
|
// To be called when entities are deleted
|
283
|
-
if(list.indexOf(this.entity) >= 0) {
|
283
|
+
if(list === true || list.indexOf(this.entity) >= 0) {
|
284
284
|
this.stopEditing();
|
285
285
|
this.entity = null;
|
286
|
-
this.title.innerHTML = '
|
286
|
+
this.title.innerHTML = 'About Linny-R';
|
287
287
|
this.viewer.innerHTML = this.about_linny_r;
|
288
288
|
}
|
289
289
|
}
|
@@ -540,18 +540,17 @@ class DocumentationManager {
|
|
540
540
|
|
541
541
|
showArrowLinks(arrow) {
|
542
542
|
// Show list of links represented by a composite arrow.
|
543
|
-
const
|
544
|
-
n = arrow.links.length,
|
545
|
-
msg = 'Arrow represents ' + pluralS(n, 'link');
|
543
|
+
const msg = 'Arrow represents ' + pluralS(arrow.links.length, 'link');
|
546
544
|
UI.setMessage(msg);
|
547
545
|
if(this.visible && !this.editing) {
|
548
546
|
// Set the dialog title.
|
549
547
|
this.title.innerHTML = msg;
|
550
548
|
// Show list.
|
551
549
|
const lis = [];
|
552
|
-
let
|
553
|
-
|
554
|
-
|
550
|
+
let dn,
|
551
|
+
c,
|
552
|
+
af;
|
553
|
+
for(const l of arrow.links) {
|
555
554
|
dn = l.displayName;
|
556
555
|
if(l.from_node instanceof Process) {
|
557
556
|
c = UI.color.produced;
|
@@ -600,9 +599,7 @@ class DocumentationManager {
|
|
600
599
|
this.title.innerHTML = msg;
|
601
600
|
// Show list.
|
602
601
|
const lis = [];
|
603
|
-
for(
|
604
|
-
lis.push(`<li>${iol[i].displayName}</li>`);
|
605
|
-
}
|
602
|
+
for(const io of iol) lis.push(`<li>${io.displayName}</li>`);
|
606
603
|
lis.sort(ciCompare);
|
607
604
|
this.viewer.innerHTML = `<ul>${lis.join('')}</ul>`;
|
608
605
|
}
|
@@ -714,9 +711,8 @@ class DocumentationManager {
|
|
714
711
|
this.differenceAsTable(d.settings));
|
715
712
|
if('units' in d) html.push('<h2>Units</h2>',
|
716
713
|
this.differenceAsTable(d.units));
|
717
|
-
for(
|
718
|
-
|
719
|
-
if(e in d) html.push('<h2>' + this.propertyName(e) + '</h2>',
|
714
|
+
for(const e of UI.MC.ENTITY_PROPS) if(e in d) {
|
715
|
+
html.push('<h2>' + this.propertyName(e) + '</h2>',
|
720
716
|
this.differenceAsTable(d[e]));
|
721
717
|
}
|
722
718
|
if('charts' in d) html.push('<h2><em>Charts</em></h2>',
|
@@ -759,9 +755,7 @@ class DocumentationManager {
|
|
759
755
|
const
|
760
756
|
html = ['<table>'],
|
761
757
|
keys = Object.keys(d).sort();
|
762
|
-
for(
|
763
|
-
html.push(this.differenceAsTableRow(d, keys[i]));
|
764
|
-
}
|
758
|
+
for(const k of keys) html.push(this.differenceAsTableRow(d, k));
|
765
759
|
html.push('</table>');
|
766
760
|
return html.join('\n');
|
767
761
|
}
|
@@ -148,7 +148,7 @@ class EquationManager {
|
|
148
148
|
}
|
149
149
|
|
150
150
|
updateDialog() {
|
151
|
-
// Updates equation list, highlighting selected equation (if any)
|
151
|
+
// Updates equation list, highlighting selected equation (if any).
|
152
152
|
const
|
153
153
|
ed = MODEL.equations_dataset,
|
154
154
|
ml = [],
|
@@ -160,34 +160,36 @@ class EquationManager {
|
|
160
160
|
this.outcome_btn.classList.add('not-selected');
|
161
161
|
}
|
162
162
|
let smid = 'eqmtr';
|
163
|
+
// NOTE> Selector list `msl` contains names, not IDs.
|
163
164
|
for(let i = 0; i < msl.length; i++) {
|
164
165
|
const
|
165
|
-
|
166
|
-
|
167
|
-
|
166
|
+
id = UI.nameToID(msl[i]),
|
167
|
+
m = ed.modifiers[id],
|
168
|
+
sel = safeDoubleQuotes(m.selector),
|
169
|
+
expr = m.expression,
|
170
|
+
wild = (sel.indexOf('??') >= 0),
|
171
|
+
method = sel.startsWith(':'),
|
168
172
|
multi = (this.multi_line ? '-multi' : ''),
|
169
|
-
issue = (
|
170
|
-
(
|
171
|
-
clk =
|
172
|
-
|
173
|
-
|
174
|
-
m.identifier + '\', event.shiftKey);"' : '');
|
173
|
+
issue = (expr.compile_issue ? ' compile-issue' :
|
174
|
+
(expr.compute_issue ? ' compute-issue' : '')),
|
175
|
+
clk = `" onclick="EQUATION_MANAGER.selectModifier(event, '${id}'`,
|
176
|
+
mover = (!method ? '' :
|
177
|
+
`onmouseover="EQUATION_MANAGER.showInfo('${id}', event.shiftKey);"`);
|
175
178
|
if(m === sm) smid += i;
|
176
179
|
ml.push(['<tr id="eqmtr', i, '" class="dataset-modif',
|
177
180
|
(m === sm ? ' sel-set' : ''),
|
178
181
|
'"><td class="equation-selector',
|
179
182
|
(method ? ' method' : ''),
|
180
183
|
// Display in gray when method cannot be applied.
|
181
|
-
(
|
182
|
-
(
|
184
|
+
(expr.noMethodObject ? ' no-object' : ''),
|
185
|
+
(expr.isStatic ? '' : ' it'), issue,
|
183
186
|
(wild ? ' wildcard' : ''), clk, ', false);"', mover, '>',
|
184
187
|
(m.outcome_equation ? '<span class="outcome"></span>' : ''),
|
185
|
-
(wild ? wildcardFormat(
|
188
|
+
(wild ? wildcardFormat(sel) : sel),
|
186
189
|
'</td><td class="equation-expression', multi, issue,
|
187
190
|
(issue ? '"title="' +
|
188
|
-
safeDoubleQuotes(
|
189
|
-
|
190
|
-
clk, ');">', m.expression.text, '</td></tr>'].join(''));
|
191
|
+
safeDoubleQuotes(expr.compile_issue || expr.compute_issue) : ''),
|
192
|
+
clk, ');">', expr.text, '</td></tr>'].join(''));
|
191
193
|
}
|
192
194
|
this.table.innerHTML = ml.join('');
|
193
195
|
this.scroll_area.style.display = 'block';
|
@@ -208,10 +210,10 @@ class EquationManager {
|
|
208
210
|
|
209
211
|
selectModifier(event, id, x=true) {
|
210
212
|
// Select modifier, or when Alt- or double-clicked, edit its expression
|
211
|
-
// or the equation name (= name of the modifier)
|
213
|
+
// or the equation name (= name of the modifier).
|
212
214
|
if(MODEL.equations_dataset) {
|
213
215
|
const
|
214
|
-
m = MODEL.equations_dataset.modifiers[
|
216
|
+
m = MODEL.equations_dataset.modifiers[id] || null,
|
215
217
|
edit = event.altKey || this.doubleClicked(m);
|
216
218
|
this.selected_modifier = m;
|
217
219
|
if(m && edit) {
|
@@ -327,10 +329,8 @@ class EquationManager {
|
|
327
329
|
}
|
328
330
|
// Update all chartvariables referencing this dataset + old selector
|
329
331
|
let cv_cnt = 0;
|
330
|
-
for(
|
331
|
-
const
|
332
|
-
for(let j = 0; j < c.variables.length; j++) {
|
333
|
-
const v = c.variables[j];
|
332
|
+
for(const c of MODEL.charts) {
|
333
|
+
for(const v of c.variables) {
|
334
334
|
if(v.object === MODEL.equations_dataset && v.attribute === olds) {
|
335
335
|
v.attribute = m.selector;
|
336
336
|
cv_cnt++;
|