linny-r 1.5.0 → 1.5.3

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.5.0",
3
+ "version": "1.5.3",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
Binary file
package/static/index.html CHANGED
@@ -1819,8 +1819,10 @@ NOTE: * and ? will be interpreted as wildcards"
1819
1819
  title="Clone selected chart">
1820
1820
  <img id="chart-results-btn" class="btn enab" src="images/experiment.png"
1821
1821
  title="Show chart for experiment results (if any)">
1822
+ <img id="chart-runstat-btn" class="btn enab" src="images/runstat.png"
1823
+ title="Plot statistic for selected runs as bar chart">
1822
1824
  <img id="chart-delete-btn" class="btn enab" src="images/delete.png"
1823
- title="Delete selected chart" style="margin-left: 105px">
1825
+ title="Delete selected chart">
1824
1826
  </div>
1825
1827
  <select id="chart-selector">
1826
1828
  <!-- options are added by the chart manager -->
@@ -2683,6 +2683,7 @@ td.equation-expression {
2683
2683
  position: absolute;
2684
2684
  top: 1px;
2685
2685
  left: 1px;
2686
+ width: 202px;
2686
2687
  }
2687
2688
 
2688
2689
  #chart-buttons > img.btn,
@@ -2691,6 +2692,10 @@ td.equation-expression {
2691
2692
  width: 20px;
2692
2693
  }
2693
2694
 
2695
+ #chart-delete-btn {
2696
+ float: right;
2697
+ }
2698
+
2694
2699
  #chart-histogram {
2695
2700
  position: absolute;
2696
2701
  top: 46px;
