linny-r 2.0.11 → 2.0.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linny-r",
3
- "version": "2.0.11",
3
+ "version": "2.0.12",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
@@ -1266,30 +1266,30 @@ class ExperimentManager {
1266
1266
  }
1267
1267
 
1268
1268
  startExperiment(n=-1) {
1269
- // Recompile expressions, as these may have been changed by the modeler
1269
+ // Recompile expressions, as these may have been changed by the modeler.
1270
1270
  MODEL.compileExpressions();
1271
- // Start sequence of solving model parametrizations
1271
+ // Start sequence of solving model parametrizations.
1272
1272
  const x = this.selected_experiment;
1273
1273
  if(x) {
1274
- // Store original model settings
1274
+ // Store original model settings.
1275
1275
  x.original_model_settings = MODEL.settingsString;
1276
1276
  x.original_round_sequence = MODEL.round_sequence;
1277
- // NOTE: switch off run chart display
1277
+ // NOTE: Switch off run chart display.
1278
1278
  CHART_MANAGER.setRunsChart(false);
1279
1279
  // When Chart manager is showing, close it and notify modeler that charts
1280
- // should not be viewed during experiments
1280
+ // should not be viewed during experiments.
1281
1281
  if(CHART_MANAGER.visible) {
1282
1282
  UI.buttons.chart.dispatchEvent(new Event('click'));
1283
1283
  UI.notify(UI.NOTICE.NO_CHARTS);
1284
1284
  }
1285
- // Change the buttons -- will return TRUE if experiment was paused
1285
+ // Change the buttons -- will return TRUE if experiment was paused.
1286
1286
  const paused = this.resumeButtons();
1287
1287
  if(x.completed && n >= 0) {
1288
1288
  x.single_run = n;
1289
1289
  x.active_combination_index = n;
1290
1290
  MODEL.running_experiment = x;
1291
1291
  } else if(!paused) {
1292
- // Clear previous run results (if any) unless resuming
1292
+ // Clear previous run results (if any) unless resuming.
1293
1293
  x.clearRuns();
1294
1294
  x.inferVariables();
1295
1295
  x.time_started = new Date().getTime();
@@ -1369,11 +1369,14 @@ class ExperimentManager {
1369
1369
  // Perform post-processing after run results have been added.
1370
1370
  const x = MODEL.running_experiment;
1371
1371
  if(!x) return;
1372
- const aci = x.active_combination_index;
1372
+ const
1373
+ aci = x.active_combination_index,
1374
+ single = (aci == x.single_run);
1373
1375
  // Always add solver messages.
1374
1376
  x.runs[aci].addMessages();
1375
1377
  const n = x.combinations.length;
1376
- if(!VM.halted && aci < n - 1 && aci != x.single_run) {
1378
+ if(!VM.halted && aci < n - 1 && !single) {
1379
+ // Continue with the next run.
1377
1380
  if(this.must_pause) {
1378
1381
  this.pausedButtons(aci);
1379
1382
  this.must_pause = false;
@@ -1384,12 +1387,13 @@ class ExperimentManager {
1384
1387
  // NOTE: When executing a remote command, wait for 1 second to
1385
1388
  // allow enough time for report writing.
1386
1389
  if(RECEIVER.active && RECEIVER.experiment) {
1387
- UI.setMessage('Reporting run #' + (x.active_combination_index - 1));
1390
+ UI.setMessage('Reporting run #' + aci);
1388
1391
  delay = 1000;
1389
1392
  }
1390
1393
  setTimeout(() => EXPERIMENT_MANAGER.runModel(), delay);
1391
1394
  }
1392
1395
  } else {
1396
+ // Stop the run sequence.
1393
1397
  x.time_stopped = new Date().getTime();
1394
1398
  if(x.single_run >= 0) {
1395
1399
  x.single_run = -1;
@@ -1409,18 +1413,19 @@ class ExperimentManager {
1409
1413
  RECEIVER.experiment = '';
1410
1414
  RECEIVER.callBack();
1411
1415
  }
1412
- // Restore original model settings
1416
+ // Restore original model settings.
1413
1417
  MODEL.running_experiment = null;
1414
1418
  MODEL.parseSettings(x.original_model_settings);
1415
1419
  MODEL.round_sequence = x.original_round_sequence;
1416
1420
  // Reset the Virtual Machine so t=0 at the status line,
1417
1421
  // and ALL expressions are reset as well.
1418
- VM.reset();
1422
+ if(!single) VM.reset();
1419
1423
  this.readyButtons();
1420
1424
  }
1421
1425
  this.drawTable();
1422
1426
  // Reset the model, as results of last run will be showing still.
1423
- UI.resetModel();
1427
+ // NOTE: Do NOT do this after a single run.
1428
+ if(!single) UI.resetModel();
1424
1429
  CHART_MANAGER.resetChartVectors();
1425
1430
  // NOTE: Clear chart only when done; charts do not update when an
1426
1431
  // experiment is running.
@@ -1710,14 +1710,14 @@ class GUIController extends Controller {
1710
1710
  //
1711
1711
 
1712
1712
  draggableDialog(d) {
1713
- // Make dialog draggable
1713
+ // Make dialog draggable.
1714
1714
  const
1715
1715
  dlg = document.getElementById(d + '-dlg'),
1716
1716
  hdr = document.getElementById(d + '-hdr');
1717
1717
  let cx = 0,
1718
1718
  cy = 0;
1719
1719
  if(dlg && hdr) {
1720
- // NOTE: dialogs are draggable only by their header
1720
+ // NOTE: Dialogs are draggable only by their header.
1721
1721
  hdr.onmousedown = dialogHeaderMouseDown;
1722
1722
  dlg.onmousedown = dialogMouseDown;
1723
1723
  return dlg;
@@ -1728,13 +1728,13 @@ class GUIController extends Controller {
1728
1728
 
1729
1729
  function dialogMouseDown(e) {
1730
1730
  e = e || window.event;
1731
- // NOTE: no `preventDefault` so the header will also receive it
1732
- // Find the dialog element
1731
+ // NOTE: No `preventDefault`, as this disables selector elements.
1732
+ // Find the dialog element.
1733
1733
  let de = e.target;
1734
1734
  while(de && !de.id.endsWith('-dlg')) { de = de.parentElement; }
1735
- // Moves the dialog (`this`) to the top of the order
1735
+ // Move the dialog (`this`) to the top of the order.
1736
1736
  const doi = UI.dr_dialog_order.indexOf(de);
1737
- // NOTE: do not reorder when already at end of list (= at top)
1737
+ // NOTE: Do not reorder when already at end of list (= at top).
1738
1738
  if(doi >= 0 && doi !== UI.dr_dialog_order.length - 1) {
1739
1739
  UI.dr_dialog_order.splice(doi, 1);
1740
1740
  UI.dr_dialog_order.push(de);
@@ -1745,12 +1745,12 @@ class GUIController extends Controller {
1745
1745
  function dialogHeaderMouseDown(e) {
1746
1746
  e = e || window.event;
1747
1747
  e.preventDefault();
1748
- // Find the dialog element
1748
+ // Find the dialog element.
1749
1749
  let de = e.target;
1750
1750
  while(de && !de.id.endsWith('-dlg')) { de = de.parentElement; }
1751
- // Record the affected dialog
1751
+ // Record the affected dialog.
1752
1752
  UI.dr_dialog = de;
1753
- // Get the mouse cursor position at startup
1753
+ // Get the mouse cursor position at startup.
1754
1754
  cx = e.clientX;
1755
1755
  cy = e.clientY;
1756
1756
  document.onmouseup = stopDragDialog;
@@ -2258,6 +2258,8 @@ class GUIController extends Controller {
2258
2258
  this.net_move_y = 0;
2259
2259
  // Get the paper coordinates indicated by the cursor.
2260
2260
  const cp = this.paper.cursorPosition(e.pageX, e.pageY);
2261
+ this.mouse_x = cp[0];
2262
+ this.mouse_y = cp[1];
2261
2263
  this.mouse_down_x = cp[0];
2262
2264
  this.mouse_down_y = cp[1];
2263
2265
  // De-activate "stay active" buttons if dysfunctional, or if SHIFT,
@@ -1261,7 +1261,7 @@ class GUIDatasetManager extends DatasetManager {
1261
1261
  ods = MODEL.namedObjectByID(id),
1262
1262
  ds = ods || MODEL.addDataset(n);
1263
1263
  // NOTE: `ds` will now be either a new dataset or an existing one.
1264
- if(ds) {
1264
+ if(ds instanceof Dataset) {
1265
1265
  // Keep track of added/updated datasets.
1266
1266
  const
1267
1267
  odv = ds.default_value,
@@ -1274,6 +1274,8 @@ class GUIDatasetManager extends DatasetManager {
1274
1274
  added++;
1275
1275
  }
1276
1276
  ds.computeStatistics();
1277
+ } else {
1278
+ UI.warn(`Name conflict: ${ds.type} "${ds.displayName}" already exists`);
1277
1279
  }
1278
1280
  }
1279
1281
  // Notify modeler of changes (if any).
@@ -844,8 +844,8 @@ class GUIExperimentManager extends ExperimentManager {
844
844
  }
845
845
 
846
846
  showInfo(n, shift) {
847
- // Display documentation for the n-th experiment defined in the model
848
- // NOTE: skip when viewer is showing!
847
+ // Display documentation for the n-th experiment defined in the model.
848
+ // NOTE: Skip when viewer is showing!
849
849
  if(!UI.hidden('experiment-viewer')) return;
850
850
  if(n < MODEL.experiments.length) {
851
851
  // NOTE: mouse move over title in viewer passes n = -1
@@ -856,7 +856,7 @@ class GUIExperimentManager extends ExperimentManager {
856
856
 
857
857
  showRunInfo(n, shift) {
858
858
  // Display information on the n-th combination if docu-viewer is visible
859
- // and cursor is moved over run cell while Shift button is held down
859
+ // and cursor is moved over run cell while Shift button is held down.
860
860
  if(shift && DOCUMENTATION_MANAGER.visible) {
861
861
  const info = this.runInfo(n);
862
862
  if(info) {
@@ -949,7 +949,16 @@ class Finder {
949
949
  }
950
950
  }
951
951
  // Set the new default selector (if changed).
952
- if(md.new_defsel !== false) ds.default_selector = md.new_defsel;
952
+ if(md.new_defsel !== false) {
953
+ // NOTE: `new_defsel` is a key; the actual selector name may have upper case
954
+ // letters, so get the selector name.
955
+ const dsm = ds.modifiers[md.new_defsel];
956
+ if(dsm) {
957
+ ds.default_selector = dsm.selector;
958
+ } else {
959
+ throw(`Unknown selector: ${md.new_defsel}`);
960
+ }
961
+ }
953
962
  }
954
963
  // Notify modeler of changes (if any).
955
964
  const msg = [];
@@ -7204,7 +7204,13 @@ function VMI_push_run_result(x, args) {
7204
7204
  let xp = rrspec.x,
7205
7205
  rn = rrspec.r,
7206
7206
  rri = rrspec.v;
7207
- if(xp === false) xp = MODEL.running_experiment;
7207
+ if(xp === false) {
7208
+ // If no experiment is specified, use the running experiment.
7209
+ // NOTE: To facilitate testing a "single run" without using the
7210
+ // Experiment manager, default to the experiment that is selected
7211
+ // in the Experiment manager (but not "running").
7212
+ xp = MODEL.running_experiment || EXPERIMENT_MANAGER.selected_experiment;
7213
+ }
7208
7214
  if(xp instanceof Experiment) {
7209
7215
  if(Array.isArray(rn)) {
7210
7216
  // Let the running experiment infer run number from selector list `rn`
@@ -7212,7 +7218,22 @@ function VMI_push_run_result(x, args) {
7212
7218
  rn = xp.matchingCombinationIndex(rn);
7213
7219
  } else if(rn < 0) {
7214
7220
  // Relative run number: use current run # + r (first run has number 0).
7215
- rn += xp.active_combination_index;
7221
+ if(xp === MODEL.running_experiment) {
7222
+ rn += xp.active_combination_index;
7223
+ } else if(xp.chart_combinations.length) {
7224
+ // Modeler has selected one or more runs in the viewer table.
7225
+ // FInd the highest number of a selected run that has been performed.
7226
+ let last = -1;
7227
+ for(const ccn of xp.chart_combinations) {
7228
+ if(ccn > last && ccn < xp.runs.length) last = ccn;
7229
+ }
7230
+ // If no performed runs are selected, use the last performed run.
7231
+ if(last < 0) last = xp.runs.length - 1;
7232
+ rn += last;
7233
+ } else {
7234
+ // Choose the run relative to the total number of completed runs.
7235
+ rn += xp.runs.length - 1;
7236
+ }
7216
7237
  } else if(rrspec.nr !== false) {
7217
7238
  // Run number inferred from local time step of expression.
7218
7239
  const