linny-r 1.1.20 → 1.1.22
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/README.md +4 -4
- package/package.json +1 -1
- package/static/index.html +82 -1
- package/static/linny-r.css +61 -3
- package/static/scripts/linny-r-gui.js +93 -3
- package/static/scripts/linny-r-model.js +157 -68
- package/static/scripts/linny-r-utils.js +67 -1
- package/static/scripts/linny-r-vm.js +14 -13
package/README.md
CHANGED
@@ -25,7 +25,7 @@ Technical documentation will be developed on GitHub: https://github.com/pwgbots/
|
|
25
25
|
Linny-R is developed as a JavaScript package, and requires that **Node.js** is installed on your computer.
|
26
26
|
This software can be downloaded from <a href="https://nodejs.org" target="_blank">https://nodejs.org</a>.
|
27
27
|
Make sure that you choose the correct installer for your computer.
|
28
|
-
Linny-R is developed using the _current_ release. Presently (
|
28
|
+
Linny-R is developed using the _current_ release. Presently (February 2023) this is 19.6.0.
|
29
29
|
|
30
30
|
Run the installer and accept the default settings.
|
31
31
|
There is **no** need to install the optional _Tools for Native Modules_.
|
@@ -36,7 +36,7 @@ Verify the installation by typing:
|
|
36
36
|
|
37
37
|
``node --version``
|
38
38
|
|
39
|
-
The response should be the version number of Node.js, for example: v19.
|
39
|
+
The response should be the version number of Node.js, for example: v19.6.0.
|
40
40
|
|
41
41
|
## Installing Linny-R
|
42
42
|
It is advisable to install Linny-R in a directory on your computer, not in a cloud.
|
@@ -169,8 +169,8 @@ Open the Command Line Interface (CLI) of your computer, change to your `WORKING_
|
|
169
169
|
This response should be something similar to:
|
170
170
|
|
171
171
|
<pre>
|
172
|
-
Node.js server for Linny-R version 1.1.
|
173
|
-
Node.js version: v19.
|
172
|
+
Node.js server for Linny-R version 1.1.21
|
173
|
+
Node.js version: v19.6.0
|
174
174
|
... etc.
|
175
175
|
</pre>
|
176
176
|
|
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,
|
@@ -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;
|
@@ -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
|
}
|
@@ -6739,9 +6739,14 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
|
|
6739
6739
|
options.push('<option selected disabled value="-1"></option>');
|
6740
6740
|
const d = MODEL.equations_dataset;
|
6741
6741
|
if(d) {
|
6742
|
+
const slist = [];
|
6742
6743
|
for(let m in d.modifiers) if(d.modifiers.hasOwnProperty(m)) {
|
6743
|
-
|
6744
|
-
|
6744
|
+
slist.push(d.modifiers[m].selector);
|
6745
|
+
}
|
6746
|
+
// Sort to present equations in alphabetical order
|
6747
|
+
slist.sort();
|
6748
|
+
for(let i = 0; i < slist.length; i++) {
|
6749
|
+
options.push(`<option value="${slist[i]}">${slist[i]}</option>`);
|
6745
6750
|
}
|
6746
6751
|
}
|
6747
6752
|
va.innerHTML = options.join('');
|
@@ -9819,6 +9824,12 @@ class GUIChartManager extends ChartManager {
|
|
9819
9824
|
} else if(this.chart_index >= 0) {
|
9820
9825
|
// Only accept when all conditions are met
|
9821
9826
|
ev.preventDefault();
|
9827
|
+
if(obj instanceof DatasetModifier) {
|
9828
|
+
// Equations can be added directly as chart variable
|
9829
|
+
this.addVariable(obj.name);
|
9830
|
+
return;
|
9831
|
+
}
|
9832
|
+
// For other entities, the attribute must be specified
|
9822
9833
|
this.add_variable_modal.show();
|
9823
9834
|
const
|
9824
9835
|
tn = VM.object_types.indexOf(obj.type),
|
@@ -11282,6 +11293,8 @@ class GUIExperimentManager extends ExperimentManager {
|
|
11282
11293
|
'click', () => EXPERIMENT_MANAGER.designMode());
|
11283
11294
|
document.getElementById('xv-copy-btn').addEventListener(
|
11284
11295
|
'click', () => EXPERIMENT_MANAGER.copyTableToClipboard());
|
11296
|
+
document.getElementById('xv-download-btn').addEventListener(
|
11297
|
+
'click', () => EXPERIMENT_MANAGER.promptForDownload());
|
11285
11298
|
// The viewer's drop-down selectors
|
11286
11299
|
document.getElementById('viewer-variable').addEventListener(
|
11287
11300
|
'change', () => EXPERIMENT_MANAGER.setVariable());
|
@@ -11380,6 +11393,12 @@ class GUIExperimentManager extends ExperimentManager {
|
|
11380
11393
|
this.clusters_modal.element('delete-btn').addEventListener(
|
11381
11394
|
'click', () => EXPERIMENT_MANAGER.deleteClusterFromIgnoreList());
|
11382
11395
|
|
11396
|
+
this.download_modal = new ModalDialog('xp-download');
|
11397
|
+
this.download_modal.ok.addEventListener(
|
11398
|
+
'click', () => EXPERIMENT_MANAGER.downloadDataAsCSV());
|
11399
|
+
this.download_modal.cancel.addEventListener(
|
11400
|
+
'click', () => EXPERIMENT_MANAGER.download_modal.hide());
|
11401
|
+
|
11383
11402
|
// Initialize properties
|
11384
11403
|
this.reset();
|
11385
11404
|
}
|
@@ -12739,6 +12758,77 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
|
|
12739
12758
|
UI.notify('Table copied to clipboard (as HTML)');
|
12740
12759
|
}
|
12741
12760
|
|
12761
|
+
promptForDownload() {
|
12762
|
+
// Show the download modal
|
12763
|
+
const x = this.selected_experiment;
|
12764
|
+
if(!x) return;
|
12765
|
+
const
|
12766
|
+
md = this.download_modal,
|
12767
|
+
ds = x.download_settings,
|
12768
|
+
runs = x.runs.length,
|
12769
|
+
sruns = x.chart_combinations.length;
|
12770
|
+
if(!runs) {
|
12771
|
+
UI.notify('No experiment results');
|
12772
|
+
return;
|
12773
|
+
}
|
12774
|
+
md.element(ds.variables + '-v').checked = true;
|
12775
|
+
// Disable "selected runs" button when no runs have been selected
|
12776
|
+
if(sruns) {
|
12777
|
+
md.element('selected-r').disabled = false;
|
12778
|
+
md.element(ds.runs + '-r').checked = true;
|
12779
|
+
} else {
|
12780
|
+
md.element('selected-r').disabled = true;
|
12781
|
+
// Check "all runs" but do not change download setting
|
12782
|
+
md.element('all-r').checked = true;
|
12783
|
+
}
|
12784
|
+
this.download_modal.show();
|
12785
|
+
md.element('statistics').checked = ds.statistics;
|
12786
|
+
md.element('series').checked = ds.series;
|
12787
|
+
md.element('solver').checked = ds.solver;
|
12788
|
+
md.element('separator').value = ds.separator;
|
12789
|
+
md.element('quotes').value = ds.quotes;
|
12790
|
+
md.element('precision').value = ds.precision;
|
12791
|
+
md.element('var-count').innerText = x.variables.length;
|
12792
|
+
md.element('run-count').innerText = runs;
|
12793
|
+
md.element('run-s').innerText = (sruns === 1 ? '' : 's');
|
12794
|
+
}
|
12795
|
+
|
12796
|
+
downloadDataAsCSV() {
|
12797
|
+
// Push results to browser
|
12798
|
+
if(this.selected_experiment) {
|
12799
|
+
const md = this.download_modal;
|
12800
|
+
this.selected_experiment.download_settings = {
|
12801
|
+
variables: md.element('all-v').checked ? 'all' : 'selected',
|
12802
|
+
runs: md.element('all-r').checked ? 'all' : 'selected',
|
12803
|
+
statistics: md.element('statistics').checked,
|
12804
|
+
series: md.element('series').checked,
|
12805
|
+
solver: md.element('solver').checked,
|
12806
|
+
separator: md.element('separator').value,
|
12807
|
+
quotes: md.element('quotes').value,
|
12808
|
+
precision: safeStrToInt(md.element('precision').value,
|
12809
|
+
CONFIGURATION.results_precision),
|
12810
|
+
};
|
12811
|
+
md.hide();
|
12812
|
+
const data = this.selected_experiment.resultsAsCSV;
|
12813
|
+
if(data) {
|
12814
|
+
UI.setMessage('CSV file size: ' + UI.sizeInBytes(data.length));
|
12815
|
+
const el = document.getElementById('xml-saver');
|
12816
|
+
el.href = 'data:attachment/text,' + encodeURI(data);
|
12817
|
+
console.log('Encoded CSV file size:', el.href.length);
|
12818
|
+
el.download = 'results.csv';
|
12819
|
+
if(el.href.length > 25*1024*1024 &&
|
12820
|
+
navigator.userAgent.search('Chrome') <= 0) {
|
12821
|
+
UI.notify('CSV file size exceeds 25 MB. ' +
|
12822
|
+
'If it does not download, select fewer runs');
|
12823
|
+
}
|
12824
|
+
el.click();
|
12825
|
+
UI.normalCursor();
|
12826
|
+
} else {
|
12827
|
+
UI.notify('No data');
|
12828
|
+
}
|
12829
|
+
}
|
12830
|
+
}
|
12831
|
+
|
12742
12832
|
} // END of class GUIExperimentManager
|
12743
12833
|
|
12744
12834
|
|
@@ -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
|
@@ -7482,71 +7482,7 @@ class Dataset {
|
|
7482
7482
|
for(let k in this.modifiers) if(this.modifiers.hasOwnProperty(k)) {
|
7483
7483
|
sl.push(this.modifiers[k].selector);
|
7484
7484
|
}
|
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
|
-
});
|
7485
|
+
return sl.sort(compareSelectors);
|
7550
7486
|
}
|
7551
7487
|
|
7552
7488
|
get plainSelectors() {
|
@@ -7803,9 +7739,14 @@ class Dataset {
|
|
7803
7739
|
p += ' outcome="1"';
|
7804
7740
|
}
|
7805
7741
|
if(this.black_box) p += ' black-box="1"';
|
7806
|
-
const ml = []
|
7742
|
+
const ml = [],
|
7743
|
+
sl = [];
|
7807
7744
|
for(let m in this.modifiers) if(this.modifiers.hasOwnProperty(m)) {
|
7808
|
-
|
7745
|
+
sl.push(m);
|
7746
|
+
}
|
7747
|
+
sl.sort(compareSelectors);
|
7748
|
+
for(let i = 0; i < sl.length; i++) {
|
7749
|
+
ml.push(this.modifiers[sl[i]].asXML);
|
7809
7750
|
}
|
7810
7751
|
// NOTE: "black-boxed" datasets are stored anonymously without comments
|
7811
7752
|
const id = UI.nameToID(n);
|
@@ -9651,6 +9592,16 @@ class Experiment {
|
|
9651
9592
|
constructor(n) {
|
9652
9593
|
this.title = n;
|
9653
9594
|
this.comments = '';
|
9595
|
+
this.download_settings = {
|
9596
|
+
variables: 'selected',
|
9597
|
+
runs: 'selected',
|
9598
|
+
statistics: true,
|
9599
|
+
series: false,
|
9600
|
+
solver: false,
|
9601
|
+
separator: 'semicolon',
|
9602
|
+
quotes: 'none',
|
9603
|
+
precision: CONFIGURATION.results_precision
|
9604
|
+
};
|
9654
9605
|
this.dimensions = [];
|
9655
9606
|
this.charts = [];
|
9656
9607
|
this.actual_dimensions = [];
|
@@ -9774,6 +9725,14 @@ class Experiment {
|
|
9774
9725
|
(this.completed ? '" completed="1' : ''),
|
9775
9726
|
'" started="', this.time_started,
|
9776
9727
|
'" stopped="', this.time_stopped,
|
9728
|
+
'" variables="', this.download_settings.variables,
|
9729
|
+
'" runs="', this.download_settings.runs,
|
9730
|
+
'" statistics="', this.download_settings.statistics ? 1 : 0,
|
9731
|
+
'" series="', this.download_settings.series ? 1 : 0,
|
9732
|
+
'" solver="', this.download_settings.solver ? 1 : 0,
|
9733
|
+
'" separator="', this.download_settings.separator,
|
9734
|
+
'" quotes="', this.download_settings.quotes,
|
9735
|
+
'" precision="', this.download_settings.precision,
|
9777
9736
|
'"><title>', xmlEncoded(this.title),
|
9778
9737
|
'</title><notes>', xmlEncoded(this.comments),
|
9779
9738
|
'</notes><dimensions>', d,
|
@@ -9796,6 +9755,18 @@ class Experiment {
|
|
9796
9755
|
this.completed = nodeParameterValue(node, 'completed') === '1';
|
9797
9756
|
this.time_started = safeStrToInt(nodeParameterValue(node, 'started'));
|
9798
9757
|
this.time_stopped = safeStrToInt(nodeParameterValue(node, 'stopped'));
|
9758
|
+
// Restore last download dialog settings for this experiment
|
9759
|
+
this.download_settings = {
|
9760
|
+
variables: nodeParameterValue(node, 'variables') || 'selected',
|
9761
|
+
runs: nodeParameterValue(node, 'runs') || 'selected',
|
9762
|
+
statistics: nodeParameterValue(node, 'statistics') !== '0',
|
9763
|
+
series: nodeParameterValue(node, 'series') === '1',
|
9764
|
+
solver: nodeParameterValue(node, 'solver') === '1',
|
9765
|
+
separator: nodeParameterValue(node, 'separator') || 'semicolon',
|
9766
|
+
quotes: nodeParameterValue(node, 'quotes') || 'none',
|
9767
|
+
precision: safeStrToInt(nodeParameterValue(node, 'precision'),
|
9768
|
+
CONFIGURATION.results_precision)
|
9769
|
+
};
|
9799
9770
|
this.title = xmlDecoded(nodeContentByTag(node, 'title'));
|
9800
9771
|
this.comments = xmlDecoded(nodeContentByTag(node, 'notes'));
|
9801
9772
|
let c, n = childNodeByTag(node, 'dimensions');
|
@@ -10021,6 +9992,124 @@ class Experiment {
|
|
10021
9992
|
return null;
|
10022
9993
|
}
|
10023
9994
|
|
9995
|
+
get resultsAsCSV() {
|
9996
|
+
// Return results as specfied by the download settings
|
9997
|
+
const
|
9998
|
+
allruns = this.download_settings.runs === 'all',
|
9999
|
+
sep = (this.download_settings.separator === 'tab' ? '\t' :
|
10000
|
+
(this.download_settings.separator === 'comma' ? ',' : ';')),
|
10001
|
+
quo = (this.download_settings.quotes === 'single' ? "'" :
|
10002
|
+
(this.download_settings.quotes === 'double' ? '"' : '')),
|
10003
|
+
vars = [],
|
10004
|
+
data = {
|
10005
|
+
nr: `${quo}Run number${quo}${sep}`,
|
10006
|
+
combi: `${quo}Selectors${quo}${sep}`,
|
10007
|
+
rsecs: `${quo}Run duration${quo}${sep}`,
|
10008
|
+
ssecs: `${quo}Solver time${quo}${sep}`,
|
10009
|
+
warnings: `${quo}Warnings${quo}${sep}`,
|
10010
|
+
variable: `${quo}Variable${quo}${sep}`,
|
10011
|
+
N: `${quo}N${quo}${sep}`,
|
10012
|
+
sum: `${quo}Sum${quo}${sep}`,
|
10013
|
+
mean: `${quo}Mean${quo}${sep}`,
|
10014
|
+
variance: `${quo}Variance${quo}${sep}`,
|
10015
|
+
minimum: `${quo}Minimum${quo}${sep}`,
|
10016
|
+
maximum: `${quo}Maximum${quo}${sep}`,
|
10017
|
+
NZ: `${quo}Non-zero${quo}${sep}`,
|
10018
|
+
last: `${quo}Last${quo}${sep}`,
|
10019
|
+
exceptions: `${quo}Exceptions${quo}${sep}`,
|
10020
|
+
run: []
|
10021
|
+
};
|
10022
|
+
// Make list of indices of variables to include
|
10023
|
+
if(this.download_settings.variables === 'selected') {
|
10024
|
+
// Only one variable
|
10025
|
+
vars.push(this.resultIndex(this.selected_variable));
|
10026
|
+
} else {
|
10027
|
+
// All variables
|
10028
|
+
for(let i = 0; i < this.variables.length; i++) {
|
10029
|
+
vars.push(i);
|
10030
|
+
}
|
10031
|
+
}
|
10032
|
+
const nvars = vars.length;
|
10033
|
+
for(let i = 0; i < this.combinations.length; i++) {
|
10034
|
+
if(i < this.runs.length &&
|
10035
|
+
(allruns || this.chart_combinations.indexOf(i) >= 0)) {
|
10036
|
+
data.run.push(i);
|
10037
|
+
}
|
10038
|
+
}
|
10039
|
+
let series_length = 0;
|
10040
|
+
for(let i = 0; i < data.run.length; i++) {
|
10041
|
+
const
|
10042
|
+
rnr = data.run[i],
|
10043
|
+
r = this.runs[rnr];
|
10044
|
+
data.nr += r.number;
|
10045
|
+
data.combi += quo + this.combinations[rnr].join('|') + quo;
|
10046
|
+
// Run duration in seconds
|
10047
|
+
data.rsecs += VM.sig2Dig((r.time_recorded - r.time_started) * 0.001);
|
10048
|
+
data.ssecs += VM.sig2Dig(r.solver_seconds);
|
10049
|
+
data.warnings += r.warning_count;
|
10050
|
+
for(let j = 0; j < nvars; j++) {
|
10051
|
+
// Add empty cells for run attributes
|
10052
|
+
data.nr += sep;
|
10053
|
+
data.combi += sep;
|
10054
|
+
data.rsecs += sep;
|
10055
|
+
data.ssecs += sep;
|
10056
|
+
data.warnings += sep;
|
10057
|
+
const rr = r.results[vars[j]];
|
10058
|
+
data.variable += rr.displayName + sep;
|
10059
|
+
// Series may differ in length; the longest determines the
|
10060
|
+
// number of rows of series data to be added
|
10061
|
+
series_length = Math.max(series_length, rr.vector.length);
|
10062
|
+
if(this.download_settings.statistics) {
|
10063
|
+
data.N += rr.N + sep;
|
10064
|
+
data.sum += rr.sum + sep;
|
10065
|
+
data.mean += rr.mean + sep;
|
10066
|
+
data.variance += rr.variance + sep;
|
10067
|
+
data.minimum += rr.minimum + sep;
|
10068
|
+
data.maximum += rr.maximum + sep;
|
10069
|
+
data.NZ += rr.non_zero_tally + sep;
|
10070
|
+
data.last += rr.last + sep;
|
10071
|
+
data.exceptions += rr.exceptions + sep;
|
10072
|
+
}
|
10073
|
+
}
|
10074
|
+
}
|
10075
|
+
const ds = [data.nr, data.combi];
|
10076
|
+
if(this.download_settings.solver) {
|
10077
|
+
ds.push(data.rsecs, data.ssecs, data.warnings);
|
10078
|
+
}
|
10079
|
+
// Always add the row with variable names
|
10080
|
+
ds.push(data.variable);
|
10081
|
+
if(this.download_settings.statistics) {
|
10082
|
+
ds.push(data.N, data.sum, data.mean, data.variance, data.minimum,
|
10083
|
+
data.maximum, data.NZ, data.last, data.exceptions);
|
10084
|
+
}
|
10085
|
+
if(this.download_settings.series) {
|
10086
|
+
ds.push('t');
|
10087
|
+
const
|
10088
|
+
prec = this.download_settings.precision,
|
10089
|
+
row = [];
|
10090
|
+
for(let i = 0; i < series_length; i++) {
|
10091
|
+
row.length = 0;
|
10092
|
+
row.push(i);
|
10093
|
+
for(let j = 0; j < data.run.length; j++) {
|
10094
|
+
const rnr = data.run[j];
|
10095
|
+
for(let k = 0; k < vars.length; k++) {
|
10096
|
+
const rr = this.runs[rnr].results[vars[k]];
|
10097
|
+
if(i < rr.vector.length) {
|
10098
|
+
const v = rr.vector[i];
|
10099
|
+
if(v >= VM.MINUS_INFINITY && v <= VM.PLUS_INFINITY) {
|
10100
|
+
row.push(v.toPrecision(prec));
|
10101
|
+
} else {
|
10102
|
+
row.push('');
|
10103
|
+
}
|
10104
|
+
}
|
10105
|
+
}
|
10106
|
+
}
|
10107
|
+
ds.push(row.join(sep));
|
10108
|
+
}
|
10109
|
+
}
|
10110
|
+
return ds.join('\n');
|
10111
|
+
}
|
10112
|
+
|
10024
10113
|
} // END of CLASS Experiment
|
10025
10114
|
|
10026
10115
|
|
@@ -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
|
//
|
@@ -305,6 +305,7 @@ class Expression {
|
|
305
305
|
// Pop the time step
|
306
306
|
this.step.pop();
|
307
307
|
this.trace('--STOP: ' + this.variableName);
|
308
|
+
DEBUGGING = false;
|
308
309
|
// Clear context for #
|
309
310
|
this.wildcard_number = false;
|
310
311
|
// If error, display the call stack (only once)
|
@@ -1047,7 +1048,7 @@ class ExpressionParser {
|
|
1047
1048
|
// NOTE: + and - operators are special case, since they may also
|
1048
1049
|
// be part of a floating point number, hence the more elaborate check
|
1049
1050
|
while(pl <= this.eot && (SEPARATOR_CHARS.indexOf(cpl) < 0 ||
|
1050
|
-
('+-'.indexOf(cpl) >= 0 && digs && pcpl === 'e'))) {
|
1051
|
+
('+-'.indexOf(cpl) >= 0 && digs && pcpl.toLowerCase() === 'e'))) {
|
1051
1052
|
digs = digs || '0123456789'.indexOf(cpl) >= 0;
|
1052
1053
|
this.los++;
|
1053
1054
|
pl++;
|
@@ -6034,21 +6035,19 @@ function VMI_jump(x, index) {
|
|
6034
6035
|
}
|
6035
6036
|
|
6036
6037
|
function VMI_jump_if_false(x, index) {
|
6037
|
-
//
|
6038
|
+
// Tests the top number A of the stack, and if A is FALSE (zero or
|
6038
6039
|
// VM.UNDEFINED) sets the program counter of the VM to `index` minus 1,
|
6039
6040
|
// as the counter is ALWAYS increased by 1 after calling a VMI function
|
6040
6041
|
const r = x.top(true);
|
6041
|
-
|
6042
|
-
if(r
|
6043
|
-
|
6044
|
-
|
6045
|
-
|
6046
|
-
|
6047
|
-
|
6048
|
-
|
6049
|
-
|
6050
|
-
x.stack.pop();
|
6051
|
-
}
|
6042
|
+
if(DEBUGGING) console.log(`JUMP-IF-FALSE (${r}, ${index})`);
|
6043
|
+
if(r === 0 || r === VM.UNDEFINED || r === false) {
|
6044
|
+
// Only jump on FALSE, leaving the stack "as is", so that in case
|
6045
|
+
// of no THEN the expression result equals the IF condition value
|
6046
|
+
// NOTE: Also do this on a stack error (r === false)
|
6047
|
+
x.program_counter = index - 1;
|
6048
|
+
} else {
|
6049
|
+
// Remove the value from the stack
|
6050
|
+
x.stack.pop();
|
6052
6051
|
}
|
6053
6052
|
}
|
6054
6053
|
|
@@ -6249,6 +6248,8 @@ function VMI_set_const_bounds(args) {
|
|
6249
6248
|
// if this is the first round
|
6250
6249
|
if(VM.current_round) {
|
6251
6250
|
l = vbl.actualLevel(VM.t);
|
6251
|
+
//PATCH!!
|
6252
|
+
if(l < 0.0005) l = 0;
|
6252
6253
|
} else {
|
6253
6254
|
l = 0;
|
6254
6255
|
}
|