linny-r 1.5.1 → 1.5.4

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.1",
3
+ "version": "1.5.4",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
package/server.js CHANGED
@@ -160,25 +160,46 @@ const SERVER = http.createServer((req, res) => {
160
160
  }
161
161
  });
162
162
 
163
- // Start listening at the specified port number
164
- console.log('Listening at: http://127.0.0.1:' + SETTINGS.port);
165
- SERVER.listen(SETTINGS.port);
166
-
167
- // Finally, launch the GUI if this command line argument is set
168
- if(SETTINGS.launch) {
169
- console.log('Launching Linny-R in the default browser');
170
- const cmd = (PLATFORM.startsWith('win') ? 'start' : 'open');
171
- child_process.exec(cmd + ' http://127.0.0.1:' + SETTINGS.port,
172
- (error, stdout, stderr) => {
173
- if(error) {
174
- console.log('NOTICE: Failed to launch GUI in browser');
175
- console.log(error);
176
- console.log(stdout);
177
- console.log(stderr);
178
- }
179
- });
163
+ // Prepare for error on start-up.
164
+ SERVER.on('error', (err) => {
165
+ if(err.code === 'EADDRINUSE') {
166
+ success = false;
167
+ console.log('ERROR: Port', SETTINGS.port, 'is already in use.');
168
+ console.log('Some Linny-R server may be in another CLI box -- Please check.');
169
+ } else {
170
+ throw err;
171
+ }
172
+ });
173
+
174
+ // Start listening at the specified port number.
175
+ const options = {
176
+ port: SETTINGS.port,
177
+ exclusive: true
178
+ };
179
+ SERVER.listen(options, launchGUI);
180
+
181
+
182
+ function launchGUI(err) {
183
+ if(SERVER.listening) {
184
+ console.log('Listening at: http://127.0.0.1:' + SETTINGS.port);
185
+ }
186
+ // Launch the GUI if this command line argument is set.
187
+ if(SETTINGS.launch) {
188
+ console.log('Launching Linny-R in the default browser');
189
+ const cmd = (PLATFORM.startsWith('win') ? 'start' : 'open');
190
+ child_process.exec(cmd + ' http://127.0.0.1:' + SETTINGS.port,
191
+ (error, stdout, stderr) => {
192
+ if(error) {
193
+ console.log('NOTICE: Failed to launch GUI in browser');
194
+ console.log(error);
195
+ console.log(stdout);
196
+ console.log(stderr);
197
+ }
198
+ });
199
+ }
180
200
  }
181
201
 
202
+
182
203
  // Server action logging functionality
183
204
  // ===================================
184
205
  // Only actions are logged to the console as with date and time;
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,14 +1153,34 @@ 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
- if(CHART_MANAGER.runs_chart && selx && selx.charts.indexOf(chart) >= 0) {
1158
+ if(CHART_MANAGER.runs_chart && selx &&
1159
+ (selx.charts.indexOf(chart) >= 0 || CHART_MANAGER.runs_stat)) {
1151
1160
  return selx.chart_combinations;
1152
1161
  }
1153
1162
  return [];
1154
1163
  }
1155
1164
 
1165
+ get selectedStatisticName() {
1166
+ // Return full name of selected statistic.
1167
+ const x = this.selected_experiment;
1168
+ if(!x) return '';
1169
+ if(x.selected_scale === 'sec') return 'Solver time';
1170
+ const sn = {
1171
+ 'N': 'Count',
1172
+ 'mean': 'Mean',
1173
+ 'sd': 'Standard deviation',
1174
+ 'sum': 'Sum',
1175
+ 'min': 'Lowest value',
1176
+ 'max': 'Highest value',
1177
+ 'nz': 'Non-zero count',
1178
+ 'except': 'Exception count',
1179
+ 'last': 'Value at final time step'
1180
+ };
1181
+ return sn[x.selected_statistic] || '';
1182
+ }
1183
+
1156
1184
  selectExperiment(title) {
1157
1185
  const xi = MODEL.indexOfExperiment(title);
1158
1186
  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) {
@@ -839,7 +865,7 @@ class GUIChartManager extends ChartManager {
839
865
  }
840
866
 
841
867
  drawTable() {
842
- // Shows the statistics on the chart variables
868
+ // Shows the statistics on the chart variables.
843
869
  const html = [];
844
870
  let vbl = [];
845
871
  if(this.chart_index >= 0) vbl = MODEL.charts[this.chart_index].variables;
@@ -976,8 +1002,8 @@ class GUIChartManager extends ChartManager {
976
1002
  }
977
1003
 
978
1004
  drawChart() {
979
- // Displays the selected chart unless an experiment is running, or already
980
- // busy with an earlier drawChart call
1005
+ // Displays the selected chart unless an experiment is running, or
1006
+ // already busy with an earlier drawChart call.
981
1007
  if(MODEL.running_experiment) {
982
1008
  UI.notify(UI.NOTICE.NO_CHARTS);
983
1009
  } else if(this.chart_index >= 0 && !this.drawing_chart) {
@@ -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) {