@@ -743,6 +743,7 @@ class ChartManager {
743
743
  this.stretch_factor = 1;
744
744
  this.drawing_graph = false;
745
745
  this.runs_chart = false;
746
+ this.runs_stat = false;
746
747
  // Arrows indicating sort direction.
747
748
  this.sort_arrows = {
748
749
  'not' : '',
@@ -829,6 +830,7 @@ class ChartManager {
829
830
  this.stretch_factor = 1;
830
831
  this.drawing_graph = false;
831
832
  this.runs_chart = false;
833
+ this.runs_stat = false;
832
834
  }
833
835
 
834
836
  resetChartVectors() {
@@ -839,10 +841,16 @@ class ChartManager {
839
841
  }
840
842
 
841
843
  setRunsChart(show) {
842
- // Indicates whether the chart manager should display a run result chart
844
+ // Indicate whether the chart manager should display a run result chart.
843
845
  this.runs_chart = show;
844
846
  }
845
847
 
848
+ setRunsStat(show) {
849
+ // Indicate whether the chart manager should display selected statistic
850
+ // for selected runs as a bar chart.
851
+ this.runs_stat = show;
852
+ }
853
+
846
854
  // Dummy methods: actions that are meaningful only for the graphical UI
847
855
  updateDialog() {}
848
856
  updateExperimentInfo() {}
@@ -1145,7 +1153,7 @@ class ExperimentManager {
1145
1153
  }
1146
1154
 
1147
1155
  selectedRuns(chart) {
1148
- // Returns list of run numbers selected in the Experiment Manager
1156
+ // Return list of run numbers selected in the Experiment Manager.
1149
1157
  const selx = this.selected_experiment;
1150
1158
  if(CHART_MANAGER.runs_chart && selx && selx.charts.indexOf(chart) >= 0) {
1151
1159
  return selx.chart_combinations;
@@ -1153,6 +1161,25 @@ class ExperimentManager {
1153
1161
  return [];
1154
1162
  }
1155
1163
 
1164
+ get selectedStatisticName() {
1165
+ // Return full name of selected statistic.
1166
+ const x = this.selected_experiment;
1167
+ if(!x) return '';
1168
+ if(x.selected_scale === 'sec') return 'Solver time';
1169
+ const sn = {
1170
+ 'N': 'Count',
1171
+ 'mean': 'Mean',
1172
+ 'sd': 'Standard deviation',
1173
+ 'sum': 'Sum',
1174
+ 'min': 'Lowest value',
1175
+ 'max': 'Highest value',
1176
+ 'nz': 'Non-zero count',
1177
+ 'except': 'Exception count',
1178
+ 'last': 'Value at final time step'
1179
+ };
1180
+ return sn[x.selected_statistic] || '';
1181
+ }
1182
+
1156
1183
  selectExperiment(title) {
1157
1184
  const xi = MODEL.indexOfExperiment(title);
1158
1185
  this.selected_experiment = (xi < 0 ? null : MODEL.experiments[xi]);
@@ -54,6 +54,9 @@ class GUIChartManager extends ChartManager {
54
54
  this.results_btn = document.getElementById('chart-results-btn');
55
55
  this.results_btn.addEventListener(
56
56
  'click', () => CHART_MANAGER.toggleRunResults());
57
+ this.runstat_btn = document.getElementById('chart-runstat-btn');
58
+ this.runstat_btn.addEventListener(
59
+ 'click', () => CHART_MANAGER.toggleRunStat());
57
60
  document.getElementById('chart-delete-btn').addEventListener(
58
61
  'click', () => CHART_MANAGER.deleteChart());
59
62
  this.control_panel = document.getElementById('chart-control-panel');
@@ -197,12 +200,12 @@ class GUIChartManager extends ChartManager {
197
200
  this.variable_index = -1;
198
201
  this.stretch_factor = 1;
199
202
  this.drawing_graph = false;
200
- this.runs_chart = false;
201
203
  // Clear the model-related DOM elements
202
204
  this.chart_selector.innerHTML = '';
203
205
  this.variables_table.innerHTML = '';
204
206
  this.options_shown = true;
205
207
  this.setRunsChart(false);
208
+ this.setRunsStat(false);
206
209
  this.last_time_selected = 0;
207
210
  this.paste_color = '';
208
211
  this.hideSortingMenu();
@@ -234,12 +237,26 @@ class GUIChartManager extends ChartManager {
234
237
  }
235
238
 
236
239
  setRunsChart(show) {
237
- // Indicates whether the chart manager should display a run result chart
240
+ // Indicates whether the chart manager should display a run result chart.
238
241
  this.runs_chart = show;
239
242
  if(show) {
240
243
  this.results_btn.classList.add('stay-activ');
244
+ this.runstat_btn.style.display = 'inline-block';
241
245
  } else {
242
246
  this.results_btn.classList.remove('stay-activ');
247
+ this.runstat_btn.style.display = 'none';
248
+ }
249
+ }
250
+
251
+ setRunsStat(show) {
252
+ // Indicates whether the chart manager should display run results
253
+ // as bar chart (for runs and statistic selected in the Experiment
254
+ // Manager).
255
+ this.runs_stat = show;
256
+ if(show) {
257
+ this.runstat_btn.classList.add('stay-activ');
258
+ } else {
259
+ this.runstat_btn.classList.remove('stay-activ');
243
260
  }
244
261
  }
245
262
 
@@ -562,13 +579,22 @@ class GUIChartManager extends ChartManager {
562
579
  }
563
580
 
564
581
  toggleRunResults() {
565
- // Toggles the Boolean property that signals charts that they must plot
566
- // run results if they are part of the selected experiment chart set
582
+ // Toggle the Boolean property that signals charts that they must plot
583
+ // run results if they are part of the selected experiment chart set.
567
584
  this.setRunsChart(!this.runs_chart);
568
585
  this.resetChartVectors();
569
586
  this.updateDialog();
570
587
  }
571
588
 
589
+ toggleRunStat() {
590
+ // Toggles the Boolean property that signals charts that they must
591
+ // plot the selected statistic for the selected runs if they are
592
+ // part of the selected experiment chart set.
593
+ this.setRunsStat(!this.runs_stat);
594
+ this.resetChartVectors();
595
+ this.updateDialog();
596
+ }
597
+
572
598
  deleteChart() {
573
599
  // Deletes the shown chart (if any)
574
600
  if(this.chart_index >= 0) {
@@ -944,11 +944,14 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
944
944
  }
945
945
 
946
946
  setStatistic() {
947
- // Update view for selected variable
947
+ // Update view for selected variable.
948
948
  const x = this.selected_experiment;
949
949
  if(x) {
950
950
  x.selected_statistic = document.getElementById('viewer-statistic').value;
951
951
  this.updateData();
952
+ // NOTE: Update of Chart Manager is needed only when it is showing
953
+ // run statistics.
954
+ if(CHART_MANAGER.runs_stat) CHART_MANAGER.updateDialog();
952
955
  }
953
956
  }
954
957
 
@@ -1012,6 +1015,9 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1012
1015
  if(x) {
1013
1016
  x.selected_scale = document.getElementById('viewer-scale').value;
1014
1017
  this.updateData();
1018
+ // NOTE: Update of Chart Manager is needed when it is showing
1019
+ // run statistics because solver times may be plotted.
1020
+ if(CHART_MANAGER.runs_stat) CHART_MANAGER.updateDialog();
1015
1021
  }
1016
1022
  }
1017
1023
 
@@ -9614,10 +9614,13 @@ class Chart {
9614
9614
  let runnrs = '';
9615
9615
  const
9616
9616
  selx = EXPERIMENT_MANAGER.selected_experiment,
9617
- runs = EXPERIMENT_MANAGER.selectedRuns(this);
9617
+ runs = EXPERIMENT_MANAGER.selectedRuns(this),
9618
+ stat_bars = CHART_MANAGER.runs_stat;
9618
9619
  if(runs.length > 0) {
9619
- runnrs = [' (', selx.title, ': run', (runs.length > 1 ? 's' : ''), ' #',
9620
- runs.join(', '), ')'].join('');
9620
+ const stat = (stat_bars ?
9621
+ EXPERIMENT_MANAGER.selectedStatisticName + ' for ': '');
9622
+ runnrs = [' (', selx.title, ': ', stat, 'run',
9623
+ (runs.length > 1 ? 's' : ''), ' #', runs.join(', '), ')'].join('');
9621
9624
  }
9622
9625
  // Let Chart Manager display experiment title if selected runs are shown
9623
9626
  CHART_MANAGER.updateExperimentInfo();
@@ -9638,17 +9641,53 @@ class Chart {
9638
9641
  // NOTE: let scale start at 0 unless minimum < 0 EXCEPT for a histogram
9639
9642
  let minv = (this.histogram ? VM.PLUS_INFINITY : 0),
9640
9643
  maxv = VM.MINUS_INFINITY;
9644
+ const bar_values = {};
9641
9645
  for(let i = 0; i < this.variables.length; i++) {
9642
9646
  const v = this.variables[i];
9643
9647
  if(v.visible) {
9648
+ bar_values[i] = {};
9644
9649
  if(runs.length > 0) {
9645
9650
  for(let j = 0; j < runs.length; j++) {
9646
9651
  // NOTE: run index >= 0 makes variables use run results vector,
9647
9652
  // scaled to the just established chart time scale
9648
9653
  this.run_index = runs[j];
9649
- v.computeVector();
9650
- minv = Math.min(minv, v.lowestValueInVector);
9651
- maxv = Math.max(maxv, v.highestValueInVector);
9654
+ if(stat_bars) {
9655
+ const rri = selx.resultIndex(v.displayName);
9656
+ let bv;
9657
+ if(rri >= 0) {
9658
+ const
9659
+ r = selx.runs[this.run_index],
9660
+ rr = r.results[rri];
9661
+ if(selx.selected_scale === 'sec') {
9662
+ bv = r.solver_seconds;
9663
+ } else if(selx.selected_statistic === 'N') {
9664
+ bv = rr.N;
9665
+ } else if(selx.selected_statistic === 'sum') {
9666
+ bv = rr.sum;
9667
+ } else if(selx.selected_statistic === 'mean') {
9668
+ bv = rr.mean;
9669
+ } else if(selx.selected_statistic === 'sd') {
9670
+ bv = Math.sqrt(rr.variance);
9671
+ } else if(selx.selected_statistic === 'min') {
9672
+ bv = rr.minimum;
9673
+ } else if(selx.selected_statistic === 'max') {
9674
+ bv = rr.maximum;
9675
+ } else if(selx.selected_statistic === 'nz') {
9676
+ bv = rr.non_zero_tally;
9677
+ } else if(selx.selected_statistic === 'except') {
9678
+ bv = rr.exceptions;
9679
+ } else if(selx.selected_statistic === 'last') {
9680
+ bv = rr.last;
9681
+ }
9682
+ bar_values[i][this.run_index] = bv;
9683
+ minv = Math.min(minv, bv);
9684
+ maxv = Math.max(maxv, bv);
9685
+ }
9686
+ } else {
9687
+ v.computeVector();
9688
+ minv = Math.min(minv, v.lowestValueInVector);
9689
+ maxv = Math.max(maxv, v.highestValueInVector);
9690
+ }
9652
9691
  }
9653
9692
  } else {
9654
9693
  this.run_index = -1;
@@ -9732,8 +9771,9 @@ class Chart {
9732
9771
  }
9733
9772
  }
9734
9773
 
9735
- // The time step vector is not used when making a histogram.
9736
- if(!this.histogram) this.sortLeadTimeVector();
9774
+ // The time step vector is not used when making a histogram or when
9775
+ // plotting run statistics as bar chart.
9776
+ if(!(this.histogram || stat_bars)) this.sortLeadTimeVector();
9737
9777
 
9738
9778
  // Draw the grid rectangle
9739
9779
  this.addSVG(['<rect id="c_h_a_r_t__a_r_e_a__ID*" x="', rl, '" y="', rt,
@@ -9762,6 +9802,26 @@ class Chart {
9762
9802
  }
9763
9803
  this.addText(x + 5, y, VM.sig2Dig(b), 'black', font_height,
9764
9804
  'text-anchor="end"');
9805
+ } else if(stat_bars && runs.length > 0) {
9806
+ const dx = rw / runs.length;
9807
+ // If multiple bars (`vv` is number of visible variables), draw
9808
+ // ticks to mark horizontal area per run number.
9809
+ if(vv > 1) {
9810
+ this.addSVG(['<line x1="', rl, '" y1="', rt + rh - 3,
9811
+ '" x2="', rl, '" y2="', rt + rh + 3,
9812
+ '" stroke="black" stroke-width="1.5"/>']);
9813
+ }
9814
+ x = rl + dx;
9815
+ for(let i = 0; i < runs.length; i++) {
9816
+ if(vv > 1) {
9817
+ this.addSVG(['<line x1="', x, '" y1="', rt + rh - 3,
9818
+ '" x2="', x, '" y2="', rt + rh + 3,
9819
+ '" stroke="black" stroke-width="1.5"/>']);
9820
+ }
9821
+ // Draw experiment number in middle of its horizontal area.
9822
+ this.addText(x - dx / 2, y, '#' + runs[i]);
9823
+ x += dx;
9824
+ }
9765
9825
  } else {
9766
9826
  // Draw the time labels along the horizontal axis.
9767
9827
  // TO DO: convert to time units if modeler checks this additional option
@@ -9872,6 +9932,7 @@ class Chart {
9872
9932
  '" y2="', y0, '" stroke="black" stroke-width="2"/>']);
9873
9933
  this.addSVG(['<line x1="', x0, '" y1="', rt, '" x2="', x0,
9874
9934
  '" y2="', rt + rh, '" stroke="black" stroke-width="2"/>']);
9935
+
9875
9936
  // Now draw the chart's data elements
9876
9937
  // NOTE: `vv` still is the number of visible chart variables
9877
9938
  if(vv > 0 && this.histogram) {
@@ -9926,6 +9987,32 @@ class Chart {
9926
9987
  vnr++;
9927
9988
  }
9928
9989
  }
9990
+ } else if(vv > 0 && stat_bars) {
9991
+ dx = rw / runs.length;
9992
+ const
9993
+ varsp = dx / vv,
9994
+ barw = 0.85 * varsp,
9995
+ barsp = 0.075 * varsp;
9996
+ let vcnt = 0;
9997
+ for(let vi = 0; vi < this.variables.length; vi++) {
9998
+ const v = this.variables[vi];
9999
+ if(v.visible) {
10000
+ for(let ri = 0; ri < runs.length; ri++) {
10001
+ const
10002
+ rnr = runs[ri],
10003
+ bv = bar_values[vi][rnr],
10004
+ bart = rt + (maxy - bv) * dy,
10005
+ barh = (bv - miny) * dy;
10006
+ x = rl + ri * dx + barsp + vcnt * varsp;
10007
+ this.addSVG(['<rect x="', x, '" y="', bart,
10008
+ '" width="', barw, '" height="', barh,
10009
+ '" stroke="', v.color, '" stroke-width="',
10010
+ v.line_width, '" fill="', v.color,
10011
+ '" fill-opacity="0.4" pointer-events="none"></rect>']);
10012
+ }
10013
+ vcnt += 1;
10014
+ }
10015
+ }
9929
10016
  } else if(vv > 0) {
9930
10017
  // Draw areas of stacked variables
9931
10018
  if(runs.length <= 1) {
@@ -1960,8 +1960,11 @@ class VirtualMachine {
1960
1960
  // cash flows in the objective function (typically +/- 1)
1961
1961
  this.BASE_PENALTY = 10;
1962
1962
  // Peak variable penalty is added to make solver choose the *smallest*
1963
- // value that is greater than or equal to X[t] for all t as "peak value"
1964
- this.PEAK_VAR_PENALTY = 0.01;
1963
+ // value that is greater than or equal to X[t] for all t as "peak value".
1964
+ // NOTE: The penalty is expressed in the currency unit, so it will be
1965
+ // divided by the cash scalar so as not to interfere with the optimal
1966
+ // solution (highest total cash flow).
1967
+ this.PEAK_VAR_PENALTY = 0.1;
1965
1968
 
1966
1969
  // NOTE: the VM uses numbers >> +INF to denote special computation results
1967
1970
  this.EXCEPTION = 1e+36; // to test for any exceptional value
@@ -7445,14 +7448,19 @@ function VMI_set_objective(empty) {
7445
7448
  VM.objective[i] = VM.coefficients[i];
7446
7449
  }
7447
7450
  // NOTE: For peak increase to function properly, the peak variables
7448
- // must have a small penalty in the objective function
7451
+ // must have a small penalty (about 0.1 currency unit) in the objective
7452
+ // function.
7449
7453
  if(VM.chunk_variables.length > 0) {
7450
7454
  for(let i = 0; i < VM.chunk_variables.length; i++) {
7451
7455
  const vn = VM.chunk_variables[i][0];
7452
7456
  if(vn.indexOf('peak') > 0) {
7453
- // NOTE: chunk offset takes into account that indices are 0-based
7454
- VM.objective[VM.chunk_offset + i] = -VM.PEAK_VAR_PENALTY;
7455
- if(vn.startsWith('b')) VM.objective[VM.chunk_offset + i] -= VM.PEAK_VAR_PENALTY;
7457
+ const pvp = VM.PEAK_VAR_PENALTY / VM.cash_scalar;
7458
+ // NOTE: Chunk offset takes into account that indices are 0-based.
7459
+ VM.objective[VM.chunk_offset + i] = -pvp;
7460
+ // Put higher penalty on "block peak" than on "look-ahead peak"
7461
+ // to ensure that block peak will always be the smaller value
7462
+ // of the two peaks.
7463
+ if(vn.startsWith('b')) VM.objective[VM.chunk_offset + i] -= pvp;
7456
7464
  }
7457
7465
  }
7458
7466
  }