linny-r 1.1.21 → 1.1.23
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/index.html +85 -4
- package/static/linny-r.css +61 -3
- package/static/scripts/linny-r-ctrl.js +5 -1
- package/static/scripts/linny-r-gui.js +122 -2
- package/static/scripts/linny-r-model.js +188 -78
- package/static/scripts/linny-r-utils.js +67 -1
- package/static/scripts/linny-r-vm.js +24 -15
package/package.json
CHANGED
package/static/index.html
CHANGED
@@ -159,7 +159,9 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
159
159
|
'[LINNY_R_VERSION]', v);
|
160
160
|
// Update the version number in the browser's upper left corner
|
161
161
|
document.getElementById('linny-r-version-number').innerHTML = v;
|
162
|
-
|
162
|
+
// NOTE: server detects "version 0" when npmjs website was
|
163
|
+
// not reached; if so, do not suggest a new version exists
|
164
|
+
if(info[1] !== 'up-to-date' && info[1] !== '0') {
|
163
165
|
// Inform user that newer version exists
|
164
166
|
UI.check_update_modal.element('msg').innerHTML = [
|
165
167
|
'<a href="', GITHUB_REPOSITORY,
|
@@ -1402,7 +1404,7 @@ and move the cursor over the status bar">
|
|
1402
1404
|
<option value="1">Product</option>
|
1403
1405
|
<option value="2">Cluster</option>
|
1404
1406
|
<option value="3">Link</option>
|
1405
|
-
<option value="4">Constraint</option>
|
1407
|
+
<!-- <option value="4">Constraint</option> -->
|
1406
1408
|
<option value="5">Actor</option>
|
1407
1409
|
<option value="6">Dataset</option>
|
1408
1410
|
<option value="7">Equation</option>
|
@@ -1818,7 +1820,7 @@ NOTE: * and ? will be interpreted as wildcards"
|
|
1818
1820
|
<option value="1">Product</option>
|
1819
1821
|
<option value="2">Cluster</option>
|
1820
1822
|
<option value="3">Link</option>
|
1821
|
-
<option value="4">Constraint</option>
|
1823
|
+
<!-- <option value="4">Constraint</option> -->
|
1822
1824
|
<option value="5">Actor</option>
|
1823
1825
|
<option value="6">Dataset</option>
|
1824
1826
|
<option value="7">Equation</option>
|
@@ -1955,7 +1957,7 @@ NOTE: * and ? will be interpreted as wildcards"
|
|
1955
1957
|
<option value="1">Product</option>
|
1956
1958
|
<option id="add-sa-variable-cluster" value="2">Cluster</option>
|
1957
1959
|
<option value="3">Link</option>
|
1958
|
-
<option value="4">Constraint</option>
|
1960
|
+
<!-- <option value="4">Constraint</option> -->
|
1959
1961
|
<option value="5">Actor</option>
|
1960
1962
|
<option value="6">Dataset</option>
|
1961
1963
|
<option id="add-sa-variable-equation" value="7">Equation</option>
|
@@ -2107,6 +2109,8 @@ NOTE: * and ? will be interpreted as wildcards"
|
|
2107
2109
|
<div id="xv-no-scale" class="color-scale no-colors"></div>
|
2108
2110
|
<img id="xv-copy-btn" class="btn enab" src="images/table-to-clpbrd.png"
|
2109
2111
|
title="Copy table to clipboard (as HTML)">
|
2112
|
+
<img id="xv-download-btn" class="btn enab" src="images/save-data.png"
|
2113
|
+
title="Download results (CSV file)">
|
2110
2114
|
</div>
|
2111
2115
|
</div>
|
2112
2116
|
<div id="experiment-resize" class="resizer"></div>
|
@@ -2151,6 +2155,83 @@ NOTE: * and ? will be interpreted as wildcards"
|
|
2151
2155
|
</div>
|
2152
2156
|
</div>
|
2153
2157
|
|
2158
|
+
<!-- the DOWNLOAD dialog asks which outcomes to save, and how -->
|
2159
|
+
<div id="xp-download-modal" class="modal">
|
2160
|
+
<div id="xp-download-dlg" class="inp-dlg">
|
2161
|
+
<div class="dlg-title">
|
2162
|
+
Download results
|
2163
|
+
<img class="cancel-btn" src="images/cancel.png">
|
2164
|
+
<img class="ok-btn" src="images/ok.png">
|
2165
|
+
</div>
|
2166
|
+
<fieldset id="xp-download-variables">
|
2167
|
+
<legend>Variables:</legend>
|
2168
|
+
<div>
|
2169
|
+
<input type="radio" id="xp-download-selected-v"
|
2170
|
+
name="variables" value="selected">
|
2171
|
+
<label for="selected">Only the selected variable</label>
|
2172
|
+
</div>
|
2173
|
+
<div>
|
2174
|
+
<input type="radio" id="xp-download-all-v"
|
2175
|
+
name="variables" value="all">
|
2176
|
+
<label for="all">All variables (N =
|
2177
|
+
<span id="xp-download-var-count"></span>)
|
2178
|
+
</label>
|
2179
|
+
</div>
|
2180
|
+
</fieldset>
|
2181
|
+
<fieldset id="xp-download-runs">
|
2182
|
+
<legend>Runs:</legend>
|
2183
|
+
<div>
|
2184
|
+
<input type="radio" id="xp-download-selected-r"
|
2185
|
+
name="runs" value="selected">
|
2186
|
+
<label for="selected">Only the selected
|
2187
|
+
run<span id="xp-download-run-s">s</span>
|
2188
|
+
</label>
|
2189
|
+
</div>
|
2190
|
+
<div>
|
2191
|
+
<input type="radio" id="xp-download-all-r"
|
2192
|
+
name="runs" value="all">
|
2193
|
+
<label for="all">All runs (N =
|
2194
|
+
<span id="xp-download-run-count"></span>)
|
2195
|
+
</label>
|
2196
|
+
</div>
|
2197
|
+
</fieldset>
|
2198
|
+
<fieldset id="xp-download-data">
|
2199
|
+
<legend>Data:</legend>
|
2200
|
+
<div>
|
2201
|
+
<input type="checkbox" id="xp-download-statistics" value="1">
|
2202
|
+
<label for="stats">Statistics</label>
|
2203
|
+
</div>
|
2204
|
+
<div>
|
2205
|
+
<input type="checkbox" id="xp-download-series" value="1">
|
2206
|
+
<label for="series">Time series data</label>
|
2207
|
+
</div>
|
2208
|
+
<div>
|
2209
|
+
<input type="checkbox" id="xp-download-solver" value="1">
|
2210
|
+
<label for="series">Solver information</label>
|
2211
|
+
</div>
|
2212
|
+
</fieldset>
|
2213
|
+
<fieldset id="xp-download-format">
|
2214
|
+
<legend>Format:</legend>
|
2215
|
+
<div style="margin-left: 5px">
|
2216
|
+
<label for="xp-download-separator">Separator:</label>
|
2217
|
+
<select id="xp-download-separator">
|
2218
|
+
<option value="comma">Comma</option>
|
2219
|
+
<option value="semicolon">Semicolon</option>
|
2220
|
+
<option value="tab">Tab</option>
|
2221
|
+
</select>
|
2222
|
+
<label for="xp-download-quotes">String quotes:</label>
|
2223
|
+
<select id="xp-download-quotes">
|
2224
|
+
<option value="none">None</option>
|
2225
|
+
<option value="single">Single</option>
|
2226
|
+
<option value="double">Double</option>
|
2227
|
+
</select>
|
2228
|
+
<label for="xp-download-precision">Precision:</label>
|
2229
|
+
<input type="text" id="xp-download-precision"> digits
|
2230
|
+
</div>
|
2231
|
+
</fieldset>
|
2232
|
+
</div>
|
2233
|
+
</div>
|
2234
|
+
|
2154
2235
|
<!-- the SETTINGS dialog permits editing the model settings dimensions -->
|
2155
2236
|
<div id="xp-settings-modal" class="modal">
|
2156
2237
|
<div id="xp-settings-dlg" class="inp-dlg">
|
package/static/linny-r.css
CHANGED
@@ -2966,9 +2966,9 @@ td.sa-not-run {
|
|
2966
2966
|
display: none;
|
2967
2967
|
z-index: 20;
|
2968
2968
|
margin: 0;
|
2969
|
-
width:
|
2969
|
+
width: 425px;
|
2970
2970
|
height: 275px;
|
2971
|
-
min-width:
|
2971
|
+
min-width: 425px;
|
2972
2972
|
min-height: 250px;
|
2973
2973
|
max-height: 99vh;
|
2974
2974
|
max-width: 99vw;
|
@@ -3403,7 +3403,8 @@ div.no-colors {
|
|
3403
3403
|
#sa-copy-btn,
|
3404
3404
|
#sa-copy-data-btn,
|
3405
3405
|
#xv-copy-btn,
|
3406
|
-
#xv-chart-btn
|
3406
|
+
#xv-chart-btn,
|
3407
|
+
#xv-download-btn {
|
3407
3408
|
display: inline-block;
|
3408
3409
|
vertical-align: bottom;
|
3409
3410
|
width: 19px;
|
@@ -3530,6 +3531,63 @@ span.sd-clear {
|
|
3530
3531
|
margin-left: 5px;
|
3531
3532
|
}
|
3532
3533
|
|
3534
|
+
/* the DOWNLOAD dialog prompts for selection of outcomes */
|
3535
|
+
#xp-download-dlg {
|
3536
|
+
width: 335px;
|
3537
|
+
height: 168px;
|
3538
|
+
}
|
3539
|
+
|
3540
|
+
#xp-download-variables {
|
3541
|
+
position: absolute;
|
3542
|
+
top: 22px;
|
3543
|
+
left: 1px;
|
3544
|
+
width: 163px;
|
3545
|
+
border: none;
|
3546
|
+
padding-left: 0px;
|
3547
|
+
}
|
3548
|
+
|
3549
|
+
#xp-download-runs {
|
3550
|
+
position: absolute;
|
3551
|
+
top: 22px;
|
3552
|
+
left: 180px;
|
3553
|
+
width: 146px;
|
3554
|
+
border: none;
|
3555
|
+
padding-left: 0px;
|
3556
|
+
}
|
3557
|
+
|
3558
|
+
#xp-download-data {
|
3559
|
+
position: absolute;
|
3560
|
+
top: 84px;
|
3561
|
+
left: 1px;
|
3562
|
+
width: 163px;
|
3563
|
+
border: none;
|
3564
|
+
padding-left: 0px;
|
3565
|
+
}
|
3566
|
+
|
3567
|
+
#xp-download-format {
|
3568
|
+
position: absolute;
|
3569
|
+
top: 84px;
|
3570
|
+
left: 180px;
|
3571
|
+
width: 146px;
|
3572
|
+
border: none;
|
3573
|
+
padding-left: 0px;
|
3574
|
+
}
|
3575
|
+
|
3576
|
+
#xp-download-separator,
|
3577
|
+
#xp-download-quotes {
|
3578
|
+
font-size: 11px;
|
3579
|
+
height: 19px;
|
3580
|
+
margin-bottom: 3px;
|
3581
|
+
}
|
3582
|
+
|
3583
|
+
#xp-download-precision {
|
3584
|
+
font-size: 12px;
|
3585
|
+
height: 15px !important;
|
3586
|
+
width: 20px;
|
3587
|
+
text-align: center;
|
3588
|
+
}
|
3589
|
+
|
3590
|
+
|
3533
3591
|
/* the ACTOR DIMENSION dialog allows editing this dimension */
|
3534
3592
|
#xp-actor-dimension-dlg {
|
3535
3593
|
width: 150px;
|
@@ -738,8 +738,11 @@ class SensitivityAnalysis {
|
|
738
738
|
oax = (obj ? obj.attributeExpression(vn[1]) : null);
|
739
739
|
if(oax) {
|
740
740
|
this.parameters.push(oax);
|
741
|
+
} else if(vn.length === 1 && obj instanceof Dataset) {
|
742
|
+
// Dataset without selector => push the dataset vector
|
743
|
+
this.parameters.push(obj.vector);
|
741
744
|
} else {
|
742
|
-
UI.alert(`Parameter ${p} is not
|
745
|
+
UI.alert(`Parameter ${p} is not a dataset or expression`);
|
743
746
|
}
|
744
747
|
}
|
745
748
|
this.chart = new Chart(this.chart_title);
|
@@ -841,6 +844,7 @@ class SensitivityAnalysis {
|
|
841
844
|
VM.halt();
|
842
845
|
this.readyButtons();
|
843
846
|
this.showProgress('');
|
847
|
+
this.must_pause = false;
|
844
848
|
}
|
845
849
|
|
846
850
|
clearResults() {
|
@@ -2808,7 +2808,7 @@ class ModalDialog {
|
|
2808
2808
|
}
|
2809
2809
|
|
2810
2810
|
show(name=null) {
|
2811
|
-
// Makes dialog visible and focuses on element with
|
2811
|
+
// Makes dialog visible and focuses on element with `focal`
|
2812
2812
|
this.modal.style.display = 'block';
|
2813
2813
|
if(name) this.element(name).focus();
|
2814
2814
|
}
|
@@ -10513,7 +10513,7 @@ class GUISensitivityAnalysis extends SensitivityAnalysis {
|
|
10513
10513
|
this.base_selectors.addEventListener(
|
10514
10514
|
'blur', () => SENSITIVITY_ANALYSIS.setBaseSelectors());
|
10515
10515
|
|
10516
|
-
this.delta = document.getElementById('
|
10516
|
+
this.delta = document.getElementById('sensitivity-delta');
|
10517
10517
|
this.delta.addEventListener(
|
10518
10518
|
'focus', () => SENSITIVITY_ANALYSIS.editDelta());
|
10519
10519
|
this.delta.addEventListener(
|
@@ -10610,6 +10610,8 @@ class GUISensitivityAnalysis extends SensitivityAnalysis {
|
|
10610
10610
|
}
|
10611
10611
|
|
10612
10612
|
updateControlPanel() {
|
10613
|
+
// Shows the control panel, or when the analysis is running the
|
10614
|
+
// legend to the outcomes (also to prevent changes to parameters)
|
10613
10615
|
this.base_selectors.value = MODEL.base_case_selectors;
|
10614
10616
|
this.delta.value = VM.sig4Dig(MODEL.sensitivity_delta);
|
10615
10617
|
const tr = [];
|
@@ -10900,6 +10902,8 @@ class GUISensitivityAnalysis extends SensitivityAnalysis {
|
|
10900
10902
|
// NOTE: clusters have no suitable attributes, and equations are endogenous
|
10901
10903
|
md.element('cluster').style.display = 'none';
|
10902
10904
|
md.element('equation').style.display = 'none';
|
10905
|
+
// NOTE: update to ensure that valid attributes are selectable
|
10906
|
+
X_EDIT.updateVariableBar('add-sa-');
|
10903
10907
|
md.show();
|
10904
10908
|
}
|
10905
10909
|
|
@@ -10909,6 +10913,8 @@ class GUISensitivityAnalysis extends SensitivityAnalysis {
|
|
10909
10913
|
md.element('type').innerText = 'outcome';
|
10910
10914
|
md.element('cluster').style.display = 'block';
|
10911
10915
|
md.element('equation').style.display = 'block';
|
10916
|
+
// NOTE: update to ensure that valid attributes are selectable
|
10917
|
+
X_EDIT.updateVariableBar('add-sa-');
|
10912
10918
|
md.show();
|
10913
10919
|
}
|
10914
10920
|
|
@@ -10977,9 +10983,14 @@ class GUISensitivityAnalysis extends SensitivityAnalysis {
|
|
10977
10983
|
a = md.selectedOption('attr').text;
|
10978
10984
|
let n = '';
|
10979
10985
|
if(e === 'Equation' && a) {
|
10986
|
+
// For equations, the attribute denotes the name
|
10980
10987
|
n = a;
|
10981
10988
|
} else if(o && a) {
|
10989
|
+
// Most variables are defined by name + attribute ...
|
10982
10990
|
n = o + UI.OA_SEPARATOR + a;
|
10991
|
+
} else if(e === 'Dataset' && o) {
|
10992
|
+
// ... but for datasets the selector is optional
|
10993
|
+
n = o;
|
10983
10994
|
}
|
10984
10995
|
if(n) {
|
10985
10996
|
if(t === 'parameter' && MODEL.sensitivity_parameters.indexOf(n) < 0) {
|
@@ -11021,6 +11032,7 @@ class GUISensitivityAnalysis extends SensitivityAnalysis {
|
|
11021
11032
|
this.start_btn.classList.add('off');
|
11022
11033
|
this.pause_btn.classList.remove('off');
|
11023
11034
|
this.stop_btn.classList.add('off');
|
11035
|
+
this.must_pause = false;
|
11024
11036
|
return paused;
|
11025
11037
|
}
|
11026
11038
|
|
@@ -11029,6 +11041,7 @@ class GUISensitivityAnalysis extends SensitivityAnalysis {
|
|
11029
11041
|
this.pause_btn.classList.add('off');
|
11030
11042
|
this.stop_btn.classList.add('off');
|
11031
11043
|
this.start_btn.classList.remove('off', 'blink');
|
11044
|
+
this.must_pause = false;
|
11032
11045
|
}
|
11033
11046
|
|
11034
11047
|
pausedButtons(aci) {
|
@@ -11047,6 +11060,8 @@ class GUISensitivityAnalysis extends SensitivityAnalysis {
|
|
11047
11060
|
this.readyButtons();
|
11048
11061
|
this.reset_btn.classList.add('off');
|
11049
11062
|
this.selected_run = -1;
|
11063
|
+
this.must_pause = false;
|
11064
|
+
this.progress.innerHTML = '';
|
11050
11065
|
this.updateDialog();
|
11051
11066
|
}
|
11052
11067
|
|
@@ -11293,6 +11308,8 @@ class GUIExperimentManager extends ExperimentManager {
|
|
11293
11308
|
'click', () => EXPERIMENT_MANAGER.designMode());
|
11294
11309
|
document.getElementById('xv-copy-btn').addEventListener(
|
11295
11310
|
'click', () => EXPERIMENT_MANAGER.copyTableToClipboard());
|
11311
|
+
document.getElementById('xv-download-btn').addEventListener(
|
11312
|
+
'click', () => EXPERIMENT_MANAGER.promptForDownload());
|
11296
11313
|
// The viewer's drop-down selectors
|
11297
11314
|
document.getElementById('viewer-variable').addEventListener(
|
11298
11315
|
'change', () => EXPERIMENT_MANAGER.setVariable());
|
@@ -11391,6 +11408,12 @@ class GUIExperimentManager extends ExperimentManager {
|
|
11391
11408
|
this.clusters_modal.element('delete-btn').addEventListener(
|
11392
11409
|
'click', () => EXPERIMENT_MANAGER.deleteClusterFromIgnoreList());
|
11393
11410
|
|
11411
|
+
this.download_modal = new ModalDialog('xp-download');
|
11412
|
+
this.download_modal.ok.addEventListener(
|
11413
|
+
'click', () => EXPERIMENT_MANAGER.downloadDataAsCSV());
|
11414
|
+
this.download_modal.cancel.addEventListener(
|
11415
|
+
'click', () => EXPERIMENT_MANAGER.download_modal.hide());
|
11416
|
+
|
11394
11417
|
// Initialize properties
|
11395
11418
|
this.reset();
|
11396
11419
|
}
|
@@ -11794,6 +11817,32 @@ class GUIExperimentManager extends ExperimentManager {
|
|
11794
11817
|
}
|
11795
11818
|
}
|
11796
11819
|
|
11820
|
+
toggleChartRow(r, n, shift) {
|
11821
|
+
// Toggle `n` consecutive rows, starting at row `r` (0 = top), to be
|
11822
|
+
// (no longer) part of the chart combination set
|
11823
|
+
const
|
11824
|
+
x = this.selected_experiment,
|
11825
|
+
// Let `n` be the number of the first run on row `r`
|
11826
|
+
nconf = r * this.nr_of_configurations;
|
11827
|
+
if(x && r < x.combinations.length / this.nr_of_configurations) {
|
11828
|
+
// NOTE: first cell of row determines ADD or REMOVE
|
11829
|
+
const add = x.chart_combinations.indexOf(n) < 0;
|
11830
|
+
for(let i = 0; i < this.nr_of_configurations; i++) {
|
11831
|
+
const ic = x.chart_combinations.indexOf(i);
|
11832
|
+
if(add) {
|
11833
|
+
if(ic < 0) x.chart_combinations.push(nconf + i);
|
11834
|
+
} else {
|
11835
|
+
if(!add) x.chart_combinations.splice(nconf + i, 1);
|
11836
|
+
}
|
11837
|
+
}
|
11838
|
+
this.updateData();
|
11839
|
+
}
|
11840
|
+
}
|
11841
|
+
|
11842
|
+
toggleChartColumn(c, shift) {
|
11843
|
+
// Toggle column `c` (0 = leftmost) to be part of the chart combination set
|
11844
|
+
}
|
11845
|
+
|
11797
11846
|
toggleChartCombi(n, shift, alt) {
|
11798
11847
|
// Set `n` to be the chart combination, or toggle if Shift-key is pressed,
|
11799
11848
|
// or execute single run if Alt-key is pressed
|
@@ -12750,6 +12799,77 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
|
|
12750
12799
|
UI.notify('Table copied to clipboard (as HTML)');
|
12751
12800
|
}
|
12752
12801
|
|
12802
|
+
promptForDownload() {
|
12803
|
+
// Show the download modal
|
12804
|
+
const x = this.selected_experiment;
|
12805
|
+
if(!x) return;
|
12806
|
+
const
|
12807
|
+
md = this.download_modal,
|
12808
|
+
ds = x.download_settings,
|
12809
|
+
runs = x.runs.length,
|
12810
|
+
sruns = x.chart_combinations.length;
|
12811
|
+
if(!runs) {
|
12812
|
+
UI.notify('No experiment results');
|
12813
|
+
return;
|
12814
|
+
}
|
12815
|
+
md.element(ds.variables + '-v').checked = true;
|
12816
|
+
// Disable "selected runs" button when no runs have been selected
|
12817
|
+
if(sruns) {
|
12818
|
+
md.element('selected-r').disabled = false;
|
12819
|
+
md.element(ds.runs + '-r').checked = true;
|
12820
|
+
} else {
|
12821
|
+
md.element('selected-r').disabled = true;
|
12822
|
+
// Check "all runs" but do not change download setting
|
12823
|
+
md.element('all-r').checked = true;
|
12824
|
+
}
|
12825
|
+
this.download_modal.show();
|
12826
|
+
md.element('statistics').checked = ds.statistics;
|
12827
|
+
md.element('series').checked = ds.series;
|
12828
|
+
md.element('solver').checked = ds.solver;
|
12829
|
+
md.element('separator').value = ds.separator;
|
12830
|
+
md.element('quotes').value = ds.quotes;
|
12831
|
+
md.element('precision').value = ds.precision;
|
12832
|
+
md.element('var-count').innerText = x.runs[0].results.length;
|
12833
|
+
md.element('run-count').innerText = runs;
|
12834
|
+
md.element('run-s').innerText = (sruns === 1 ? '' : 's');
|
12835
|
+
}
|
12836
|
+
|
12837
|
+
downloadDataAsCSV() {
|
12838
|
+
// Push results to browser
|
12839
|
+
if(this.selected_experiment) {
|
12840
|
+
const md = this.download_modal;
|
12841
|
+
this.selected_experiment.download_settings = {
|
12842
|
+
variables: md.element('all-v').checked ? 'all' : 'selected',
|
12843
|
+
runs: md.element('all-r').checked ? 'all' : 'selected',
|
12844
|
+
statistics: md.element('statistics').checked,
|
12845
|
+
series: md.element('series').checked,
|
12846
|
+
solver: md.element('solver').checked,
|
12847
|
+
separator: md.element('separator').value,
|
12848
|
+
quotes: md.element('quotes').value,
|
12849
|
+
precision: safeStrToInt(md.element('precision').value,
|
12850
|
+
CONFIGURATION.results_precision),
|
12851
|
+
};
|
12852
|
+
md.hide();
|
12853
|
+
const data = this.selected_experiment.resultsAsCSV;
|
12854
|
+
if(data) {
|
12855
|
+
UI.setMessage('CSV file size: ' + UI.sizeInBytes(data.length));
|
12856
|
+
const el = document.getElementById('xml-saver');
|
12857
|
+
el.href = 'data:attachment/text,' + encodeURI(data);
|
12858
|
+
console.log('Encoded CSV file size:', el.href.length);
|
12859
|
+
el.download = 'results.csv';
|
12860
|
+
if(el.href.length > 25*1024*1024 &&
|
12861
|
+
navigator.userAgent.search('Chrome') <= 0) {
|
12862
|
+
UI.notify('CSV file size exceeds 25 MB. ' +
|
12863
|
+
'If it does not download, select fewer runs');
|
12864
|
+
}
|
12865
|
+
el.click();
|
12866
|
+
UI.normalCursor();
|
12867
|
+
} else {
|
12868
|
+
UI.notify('No data');
|
12869
|
+
}
|
12870
|
+
}
|
12871
|
+
}
|
12872
|
+
|
12753
12873
|
} // END of class GUIExperimentManager
|
12754
12874
|
|
12755
12875
|
|
@@ -10,7 +10,7 @@ Linny-R project.
|
|
10
10
|
*/
|
11
11
|
|
12
12
|
/*
|
13
|
-
Copyright (c) 2017-
|
13
|
+
Copyright (c) 2017-2023 Delft University of Technology
|
14
14
|
|
15
15
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
16
16
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -2485,12 +2485,15 @@ class LinnyRModel {
|
|
2485
2485
|
const ds_dict = {};
|
2486
2486
|
for(let k in this.datasets) if(this.datasets.hasOwnProperty(k)) {
|
2487
2487
|
const ds = this.datasets[k];
|
2488
|
-
|
2489
|
-
|
2490
|
-
|
2491
|
-
|
2492
|
-
|
2493
|
-
|
2488
|
+
// NOTE: ignore selectors of the equations dataset
|
2489
|
+
if(ds !== this.equations_dataset) {
|
2490
|
+
for(let m in ds.modifiers) if(ds.modifiers.hasOwnProperty(m)) {
|
2491
|
+
const s = ds.modifiers[m].selector;
|
2492
|
+
if(s in ds_dict) {
|
2493
|
+
ds_dict[s].push(ds);
|
2494
|
+
} else {
|
2495
|
+
ds_dict[s] = [ds];
|
2496
|
+
}
|
2494
2497
|
}
|
2495
2498
|
}
|
2496
2499
|
}
|
@@ -7482,71 +7485,7 @@ class Dataset {
|
|
7482
7485
|
for(let k in this.modifiers) if(this.modifiers.hasOwnProperty(k)) {
|
7483
7486
|
sl.push(this.modifiers[k].selector);
|
7484
7487
|
}
|
7485
|
-
return sl.sort(
|
7486
|
-
// Dataset selectors comparison is case-insensitive, and puts wildcards
|
7487
|
-
// last, where * comes later than ?
|
7488
|
-
// NOTE: without wildcards, strings that are identical except for the
|
7489
|
-
// digits they *end* on are sorted on this "end number" (so abc12 > abc2)
|
7490
|
-
// NOTE: this also applies to percentages ("end number"+ %)
|
7491
|
-
if(s1 === s2) return 0;
|
7492
|
-
if(s1 === '*') return 1;
|
7493
|
-
if(s2 === '*') return -1;
|
7494
|
-
const
|
7495
|
-
star1 = s1.indexOf('*'),
|
7496
|
-
star2 = s2.indexOf('*');
|
7497
|
-
if(star1 >= 0) {
|
7498
|
-
if(star2 < 0) return 1;
|
7499
|
-
return s1.localeCompare(s2);
|
7500
|
-
}
|
7501
|
-
if(star2 >= 0) return -1;
|
7502
|
-
// Replace ? by | because | has a higher ASCII value than all other chars
|
7503
|
-
let s_1 = s1.replace('?', '|').toLowerCase(),
|
7504
|
-
s_2 = s2.replace('?', '|').toLowerCase(),
|
7505
|
-
// NOTE: treat selectors ending on a number or percentage as special case
|
7506
|
-
n_1 = endsWithDigits(s_1),
|
7507
|
-
p_1 = (s1.endsWith('%') ? endsWithDigits(s1.slice(0, -1)) : '');
|
7508
|
-
if(n_1) {
|
7509
|
-
const
|
7510
|
-
ss_1 = s1.slice(0, -n_1.length),
|
7511
|
-
n_2 = endsWithDigits(s2);
|
7512
|
-
if(n_2 && ss_1 === s2.slice(0, -n_2.length)) {
|
7513
|
-
return parseInt(n_1) - parseInt(n_2);
|
7514
|
-
}
|
7515
|
-
} else if(p_1) {
|
7516
|
-
const
|
7517
|
-
ss_1 = s1.slice(0, -p_1.length - 1),
|
7518
|
-
p_2 = (s2.endsWith('%') ? endsWithDigits(s2.slice(0, -1)) : '');
|
7519
|
-
if(p_2 && ss_1 === s2.slice(0, -p_2.length - 1)) {
|
7520
|
-
return parseInt(p_1) - parseInt(p_2);
|
7521
|
-
}
|
7522
|
-
}
|
7523
|
-
// Also sort selectors ending on minuses lower than those ending on plusses,
|
7524
|
-
// and such that X-- comes before X-, like X+ automatically comes before X++
|
7525
|
-
// ASCII(+) = 43, ASCII(-) = 45, so replace trailing minuses by as many spaces
|
7526
|
-
// (ASCII 32) and add a '!' (ASCII 33) -- this then "sorts things out"
|
7527
|
-
let n = s_1.length,
|
7528
|
-
i = n - 1;
|
7529
|
-
while(i >= 0 && s_1[i] === '-') i--;
|
7530
|
-
// If trailing minuses, replace by as many spaces and add an exclamation point
|
7531
|
-
if(i < n - 1) {
|
7532
|
-
s_1 = s_1.substr(0, i);
|
7533
|
-
while(s_1.length < n) s_1 += ' ';
|
7534
|
-
s_1 += '!';
|
7535
|
-
}
|
7536
|
-
// Do the same for the second "normalized" selector
|
7537
|
-
n = s_2.length;
|
7538
|
-
i = n - 1;
|
7539
|
-
while(i >= 0 && s_2[i] === '-') i--;
|
7540
|
-
if(i < n - 1) {
|
7541
|
-
s_2 = s_2.substr(0, i);
|
7542
|
-
while(s_2.length < n) s_2 += ' ';
|
7543
|
-
s_2 += '!';
|
7544
|
-
}
|
7545
|
-
// Now compare the two "normalized" selectors
|
7546
|
-
if(s_1 < s_2) return -1;
|
7547
|
-
if(s_1 > s_2) return 1;
|
7548
|
-
return 0;
|
7549
|
-
});
|
7488
|
+
return sl.sort(compareSelectors);
|
7550
7489
|
}
|
7551
7490
|
|
7552
7491
|
get plainSelectors() {
|
@@ -7708,9 +7647,11 @@ class Dataset {
|
|
7708
7647
|
attributeExpression(a) {
|
7709
7648
|
// Returns expression for selector `a`, or NULL if no such selector exists
|
7710
7649
|
// NOTE: selectors no longer are case-sensitive
|
7711
|
-
|
7712
|
-
|
7713
|
-
|
7650
|
+
if(a) {
|
7651
|
+
a = UI.nameToID(a);
|
7652
|
+
for(let m in this.modifiers) if(this.modifiers.hasOwnProperty(m)) {
|
7653
|
+
if(m === a) return this.modifiers[m].expression;
|
7654
|
+
}
|
7714
7655
|
}
|
7715
7656
|
return null;
|
7716
7657
|
}
|
@@ -7803,9 +7744,14 @@ class Dataset {
|
|
7803
7744
|
p += ' outcome="1"';
|
7804
7745
|
}
|
7805
7746
|
if(this.black_box) p += ' black-box="1"';
|
7806
|
-
const ml = []
|
7747
|
+
const ml = [],
|
7748
|
+
sl = [];
|
7807
7749
|
for(let m in this.modifiers) if(this.modifiers.hasOwnProperty(m)) {
|
7808
|
-
|
7750
|
+
sl.push(m);
|
7751
|
+
}
|
7752
|
+
sl.sort(compareSelectors);
|
7753
|
+
for(let i = 0; i < sl.length; i++) {
|
7754
|
+
ml.push(this.modifiers[sl[i]].asXML);
|
7809
7755
|
}
|
7810
7756
|
// NOTE: "black-boxed" datasets are stored anonymously without comments
|
7811
7757
|
const id = UI.nameToID(n);
|
@@ -9285,7 +9231,12 @@ class ExperimentRunResult {
|
|
9285
9231
|
obj = MODEL.objectByID(this.object_id),
|
9286
9232
|
dn = obj.displayName;
|
9287
9233
|
// NOTE: for equations dataset, only display the modifier selector
|
9288
|
-
if(obj === MODEL.equations_dataset)
|
9234
|
+
if(obj === MODEL.equations_dataset) {
|
9235
|
+
const m = obj.modifiers[this.attribute.toLowerCase()];
|
9236
|
+
if(m) return m.selector;
|
9237
|
+
console.log('WARNING: Run result of non-existent equation', this.attribute);
|
9238
|
+
return this.attribute;
|
9239
|
+
}
|
9289
9240
|
return (this.attribute ? dn + '|' + this.attribute : dn);
|
9290
9241
|
}
|
9291
9242
|
|
@@ -9651,6 +9602,16 @@ class Experiment {
|
|
9651
9602
|
constructor(n) {
|
9652
9603
|
this.title = n;
|
9653
9604
|
this.comments = '';
|
9605
|
+
this.download_settings = {
|
9606
|
+
variables: 'selected',
|
9607
|
+
runs: 'selected',
|
9608
|
+
statistics: true,
|
9609
|
+
series: false,
|
9610
|
+
solver: false,
|
9611
|
+
separator: 'semicolon',
|
9612
|
+
quotes: 'none',
|
9613
|
+
precision: CONFIGURATION.results_precision
|
9614
|
+
};
|
9654
9615
|
this.dimensions = [];
|
9655
9616
|
this.charts = [];
|
9656
9617
|
this.actual_dimensions = [];
|
@@ -9774,6 +9735,14 @@ class Experiment {
|
|
9774
9735
|
(this.completed ? '" completed="1' : ''),
|
9775
9736
|
'" started="', this.time_started,
|
9776
9737
|
'" stopped="', this.time_stopped,
|
9738
|
+
'" variables="', this.download_settings.variables,
|
9739
|
+
'" runs="', this.download_settings.runs,
|
9740
|
+
'" statistics="', this.download_settings.statistics ? 1 : 0,
|
9741
|
+
'" series="', this.download_settings.series ? 1 : 0,
|
9742
|
+
'" solver="', this.download_settings.solver ? 1 : 0,
|
9743
|
+
'" separator="', this.download_settings.separator,
|
9744
|
+
'" quotes="', this.download_settings.quotes,
|
9745
|
+
'" precision="', this.download_settings.precision,
|
9777
9746
|
'"><title>', xmlEncoded(this.title),
|
9778
9747
|
'</title><notes>', xmlEncoded(this.comments),
|
9779
9748
|
'</notes><dimensions>', d,
|
@@ -9796,6 +9765,18 @@ class Experiment {
|
|
9796
9765
|
this.completed = nodeParameterValue(node, 'completed') === '1';
|
9797
9766
|
this.time_started = safeStrToInt(nodeParameterValue(node, 'started'));
|
9798
9767
|
this.time_stopped = safeStrToInt(nodeParameterValue(node, 'stopped'));
|
9768
|
+
// Restore last download dialog settings for this experiment
|
9769
|
+
this.download_settings = {
|
9770
|
+
variables: nodeParameterValue(node, 'variables') || 'selected',
|
9771
|
+
runs: nodeParameterValue(node, 'runs') || 'selected',
|
9772
|
+
statistics: nodeParameterValue(node, 'statistics') !== '0',
|
9773
|
+
series: nodeParameterValue(node, 'series') === '1',
|
9774
|
+
solver: nodeParameterValue(node, 'solver') === '1',
|
9775
|
+
separator: nodeParameterValue(node, 'separator') || 'semicolon',
|
9776
|
+
quotes: nodeParameterValue(node, 'quotes') || 'none',
|
9777
|
+
precision: safeStrToInt(nodeParameterValue(node, 'precision'),
|
9778
|
+
CONFIGURATION.results_precision)
|
9779
|
+
};
|
9799
9780
|
this.title = xmlDecoded(nodeContentByTag(node, 'title'));
|
9800
9781
|
this.comments = xmlDecoded(nodeContentByTag(node, 'notes'));
|
9801
9782
|
let c, n = childNodeByTag(node, 'dimensions');
|
@@ -10021,6 +10002,135 @@ class Experiment {
|
|
10021
10002
|
return null;
|
10022
10003
|
}
|
10023
10004
|
|
10005
|
+
get resultsAsCSV() {
|
10006
|
+
// Return results as specfied by the download settings
|
10007
|
+
// NOTE: no runs => no results => return empty string
|
10008
|
+
if(this.runs.length === 0) return '';
|
10009
|
+
const
|
10010
|
+
// Local function to convert number to string
|
10011
|
+
numval = (v, p) => {
|
10012
|
+
// Return 0 as single digit
|
10013
|
+
if(Math.abs(v) < VM.NEAR_ZERO) return '0';
|
10014
|
+
// Return empty string for undefined or exceptional values
|
10015
|
+
if(!v || v < VM.MINUS_INFINITY || v > VM.PLUS_INFINITY) return '';
|
10016
|
+
// Return other values as float with specified precision
|
10017
|
+
return v.toPrecision(p);
|
10018
|
+
},
|
10019
|
+
prec = this.download_settings.precision,
|
10020
|
+
allruns = this.download_settings.runs === 'all',
|
10021
|
+
sep = (this.download_settings.separator === 'tab' ? '\t' :
|
10022
|
+
(this.download_settings.separator === 'comma' ? ',' : ';')),
|
10023
|
+
quo = (this.download_settings.quotes === 'single' ? "'" :
|
10024
|
+
(this.download_settings.quotes === 'double' ? '"' : '')),
|
10025
|
+
vars = [],
|
10026
|
+
data = {
|
10027
|
+
nr: `${quo}Run number${quo}${sep}`,
|
10028
|
+
combi: `${quo}Selectors${quo}${sep}`,
|
10029
|
+
rsecs: `${quo}Run duration${quo}${sep}`,
|
10030
|
+
ssecs: `${quo}Solver time${quo}${sep}`,
|
10031
|
+
warnings: `${quo}Warnings${quo}${sep}`,
|
10032
|
+
variable: `${quo}Variable${quo}${sep}`,
|
10033
|
+
N: `${quo}N${quo}${sep}`,
|
10034
|
+
sum: `${quo}Sum${quo}${sep}`,
|
10035
|
+
mean: `${quo}Mean${quo}${sep}`,
|
10036
|
+
variance: `${quo}Variance${quo}${sep}`,
|
10037
|
+
minimum: `${quo}Minimum${quo}${sep}`,
|
10038
|
+
maximum: `${quo}Maximum${quo}${sep}`,
|
10039
|
+
NZ: `${quo}Non-zero${quo}${sep}`,
|
10040
|
+
last: `${quo}Last${quo}${sep}`,
|
10041
|
+
exceptions: `${quo}Exceptions${quo}${sep}`,
|
10042
|
+
run: []
|
10043
|
+
};
|
10044
|
+
for(let i = 0; i < this.combinations.length; i++) {
|
10045
|
+
if(i < this.runs.length &&
|
10046
|
+
(allruns || this.chart_combinations.indexOf(i) >= 0)) {
|
10047
|
+
data.run.push(i);
|
10048
|
+
}
|
10049
|
+
}
|
10050
|
+
let series_length = 0,
|
10051
|
+
// By default, assume all variables to be output
|
10052
|
+
start = 0,
|
10053
|
+
stop = this.runs[0].results.length;
|
10054
|
+
if(this.download_settings.variables === 'selected') {
|
10055
|
+
// Only one variable
|
10056
|
+
start = this.resultIndex(this.selected_variable);
|
10057
|
+
stop = start + 1;
|
10058
|
+
}
|
10059
|
+
for(let i = 0; i < data.run.length; i++) {
|
10060
|
+
const
|
10061
|
+
rnr = data.run[i],
|
10062
|
+
r = this.runs[rnr];
|
10063
|
+
data.nr += r.number;
|
10064
|
+
data.combi += quo + this.combinations[rnr].join('|') + quo;
|
10065
|
+
// Run duration in seconds
|
10066
|
+
data.rsecs += numval((r.time_recorded - r.time_started) * 0.001, 4);
|
10067
|
+
data.ssecs += numval(r.solver_seconds, 4);
|
10068
|
+
data.warnings += r.warning_count;
|
10069
|
+
for(let j = start; j < stop; j++) {
|
10070
|
+
// Add empty cells for run attributes
|
10071
|
+
data.nr += sep;
|
10072
|
+
data.combi += sep;
|
10073
|
+
data.rsecs += sep;
|
10074
|
+
data.ssecs += sep;
|
10075
|
+
data.warnings += sep;
|
10076
|
+
const rr = r.results[j];
|
10077
|
+
if(rr) {
|
10078
|
+
data.variable += rr.displayName + sep;
|
10079
|
+
// Series may differ in length; the longest determines the
|
10080
|
+
// number of rows of series data to be added
|
10081
|
+
series_length = Math.max(series_length, rr.vector.length);
|
10082
|
+
if(this.download_settings.statistics) {
|
10083
|
+
data.N += rr.N + sep;
|
10084
|
+
data.sum += numval(rr.sum, prec) + sep;
|
10085
|
+
data.mean += numval(rr.mean, prec) + sep;
|
10086
|
+
data.variance += numval(rr.variance, prec) + sep;
|
10087
|
+
data.minimum += numval(rr.minimum, prec) + sep;
|
10088
|
+
data.maximum += numval(rr.maximum, prec) + sep;
|
10089
|
+
data.NZ += rr.non_zero_tally + sep;
|
10090
|
+
data.last += numval(rr.last, prec) + sep;
|
10091
|
+
data.exceptions += rr.exceptions + sep;
|
10092
|
+
}
|
10093
|
+
} else {
|
10094
|
+
console.log('No run results for ', this.variables[vars[j]].displayName);
|
10095
|
+
}
|
10096
|
+
}
|
10097
|
+
}
|
10098
|
+
const ds = [data.nr, data.combi];
|
10099
|
+
if(this.download_settings.solver) {
|
10100
|
+
ds.push(data.rsecs, data.ssecs, data.warnings);
|
10101
|
+
}
|
10102
|
+
// Always add the row with variable names
|
10103
|
+
ds.push(data.variable);
|
10104
|
+
if(this.download_settings.statistics) {
|
10105
|
+
ds.push(data.N, data.sum, data.mean, data.variance, data.minimum,
|
10106
|
+
data.maximum, data.NZ, data.last, data.exceptions);
|
10107
|
+
}
|
10108
|
+
if(this.download_settings.series) {
|
10109
|
+
ds.push('t');
|
10110
|
+
const row = [];
|
10111
|
+
for(let i = 0; i < series_length; i++) {
|
10112
|
+
row.length = 0;
|
10113
|
+
row.push(i);
|
10114
|
+
for(let j = 0; j < data.run.length; j++) {
|
10115
|
+
const rnr = data.run[j];
|
10116
|
+
for(let k = start; k < stop; k++) {
|
10117
|
+
const rr = this.runs[rnr].results[k];
|
10118
|
+
if(rr) {
|
10119
|
+
// NOTE: only experiment variables have vector data
|
10120
|
+
if(rr.x_variable && i <= rr.N) {
|
10121
|
+
row.push(numval(rr.vector[i], prec));
|
10122
|
+
} else {
|
10123
|
+
row.push('');
|
10124
|
+
}
|
10125
|
+
}
|
10126
|
+
}
|
10127
|
+
}
|
10128
|
+
ds.push(row.join(sep));
|
10129
|
+
}
|
10130
|
+
}
|
10131
|
+
return ds.join('\n');
|
10132
|
+
}
|
10133
|
+
|
10024
10134
|
} // END of CLASS Experiment
|
10025
10135
|
|
10026
10136
|
|
@@ -9,7 +9,7 @@ This JavaScript file (linny-r-utils.js) defines a variety of "helper" functions
|
|
9
9
|
that are used in other Linny-R modules.
|
10
10
|
*/
|
11
11
|
/*
|
12
|
-
Copyright (c) 2017-
|
12
|
+
Copyright (c) 2017-2023 Delft University of Technology
|
13
13
|
|
14
14
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
15
15
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -326,6 +326,72 @@ function escapeRegex(str) {
|
|
326
326
|
return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
327
327
|
}
|
328
328
|
|
329
|
+
function compareSelectors(s1, s2) {
|
330
|
+
// Dataset selectors comparison is case-insensitive, and puts wildcards
|
331
|
+
// last, where * comes later than ?
|
332
|
+
// NOTE: without wildcards, strings that are identical except for the
|
333
|
+
// digits they *end* on are sorted on this "end number" (so abc12 > abc2)
|
334
|
+
// NOTE: this also applies to percentages ("end number"+ %)
|
335
|
+
if(s1 === s2) return 0;
|
336
|
+
if(s1 === '*') return 1;
|
337
|
+
if(s2 === '*') return -1;
|
338
|
+
const
|
339
|
+
star1 = s1.indexOf('*'),
|
340
|
+
star2 = s2.indexOf('*');
|
341
|
+
if(star1 >= 0) {
|
342
|
+
if(star2 < 0) return 1;
|
343
|
+
return s1.localeCompare(s2);
|
344
|
+
}
|
345
|
+
if(star2 >= 0) return -1;
|
346
|
+
// Replace ? by | because | has a higher ASCII value than all other chars
|
347
|
+
let s_1 = s1.replace('?', '|').toLowerCase(),
|
348
|
+
s_2 = s2.replace('?', '|').toLowerCase(),
|
349
|
+
// NOTE: treat selectors ending on a number or percentage as special case
|
350
|
+
n_1 = endsWithDigits(s_1),
|
351
|
+
p_1 = (s1.endsWith('%') ? endsWithDigits(s1.slice(0, -1)) : '');
|
352
|
+
if(n_1) {
|
353
|
+
const
|
354
|
+
ss_1 = s1.slice(0, -n_1.length),
|
355
|
+
n_2 = endsWithDigits(s2);
|
356
|
+
if(n_2 && ss_1 === s2.slice(0, -n_2.length)) {
|
357
|
+
return parseInt(n_1) - parseInt(n_2);
|
358
|
+
}
|
359
|
+
} else if(p_1) {
|
360
|
+
const
|
361
|
+
ss_1 = s1.slice(0, -p_1.length - 1),
|
362
|
+
p_2 = (s2.endsWith('%') ? endsWithDigits(s2.slice(0, -1)) : '');
|
363
|
+
if(p_2 && ss_1 === s2.slice(0, -p_2.length - 1)) {
|
364
|
+
return parseInt(p_1) - parseInt(p_2);
|
365
|
+
}
|
366
|
+
}
|
367
|
+
// Also sort selectors ending on minuses lower than those ending on plusses,
|
368
|
+
// and such that X-- comes before X-, like X+ automatically comes before X++
|
369
|
+
// ASCII(+) = 43, ASCII(-) = 45, so replace trailing minuses by as many spaces
|
370
|
+
// (ASCII 32) and add a '!' (ASCII 33) -- this then "sorts things out"
|
371
|
+
let n = s_1.length,
|
372
|
+
i = n - 1;
|
373
|
+
while(i >= 0 && s_1[i] === '-') i--;
|
374
|
+
// If trailing minuses, replace by as many spaces and add an exclamation point
|
375
|
+
if(i < n - 1) {
|
376
|
+
s_1 = s_1.substr(0, i);
|
377
|
+
while(s_1.length < n) s_1 += ' ';
|
378
|
+
s_1 += '!';
|
379
|
+
}
|
380
|
+
// Do the same for the second "normalized" selector
|
381
|
+
n = s_2.length;
|
382
|
+
i = n - 1;
|
383
|
+
while(i >= 0 && s_2[i] === '-') i--;
|
384
|
+
if(i < n - 1) {
|
385
|
+
s_2 = s_2.substr(0, i);
|
386
|
+
while(s_2.length < n) s_2 += ' ';
|
387
|
+
s_2 += '!';
|
388
|
+
}
|
389
|
+
// Now compare the two "normalized" selectors
|
390
|
+
if(s_1 < s_2) return -1;
|
391
|
+
if(s_1 > s_2) return 1;
|
392
|
+
return 0;
|
393
|
+
}
|
394
|
+
|
329
395
|
//
|
330
396
|
// Functions that perform set-like operations on lists of string
|
331
397
|
//
|
@@ -5182,7 +5182,16 @@ function VMI_push_var(x, args) {
|
|
5182
5182
|
}
|
5183
5183
|
if(Array.isArray(obj)) {
|
5184
5184
|
// Object is a vector
|
5185
|
-
|
5185
|
+
let v = t < obj.length ? obj[t] : VM.UNDEFINED;
|
5186
|
+
// NOTE: when the vector is the "active" parameter for sensitivity
|
5187
|
+
// analysis, the value is multiplied by 1 + delta %
|
5188
|
+
if(obj === MODEL.active_sensitivity_parameter) {
|
5189
|
+
// NOTE: do NOT scale exceptional values
|
5190
|
+
if(v > VM.MINUS_INFINITY && v < VM.PLUS_INFINITY) {
|
5191
|
+
v *= (1 + MODEL.sensitivity_delta * 0.01);
|
5192
|
+
}
|
5193
|
+
}
|
5194
|
+
x.push(v);
|
5186
5195
|
} else if(xv) {
|
5187
5196
|
// Variable references an earlier value computed for this expression `x`
|
5188
5197
|
x.push(t >= 0 && t < x.vector.length ? x.vector[t] : obj.dv);
|
@@ -5315,8 +5324,10 @@ function VMI_push_dataset_modifier(x, args) {
|
|
5315
5324
|
tot[1] + (tot[2] ? ':' + tot[2] : ''), ' value = ', VM.sig4Dig(v));
|
5316
5325
|
console.log(' --', x.text, ' for owner ', x.object.displayName, x.attribute);
|
5317
5326
|
}
|
5318
|
-
// NOTE:
|
5319
|
-
|
5327
|
+
// NOTE: if value is exceptional ("undefined", etc.), use default value
|
5328
|
+
if(v >= VM.PLUS_INFINITY) v = ds.defaultValue;
|
5329
|
+
// Finally, push the value onto the expression stack
|
5330
|
+
x.push(v);
|
5320
5331
|
}
|
5321
5332
|
|
5322
5333
|
|
@@ -6034,21 +6045,19 @@ function VMI_jump(x, index) {
|
|
6034
6045
|
}
|
6035
6046
|
|
6036
6047
|
function VMI_jump_if_false(x, index) {
|
6037
|
-
//
|
6048
|
+
// Tests the top number A of the stack, and if A is FALSE (zero or
|
6038
6049
|
// VM.UNDEFINED) sets the program counter of the VM to `index` minus 1,
|
6039
6050
|
// as the counter is ALWAYS increased by 1 after calling a VMI function
|
6040
6051
|
const r = x.top(true);
|
6041
|
-
|
6042
|
-
if(r
|
6043
|
-
|
6044
|
-
|
6045
|
-
|
6046
|
-
|
6047
|
-
|
6048
|
-
|
6049
|
-
|
6050
|
-
x.stack.pop();
|
6051
|
-
}
|
6052
|
+
if(DEBUGGING) console.log(`JUMP-IF-FALSE (${r}, ${index})`);
|
6053
|
+
if(r === 0 || r === VM.UNDEFINED || r === false) {
|
6054
|
+
// Only jump on FALSE, leaving the stack "as is", so that in case
|
6055
|
+
// of no THEN the expression result equals the IF condition value
|
6056
|
+
// NOTE: Also do this on a stack error (r === false)
|
6057
|
+
x.program_counter = index - 1;
|
6058
|
+
} else {
|
6059
|
+
// Remove the value from the stack
|
6060
|
+
x.stack.pop();
|
6052
6061
|
}
|
6053
6062
|
}
|
6054
6063
|
|