linny-r 1.4.5 → 1.4.6

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": "1.4.5",
3
+ "version": "1.4.6",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
package/static/index.html CHANGED
@@ -1754,6 +1754,8 @@ NOTE: * and ? will be interpreted as wildcards"
1754
1754
  title="New equation">
1755
1755
  <img id="eq-rename-btn" class="btn disab" src="images/rename.png"
1756
1756
  title="Rename selected equation">
1757
+ <img id="eq-clone-btn" class="btn disab" src="images/clone.png"
1758
+ title="Clone selected equation">
1757
1759
  <img id="eq-edit-btn" class="btn disab" src="images/edit.png"
1758
1760
  title="Edit selected equation">
1759
1761
  <img id="eq-delete-btn" class="btn disab" src="images/delete.png"
@@ -1790,6 +1792,18 @@ NOTE: * and ? will be interpreted as wildcards"
1790
1792
  </div>
1791
1793
  </div>
1792
1794
 
1795
+ <!-- the CLONE EQUATION dialog prompts for the new name of an equation -->
1796
+ <div id="clone-equation-modal" class="modal">
1797
+ <div id="clone-equation-dlg" class="inp-dlg">
1798
+ <div class="dlg-title">
1799
+ Clone equation
1800
+ <img class="cancel-btn" src="images/cancel.png">
1801
+ <img class="ok-btn" src="images/ok.png">
1802
+ </div>
1803
+ <input id="clone-equation-name" type="text" autocomplete="off">
1804
+ </div>
1805
+ </div>
1806
+
1793
1807
 
1794
1808
  <!-- the CHART dialog allows definition of charts and displays them -->
1795
1809
  <div id="chart-dlg" class="inp-dlg">
@@ -2439,7 +2439,8 @@ td.equation-expression {
2439
2439
  #new-dataset-dlg,
2440
2440
  #rename-dataset-dlg,
2441
2441
  #new-equation-dlg,
2442
- #rename-equation-dlg {
2442
+ #rename-equation-dlg,
2443
+ #clone-equation-dlg {
2443
2444
  width: 240px;
2444
2445
  height: 45px;
2445
2446
  }
