linny-r 1.4.4 → 1.4.6
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 +14 -0
- package/static/linny-r.css +4 -2
- package/static/scripts/linny-r-ctrl.js +1 -1
- package/static/scripts/linny-r-gui-equation-manager.js +46 -1
- package/static/scripts/linny-r-gui-expression-editor.js +2 -1
- package/static/scripts/linny-r-model.js +39 -14
- package/static/scripts/linny-r-utils.js +1 -1
- package/static/scripts/linny-r-vm.js +29 -28
package/package.json
CHANGED
package/static/index.html
CHANGED
@@ -1754,6 +1754,8 @@ NOTE: * and ? will be interpreted as wildcards"
|
|
1754
1754
|
title="New equation">
|
1755
1755
|
<img id="eq-rename-btn" class="btn disab" src="images/rename.png"
|
1756
1756
|
title="Rename selected equation">
|
1757
|
+
<img id="eq-clone-btn" class="btn disab" src="images/clone.png"
|
1758
|
+
title="Clone selected equation">
|
1757
1759
|
<img id="eq-edit-btn" class="btn disab" src="images/edit.png"
|
1758
1760
|
title="Edit selected equation">
|
1759
1761
|
<img id="eq-delete-btn" class="btn disab" src="images/delete.png"
|
@@ -1790,6 +1792,18 @@ NOTE: * and ? will be interpreted as wildcards"
|
|
1790
1792
|
</div>
|
1791
1793
|
</div>
|
1792
1794
|
|
1795
|
+
<!-- the CLONE EQUATION dialog prompts for the new name of an equation -->
|
1796
|
+
<div id="clone-equation-modal" class="modal">
|
1797
|
+
<div id="clone-equation-dlg" class="inp-dlg">
|
1798
|
+
<div class="dlg-title">
|
1799
|
+
Clone equation
|
1800
|
+
<img class="cancel-btn" src="images/cancel.png">
|
1801
|
+
<img class="ok-btn" src="images/ok.png">
|
1802
|
+
</div>
|
1803
|
+
<input id="clone-equation-name" type="text" autocomplete="off">
|
1804
|
+
</div>
|
1805
|
+
</div>
|
1806
|
+
|
1793
1807
|
|
1794
1808
|
<!-- the CHART dialog allows definition of charts and displays them -->
|
1795
1809
|
<div id="chart-dlg" class="inp-dlg">
|
package/static/linny-r.css
CHANGED
@@ -2439,7 +2439,8 @@ td.equation-expression {
|
|
2439
2439
|
#new-dataset-dlg,
|
2440
2440
|
#rename-dataset-dlg,
|
2441
2441
|
#new-equation-dlg,
|
2442
|
-
#rename-equation-dlg
|
2442
|
+
#rename-equation-dlg,
|
2443
|
+
#clone-equation-dlg {
|
2443
2444
|
width: 240px;
|
2444
2445
|
height: 45px;
|
2445
2446
|
}
|
@@ -2447,7 +2448,8 @@ td.equation-expression {
|
|
2447
2448
|
#new-dataset-name,
|
2448
2449
|
#rename-dataset-name,
|
2449
2450
|
#new-equation-name,
|
2450
|
-
#rename-equation-name
|
2451
|
+
#rename-equation-name,
|
2452
|
+
#clone-equation-name {
|
2451
2453
|
position: absolute;
|
2452
2454
|
bottom: 2px;
|
2453
2455
|
left: 2px;
|
@@ -1261,7 +1261,7 @@ class ExperimentManager {
|
|
1261
1261
|
}
|
1262
1262
|
|
1263
1263
|
processRestOfRun() {
|
1264
|
-
// Performs post-processing after run results have been added
|
1264
|
+
// Performs post-processing after run results have been added.
|
1265
1265
|
const x = MODEL.running_experiment;
|
1266
1266
|
if(!x) return;
|
1267
1267
|
const aci = x.active_combination_index;
|
@@ -48,6 +48,8 @@ class EquationManager {
|
|
48
48
|
'click', () => EQUATION_MANAGER.promptForEquation());
|
49
49
|
document.getElementById('eq-rename-btn').addEventListener(
|
50
50
|
'click', () => EQUATION_MANAGER.promptForName());
|
51
|
+
document.getElementById('eq-clone-btn').addEventListener(
|
52
|
+
'click', () => EQUATION_MANAGER.promptToClone());
|
51
53
|
document.getElementById('eq-edit-btn').addEventListener(
|
52
54
|
'click', () => EQUATION_MANAGER.editEquation());
|
53
55
|
document.getElementById('eq-delete-btn').addEventListener(
|
@@ -66,6 +68,12 @@ class EquationManager {
|
|
66
68
|
this.rename_modal.cancel.addEventListener(
|
67
69
|
'click', () => EQUATION_MANAGER.rename_modal.hide());
|
68
70
|
|
71
|
+
this.clone_modal = new ModalDialog('clone-equation');
|
72
|
+
this.clone_modal.ok.addEventListener(
|
73
|
+
'click', () => EQUATION_MANAGER.cloneEquation());
|
74
|
+
this.clone_modal.cancel.addEventListener(
|
75
|
+
'click', () => EQUATION_MANAGER.clone_modal.hide());
|
76
|
+
|
69
77
|
// Initialize the dialog properties
|
70
78
|
this.reset();
|
71
79
|
}
|
@@ -146,7 +154,7 @@ class EquationManager {
|
|
146
154
|
this.table.innerHTML = ml.join('');
|
147
155
|
this.scroll_area.style.display = 'block';
|
148
156
|
if(sm) UI.scrollIntoView(document.getElementById(smid));
|
149
|
-
const btns = 'eq-rename eq-edit eq-delete';
|
157
|
+
const btns = 'eq-rename eq-clone eq-edit eq-delete';
|
150
158
|
if(sm) {
|
151
159
|
UI.enableButtons(btns);
|
152
160
|
} else {
|
@@ -294,6 +302,43 @@ class EquationManager {
|
|
294
302
|
this.updateDialog();
|
295
303
|
}
|
296
304
|
|
305
|
+
promptToClone() {
|
306
|
+
// Prompts the modeler for the name of the clone to make of the
|
307
|
+
// selected equation (if any).
|
308
|
+
if(this.selected_modifier) {
|
309
|
+
this.clone_modal.element('name').value = this.selected_modifier.selector;
|
310
|
+
this.clone_modal.show('name');
|
311
|
+
}
|
312
|
+
}
|
313
|
+
|
314
|
+
cloneEquation() {
|
315
|
+
if(!this.selected_modifier) return;
|
316
|
+
const
|
317
|
+
s = this.clone_modal.element('name').value,
|
318
|
+
// New equation identifier must not equal some entity ID
|
319
|
+
obj = MODEL.objectByName(s);
|
320
|
+
if(obj) {
|
321
|
+
// NOTE: also pass selector, or warning will display dataset name.
|
322
|
+
UI.warningEntityExists(obj);
|
323
|
+
return null;
|
324
|
+
}
|
325
|
+
// Name is new and unique, so try to use it
|
326
|
+
const m = MODEL.equations_dataset.addModifier(s);
|
327
|
+
// NULL indicates invalid name, and modeler will have been warned.
|
328
|
+
if(!m) return;
|
329
|
+
// Give the new modifier the same expression as te selected one.
|
330
|
+
m.expression.text = this.selected_modifier.expression.text;
|
331
|
+
// Compile the expression. This may generate a warning when the new
|
332
|
+
// name does not provide adequate context.
|
333
|
+
m.expression.compile();
|
334
|
+
//
|
335
|
+
this.selected_modifier = m;
|
336
|
+
// Even if warning was given, close the name prompt dialog, and update
|
337
|
+
// the equation manager.
|
338
|
+
this.clone_modal.hide();
|
339
|
+
this.updateDialog();
|
340
|
+
}
|
341
|
+
|
297
342
|
deleteEquation() {
|
298
343
|
const m = this.selected_modifier;
|
299
344
|
if(m) {
|
@@ -205,7 +205,8 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
|
|
205
205
|
}
|
206
206
|
}
|
207
207
|
if(a) n += ` (${a})`;
|
208
|
-
|
208
|
+
// NOTE: Names may contain ampersand as HTML entity.
|
209
|
+
UI.edited_object = MODEL.objectByName(n.replace('&', '&'));
|
209
210
|
this.edited_input_id = UI.edited_object.type.toLowerCase() + '-' + ids[1];
|
210
211
|
this.edited_expression = UI.edited_object.attributeExpression(ids[1]);
|
211
212
|
}
|
@@ -2963,7 +2963,7 @@ class LinnyRModel {
|
|
2963
2963
|
const cv = new ChartVariable(null);
|
2964
2964
|
cv.setProperties(v.object, v.attribute, false, '#000000');
|
2965
2965
|
vbls.push(cv);
|
2966
|
-
names.push(
|
2966
|
+
names.push(vn);
|
2967
2967
|
}
|
2968
2968
|
} else if(names.indexOf(vn) < 0) {
|
2969
2969
|
// Keep track of the dataset and dataset modifier variables,
|
@@ -2988,7 +2988,8 @@ class LinnyRModel {
|
|
2988
2988
|
if(names.indexOf(n) < 0) {
|
2989
2989
|
// Here, too, NULL can be used as "owner chart".
|
2990
2990
|
const cv = new ChartVariable(null);
|
2991
|
-
|
2991
|
+
// NOTE: For equations, the object is the dataset modifier.
|
2992
|
+
cv.setProperties(eq ? dm : ds, dm.selector, false, '#000000');
|
2992
2993
|
vbls.push(cv);
|
2993
2994
|
}
|
2994
2995
|
}
|
@@ -8433,15 +8434,26 @@ class Dataset {
|
|
8433
8434
|
}
|
8434
8435
|
|
8435
8436
|
get plainSelectors() {
|
8436
|
-
//
|
8437
|
-
const sl = this.selectorList;
|
8438
|
-
// NOTE:
|
8437
|
+
// Return sorted list of selectors that do not contain wildcards.
|
8438
|
+
const sl = this.selectorList.slice();
|
8439
|
+
// NOTE: Wildcard selectors will always be at the end of the list
|
8439
8440
|
for(let i = sl.length - 1; i >= 0; i--) {
|
8440
8441
|
if(sl[i].indexOf('*') >= 0 || sl[i].indexOf('?') >= 0) sl.pop();
|
8441
8442
|
}
|
8442
8443
|
return sl;
|
8443
8444
|
}
|
8444
8445
|
|
8446
|
+
get wildcardSelectors() {
|
8447
|
+
// Return sorted list of selectors that DO contain wildcards.
|
8448
|
+
const sl = this.selectorList;
|
8449
|
+
// NOTE: Wildcard selectors will always be at the end of the list.
|
8450
|
+
let i = sl.length - 1;
|
8451
|
+
while(i >= 0 && (sl[i].indexOf('*') >= 0 || sl[i].indexOf('?') >= 0)) {
|
8452
|
+
i--;
|
8453
|
+
}
|
8454
|
+
return sl.slice(i+1);
|
8455
|
+
}
|
8456
|
+
|
8445
8457
|
isWildcardSelector(s) {
|
8446
8458
|
// Returns TRUE if `s` contains * or ?
|
8447
8459
|
// NOTE: for equations, the wildcard must be ??
|
@@ -8913,7 +8925,16 @@ class ChartVariable {
|
|
8913
8925
|
// by its scale factor unless it equals 1 (no scaling).
|
8914
8926
|
const sf = (this.scale_factor === 1 ? '' :
|
8915
8927
|
` (x${VM.sig4Dig(this.scale_factor)})`);
|
8916
|
-
//
|
8928
|
+
//Display name of equation is just the equations dataset selector.
|
8929
|
+
if(this.object instanceof DatasetModifier) {
|
8930
|
+
let eqn = this.object.selector;
|
8931
|
+
if(this.wildcard_index !== false) {
|
8932
|
+
eqn = eqn.replace('??', this.wildcard_index);
|
8933
|
+
}
|
8934
|
+
return eqn + sf;
|
8935
|
+
}
|
8936
|
+
// NOTE: Same holds for "dummy variables" added for wildcard
|
8937
|
+
// dataset selectors.
|
8917
8938
|
if(this.object === MODEL.equations_dataset) {
|
8918
8939
|
let eqn = this.attribute;
|
8919
8940
|
if(this.wildcard_index !== false) {
|
@@ -9299,6 +9320,8 @@ class Chart {
|
|
9299
9320
|
vlist.push(v);
|
9300
9321
|
}
|
9301
9322
|
return vlist;
|
9323
|
+
// FALL-THROUGH: Equation name has no wildcard => use the equation
|
9324
|
+
// object (= dataset modifier) as `obj`.
|
9302
9325
|
}
|
9303
9326
|
const v = new ChartVariable(this);
|
9304
9327
|
v.setProperties(obj, a, false, this.nextAvailableDefaultColor, 1, 1);
|
@@ -10109,8 +10132,8 @@ class ActorSelector {
|
|
10109
10132
|
class ExperimentRunResult {
|
10110
10133
|
constructor(r, v, a='') {
|
10111
10134
|
// NOTE: constructor can be called with `v` a chart variable, a dataset,
|
10112
|
-
// or an XML node; if `v` is the equations dataset, then `a`
|
10113
|
-
//
|
10135
|
+
// or an XML node; if `v` is the equations dataset, then `a` is the
|
10136
|
+
// identifier of the dataset modifier to be used.
|
10114
10137
|
this.run = r;
|
10115
10138
|
if(v instanceof ChartVariable) {
|
10116
10139
|
this.x_variable = true;
|
@@ -10182,7 +10205,7 @@ class ExperimentRunResult {
|
|
10182
10205
|
this.exceptions = 0;
|
10183
10206
|
const
|
10184
10207
|
// NOTE: run result dataset selector will be plain (no wildcards)
|
10185
|
-
x = v.
|
10208
|
+
x = v.modifiers[this.attribute].expression,
|
10186
10209
|
t_end = MODEL.end_period - MODEL.start_period + 1;
|
10187
10210
|
// N = # time steps
|
10188
10211
|
this.N = t_end;
|
@@ -10558,7 +10581,7 @@ class ExperimentRun {
|
|
10558
10581
|
new ExperimentRunResult(this, this.experiment.variables[vi]));
|
10559
10582
|
this.step++;
|
10560
10583
|
UI.setProgressNeedle(this.step / this.steps);
|
10561
|
-
setTimeout(
|
10584
|
+
setTimeout((x) => x.addChartResults(vi + 1), 0, this);
|
10562
10585
|
} else {
|
10563
10586
|
this.addOutcomeResults(0);
|
10564
10587
|
}
|
@@ -10571,7 +10594,7 @@ class ExperimentRun {
|
|
10571
10594
|
this.results.push(new ExperimentRunResult(this, MODEL.outcomes[oi]));
|
10572
10595
|
this.step++;
|
10573
10596
|
UI.setProgressNeedle(this.step / this.steps);
|
10574
|
-
setTimeout(
|
10597
|
+
setTimeout((x) => x.addOutcomeResults(oi + 1), 0, this);
|
10575
10598
|
} else {
|
10576
10599
|
this.addEquationResults(0);
|
10577
10600
|
}
|
@@ -10586,7 +10609,7 @@ class ExperimentRun {
|
|
10586
10609
|
new ExperimentRunResult(this, MODEL.equations_dataset, k));
|
10587
10610
|
this.step++;
|
10588
10611
|
UI.setProgressNeedle(this.step / this.steps);
|
10589
|
-
setTimeout(
|
10612
|
+
setTimeout((x) => x.addEquationResults(ei + 1), 0, this);
|
10590
10613
|
} else {
|
10591
10614
|
// Register when this result was stored
|
10592
10615
|
this.time_recorded = new Date().getTime();
|
@@ -10596,8 +10619,10 @@ class ExperimentRun {
|
|
10596
10619
|
// Log the time it took to compute all results
|
10597
10620
|
VM.logMessage(VM.block_count - 1,
|
10598
10621
|
`Processing run results took ${VM.elapsedTime} seconds.`);
|
10599
|
-
//
|
10600
|
-
|
10622
|
+
// Report results if applicable.
|
10623
|
+
if(RECEIVER.solving || MODEL.report_results) RECEIVER.report();
|
10624
|
+
// NOTE: addResults is called by either the experiment manager or
|
10625
|
+
// the sensitivity analysis; hence proceed from there.
|
10601
10626
|
if(SENSITIVITY_ANALYSIS.experiment) {
|
10602
10627
|
SENSITIVITY_ANALYSIS.processRestOfRun();
|
10603
10628
|
} else {
|
@@ -666,7 +666,7 @@ function customizeXML(str) {
|
|
666
666
|
// First modify `str` -- by default, do nothing
|
667
667
|
|
668
668
|
/*
|
669
|
-
if(str.indexOf('<
|
669
|
+
if(str.indexOf('<version>1.4.') >= 0) {
|
670
670
|
str = str.replace(/<url>NL\/(\w+)\.csv<\/url>/g, '<url></url>');
|
671
671
|
}
|
672
672
|
*/
|
@@ -348,6 +348,8 @@ class Expression {
|
|
348
348
|
v[t] = this.stack.pop();
|
349
349
|
}
|
350
350
|
this.trace('RESULT = ' + VM.sig4Dig(v[t]));
|
351
|
+
// Store wildcard result also in "normal" vector
|
352
|
+
this.vector[t] = v[t];
|
351
353
|
// Pop the time step.
|
352
354
|
this.step.pop();
|
353
355
|
this.trace('--STOP: ' + this.variableName);
|
@@ -5268,10 +5270,13 @@ Solver status = ${json.status}`);
|
|
5268
5270
|
UI.drawDiagram(MODEL);
|
5269
5271
|
// Show the reset button (GUI only)
|
5270
5272
|
UI.readyToReset();
|
5271
|
-
|
5272
|
-
|
5273
|
-
|
5274
|
-
if(MODEL.
|
5273
|
+
if(MODEL.running_experiment) {
|
5274
|
+
// If experiment is active, signal the manager.
|
5275
|
+
EXPERIMENT_MANAGER.processRun();
|
5276
|
+
} else if(RECEIVER.solving || MODEL.report_results) {
|
5277
|
+
// Otherwise report results now, if applicable.
|
5278
|
+
RECEIVER.report();
|
5279
|
+
}
|
5275
5280
|
// Warn modeler if any issues occurred
|
5276
5281
|
if(this.block_issues) {
|
5277
5282
|
let msg = 'Issues occurred in ' +
|
@@ -5677,8 +5682,8 @@ function valueOfNumberSign(x) {
|
|
5677
5682
|
// ending on digits, or tne number context of an entity. The latter typically
|
5678
5683
|
// is the number its name or any of its prefixes ends on, but notes are
|
5679
5684
|
// more "creative" and can return the number context of nearby entities.
|
5680
|
-
let s = 'NO SELECTOR',
|
5681
|
-
m = 'NO MATCH',
|
5685
|
+
let s = '!NO SELECTOR',
|
5686
|
+
m = '!NO MATCH',
|
5682
5687
|
n = VM.UNDEFINED;
|
5683
5688
|
// NOTE: Give wildcard selectors precedence over experiment selectors
|
5684
5689
|
// because a wildcard selector is an immediate property of the dataset
|
@@ -5692,17 +5697,15 @@ function valueOfNumberSign(x) {
|
|
5692
5697
|
// Check whether `x` is a dataset modifier expression.
|
5693
5698
|
// NOTE: This includes equations.
|
5694
5699
|
if(x.object instanceof Dataset) {
|
5695
|
-
if(x.attribute)
|
5696
|
-
|
5697
|
-
|
5698
|
-
|
5699
|
-
|
5700
|
-
|
5701
|
-
|
5702
|
-
|
5703
|
-
|
5704
|
-
s = 'experiment';
|
5705
|
-
}
|
5700
|
+
if(x.attribute) s = x.attribute;
|
5701
|
+
// Selector may also be defined by a running experiment.
|
5702
|
+
if(MODEL.running_experiment) {
|
5703
|
+
const
|
5704
|
+
ac = MODEL.running_experiment.activeCombination,
|
5705
|
+
mn = matchingNumberInList(ac, s);
|
5706
|
+
if(mn !== false) {
|
5707
|
+
m = 'x-run';
|
5708
|
+
n = mn;
|
5706
5709
|
}
|
5707
5710
|
}
|
5708
5711
|
}
|
@@ -5715,10 +5718,6 @@ function valueOfNumberSign(x) {
|
|
5715
5718
|
m = d;
|
5716
5719
|
n = parseInt(d);
|
5717
5720
|
}
|
5718
|
-
} else if(m !== 'NO MATCH') {
|
5719
|
-
// If matching `m` against `s` do not yield an integer string,
|
5720
|
-
// use UNDEFINED
|
5721
|
-
n = matchingNumber(m, s) || VM.UNDEFINED;
|
5722
5721
|
}
|
5723
5722
|
}
|
5724
5723
|
// For datasets, set the parent anchor to be the context-sensitive number
|
@@ -5941,7 +5940,7 @@ function VMI_push_dataset_modifier(x, args) {
|
|
5941
5940
|
// If `s` is not specified, the modifier to be used must be inferred from
|
5942
5941
|
// the running experiment UNLESS the field `ud` ("use data") is defined
|
5943
5942
|
// for the first argument, and evaluates as TRUE.
|
5944
|
-
// NOTE: Ensure that number 0 is not interpreted as FALSE.
|
5943
|
+
// NOTE: Ensure that number 0 is not interpreted as FALSE.
|
5945
5944
|
let wcnr = (args[0].s === undefined ? false : args[0].s);
|
5946
5945
|
const
|
5947
5946
|
ds = args[0].d,
|
@@ -5960,7 +5959,6 @@ function VMI_push_dataset_modifier(x, args) {
|
|
5960
5959
|
let t = tot[0],
|
5961
5960
|
// By default, use the vector of the dataset to compute the value.
|
5962
5961
|
obj = ds.vector;
|
5963
|
-
|
5964
5962
|
if(ds.array) {
|
5965
5963
|
// For array-type datasets, do NOT adjust "index" t to model run period.
|
5966
5964
|
// NOTE: Indices start at 1, but arrays are zero-based, so subtract 1.
|
@@ -5994,16 +5992,17 @@ function VMI_push_dataset_modifier(x, args) {
|
|
5994
5992
|
if(wcnr === '?') {
|
5995
5993
|
wcnr = x.wildcard_vector_index;
|
5996
5994
|
}
|
5997
|
-
} else if(!ud
|
5995
|
+
} else if(!ud) {
|
5998
5996
|
// In no selector and not "use data", check whether a running experiment
|
5999
5997
|
// defines the expression to use. If not, `obj` will be the dataset
|
6000
5998
|
// vector (so same as when "use data" is set).
|
6001
5999
|
obj = ds.activeModifierExpression;
|
6002
|
-
if(wcnr === false &&
|
6000
|
+
if(wcnr === false && MODEL.running_experiment) {
|
6003
6001
|
// If experiment run defines the modifier selector, the active
|
6004
6002
|
// combination may provide a context for #.
|
6005
|
-
|
6006
|
-
|
6003
|
+
const sel = (obj instanceof Expression ? obj.attribute : x.attribute);
|
6004
|
+
wcnr = matchingNumberInList(
|
6005
|
+
MODEL.running_experiment.activeCombination, sel);
|
6007
6006
|
}
|
6008
6007
|
}
|
6009
6008
|
if(!obj) {
|
@@ -6040,7 +6039,9 @@ function VMI_push_dataset_modifier(x, args) {
|
|
6040
6039
|
// `obj` is an expression.
|
6041
6040
|
// NOTE: Readjust `t` when `obj` is an expression for an *array-type*
|
6042
6041
|
// dataset modifier.
|
6043
|
-
if(obj.object instanceof Dataset && obj.object.array)
|
6042
|
+
if(obj.object instanceof Dataset && obj.object.array) {
|
6043
|
+
t++;
|
6044
|
+
}
|
6044
6045
|
v = obj.result(t, wcnr);
|
6045
6046
|
}
|
6046
6047
|
// Trace only now that time step t has been computed.
|