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.
Files changed (36) hide show
  1. package/README.md +3 -40
  2. package/package.json +6 -2
  3. package/server.js +19 -157
  4. package/static/images/solve-not-same-changed.png +0 -0
  5. package/static/images/solve-not-same-not-changed.png +0 -0
  6. package/static/images/solve-same-changed.png +0 -0
  7. package/static/images/solve-same-not-changed.png +0 -0
  8. package/static/index.html +137 -20
  9. package/static/linny-r.css +260 -23
  10. package/static/scripts/iro.min.js +7 -7
  11. package/static/scripts/linny-r-ctrl.js +126 -85
  12. package/static/scripts/linny-r-gui-actor-manager.js +23 -33
  13. package/static/scripts/linny-r-gui-chart-manager.js +56 -53
  14. package/static/scripts/linny-r-gui-constraint-editor.js +10 -14
  15. package/static/scripts/linny-r-gui-controller.js +644 -260
  16. package/static/scripts/linny-r-gui-dataset-manager.js +64 -66
  17. package/static/scripts/linny-r-gui-documentation-manager.js +11 -17
  18. package/static/scripts/linny-r-gui-equation-manager.js +22 -22
  19. package/static/scripts/linny-r-gui-experiment-manager.js +124 -141
  20. package/static/scripts/linny-r-gui-expression-editor.js +26 -12
  21. package/static/scripts/linny-r-gui-file-manager.js +42 -48
  22. package/static/scripts/linny-r-gui-finder.js +294 -55
  23. package/static/scripts/linny-r-gui-model-autosaver.js +2 -4
  24. package/static/scripts/linny-r-gui-monitor.js +35 -41
  25. package/static/scripts/linny-r-gui-paper.js +42 -70
  26. package/static/scripts/linny-r-gui-power-grid-manager.js +31 -34
  27. package/static/scripts/linny-r-gui-receiver.js +1 -2
  28. package/static/scripts/linny-r-gui-repository-browser.js +44 -46
  29. package/static/scripts/linny-r-gui-scale-unit-manager.js +32 -32
  30. package/static/scripts/linny-r-gui-sensitivity-analysis.js +61 -67
  31. package/static/scripts/linny-r-gui-undo-redo.js +94 -95
  32. package/static/scripts/linny-r-milp.js +20 -24
  33. package/static/scripts/linny-r-model.js +1932 -2274
  34. package/static/scripts/linny-r-utils.js +27 -27
  35. package/static/scripts/linny-r-vm.js +900 -998
  36. package/static/show-png.html +0 -113
