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 +1 -1
- package/static/images/runstat.png +0 -0
- package/static/index.html +3 -1
- package/static/linny-r.css +5 -0
- package/static/scripts/linny-r-ctrl.js +29 -2
- package/static/scripts/linny-r-gui-chart-manager.js +30 -4
- package/static/scripts/linny-r-gui-experiment-manager.js +7 -1
- package/static/scripts/linny-r-model.js +95 -8
- package/static/scripts/linny-r-vm.js +14 -6
package/package.json
CHANGED
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"
|
1825
|
+
title="Delete selected chart">
|
1824
1826
|
</div>
|
1825
1827
|
<select id="chart-selector">
|
1826
1828
|
<!-- options are added by the chart manager -->
|
package/static/linny-r.css
CHANGED
@@ -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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
|
9620
|
-
|
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
|
-
|
9650
|
-
|
9651
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
7454
|
-
|
7455
|
-
|
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
|
}
|