@@ -2447,7 +2448,8 @@ td.equation-expression {
2447
2448
  #new-dataset-name,
2448
2449
  #rename-dataset-name,
2449
2450
  #new-equation-name,
2450
- #rename-equation-name {
2451
+ #rename-equation-name,
2452
+ #clone-equation-name {
2451
2453
  position: absolute;
2452
2454
  bottom: 2px;
2453
2455
  left: 2px;
@@ -1261,7 +1261,7 @@ class ExperimentManager {
1261
1261
  }
1262
1262
 
1263
1263
  processRestOfRun() {
1264
- // Performs post-processing after run results have been added
1264
+ // Performs post-processing after run results have been added.
1265
1265
  const x = MODEL.running_experiment;
1266
1266
  if(!x) return;
1267
1267
  const aci = x.active_combination_index;
@@ -48,6 +48,8 @@ class EquationManager {
48
48
  'click', () => EQUATION_MANAGER.promptForEquation());
49
49
  document.getElementById('eq-rename-btn').addEventListener(
50
50
  'click', () => EQUATION_MANAGER.promptForName());
51
+ document.getElementById('eq-clone-btn').addEventListener(
52
+ 'click', () => EQUATION_MANAGER.promptToClone());
51
53
  document.getElementById('eq-edit-btn').addEventListener(
52
54
  'click', () => EQUATION_MANAGER.editEquation());
53
55
  document.getElementById('eq-delete-btn').addEventListener(
@@ -66,6 +68,12 @@ class EquationManager {
66
68
  this.rename_modal.cancel.addEventListener(
67
69
  'click', () => EQUATION_MANAGER.rename_modal.hide());
68
70
 
71
+ this.clone_modal = new ModalDialog('clone-equation');
72
+ this.clone_modal.ok.addEventListener(
73
+ 'click', () => EQUATION_MANAGER.cloneEquation());
74
+ this.clone_modal.cancel.addEventListener(
75
+ 'click', () => EQUATION_MANAGER.clone_modal.hide());
76
+
69
77
  // Initialize the dialog properties
70
78
  this.reset();
71
79
  }
@@ -146,7 +154,7 @@ class EquationManager {
146
154
  this.table.innerHTML = ml.join('');
147
155
  this.scroll_area.style.display = 'block';
148
156
  if(sm) UI.scrollIntoView(document.getElementById(smid));
149
- const btns = 'eq-rename eq-edit eq-delete';
157
+ const btns = 'eq-rename eq-clone eq-edit eq-delete';
150
158
  if(sm) {
151
159
  UI.enableButtons(btns);
152
160
  } else {
@@ -294,6 +302,43 @@ class EquationManager {
294
302
  this.updateDialog();
295
303
  }
296
304
 
305
+ promptToClone() {
306
+ // Prompts the modeler for the name of the clone to make of the
307
+ // selected equation (if any).
308
+ if(this.selected_modifier) {
309
+ this.clone_modal.element('name').value = this.selected_modifier.selector;
310
+ this.clone_modal.show('name');
311
+ }
312
+ }
313
+
314
+ cloneEquation() {
315
+ if(!this.selected_modifier) return;
316
+ const
317
+ s = this.clone_modal.element('name').value,
318
+ // New equation identifier must not equal some entity ID
319
+ obj = MODEL.objectByName(s);
320
+ if(obj) {
321
+ // NOTE: also pass selector, or warning will display dataset name.
322
+ UI.warningEntityExists(obj);
323
+ return null;
324
+ }
325
+ // Name is new and unique, so try to use it
326
+ const m = MODEL.equations_dataset.addModifier(s);
327
+ // NULL indicates invalid name, and modeler will have been warned.
328
+ if(!m) return;
329
+ // Give the new modifier the same expression as te selected one.
330
+ m.expression.text = this.selected_modifier.expression.text;
331
+ // Compile the expression. This may generate a warning when the new
332
+ // name does not provide adequate context.
333
+ m.expression.compile();
334
+ //
335
+ this.selected_modifier = m;
336
+ // Even if warning was given, close the name prompt dialog, and update
337
+ // the equation manager.
338
+ this.clone_modal.hide();
339
+ this.updateDialog();
340
+ }
341
+
297
342
  deleteEquation() {
298
343
  const m = this.selected_modifier;
299
344
  if(m) {
@@ -2963,7 +2963,7 @@ class LinnyRModel {
2963
2963
  const cv = new ChartVariable(null);
2964
2964
  cv.setProperties(v.object, v.attribute, false, '#000000');
2965
2965
  vbls.push(cv);
2966
- names.push(uvn);
2966
+ names.push(vn);
2967
2967
  }
2968
2968
  } else if(names.indexOf(vn) < 0) {
2969
2969
  // Keep track of the dataset and dataset modifier variables,
@@ -8434,15 +8434,26 @@ class Dataset {
8434
8434
  }
8435
8435
 
8436
8436
  get plainSelectors() {
8437
- // Returns sorted list of selectors that do not contain wildcards
8438
- const sl = this.selectorList;
8439
- // NOTE: wildcard selectors will always be at the end of the list
8437
+ // Return sorted list of selectors that do not contain wildcards.
8438
+ const sl = this.selectorList.slice();
8439
+ // NOTE: Wildcard selectors will always be at the end of the list
8440
8440
  for(let i = sl.length - 1; i >= 0; i--) {
8441
8441
  if(sl[i].indexOf('*') >= 0 || sl[i].indexOf('?') >= 0) sl.pop();
8442
8442
  }
8443
8443
  return sl;
8444
8444
  }
8445
8445
 
8446
+ get wildcardSelectors() {
8447
+ // Return sorted list of selectors that DO contain wildcards.
8448
+ const sl = this.selectorList;
8449
+ // NOTE: Wildcard selectors will always be at the end of the list.
8450
+ let i = sl.length - 1;
8451
+ while(i >= 0 && (sl[i].indexOf('*') >= 0 || sl[i].indexOf('?') >= 0)) {
8452
+ i--;
8453
+ }
8454
+ return sl.slice(i+1);
8455
+ }
8456
+
8446
8457
  isWildcardSelector(s) {
8447
8458
  // Returns TRUE if `s` contains * or ?
8448
8459
  // NOTE: for equations, the wildcard must be ??
@@ -8915,16 +8926,22 @@ class ChartVariable {
8915
8926
  const sf = (this.scale_factor === 1 ? '' :
8916
8927
  ` (x${VM.sig4Dig(this.scale_factor)})`);
8917
8928
  //Display name of equation is just the equations dataset selector.
8918
- if(this.object instanceof DatasetModifier ||
8919
- // NOTE: Same holds for "dummy variables" added for wildcard
8920
- // dataset selectors.
8921
- this.object === MODEL.equations_dataset) {
8929
+ if(this.object instanceof DatasetModifier) {
8922
8930
  let eqn = this.object.selector;
8923
8931
  if(this.wildcard_index !== false) {
8924
8932
  eqn = eqn.replace('??', this.wildcard_index);
8925
8933
  }
8926
8934
  return eqn + sf;
8927
8935
  }
8936
+ // NOTE: Same holds for "dummy variables" added for wildcard
8937
+ // dataset selectors.
8938
+ if(this.object === MODEL.equations_dataset) {
8939
+ let eqn = this.attribute;
8940
+ if(this.wildcard_index !== false) {
8941
+ eqn = eqn.replace('??', this.wildcard_index);
8942
+ }
8943
+ return eqn + sf;
8944
+ }
8928
8945
  // NOTE: Do not display the vertical bar if no attribute is specified.
8929
8946
  if(!this.attribute) return this.object.displayName + sf;
8930
8947
  return this.object.displayName + UI.OA_SEPARATOR + this.attribute + sf;
@@ -10115,8 +10132,8 @@ class ActorSelector {
10115
10132
  class ExperimentRunResult {
10116
10133
  constructor(r, v, a='') {
10117
10134
  // NOTE: constructor can be called with `v` a chart variable, a dataset,
10118
- // or an XML node; if `v` is the equations dataset, then `a` indicates the
10119
- // attribute to be used
10135
+ // or an XML node; if `v` is the equations dataset, then `a` is the
10136
+ // identifier of the dataset modifier to be used.
10120
10137
  this.run = r;
10121
10138
  if(v instanceof ChartVariable) {
10122
10139
  this.x_variable = true;
@@ -10188,7 +10205,7 @@ class ExperimentRunResult {
10188
10205
  this.exceptions = 0;
10189
10206
  const
10190
10207
  // NOTE: run result dataset selector will be plain (no wildcards)
10191
- x = v.attributeExpression(this.attribute),
10208
+ x = v.modifiers[this.attribute].expression,
10192
10209
  t_end = MODEL.end_period - MODEL.start_period + 1;
10193
10210
  // N = # time steps
10194
10211
  this.N = t_end;
@@ -10564,7 +10581,7 @@ class ExperimentRun {
10564
10581
  new ExperimentRunResult(this, this.experiment.variables[vi]));
10565
10582
  this.step++;
10566
10583
  UI.setProgressNeedle(this.step / this.steps);
10567
- setTimeout(function(x) { x.addChartResults(vi + 1); }, 0, this);
10584
+ setTimeout((x) => x.addChartResults(vi + 1), 0, this);
10568
10585
  } else {
10569
10586
  this.addOutcomeResults(0);
10570
10587
  }
@@ -10577,7 +10594,7 @@ class ExperimentRun {
10577
10594
  this.results.push(new ExperimentRunResult(this, MODEL.outcomes[oi]));
10578
10595
  this.step++;
10579
10596
  UI.setProgressNeedle(this.step / this.steps);
10580
- setTimeout(function(x) { x.addOutcomeResults(oi + 1); }, 0, this);
10597
+ setTimeout((x) => x.addOutcomeResults(oi + 1), 0, this);
10581
10598
  } else {
10582
10599
  this.addEquationResults(0);
10583
10600
  }
@@ -10592,7 +10609,7 @@ class ExperimentRun {
10592
10609
  new ExperimentRunResult(this, MODEL.equations_dataset, k));
10593
10610
  this.step++;
10594
10611
  UI.setProgressNeedle(this.step / this.steps);
10595
- setTimeout(function(x) { x.addEquationResults(ei + 1); }, 0, this);
10612
+ setTimeout((x) => x.addEquationResults(ei + 1), 0, this);
10596
10613
  } else {
10597
10614
  // Register when this result was stored
10598
10615
  this.time_recorded = new Date().getTime();
@@ -10602,8 +10619,10 @@ class ExperimentRun {
10602
10619
  // Log the time it took to compute all results
10603
10620
  VM.logMessage(VM.block_count - 1,
10604
10621
  `Processing run results took ${VM.elapsedTime} seconds.`);
10605
- // NOTE: addResults is called by either the experiment manager or the
10606
- // sensitivity analysis => proceed there
10622
+ // Report results if applicable.
10623
+ if(RECEIVER.solving || MODEL.report_results) RECEIVER.report();
10624
+ // NOTE: addResults is called by either the experiment manager or
10625
+ // the sensitivity analysis; hence proceed from there.
10607
10626
  if(SENSITIVITY_ANALYSIS.experiment) {
10608
10627
  SENSITIVITY_ANALYSIS.processRestOfRun();
10609
10628
  } else {
@@ -348,6 +348,8 @@ class Expression {
348
348
  v[t] = this.stack.pop();
349
349
  }
350
350
  this.trace('RESULT = ' + VM.sig4Dig(v[t]));
351
+ // Store wildcard result also in "normal" vector
352
+ this.vector[t] = v[t];
351
353
  // Pop the time step.
352
354
  this.step.pop();
353
355
  this.trace('--STOP: ' + this.variableName);
@@ -5268,10 +5270,13 @@ Solver status = ${json.status}`);
5268
5270
  UI.drawDiagram(MODEL);
5269
5271
  // Show the reset button (GUI only)
5270
5272
  UI.readyToReset();
5271
- // If receiver is active, report results
5272
- if(RECEIVER.solving || MODEL.report_results) RECEIVER.report();
5273
- // If experiment is active, signal the manager
5274
- if(MODEL.running_experiment) EXPERIMENT_MANAGER.processRun();
5273
+ if(MODEL.running_experiment) {
5274
+ // If experiment is active, signal the manager.
5275
+ EXPERIMENT_MANAGER.processRun();
5276
+ } else if(RECEIVER.solving || MODEL.report_results) {
5277
+ // Otherwise report results now, if applicable.
5278
+ RECEIVER.report();
5279
+ }
5275
5280
  // Warn modeler if any issues occurred
5276
5281
  if(this.block_issues) {
5277
5282
  let msg = 'Issues occurred in ' +
@@ -5677,8 +5682,8 @@ function valueOfNumberSign(x) {
5677
5682
  // ending on digits, or tne number context of an entity. The latter typically
5678
5683
  // is the number its name or any of its prefixes ends on, but notes are
5679
5684
  // more "creative" and can return the number context of nearby entities.
5680
- let s = 'NO SELECTOR',
5681
- m = 'NO MATCH',
5685
+ let s = '!NO SELECTOR',
5686
+ m = '!NO MATCH',
5682
5687
  n = VM.UNDEFINED;
5683
5688
  // NOTE: Give wildcard selectors precedence over experiment selectors
5684
5689
  // because a wildcard selector is an immediate property of the dataset
@@ -5692,17 +5697,15 @@ function valueOfNumberSign(x) {
5692
5697
  // Check whether `x` is a dataset modifier expression.
5693
5698
  // NOTE: This includes equations.
5694
5699
  if(x.object instanceof Dataset) {
5695
- if(x.attribute) {
5696
- s = x.attribute;
5697
- } else {
5698
- // Selector may also be defined by a running experiment.
5699
- if(MODEL.running_experiment) {
5700
- // Let `m` be the primary selector for the current experiment run.
5701
- const sl = MODEL.running_experiment.activeCombination;
5702
- if(sl) {
5703
- m = sl[0];
5704
- s = 'experiment';
5705
- }
5700
+ if(x.attribute) s = x.attribute;
5701
+ // Selector may also be defined by a running experiment.
5702
+ if(MODEL.running_experiment) {
5703
+ const
5704
+ ac = MODEL.running_experiment.activeCombination,
5705
+ mn = matchingNumberInList(ac, s);
5706
+ if(mn !== false) {
5707
+ m = 'x-run';
5708
+ n = mn;
5706
5709
  }
5707
5710
  }
5708
5711
  }
@@ -5715,10 +5718,6 @@ function valueOfNumberSign(x) {
5715
5718
  m = d;
5716
5719
  n = parseInt(d);
5717
5720
  }
5718
- } else if(m !== 'NO MATCH') {
5719
- // If matching `m` against `s` do not yield an integer string,
5720
- // use UNDEFINED
5721
- n = matchingNumber(m, s) || VM.UNDEFINED;
5722
5721
  }
5723
5722
  }
5724
5723
  // For datasets, set the parent anchor to be the context-sensitive number
@@ -5941,7 +5940,7 @@ function VMI_push_dataset_modifier(x, args) {
5941
5940
  // If `s` is not specified, the modifier to be used must be inferred from
5942
5941
  // the running experiment UNLESS the field `ud` ("use data") is defined
5943
5942
  // for the first argument, and evaluates as TRUE.
5944
- // NOTE: Ensure that number 0 is not interpreted as FALSE.
5943
+ // NOTE: Ensure that number 0 is not interpreted as FALSE.
5945
5944
  let wcnr = (args[0].s === undefined ? false : args[0].s);
5946
5945
  const
5947
5946
  ds = args[0].d,
@@ -5960,7 +5959,6 @@ function VMI_push_dataset_modifier(x, args) {
5960
5959
  let t = tot[0],
5961
5960
  // By default, use the vector of the dataset to compute the value.
5962
5961
  obj = ds.vector;
5963
-
5964
5962
  if(ds.array) {
5965
5963
  // For array-type datasets, do NOT adjust "index" t to model run period.
5966
5964
  // NOTE: Indices start at 1, but arrays are zero-based, so subtract 1.
@@ -5994,16 +5992,17 @@ function VMI_push_dataset_modifier(x, args) {
5994
5992
  if(wcnr === '?') {
5995
5993
  wcnr = x.wildcard_vector_index;
5996
5994
  }
5997
- } else if(!ud ) {
5995
+ } else if(!ud) {
5998
5996
  // In no selector and not "use data", check whether a running experiment
5999
5997
  // defines the expression to use. If not, `obj` will be the dataset
6000
5998
  // vector (so same as when "use data" is set).
6001
5999
  obj = ds.activeModifierExpression;
6002
- if(wcnr === false && obj instanceof Expression && MODEL.running_experiment) {
6000
+ if(wcnr === false && MODEL.running_experiment) {
6003
6001
  // If experiment run defines the modifier selector, the active
6004
6002
  // combination may provide a context for #.
6005
- wcnr = matchingNumberInList(MODEL.running_experiment.activeCombination,
6006
- obj.attribute);
6003
+ const sel = (obj instanceof Expression ? obj.attribute : x.attribute);
6004
+ wcnr = matchingNumberInList(
6005
+ MODEL.running_experiment.activeCombination, sel);
6007
6006
  }
6008
6007
  }
6009
6008
  if(!obj) {
@@ -6040,7 +6039,9 @@ function VMI_push_dataset_modifier(x, args) {
6040
6039
  // `obj` is an expression.
6041
6040
  // NOTE: Readjust `t` when `obj` is an expression for an *array-type*
6042
6041
  // dataset modifier.
6043
- if(obj.object instanceof Dataset && obj.object.array) t++;
6042
+ if(obj.object instanceof Dataset && obj.object.array) {
6043
+ t++;
6044
+ }
6044
6045
  v = obj.result(t, wcnr);
6045
6046
  }
6046
6047
  // Trace only now that time step t has been computed.