linny-r 1.4.4 → 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.4",
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) {
@@ -205,7 +205,8 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
205
205
  }
206
206
  }
207
207
  if(a) n += ` (${a})`;
208
- UI.edited_object = MODEL.objectByName(n);
208
+ // NOTE: Names may contain ampersand as HTML entity.
209
+ UI.edited_object = MODEL.objectByName(n.replace('&amp;', '&'));
209
210
  this.edited_input_id = UI.edited_object.type.toLowerCase() + '-' + ids[1];
210
211
  this.edited_expression = UI.edited_object.attributeExpression(ids[1]);
211
212
  }
@@ -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,
@@ -2988,7 +2988,8 @@ class LinnyRModel {
2988
2988
  if(names.indexOf(n) < 0) {
2989
2989
  // Here, too, NULL can be used as "owner chart".
2990
2990
  const cv = new ChartVariable(null);
2991
- cv.setProperties(ds, dm.selector, false, '#000000');
2991
+ // NOTE: For equations, the object is the dataset modifier.
2992
+ cv.setProperties(eq ? dm : ds, dm.selector, false, '#000000');
2992
2993
  vbls.push(cv);
2993
2994
  }
2994
2995
  }
@@ -8433,15 +8434,26 @@ class Dataset {
8433
8434
  }
8434
8435
 
8435
8436
  get plainSelectors() {
8436
- // Returns sorted list of selectors that do not contain wildcards
8437
- const sl = this.selectorList;
8438
- // 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
8439
8440
  for(let i = sl.length - 1; i >= 0; i--) {
8440
8441
  if(sl[i].indexOf('*') >= 0 || sl[i].indexOf('?') >= 0) sl.pop();
8441
8442
  }
8442
8443
  return sl;
8443
8444
  }
8444
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
+
8445
8457
  isWildcardSelector(s) {
8446
8458
  // Returns TRUE if `s` contains * or ?
8447
8459
  // NOTE: for equations, the wildcard must be ??
@@ -8913,7 +8925,16 @@ class ChartVariable {
8913
8925
  // by its scale factor unless it equals 1 (no scaling).
8914
8926
  const sf = (this.scale_factor === 1 ? '' :
8915
8927
  ` (x${VM.sig4Dig(this.scale_factor)})`);
8916
- // NOTE: Display name of equation is just the equations dataset modifier.
8928
+ //Display name of equation is just the equations dataset selector.
8929
+ if(this.object instanceof DatasetModifier) {
8930
+ let eqn = this.object.selector;
8931
+ if(this.wildcard_index !== false) {
8932
+ eqn = eqn.replace('??', this.wildcard_index);
8933
+ }
8934
+ return eqn + sf;
8935
+ }
8936
+ // NOTE: Same holds for "dummy variables" added for wildcard
8937
+ // dataset selectors.
8917
8938
  if(this.object === MODEL.equations_dataset) {
8918
8939
  let eqn = this.attribute;
8919
8940
  if(this.wildcard_index !== false) {
@@ -9299,6 +9320,8 @@ class Chart {
9299
9320
  vlist.push(v);
9300
9321
  }
9301
9322
  return vlist;
9323
+ // FALL-THROUGH: Equation name has no wildcard => use the equation
9324
+ // object (= dataset modifier) as `obj`.
9302
9325
  }
9303
9326
  const v = new ChartVariable(this);
9304
9327
  v.setProperties(obj, a, false, this.nextAvailableDefaultColor, 1, 1);
@@ -10109,8 +10132,8 @@ class ActorSelector {
10109
10132
  class ExperimentRunResult {
10110
10133
  constructor(r, v, a='') {
10111
10134
  // NOTE: constructor can be called with `v` a chart variable, a dataset,
10112
- // or an XML node; if `v` is the equations dataset, then `a` indicates the
10113
- // 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.
10114
10137
  this.run = r;
10115
10138
  if(v instanceof ChartVariable) {
10116
10139
  this.x_variable = true;
@@ -10182,7 +10205,7 @@ class ExperimentRunResult {
10182
10205
  this.exceptions = 0;
10183
10206
  const
10184
10207
  // NOTE: run result dataset selector will be plain (no wildcards)
10185
- x = v.attributeExpression(this.attribute),
10208
+ x = v.modifiers[this.attribute].expression,
10186
10209
  t_end = MODEL.end_period - MODEL.start_period + 1;
10187
10210
  // N = # time steps
10188
10211
  this.N = t_end;
@@ -10558,7 +10581,7 @@ class ExperimentRun {
10558
10581
  new ExperimentRunResult(this, this.experiment.variables[vi]));
10559
10582
  this.step++;
10560
10583
  UI.setProgressNeedle(this.step / this.steps);
10561
- setTimeout(function(x) { x.addChartResults(vi + 1); }, 0, this);
10584
+ setTimeout((x) => x.addChartResults(vi + 1), 0, this);
10562
10585
  } else {
10563
10586
  this.addOutcomeResults(0);
10564
10587
  }
@@ -10571,7 +10594,7 @@ class ExperimentRun {
10571
10594
  this.results.push(new ExperimentRunResult(this, MODEL.outcomes[oi]));
10572
10595
  this.step++;
10573
10596
  UI.setProgressNeedle(this.step / this.steps);
10574
- setTimeout(function(x) { x.addOutcomeResults(oi + 1); }, 0, this);
10597
+ setTimeout((x) => x.addOutcomeResults(oi + 1), 0, this);
10575
10598
  } else {
10576
10599
  this.addEquationResults(0);
10577
10600
  }
@@ -10586,7 +10609,7 @@ class ExperimentRun {
10586
10609
  new ExperimentRunResult(this, MODEL.equations_dataset, k));
10587
10610
  this.step++;
10588
10611
  UI.setProgressNeedle(this.step / this.steps);
10589
- setTimeout(function(x) { x.addEquationResults(ei + 1); }, 0, this);
10612
+ setTimeout((x) => x.addEquationResults(ei + 1), 0, this);
10590
10613
  } else {
10591
10614
  // Register when this result was stored
10592
10615
  this.time_recorded = new Date().getTime();
@@ -10596,8 +10619,10 @@ class ExperimentRun {
10596
10619
  // Log the time it took to compute all results
10597
10620
  VM.logMessage(VM.block_count - 1,
10598
10621
  `Processing run results took ${VM.elapsedTime} seconds.`);
10599
- // NOTE: addResults is called by either the experiment manager or the
10600
- // 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.
10601
10626
  if(SENSITIVITY_ANALYSIS.experiment) {
10602
10627
  SENSITIVITY_ANALYSIS.processRestOfRun();
10603
10628
  } else {
@@ -666,7 +666,7 @@ function customizeXML(str) {
666
666
  // First modify `str` -- by default, do nothing
667
667
 
668
668
  /*
669
- if(str.indexOf('<author>XXX</author>') >= 0) {
669
+ if(str.indexOf('<version>1.4.') >= 0) {
670
670
  str = str.replace(/<url>NL\/(\w+)\.csv<\/url>/g, '<url></url>');
671
671
  }
672
672
  */
@@ -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.