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.
Files changed (31) hide show
  1. package/README.md +3 -40
  2. package/package.json +1 -1
  3. package/server.js +19 -157
  4. package/static/index.html +74 -21
  5. package/static/linny-r.css +22 -16
  6. package/static/scripts/iro.min.js +7 -7
  7. package/static/scripts/linny-r-ctrl.js +51 -72
  8. package/static/scripts/linny-r-gui-actor-manager.js +23 -33
  9. package/static/scripts/linny-r-gui-chart-manager.js +50 -45
  10. package/static/scripts/linny-r-gui-constraint-editor.js +6 -10
  11. package/static/scripts/linny-r-gui-controller.js +254 -230
  12. package/static/scripts/linny-r-gui-dataset-manager.js +143 -32
  13. package/static/scripts/linny-r-gui-documentation-manager.js +11 -17
  14. package/static/scripts/linny-r-gui-equation-manager.js +22 -22
  15. package/static/scripts/linny-r-gui-experiment-manager.js +102 -129
  16. package/static/scripts/linny-r-gui-file-manager.js +53 -46
  17. package/static/scripts/linny-r-gui-finder.js +105 -51
  18. package/static/scripts/linny-r-gui-model-autosaver.js +2 -4
  19. package/static/scripts/linny-r-gui-monitor.js +35 -41
  20. package/static/scripts/linny-r-gui-paper.js +42 -70
  21. package/static/scripts/linny-r-gui-power-grid-manager.js +31 -34
  22. package/static/scripts/linny-r-gui-receiver.js +1 -2
  23. package/static/scripts/linny-r-gui-repository-browser.js +44 -46
  24. package/static/scripts/linny-r-gui-scale-unit-manager.js +32 -32
  25. package/static/scripts/linny-r-gui-sensitivity-analysis.js +61 -67
  26. package/static/scripts/linny-r-gui-undo-redo.js +94 -95
  27. package/static/scripts/linny-r-milp.js +20 -24
  28. package/static/scripts/linny-r-model.js +1832 -2248
  29. package/static/scripts/linny-r-utils.js +35 -27
  30. package/static/scripts/linny-r-vm.js +807 -905
  31. 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(let i = 0; i < this.dataset_table.rows.length; i++) {
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(let i = 0; i < this.dataset_table.rows.length; i++) {
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(let i = 0; i < dnl.length; i++) {
325
- const pref = UI.prefixesAndName(MODEL.datasets[dnl[i]].name);
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(let i = 0; i < vl.length; i++) {
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
- dsn = ds.displayName,
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(let j = 0; j < vl.length; j++) {
973
- const v = vl[j];
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(let j = 0; j < xl.length; j++) {
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(let j = 0; j < nl.length; j++) {
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 = 'Information and documentation';
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 l, dn, c, af;
553
- for(let i = 0; i < n; i++) {
554
- l = arrow.links[i];
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(let i = 0; i < iol.length; i++) {
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(let i = 0; i < UI.MC.ENTITY_PROPS.length; i++) {
718
- const e = UI.MC.ENTITY_PROPS[i];
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(let i = 0; i < keys.length; i++) {
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
- m = ed.modifiers[UI.nameToID(msl[i])],
166
- wild = (m.selector.indexOf('??') >= 0),
167
- method = m.selector.startsWith(':'),
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 = (m.expression.compile_issue ? ' compile-issue' :
170
- (m.expression.compute_issue ? ' compute-issue' : '')),
171
- clk = '" onclick="EQUATION_MANAGER.selectModifier(event, \'' +
172
- escapedSingleQuotes(m.selector) + '\'',
173
- mover = (method ? ' onmouseover="EQUATION_MANAGER.showInfo(\'' +
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
- (m.expression.noMethodObject ? ' no-object' : ''),
182
- (m.expression.isStatic ? '' : ' it'), issue,
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(m.selector) : m.selector),
188
+ (wild ? wildcardFormat(sel) : sel),
186
189
  '</td><td class="equation-expression', multi, issue,
187
190
  (issue ? '"title="' +
188
- safeDoubleQuotes(m.expression.compile_issue ||
189
- m.expression.compute_issue) : ''),
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[UI.nameToID(id)] || null,
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(let i = 0; i < MODEL.charts.length; i++) {
331
- const c = MODEL.charts[i];
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++;