@@ -301,18 +301,16 @@ class GUIExperimentManager extends ExperimentManager {
301
301
  xl = [],
302
302
  xtl = [],
303
303
  sx = this.selected_experiment;
304
- for(let i = 0; i < MODEL.experiments.length; i++) {
305
- xtl.push(MODEL.experiments[i].title);
306
- }
304
+ for(const x of MODEL.experiments) xtl.push(x.title);
307
305
  xtl.sort(ciCompare);
308
- for(let i = 0; i < xtl.length; i++) {
306
+ for(const xt of xtl) {
309
307
  const
310
- xi = MODEL.indexOfExperiment(xtl[i]),
308
+ xi = MODEL.indexOfExperiment(xt),
311
309
  x = (xi < 0 ? null : MODEL.experiments[xi]);
312
310
  xl.push(['<tr class="experiment',
313
311
  (x == sx ? ' sel-set' : ''),
314
312
  '" onclick="EXPERIMENT_MANAGER.selectExperiment(\'',
315
- escapedSingleQuotes(xtl[i]),
313
+ escapedSingleQuotes(xt),
316
314
  '\');" onmouseover="EXPERIMENT_MANAGER.showInfo(', xi,
317
315
  ', event.shiftKey);"><td>', x.title, '</td></tr>'].join(''));
318
316
  }
@@ -369,9 +367,7 @@ class GUIExperimentManager extends ExperimentManager {
369
367
  dim_count.innerHTML = pluralS(x.available_dimensions.length,
370
368
  'more dimension');
371
369
  x.inferActualDimensions();
372
- for(let i = 0; i < x.actual_dimensions.length; i++) {
373
- x.actual_dimensions[i].sort(compareSelectors);
374
- }
370
+ for(const ad of x.actual_dimensions) ad.sort(compareSelectors);
375
371
  x.inferCombinations();
376
372
  //x.combinations.sort(compareCombinations);
377
373
  combi_count.innerHTML = pluralS(x.combinations.length, 'combination');
@@ -445,19 +441,25 @@ class GUIExperimentManager extends ExperimentManager {
445
441
  }
446
442
 
447
443
  newExperiment() {
448
- const n = this.new_modal.element('name').value.trim();
449
- const x = MODEL.addExperiment(n);
450
- if(x) {
451
- this.new_modal.hide();
452
- this.selected_experiment = x;
453
- this.updateDialog();
444
+ // NOTE: Title must be a "clean" name: no \ or | and spacing reduced to
445
+ // a single space to permit using it unambiguously in experiment result
446
+ // specifiers of variable names.
447
+ const n = UI.cleanName(this.new_modal.element('name').value);
448
+ if(n) {
449
+ const x = MODEL.addExperiment(n);
450
+ if(x) {
451
+ this.new_modal.hide();
452
+ this.selected_experiment = x;
453
+ this.updateDialog();
454
+ }
455
+ } else {
456
+ this.new_modal.element('name').focus();
457
+ return;
454
458
  }
455
459
  }
456
460
 
457
461
  promptForName() {
458
462
  if(this.selected_experiment) {
459
- this.rename_modal.element('former-name').innerHTML =
460
- this.selected_experiment.title;
461
463
  this.rename_modal.element('name').value = '';
462
464
  this.rename_modal.show('name');
463
465
  }
@@ -468,12 +470,16 @@ class GUIExperimentManager extends ExperimentManager {
468
470
  const
469
471
  nel = this.rename_modal.element('name'),
470
472
  n = UI.cleanName(nel.value);
471
- // Show modeler the "cleaned" new name
473
+ // Show modeler the "cleaned" new name.
472
474
  nel.value = n;
473
- // Keep prompt open if title is empty string
475
+ // Keep prompt open if cleaned title is empty string, or identifies
476
+ // an existing experiment.
477
+ nel.focus();
474
478
  if(n) {
475
- // Warn modeler if name already in use for some experiment
476
- if(MODEL.indexOfExperiment(n) >= 0) {
479
+ // Warn modeler if name already in use for some experiment, other than
480
+ // the selected experiment (as upper/lower case changes must be possible).
481
+ if(MODEL.indexOfExperiment(n) >= 0 &&
482
+ n.toLowerCase() !== this.selected_experiment.title.toLowerCase()) {
477
483
  UI.warn(`An experiment with title "${n}" already exists`);
478
484
  } else {
479
485
  this.selected_experiment.title = n;
@@ -532,9 +538,9 @@ class GUIExperimentManager extends ExperimentManager {
532
538
  ol = [],
533
539
  ov = MODEL.outcomeNames,
534
540
  vl = [...ov];
535
- for(let i = 0; i < x.variables.length; i++) {
541
+ for(const v of x.variables) {
536
542
  const
537
- vn = x.variables[i].displayName,
543
+ vn = v.displayName,
538
544
  oi = ov.indexOf(vn);
539
545
  // If an outcome dataset or equation is plotted in an experiment
540
546
  // chart, remove its name from the outcome variable list.
@@ -546,8 +552,7 @@ class GUIExperimentManager extends ExperimentManager {
546
552
  // name will not be in the list (and its old name cannot be inferred)
547
553
  // so then clear it.
548
554
  if(vl.indexOf(x.selected_variable) < 0) x.selected_variable = '';
549
- for(let i = 0; i < vl.length; i++) {
550
- const vn = vl[i];
555
+ for(const vn of vl) {
551
556
  // NOTE: FireFox selector dropdown areas have a pale gray
552
557
  // background that darkens when color is set, so always set it
553
558
  // to white (like Chrome). Then set color of outcome variables
@@ -565,12 +570,12 @@ class GUIExperimentManager extends ExperimentManager {
565
570
  }
566
571
 
567
572
  drawTable() {
568
- // Draw experimental design as table
573
+ // Draw experimental design as table.
569
574
  const x = this.selected_experiment;
570
575
  if(x) {
571
576
  this.clean_columns = [];
572
577
  this.clean_rows = [];
573
- // Calculate the actual number of columns and rows of the table
578
+ // Calculate the actual number of columns and rows of the table.
574
579
  const
575
580
  coldims = x.configuration_dims + x.column_scenario_dims,
576
581
  rowdims = x.actual_dimensions.length - coldims,
@@ -795,32 +800,28 @@ class GUIExperimentManager extends ExperimentManager {
795
800
  const combi = x.combinations[n];
796
801
  info.title = `Combination: <tt>${tupelString(combi)}</tt>`;
797
802
  const html = [], list = [];
798
- for(let i = 0; i < combi.length; i++) {
799
- const sel = combi[i];
803
+ for(const sel of combi) {
800
804
  html.push('<h3>Selector <tt>', sel, '</tt></h3>');
801
- // List associated model settings (if any)
805
+ // List associated model settings (if any).
802
806
  list.length = 0;
803
- for(let j = 0; j < x.settings_selectors.length; j++) {
804
- const ss = x.settings_selectors[j].split('|');
805
- if(sel === ss[0]) list.push(ss[1]);
807
+ for(const ss of x.settings_selectors) {
808
+ const tuple = ss.split('|');
809
+ if(sel === tuple[0]) list.push(tuple[1]);
806
810
  }
807
811
  if(list.length > 0) {
808
812
  html.push('<p><em>Model settings:</em> <tt>', list.join(';'),
809
813
  '</tt></p>');
810
814
  }
811
- // List associated actor settings (if any)
815
+ // List associated actor settings (if any).
812
816
  list.length = 0;
813
- for(let j = 0; j < x.actor_selectors.length; j++) {
814
- const as = x.actor_selectors[j];
815
- if(sel === as.selector) {
816
- list.push(as.round_sequence);
817
- }
817
+ for(const as of x.actor_selectors) {
818
+ if(sel === as.selector) list.push(as.round_sequence);
818
819
  }
819
820
  if(list.length > 0) {
820
821
  html.push('<p><em>Actor settings:</em> <tt>', list.join(';'),
821
822
  '</tt></p>');
822
823
  }
823
- // List associated datasets (if any)
824
+ // List associated datasets (if any).
824
825
  list.length = 0;
825
826
  for(let id in MODEL.datasets) if(MODEL.datasets.hasOwnProperty(id)) {
826
827
  const ds = MODEL.datasets[id];
@@ -888,10 +889,8 @@ class GUIExperimentManager extends ExperimentManager {
888
889
  // Set reference column indices so that values for the reference|
889
890
  // configuration can be displayed in orange.
890
891
  const ref_conf_indices = [];
891
- for(let i = 0; i < x.runs.length; i++) {
892
- const
893
- r = x.runs[i],
894
- rr = r.results[rri];
892
+ for(const r of x.runs) {
893
+ const rr = r.results[rri];
895
894
  if(!rr) {
896
895
  data.push(VM.UNDEFINED);
897
896
  } else if(x.selected_scale === 'sec') {
@@ -918,12 +917,12 @@ class GUIExperimentManager extends ExperimentManager {
918
917
  }
919
918
  // Scale data as selected.
920
919
  const scaled = data.slice();
921
- // NOTE: scale only after the experiment has been completed AND
922
- // configurations have been defined (otherwise comparison is pointless)
920
+ // NOTE: Scale only after the experiment has been completed AND
921
+ // configurations have been defined (otherwise comparison is pointless).
923
922
  if(x.completed && this.nr_of_configurations > 0) {
924
923
  const n = scaled.length / this.nr_of_configurations;
925
924
  if(x.selected_scale === 'dif') {
926
- // Compute difference: current configuration - reference configuration
925
+ // Compute difference: current configuration - reference configuration.
927
926
  const rc = x.reference_configuration;
928
927
  for(let i = 0; i < this.nr_of_configurations; i++) {
929
928
  if(i != rc) {
@@ -932,47 +931,36 @@ class GUIExperimentManager extends ExperimentManager {
932
931
  }
933
932
  }
934
933
  }
935
- // Set difference for reference configuration itself to 0
934
+ // Set difference for reference configuration itself to 0.
936
935
  for(let i = 0; i < n; i++) {
937
936
  const index = rc * n + i;
938
937
  scaled[index] = 0;
939
938
  ref_conf_indices.push(index);
940
939
  }
941
940
  } else if(x.selected_scale === 'reg') {
942
- // Compute regret: current config - high value config in same scenario
941
+ // Compute regret: current config - high value config in same scenario.
943
942
  for(let i = 0; i < n; i++) {
944
- // Get high value
943
+ // Get high value.
945
944
  let high = VM.MINUS_INFINITY;
946
945
  for(let j = 0; j < this.nr_of_configurations; j++) {
947
946
  high = Math.max(high, scaled[j * n + i]);
948
947
  }
949
- // Scale (so high config always has value 0)
948
+ // Scale (so high config always has value 0).
950
949
  for(let j = 0; j < this.nr_of_configurations; j++) {
951
950
  scaled[j * n + i] -= high;
952
951
  }
953
952
  }
954
953
  }
955
954
  }
956
- // For color scales, compute normalized scores
957
- let normalized = scaled.slice(),
958
- high = VM.MINUS_INFINITY,
959
- low = VM.PLUS_INFINITY;
960
- for(let i = 0; i < normalized.length; i++) {
961
- high = Math.max(high, normalized[i]);
962
- low = Math.min(low, normalized[i]);
963
- }
964
- // Avoid too small value ranges
965
- const range = (high - low < VM.NEAR_ZERO ? 0 : high - low);
966
- if(range > 0) {
967
- for(let i = 0; i < normalized.length; i++) {
968
- normalized[i] = (normalized[i] - low) / range;
969
- }
970
- }
971
- // Format data such that they all have same number of decimals
972
- let formatted = [];
973
- for(let i = 0; i < scaled.length; i++) {
974
- formatted.push(VM.sig4Dig(scaled[i]));
975
- }
955
+ // For color scales, compute normalized scores.
956
+ const
957
+ high = Math.max(...scaled),
958
+ low = Math.min(...scaled),
959
+ // Avoid too small value ranges.
960
+ range = (high - low < VM.NEAR_ZERO ? 0 : high - low),
961
+ normalized = scaled.map((v) => range ? (v - low) / range : v),
962
+ formatted = scaled.map((v) => VM.sig4Dig(v));
963
+ // Format data such that they all have same number of decimals.
976
964
  uniformDecimals(formatted);
977
965
  // Display formatted data in cells.
978
966
  for(let i = 0; i < x.combinations.length; i++) {
@@ -1072,11 +1060,11 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1072
1060
  const
1073
1061
  spl = this.viewer.getElementsByClassName('spin-plus'),
1074
1062
  rem = (x.configuration_dims + x.column_scenario_dims < xdims);
1075
- for(let i = 0; i < spl.length; i++) {
1063
+ for(const sp of spl) {
1076
1064
  if(rem) {
1077
- spl.item(i).classList.remove('no-spin');
1065
+ sp.classList.remove('no-spin');
1078
1066
  } else {
1079
- spl.item(i).classList.add('no-spin');
1067
+ sp.classList.add('no-spin');
1080
1068
  }
1081
1069
  }
1082
1070
  if(dir != 0 ) this.drawTable();
@@ -1103,9 +1091,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1103
1091
  x.selected_color_scale = cs;
1104
1092
  this.color_scale.set(cs);
1105
1093
  const csl = this.viewer.getElementsByClassName('color-scale');
1106
- for(let i = 0; i < csl.length; i++) {
1107
- csl.item(i).classList.remove('sel-cs');
1108
- }
1094
+ for(const cs of csl) cs.classList.remove('sel-cs');
1109
1095
  document.getElementById(`xv-${cs}-scale`).classList.add('sel-cs');
1110
1096
  }
1111
1097
  this.updateData();
@@ -1189,14 +1175,14 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1189
1175
  }
1190
1176
 
1191
1177
  editIteratorRanges() {
1192
- // Open dialog for editing iterator ranges
1178
+ // Open dialog for editing iterator ranges.
1193
1179
  const
1194
1180
  x = this.selected_experiment,
1195
1181
  md = this.iterator_modal,
1196
1182
  il = ['i', 'j', 'k'];
1197
1183
  if(x) {
1198
1184
  // NOTE: there are always 3 iterators (i, j k) so these have fixed
1199
- // FROM and TO input fields in the dialog
1185
+ // FROM and TO input fields in the dialog.
1200
1186
  for(let i = 0; i < 3; i++) {
1201
1187
  const k = il[i];
1202
1188
  md.element(k + '-from').value = x.iterator_ranges[i][0];
@@ -1211,8 +1197,8 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1211
1197
  x = this.selected_experiment,
1212
1198
  md = this.iterator_modal;
1213
1199
  if(x) {
1214
- // First validate all input fields (must be integer values)
1215
- // NOTE: test using a copy so as not to overwrite values until OK
1200
+ // First validate all input fields (must be integer values).
1201
+ // NOTE: Test using a copy so as not to overwrite values until OK.
1216
1202
  const
1217
1203
  il = ['i', 'j', 'k'],
1218
1204
  ir = [[0, 0], [0, 0], [0, 0]],
@@ -1227,7 +1213,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1227
1213
  t = el.value.trim() || '0';
1228
1214
  if(t === '' || re.test(t)) el = null;
1229
1215
  }
1230
- // NULL value signals that field inputs are valid
1216
+ // NULL value signals that field inputs are valid.
1231
1217
  if(el === null) {
1232
1218
  ir[i] = [f, t];
1233
1219
  } else {
@@ -1236,7 +1222,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1236
1222
  return;
1237
1223
  }
1238
1224
  }
1239
- // Input validated, so modify the iterator dimensions
1225
+ // Input validated, so modify the iterator dimensions.
1240
1226
  x.iterator_ranges = ir;
1241
1227
  this.updateDialog();
1242
1228
  }
@@ -1244,17 +1230,17 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1244
1230
  }
1245
1231
 
1246
1232
  editSettingsDimensions() {
1247
- // Open dialog for editing model settings dimensions
1233
+ // Open dialog for editing model settings dimensions.
1248
1234
  const x = this.selected_experiment, rows = [];
1249
1235
  if(x) {
1250
- // Initialize selector list
1236
+ // Initialize selector list.
1251
1237
  for(let i = 0; i < x.settings_selectors.length; i++) {
1252
1238
  const sel = x.settings_selectors[i].split('|');
1253
1239
  rows.push('<tr onclick="EXPERIMENT_MANAGER.editSettingsSelector(', i,
1254
1240
  ');"><td width="25%">', sel[0], '</td><td>', sel[1], '</td></tr>');
1255
1241
  }
1256
1242
  this.settings_modal.element('s-table').innerHTML = rows.join('');
1257
- // Initialize combination list
1243
+ // Initialize combination list.
1258
1244
  rows.length = 0;
1259
1245
  for(let i = 0; i < x.settings_dimensions.length; i++) {
1260
1246
  const dim = x.settings_dimensions[i];
@@ -1263,14 +1249,14 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1263
1249
  }
1264
1250
  this.settings_modal.element('d-table').innerHTML = rows.join('');
1265
1251
  this.settings_modal.show();
1266
- // NOTE: clear infoline because dialog can generate warnings that would
1267
- // otherwise remain visible while no longer relevant
1252
+ // NOTE: Clear infoline because dialog can generate warnings that would
1253
+ // otherwise remain visible while no longer relevant.
1268
1254
  UI.setMessage('');
1269
1255
  }
1270
1256
  }
1271
1257
 
1272
1258
  closeSettingsDimensions() {
1273
- // Hide editor, and then update the experiment manager to reflect changes
1259
+ // Hide editor, and then update the experiment manager to reflect changes.
1274
1260
  this.settings_modal.hide();
1275
1261
  this.updateDialog();
1276
1262
  }
@@ -1303,8 +1289,10 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1303
1289
  md = this.settings_selector_modal,
1304
1290
  sc = md.element('code'),
1305
1291
  ss = md.element('string'),
1306
- code = sc.value.replace(/[^\w\+\-\%]/g, ''),
1307
- value = ss.value.trim().replace(',', '.'),
1292
+ // NOTE: Simply remove invalid characters from selector, but accept
1293
+ // '=' here to permit associating settings with iterator selectors.
1294
+ code = sc.value.replace(/[^\w\+\-\%\=]/g, ''),
1295
+ value = ss.value.trim().toLowerCase().replace(',', '.'),
1308
1296
  add = this.edited_selector_index < 0;
1309
1297
  // Remove selector if either field has been cleared
1310
1298
  if(code.length === 0 || value.length === 0) {
@@ -1312,7 +1300,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1312
1300
  x.settings_selectors.splice(this.edited_selector_index, 1);
1313
1301
  }
1314
1302
  } else {
1315
- // Check for uniqueness of code
1303
+ // Check for uniqueness of code.
1316
1304
  for(let i = 0; i < x.settings_selectors.length; i++) {
1317
1305
  // NOTE: ignore selector being edited, as this selector can be renamed
1318
1306
  if(i != this.edited_selector_index &&
@@ -1323,7 +1311,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1323
1311
  }
1324
1312
  }
1325
1313
  // Check for valid syntax -- canonical example: s=0.25h t=1-100 b=12 l=6
1326
- const re = /^(s\=\d+(\.?\d+)?(yr?|wk?|d|h|m|min|s)\s+)?(t\=\d+(\-\d+)?\s+)?(b\=\d+\s+)?(l=\d+\s+)?$/i;
1314
+ const re = /^(s\=\d+(\.?\d+)?(yr?|wk?|d|h|m|min|s)\s+)?(t\=\d+(\-\d+)?\s+)?(b\=\d+\s+)?(l=\d+\s+)?(\-[ckl]+\s+)?$/i;
1327
1315
  if(!re.test(value + ' ')) {
1328
1316
  UI.warn(`Invalid settings "${value}"`);
1329
1317
  ss.focus();
@@ -1560,40 +1548,37 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1560
1548
  if(x) {
1561
1549
  const
1562
1550
  add = this.edited_combi_dimension_index < 0,
1563
- // Trim whitespace and reduce inner spacing to a single space
1551
+ // Trim whitespace and reduce inner spacing to a single space.
1564
1552
  dimstr = this.combination_dimension_modal.element('string').value.trim();
1565
- // Remove dimension if field has been cleared
1553
+ // Remove dimension if field has been cleared.
1566
1554
  if(dimstr.length === 0) {
1567
1555
  if(!add) {
1568
1556
  x.combination_dimensions.splice(this.edited_combi_dimension_index, 1);
1569
1557
  }
1570
1558
  } else {
1571
- // Check for valid selector list
1559
+ // Check for valid selector list.
1572
1560
  const
1573
1561
  dim = dimstr.split(/\s+/g),
1574
1562
  ssl = [];
1575
- // Get this experiment's combination selector list
1576
- for(let i = 0; i < x.combination_selectors.length; i++) {
1577
- ssl.push(x.combination_selectors[i].split('|')[0]);
1578
- }
1579
- // All selectors in string should have been defined
1563
+ // Get this experiment's combination selector list.
1564
+ for(const sel of x.combination_selectors) ssl.push(sel.split('|')[0]);
1565
+ // All selectors in string should have been defined.
1580
1566
  let c = complement(dim, ssl);
1581
1567
  if(c.length > 0) {
1582
1568
  UI.warn('Combination dimension contains ' +
1583
1569
  pluralS(c.length, 'unknown selector') + ': ' + c.join(' '));
1584
1570
  return;
1585
1571
  }
1586
- // All selectors should expand to non-overlapping selector sets
1572
+ // All selectors should expand to non-overlapping selector sets.
1587
1573
  if(!x.orthogonalCombinationDimensions(dim)) return;
1588
- // Do not add when a (setwise) identical combination dimension exists
1589
- for(let i = 0; i < x.combination_dimensions.length; i++) {
1590
- const cd = x.combination_dimensions[i];
1574
+ // Do not add when a (setwise) identical combination dimension exists.
1575
+ for(const cd of x.combination_dimensions) {
1591
1576
  if(intersection(dim, cd).length === dim.length) {
1592
1577
  UI.notify('Combination already defined: ' + setString(cd));
1593
1578
  return;
1594
1579
  }
1595
1580
  }
1596
- // OK? Then add or modify
1581
+ // OK? Then add or modify.
1597
1582
  if(add) {
1598
1583
  x.combination_dimensions.push(dim);
1599
1584
  } else {
@@ -1602,15 +1587,15 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1602
1587
  }
1603
1588
  }
1604
1589
  this.combination_dimension_modal.hide();
1605
- // Update combination dimensions dialog
1590
+ // Update combination dimensions dialog.
1606
1591
  this.editCombinationDimensions();
1607
1592
  }
1608
1593
 
1609
1594
  editActorDimension() {
1610
- // Open dialog for editing the actor dimension
1595
+ // Open dialog for editing the actor dimension.
1611
1596
  const x = this.selected_experiment, rows = [];
1612
1597
  if(x) {
1613
- // Initialize selector list
1598
+ // Initialize selector list.
1614
1599
  for(let i = 0; i < x.actor_selectors.length; i++) {
1615
1600
  rows.push('<tr onclick="EXPERIMENT_MANAGER.editActorSelector(', i,
1616
1601
  ');"><td>', x.actor_selectors[i].selector,
@@ -1619,8 +1604,8 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1619
1604
  }
1620
1605
  this.actor_dimension_modal.element('table').innerHTML = rows.join('');
1621
1606
  this.actor_dimension_modal.show();
1622
- // NOTE: clear infoline because dialog can generate warnings that would
1623
- // otherwise remain visible while no longer relevant
1607
+ // NOTE: Clear infoline because dialog can generate warnings that would
1608
+ // otherwise remain visible while no longer relevant.
1624
1609
  UI.setMessage('');
1625
1610
  }
1626
1611
  }
@@ -1707,17 +1692,16 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1707
1692
  clist = [],
1708
1693
  csel = md.element('select'),
1709
1694
  sinp = md.element('selectors');
1710
- // NOTE: copy experiment property to modal dialog property, so that changes
1695
+ // NOTE: Copy experiment property to modal dialog property, so that changes
1711
1696
  // are made only when OK is clicked
1712
1697
  md.clusters = [];
1713
- for(let i = 0; i < x.clusters_to_ignore.length; i++) {
1714
- const cs = x.clusters_to_ignore[i];
1715
- md.clusters.push({cluster: cs.cluster, selectors: cs. selectors});
1698
+ for(const cs of x.clusters_to_ignore) {
1699
+ md.clusters.push({cluster: cs.cluster, selectors: cs.selectors});
1716
1700
  }
1717
1701
  md.cluster_index = -1;
1718
1702
  for(let k in MODEL.clusters) if(MODEL.clusters.hasOwnProperty(k)) {
1719
1703
  const c = MODEL.clusters[k];
1720
- // Do not add top cluster, nor clusters already on the list
1704
+ // Do not add top cluster, nor clusters already on the list.
1721
1705
  if(c !== MODEL.top_cluster && !c.ignore && !x.mayBeIgnored(c)) {
1722
1706
  clist.push(`<option value="${k}">${c.displayName}</option>`);
1723
1707
  }
@@ -1738,7 +1722,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1738
1722
  sdiv = md.element('selectors-div'),
1739
1723
  cl = md.clusters.length;
1740
1724
  if(cl > 0) {
1741
- // Show cluster+selectors list
1725
+ // Show cluster+selectors list.
1742
1726
  const ol = [];
1743
1727
  for(let i = 0; i < cl; i++) {
1744
1728
  const cti = md.clusters[i];
@@ -1751,7 +1735,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1751
1735
  clst.style.display = 'block';
1752
1736
  nlst.style.display = 'none';
1753
1737
  } else {
1754
- // Hide list and show "no clusters set to be ignored"
1738
+ // Hide list and show "no clusters set to be ignored".
1755
1739
  clst.style.display = 'none';
1756
1740
  nlst.style.display = 'block';
1757
1741
  }
@@ -1767,7 +1751,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1767
1751
  }
1768
1752
 
1769
1753
  selectCluster(n) {
1770
- // Set selected cluster index to `n`
1754
+ // Set selected cluster index to `n`.
1771
1755
  this.clusters_modal.cluster_index = n;
1772
1756
  this.updateClusterList();
1773
1757
  }
@@ -1780,7 +1764,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1780
1764
  if(c) {
1781
1765
  md.clusters.push({cluster: c, selectors: ''});
1782
1766
  md.cluster_index = md.clusters.length - 1;
1783
- // Remove cluster from select so it cannot be added again
1767
+ // Remove cluster from select so it cannot be added again.
1784
1768
  sel.remove(sel.selectedIndex);
1785
1769
  this.updateClusterList();
1786
1770
  }
@@ -1803,7 +1787,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1803
1787
  }
1804
1788
 
1805
1789
  deleteClusterFromIgnoreList() {
1806
- // Delete selected cluster+selectors from list
1790
+ // Delete selected cluster+selectors from list.
1807
1791
  const md = this.clusters_modal;
1808
1792
  if(md.cluster_index >= 0) {
1809
1793
  md.clusters.splice(md.cluster_index, 1);
@@ -1813,7 +1797,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1813
1797
  }
1814
1798
 
1815
1799
  modifyClustersToIgnore() {
1816
- // Replace current list by cluster+selectors list of modal dialog
1800
+ // Replace current list by cluster+selectors list of modal dialog.
1817
1801
  const
1818
1802
  md = this.clusters_modal,
1819
1803
  x = this.selected_experiment;
@@ -1823,7 +1807,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1823
1807
  }
1824
1808
 
1825
1809
  promptForParameter(type) {
1826
- // Open dialog for adding new dimension or chart
1810
+ // Open dialog for adding new dimension or chart.
1827
1811
  const x = this.selected_experiment;
1828
1812
  if(x) {
1829
1813
  const ol = [];
@@ -1835,9 +1819,8 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1835
1819
  ol.push(`<option value="${i}">${ds}</option>`);
1836
1820
  }
1837
1821
  } else {
1838
- for(let i = 0; i < this.suitable_charts.length; i++) {
1839
- const c = this.suitable_charts[i];
1840
- // NOTE: exclude charts already in the selected experiment
1822
+ for(const c of this.suitable_charts) {
1823
+ // NOTE: Exclude charts already in the selected experiment.
1841
1824
  if (x.charts.indexOf(c) < 0) {
1842
1825
  ol.push(`<option value="${c.title}">${c.title}</option>`);
1843
1826
  }
@@ -1849,7 +1832,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1849
1832
  }
1850
1833
 
1851
1834
  addParameter() {
1852
- // Add parameter (dimension or chart) to experiment
1835
+ // Add parameter (dimension or chart) to experiment.
1853
1836
  const
1854
1837
  x = this.selected_experiment,
1855
1838
  name = this.parameter_modal.element('select').value;
@@ -1871,7 +1854,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1871
1854
  }
1872
1855
 
1873
1856
  deleteParameter() {
1874
- // Remove selected dimension or chart from selected experiment
1857
+ // Remove selected dimension or chart from selected experiment.
1875
1858
  const
1876
1859
  x = this.selected_experiment,
1877
1860
  sp = this.selected_parameter;
@@ -1888,12 +1871,12 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1888
1871
  }
1889
1872
 
1890
1873
  editExclusions() {
1891
- // Give visual feedback by setting background color to white
1874
+ // Give visual feedback by setting background color to white.
1892
1875
  this.exclude.style.backgroundColor = 'white';
1893
1876
  }
1894
1877
 
1895
1878
  setExclusions() {
1896
- // Sanitize string before accepting it as space-separated selector list
1879
+ // Sanitize string before accepting it as space-separated selector list.
1897
1880
  const
1898
1881
  x = this.selected_experiment;
1899
1882
  if(x) {
@@ -1907,24 +1890,24 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1907
1890
  }
1908
1891
 
1909
1892
  readyButtons() {
1910
- // Set experiment run control buttons in "ready" state
1893
+ // Set experiment run control buttons in "ready" state.
1911
1894
  this.pause_btn.classList.add('off');
1912
1895
  this.stop_btn.classList.add('off');
1913
1896
  this.start_btn.classList.remove('off', 'blink');
1914
1897
  }
1915
1898
 
1916
1899
  pausedButtons(aci) {
1917
- // Set experiment run control buttons in "paused" state
1900
+ // Set experiment run control buttons in "paused" state.
1918
1901
  this.pause_btn.classList.remove('blink');
1919
1902
  this.pause_btn.classList.add('off');
1920
1903
  this.start_btn.classList.remove('off');
1921
- // Blinking start button indicates: paused -- click to resume
1904
+ // Blinking start button indicates: paused -- click to resume.
1922
1905
  this.start_btn.classList.add('blink');
1923
1906
  this.viewer_progress.innerHTML = `Run ${aci} PAUSED`;
1924
1907
  }
1925
1908
 
1926
1909
  resumeButtons() {
1927
- // Changes buttons to "running" state, and return TRUE if state was "paused"
1910
+ // Changes buttons to "running" state, and return TRUE if state was "paused".
1928
1911
  const paused = this.start_btn.classList.contains('blink');
1929
1912
  this.start_btn.classList.remove('blink');
1930
1913
  this.start_btn.classList.add('off');
@@ -1934,7 +1917,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1934
1917
  }
1935
1918
 
1936
1919
  pauseExperiment() {
1937
- // Interrupt solver but retain data on server and allow resume
1920
+ // Interrupt solver but retain data on server and allow resume.
1938
1921
  UI.notify('Run sequence will be suspended after the current run');
1939
1922
  this.pause_btn.classList.add('blink');
1940
1923
  this.stop_btn.classList.remove('off');
@@ -1942,7 +1925,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1942
1925
  }
1943
1926
 
1944
1927
  stopExperiment() {
1945
- // Interrupt solver but retain data on server (and no resume)
1928
+ // Interrupt solver but retain data on server (and no resume).
1946
1929
  VM.halt();
1947
1930
  MODEL.running_experiment = null;
1948
1931
  UI.notify('Experiment has been stopped');
@@ -1951,7 +1934,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1951
1934
  }
1952
1935
 
1953
1936
  showProgress(ci, p, n) {
1954
- // Show progress in the viewer
1937
+ // Show progress in the viewer.
1955
1938
  this.viewer_progress.innerHTML = `Run ${ci} (${p}% of ${n})`;
1956
1939
  }
1957
1940
 
@@ -1962,7 +1945,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1962
1945
  }
1963
1946
 
1964
1947
  promptForDownload() {
1965
- // Show the download modal
1948
+ // Show the download modal.
1966
1949
  const x = this.selected_experiment;
1967
1950
  if(!x) return;
1968
1951
  const
@@ -1975,13 +1958,13 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1975
1958
  return;
1976
1959
  }
1977
1960
  md.element(ds.variables + '-v').checked = true;
1978
- // Disable "selected runs" button when no runs have been selected
1961
+ // Disable "selected runs" button when no runs have been selected.
1979
1962
  if(sruns) {
1980
1963
  md.element('selected-r').disabled = false;
1981
1964
  md.element(ds.runs + '-r').checked = true;
1982
1965
  } else {
1983
1966
  md.element('selected-r').disabled = true;
1984
- // Check "all runs" but do not change download setting
1967
+ // Check "all runs" but do not change download setting.
1985
1968
  md.element('all-r').checked = true;
1986
1969
  }
1987
1970
  this.download_modal.show();
@@ -1997,7 +1980,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1997
1980
  }
1998
1981
 
1999
1982
  downloadDataAsCSV() {
2000
- // Push results to browser
1983
+ // Push results to browser.
2001
1984
  if(this.selected_experiment) {
2002
1985
  const md = this.download_modal;
2003
1986
  this.selected_experiment.download_settings = {