linny-r 1.5.8 → 1.6.0
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 +7 -3
- package/static/linny-r.css +19 -0
- package/static/scripts/linny-r-gui-actor-manager.js +3 -3
- package/static/scripts/linny-r-gui-chart-manager.js +10 -9
- package/static/scripts/linny-r-gui-controller.js +2 -1
- package/static/scripts/linny-r-gui-dataset-manager.js +17 -49
- package/static/scripts/linny-r-gui-documentation-manager.js +40 -20
- package/static/scripts/linny-r-gui-equation-manager.js +17 -4
- package/static/scripts/linny-r-gui-experiment-manager.js +41 -23
- package/static/scripts/linny-r-gui-expression-editor.js +26 -21
- package/static/scripts/linny-r-gui-monitor.js +4 -4
- package/static/scripts/linny-r-model.js +248 -77
- package/static/scripts/linny-r-utils.js +50 -11
- package/static/scripts/linny-r-vm.js +582 -290
@@ -159,22 +159,28 @@ class LinnyRModel {
|
|
159
159
|
}
|
160
160
|
|
161
161
|
get outcomeNames() {
|
162
|
+
// Return the list of names of experiment outcome variables, i.e.,
|
163
|
+
// datasets that have been designated as outcomes, and all regular
|
164
|
+
// equations (not methods).
|
162
165
|
const olist = [];
|
163
166
|
for(let k in this.datasets) if(this.datasets.hasOwnProperty(k)) {
|
164
167
|
const ds = this.datasets[k];
|
165
168
|
if(ds !== this.equations_dataset && ds.outcome) {
|
166
|
-
// NOTE:
|
169
|
+
// NOTE: Experiments store only ONE modifier result per run.
|
167
170
|
olist.push(ds.displayName);
|
168
171
|
}
|
169
172
|
}
|
170
|
-
// ALL equation results are stored, so add all equation selectors
|
173
|
+
// ALL equation results are stored, so add all equation selectors...
|
171
174
|
const dsm = this.equations_dataset.modifiers;
|
172
|
-
|
175
|
+
// ... except selectors starting with a colon (methods).
|
176
|
+
for(let k in dsm) if(dsm.hasOwnProperty(k) && !k.startsWith(':')) {
|
177
|
+
olist.push(dsm[k].selector);
|
178
|
+
}
|
173
179
|
return olist;
|
174
180
|
}
|
175
181
|
|
176
182
|
get newProcessCode() {
|
177
|
-
// Return the next unused process code
|
183
|
+
// Return the next unused process code.
|
178
184
|
const n = this.next_process_number;
|
179
185
|
this.next_process_number++;
|
180
186
|
// Process codes are decimal number STRINGS
|
@@ -417,30 +423,67 @@ class LinnyRModel {
|
|
417
423
|
}
|
418
424
|
|
419
425
|
setByType(type) {
|
420
|
-
//
|
426
|
+
// Return a "dictionary" object with entities of the specified types
|
421
427
|
if(type === 'Process') return this.processes;
|
422
428
|
if(type === 'Product') return this.products;
|
423
429
|
if(type === 'Cluster') return this.clusters;
|
430
|
+
// NOTE: the returned "dictionary" also contains the equations dataset
|
431
|
+
if(type === 'Dataset') return this.datasets;
|
424
432
|
if(type === 'Link') return this.links;
|
425
433
|
if(type === 'Constraint') return this.constraints;
|
426
434
|
if(type === 'Actor') return this.actors;
|
427
|
-
// NOTE: the returned "dictionary" also contains the equations dataset
|
428
|
-
if(type === 'Dataset') return this.datasets;
|
429
435
|
return {};
|
430
436
|
}
|
431
|
-
|
437
|
+
|
438
|
+
get allEntities() {
|
439
|
+
// Return a "dictionary" of all entities in the model.
|
440
|
+
// NOTE: This includes equations (instances of DatasetModifier) but
|
441
|
+
// not the equations dataset itself.
|
442
|
+
const all = Object.assign({}, this.processes, this.products,
|
443
|
+
this.clusters, this.datasets, this.equations_dataset.modifiers,
|
444
|
+
this.links, this.actors, this.constraints);
|
445
|
+
// Remove the equations dataset from this dictionary
|
446
|
+
delete all[this.equations_dataset.identifier];
|
447
|
+
return all;
|
448
|
+
}
|
449
|
+
|
450
|
+
get allMethods() {
|
451
|
+
// Return a list with dataset modifiers that are "methods".
|
452
|
+
const
|
453
|
+
list = [],
|
454
|
+
keys = Object.keys(this.equations_dataset.modifiers);
|
455
|
+
for(let i = 0; i < keys.length; i++) {
|
456
|
+
if(keys[i].startsWith(':')) {
|
457
|
+
list.push(this.equations_dataset.modifiers[keys[i]]);
|
458
|
+
}
|
459
|
+
}
|
460
|
+
return list;
|
461
|
+
}
|
462
|
+
|
463
|
+
endsWithMethod(name) {
|
464
|
+
// Return method (instance of DatasetModifier) if `name` ends with
|
465
|
+
// ":(whitespace)m" for some method having selector ":m".
|
466
|
+
const ml = this.allMethods;
|
467
|
+
for(let i = 0; i < ml.length; i++) {
|
468
|
+
const re = new RegExp(
|
469
|
+
':\\s*' + escapeRegex(ml[i].selector.substring(1)) + '$', 'i');
|
470
|
+
if(name.match(re)) return ml[i];
|
471
|
+
}
|
472
|
+
return null;
|
473
|
+
}
|
474
|
+
|
432
475
|
entitiesWithAttribute(attr, et='ABCDLPQ') {
|
433
|
-
//
|
476
|
+
// Return a list of entities (of any type) having the specified attribute.
|
434
477
|
const list = [];
|
435
478
|
if(attr === '' && et.indexOf('D') >= 0) {
|
436
|
-
// Only datasets can have a value for "no attribute"
|
479
|
+
// Only datasets can have a value for "no attribute".
|
437
480
|
for(let k in this.datasets) if(this.datasets.hasOwnProperty(k)) {
|
438
|
-
// NOTE:
|
481
|
+
// NOTE: Ignore the equations dataset.
|
439
482
|
if(this.datasets[k] !== this.equations_dataset) {
|
440
483
|
list.push(this.datasets[k]);
|
441
484
|
}
|
442
485
|
}
|
443
|
-
// No other types of entity, so return this list
|
486
|
+
// No other types of entity, so return this list.
|
444
487
|
return list;
|
445
488
|
}
|
446
489
|
if(VM.process_attr.indexOf(attr) >= 0 && et.indexOf('P') >= 0) {
|
@@ -477,15 +520,19 @@ class LinnyRModel {
|
|
477
520
|
}
|
478
521
|
|
479
522
|
allMatchingEntities(re, attr='') {
|
480
|
-
//
|
481
|
-
//
|
523
|
+
// Return list of enties with a display name that matches RegExp `re`,
|
524
|
+
// and having attribute `attr` if specified.
|
525
|
+
// NOTE: This routine is computationally intensive as it performs
|
526
|
+
// matches on the display names of entities while iterating over all
|
527
|
+
// relevant entity sets.
|
482
528
|
const
|
483
529
|
me = [],
|
484
530
|
res = re.toString();
|
485
531
|
|
486
532
|
function scan(dict) {
|
487
|
-
// Try to match all entities in `dict
|
488
|
-
|
533
|
+
// Try to match all entities in `dict`.
|
534
|
+
// NOTE: Ignore method identifiers.
|
535
|
+
for(let k in dict) if(dict.hasOwnProperty(k) && !k.startsWith(':')) {
|
489
536
|
const
|
490
537
|
e = dict[k],
|
491
538
|
m = [...e.displayName.matchAll(re)];
|
@@ -496,26 +543,63 @@ class LinnyRModel {
|
|
496
543
|
for(let i = 1; same && i < m.length; i++) {
|
497
544
|
same = parseInt(m[i][1]) === n;
|
498
545
|
}
|
499
|
-
// If so, add the entity to the set
|
546
|
+
// If so, add the entity to the set.
|
500
547
|
if(same) me.push(e);
|
501
548
|
}
|
502
549
|
}
|
503
550
|
}
|
504
551
|
|
505
|
-
// Links limit the search (constraints have no attributes => skip)
|
552
|
+
// Links limit the search (constraints have no attributes => skip).
|
506
553
|
if(res.indexOf(UI.LINK_ARROW) >= 0) {
|
507
554
|
scan(this.links);
|
508
555
|
} else {
|
556
|
+
// First get list of matching datasets.
|
557
|
+
scan(this.datasets);
|
558
|
+
if(me.length > 0 && attr) {
|
559
|
+
// If attribute is specified, retain only datasets having a
|
560
|
+
// modifier with selector = `attr`.
|
561
|
+
for(let i = me.length - 1; i >= 0; i--) {
|
562
|
+
if(!me[i].modifiers[attr]) me.splice(i, 1);
|
563
|
+
}
|
564
|
+
}
|
565
|
+
attr = attr.toUpperCase();
|
509
566
|
if(!attr || VM.actor_attr.indexOf(attr) >= 0) scan(this.actors);
|
510
567
|
if(!attr || VM.cluster_attr.indexOf(attr) >= 0) scan(this.clusters);
|
511
|
-
if(!attr) scan(this.datasets);
|
512
568
|
if(!attr || VM.process_attr.indexOf(attr) >= 0) scan(this.processes);
|
513
569
|
if(!attr || VM.product_attr.indexOf(attr) >= 0) scan(this.products);
|
570
|
+
// NOTE: Equations cannot have an attribute.
|
514
571
|
if(!attr && this.equations_dataset) scan(this.equations_dataset.modifiers);
|
515
572
|
}
|
516
573
|
return me;
|
517
574
|
}
|
575
|
+
|
576
|
+
entitiesEndingOn(s, attr='') {
|
577
|
+
// Return a list of entities (of any type) having a display name that
|
578
|
+
// ends on string `s`.
|
579
|
+
// NOTE: The current implementation will overlook links having a FROM
|
580
|
+
// node that ends on `s`.
|
581
|
+
const re = new RegExp(escapeRegex(s) + '$', 'gi');
|
582
|
+
return this.allMatchingEntities(re, attr);
|
583
|
+
}
|
518
584
|
|
585
|
+
entitiesInString(s) {
|
586
|
+
// Return a list of entities referenced in string `s`.
|
587
|
+
if(s.indexOf('[') < 0) return [];
|
588
|
+
const
|
589
|
+
el = [],
|
590
|
+
ml = [...s.matchAll(/\[(\{[^\}]+\}){0,1}([^\]]+)\]/g)];
|
591
|
+
for(let i = 0; i < ml.length; i++) {
|
592
|
+
const n = ml[i][2].trim();
|
593
|
+
let sep = n.lastIndexOf('|');
|
594
|
+
if(sep < 0) sep = n.lastIndexOf('@');
|
595
|
+
const
|
596
|
+
en = (sep < 0 ? n : n.substring(0, sep)).trim(),
|
597
|
+
e = this.objectByName(en);
|
598
|
+
if(e) addDistinct(e, el);
|
599
|
+
}
|
600
|
+
return el;
|
601
|
+
}
|
602
|
+
|
519
603
|
get clustersToIgnore() {
|
520
604
|
// Returns a "dictionary" with all clusters that are to be ignored
|
521
605
|
const cti = {};
|
@@ -906,6 +990,53 @@ class LinnyRModel {
|
|
906
990
|
return ss.length > 0;
|
907
991
|
}
|
908
992
|
|
993
|
+
renamePrefixedDatasets(old_prefix, new_prefix) {
|
994
|
+
// Rename all datasets having the specified old prefix so that they
|
995
|
+
// have the specified new prefix UNLESS this would cause name conflicts.
|
996
|
+
const
|
997
|
+
oldkey = old_prefix.toLowerCase().split(UI.PREFIXER).join(':_'),
|
998
|
+
newkey = new_prefix.toLowerCase().split(UI.PREFIXER).join(':_'),
|
999
|
+
dsl = [];
|
1000
|
+
// No change if new prefix is identical to old prefix.
|
1001
|
+
if(old_prefix !== new_prefix) {
|
1002
|
+
for(let k in MODEL.datasets) if(MODEL.datasets.hasOwnProperty(k)) {
|
1003
|
+
if(k.startsWith(oldkey)) dsl.push(k);
|
1004
|
+
}
|
1005
|
+
// NOTE: No check for name conflicts needed when name change is
|
1006
|
+
// merely some upper/lower case change.
|
1007
|
+
if(newkey !== oldkey) {
|
1008
|
+
let nc = 0;
|
1009
|
+
for(let i = 0; i < dsl.length; i++) {
|
1010
|
+
let nk = newkey + dsl[i].substring(oldkey.length);
|
1011
|
+
if(MODEL.datasets[nk]) nc++;
|
1012
|
+
}
|
1013
|
+
if(nc) {
|
1014
|
+
UI.warn('Renaming ' + pluralS(dsl.length, 'dataset') +
|
1015
|
+
' would cause ' + pluralS(nc, 'name conflict'));
|
1016
|
+
return false;
|
1017
|
+
}
|
1018
|
+
}
|
1019
|
+
// Reset counts of effects of a rename operation.
|
1020
|
+
this.entity_count = 0;
|
1021
|
+
this.expression_count = 0;
|
1022
|
+
// Rename datasets one by one, suppressing notifications.
|
1023
|
+
for(let i = 0; i < dsl.length; i++) {
|
1024
|
+
const d = MODEL.datasets[dsl[i]];
|
1025
|
+
d.rename(d.displayName.replace(old_prefix, new_prefix), false);
|
1026
|
+
}
|
1027
|
+
let msg = 'Renamed ' + pluralS(dsl.length, 'dataset').toLowerCase();
|
1028
|
+
if(MODEL.variable_count) msg += ', and updated ' +
|
1029
|
+
pluralS(MODEL.variable_count, 'variable') + ' in ' +
|
1030
|
+
pluralS(MODEL.expression_count, 'expression');
|
1031
|
+
UI.notify(msg);
|
1032
|
+
if(EXPERIMENT_MANAGER.selected_experiment) {
|
1033
|
+
EXPERIMENT_MANAGER.selected_experiment.inferVariables();
|
1034
|
+
}
|
1035
|
+
UI.updateControllerDialogs('CDEFJX');
|
1036
|
+
}
|
1037
|
+
return true;
|
1038
|
+
}
|
1039
|
+
|
909
1040
|
//
|
910
1041
|
// Methods that add an entity to the model
|
911
1042
|
//
|
@@ -2964,7 +3095,7 @@ class LinnyRModel {
|
|
2964
3095
|
names = [],
|
2965
3096
|
scale_re = /\s+\(x[0-9\.\,]+\)$/;
|
2966
3097
|
// First create list of distinct variables used in charts.
|
2967
|
-
// NOTE: Also include those that are not "visible"
|
3098
|
+
// NOTE: Also include those that are not checked as "visible".
|
2968
3099
|
for(let i = 0; i < this.charts.length; i++) {
|
2969
3100
|
const c = this.charts[i];
|
2970
3101
|
for(let j = 0; j < c.variables.length; j++) {
|
@@ -2976,7 +3107,7 @@ class LinnyRModel {
|
|
2976
3107
|
vn = vn.replace(scale_re, '');
|
2977
3108
|
// Add only if (now unscaled) variable has not been added already.
|
2978
3109
|
if(names.indexOf(vn) < 0) {
|
2979
|
-
// NOTE: Chart variable object is used ony as
|
3110
|
+
// NOTE: Chart variable object is used ony as a dummy, so NULL
|
2980
3111
|
// can be used as its "owner chart".
|
2981
3112
|
const cv = new ChartVariable(null);
|
2982
3113
|
cv.setProperties(v.object, v.attribute, false, '#000000');
|
@@ -3028,12 +3159,12 @@ class LinnyRModel {
|
|
3028
3159
|
}
|
3029
3160
|
|
3030
3161
|
get listOfAllSelectors() {
|
3031
|
-
// Returns list of all dataset modifier selectors as "dictionary"
|
3032
|
-
// {selector_1: [list of datasets], ...}
|
3162
|
+
// Returns list of all dataset modifier selectors as a "dictionary"
|
3163
|
+
// like so: {selector_1: [list of datasets], ...}
|
3033
3164
|
const ds_dict = {};
|
3034
3165
|
for(let k in this.datasets) if(this.datasets.hasOwnProperty(k)) {
|
3035
3166
|
const ds = this.datasets[k];
|
3036
|
-
// NOTE:
|
3167
|
+
// NOTE: Ignore selectors of the equations dataset.
|
3037
3168
|
if(ds !== this.equations_dataset) {
|
3038
3169
|
for(let m in ds.modifiers) if(ds.modifiers.hasOwnProperty(m)) {
|
3039
3170
|
const s = ds.modifiers[m].selector;
|
@@ -4909,7 +5040,7 @@ class Note extends ObjectWithXYWH {
|
|
4909
5040
|
bar = inner.lastIndexOf('|'),
|
4910
5041
|
arrow = inner.lastIndexOf('->');
|
4911
5042
|
// Special case: [[#]] denotes the number context of this note.
|
4912
|
-
if(
|
5043
|
+
if(inner === '#') {
|
4913
5044
|
this.fields.push(new NoteField(this, tag, this));
|
4914
5045
|
// Done, so move on to the next tag
|
4915
5046
|
continue;
|
@@ -4937,7 +5068,6 @@ class Note extends ObjectWithXYWH {
|
|
4937
5068
|
ena = inner.split('|');
|
4938
5069
|
}
|
4939
5070
|
// Look up entity for name and attribute.
|
4940
|
-
// NOTE: A leading colon denotes "prefix with cluster name".
|
4941
5071
|
let en = UI.colonPrefixedName(ena[0].trim(), this.clusterPrefix),
|
4942
5072
|
id = UI.nameToID(en),
|
4943
5073
|
// First try to match `id` with the IDs of wildcard equations,
|
@@ -4965,9 +5095,14 @@ class Note extends ObjectWithXYWH {
|
|
4965
5095
|
}
|
4966
5096
|
}
|
4967
5097
|
if(!obj) {
|
4968
|
-
UI.
|
5098
|
+
const m = MODEL.equations_dataset.modifiers[UI.nameToID(ena[0])];
|
5099
|
+
if(m) {
|
5100
|
+
UI.warn('Methods cannot be evaluated without prefix');
|
5101
|
+
} else {
|
5102
|
+
UI.warn(`Unknown model entity "${en}"`);
|
5103
|
+
}
|
4969
5104
|
} else if(obj instanceof DatasetModifier) {
|
4970
|
-
// NOTE: equations are (for now)
|
5105
|
+
// NOTE: equations are (for now) dimenssonless => unit '1'.
|
4971
5106
|
if(obj.dataset !== MODEL.equations_dataset) {
|
4972
5107
|
from_unit = obj.dataset.scale_unit;
|
4973
5108
|
multiplier = MODEL.unitConversionMultiplier(from_unit, to_unit);
|
@@ -6379,7 +6514,7 @@ class Cluster extends NodeBox {
|
|
6379
6514
|
}
|
6380
6515
|
|
6381
6516
|
/* DISABLED -- idea was OK but this results in many additional links
|
6382
|
-
that clutter the diagram;
|
6517
|
+
that clutter the diagram; representing these lines by block arrows
|
6383
6518
|
produces better results
|
6384
6519
|
|
6385
6520
|
// Special case: P1 --> Q with process Q outside this cluster that
|
@@ -8316,17 +8451,17 @@ class DatasetModifier {
|
|
8316
8451
|
}
|
8317
8452
|
|
8318
8453
|
get identifier() {
|
8319
|
-
// NOTE:
|
8454
|
+
// NOTE: Identifier will be unique only for equations.
|
8320
8455
|
return UI.nameToID(this.selector);
|
8321
8456
|
}
|
8322
8457
|
get displayName() {
|
8323
|
-
// NOTE:
|
8458
|
+
// NOTE: When "displayed", dataset modifiers have their selector as name.
|
8324
8459
|
return this.selector;
|
8325
8460
|
}
|
8326
8461
|
|
8327
8462
|
get asXML() {
|
8328
|
-
// NOTE:
|
8329
|
-
// saving such unidentified modifiers
|
8463
|
+
// NOTE: For some reason, selector may become empty string, so prevent
|
8464
|
+
// saving such unidentified modifiers.
|
8330
8465
|
if(this.selector.trim().length === 0) return '';
|
8331
8466
|
return ['<modifier><selector>', xmlEncoded(this.selector),
|
8332
8467
|
'</selector><expression>', xmlEncoded(this.expression.text),
|
@@ -8336,18 +8471,18 @@ class DatasetModifier {
|
|
8336
8471
|
initFromXML(node) {
|
8337
8472
|
this.expression.text = xmlDecoded(nodeContentByTag(node, 'expression'));
|
8338
8473
|
if(IO_CONTEXT) {
|
8339
|
-
// Contextualize the included expression
|
8474
|
+
// Contextualize the included expression.
|
8340
8475
|
IO_CONTEXT.rewrite(this.expression);
|
8341
8476
|
}
|
8342
8477
|
}
|
8343
8478
|
|
8344
8479
|
get hasWildcards() {
|
8345
|
-
//
|
8480
|
+
// Return TRUE if this modifier contains wildcards.
|
8346
8481
|
return this.dataset.isWildcardSelector(this.selector);
|
8347
8482
|
}
|
8348
8483
|
|
8349
8484
|
get numberContext() {
|
8350
|
-
//
|
8485
|
+
// Return the string to be used to evaluate #.
|
8351
8486
|
// NOTE: If the selector contains wildcards, return "?" to indicate
|
8352
8487
|
// that the value of # cannot be inferred at compile time.
|
8353
8488
|
if(this.hasWildcards) return '?';
|
@@ -8358,12 +8493,12 @@ class DatasetModifier {
|
|
8358
8493
|
}
|
8359
8494
|
|
8360
8495
|
match(s) {
|
8361
|
-
//
|
8496
|
+
// Return TRUE if string `s` matches with the wildcard pattern of
|
8362
8497
|
// the selector.
|
8363
8498
|
if(!this.hasWildcards) return s === this.selector;
|
8364
8499
|
let re;
|
8365
8500
|
if(this.dataset === MODEL.equations_dataset) {
|
8366
|
-
// Equations wildcards only match with digits
|
8501
|
+
// Equations wildcards only match with digits.
|
8367
8502
|
re = wildcardMatchRegex(this.selector, true);
|
8368
8503
|
} else {
|
8369
8504
|
// Selector wildcards match with any character, so replace ? by .
|
@@ -8508,14 +8643,22 @@ class Dataset {
|
|
8508
8643
|
}
|
8509
8644
|
|
8510
8645
|
get allModifiersAreStatic() {
|
8511
|
-
//
|
8646
|
+
// Return TRUE if all modifier expressions are static.
|
8512
8647
|
return this.modifiersAreStatic(Object.keys(this.modifiers));
|
8513
8648
|
}
|
8514
8649
|
|
8650
|
+
get mayBeDynamic() {
|
8651
|
+
// Return TRUE if this dataset has time series data, or if some of
|
8652
|
+
// its modifier expressions are dynamic.
|
8653
|
+
return !this.array && (this.data.length > 1 ||
|
8654
|
+
(this.data.length > 0 && !this.periodic) ||
|
8655
|
+
!this.allModifiersAreStatic);
|
8656
|
+
}
|
8657
|
+
|
8515
8658
|
get inferPrefixableModifiers() {
|
8516
|
-
//
|
8659
|
+
// Return a list of dataset modifiers with expressions that do not
|
8517
8660
|
// reference any variable and hence could probably better be represented
|
8518
|
-
// by a prefixed dataset having the expression value as its default
|
8661
|
+
// by a prefixed dataset having the expression value as its default.
|
8519
8662
|
const pml = [];
|
8520
8663
|
if(this !== this.equations_dataset) {
|
8521
8664
|
const sl = this.plainSelectors;
|
@@ -8525,7 +8668,7 @@ class Dataset {
|
|
8525
8668
|
m = this.modifiers[sl[i].toLowerCase()],
|
8526
8669
|
x = m.expression;
|
8527
8670
|
// Static expressions without variables can also be used
|
8528
|
-
// as dataset default value
|
8671
|
+
// as dataset default value.
|
8529
8672
|
if(x.isStatic && x.text.indexOf('[') < 0) pml.push(m);
|
8530
8673
|
}
|
8531
8674
|
}
|
@@ -8534,13 +8677,13 @@ class Dataset {
|
|
8534
8677
|
}
|
8535
8678
|
|
8536
8679
|
get timeStepDuration() {
|
8537
|
-
//
|
8680
|
+
// Return duration of 1 time step on the time scale of this dataset.
|
8538
8681
|
return this.time_scale * VM.time_unit_values[this.time_unit];
|
8539
8682
|
}
|
8540
8683
|
|
8541
8684
|
get defaultValue() {
|
8542
|
-
//
|
8543
|
-
// NOTE:
|
8685
|
+
// Return default value *scaled to the model time step*.
|
8686
|
+
// NOTE: Scaling is only needed for the weighted sum method.
|
8544
8687
|
if(this.method !== 'w-sum' || this.default_value >= VM.PLUS_INFINITY) {
|
8545
8688
|
return this.default_value;
|
8546
8689
|
}
|
@@ -8708,16 +8851,26 @@ class Dataset {
|
|
8708
8851
|
return null;
|
8709
8852
|
}
|
8710
8853
|
// Wildcard selectors must be exactly 2 consecutive question marks,
|
8711
|
-
// so reduce longer sequences (no warning)
|
8854
|
+
// so reduce longer sequences (no warning).
|
8712
8855
|
s = s.replace(/\?\?+/g, '??');
|
8713
8856
|
if(s.split('??').length > 2) {
|
8714
8857
|
UI.warn('Equation name can contain only 1 wildcard');
|
8715
8858
|
return null;
|
8716
8859
|
}
|
8717
|
-
// Reduce inner spaces to one, and trim outer spaces
|
8860
|
+
// Reduce inner spaces to one, and trim outer spaces.
|
8718
8861
|
s = s.replace(/\s+/g, ' ').trim();
|
8719
|
-
|
8720
|
-
|
8862
|
+
if(s.startsWith(':')) {
|
8863
|
+
// Methods must have no spaces directly after their leading colon,
|
8864
|
+
// and must not contain other colons.
|
8865
|
+
if(s.startsWith(': ')) s = ':' + s.substring(2);
|
8866
|
+
if(s.lastIndexOf(':') > 0) {
|
8867
|
+
UI.warn('Method name can contain only 1 colon');
|
8868
|
+
return null;
|
8869
|
+
}
|
8870
|
+
} else {
|
8871
|
+
// Prefix it when the IO context argument is defined
|
8872
|
+
if(ioc) s = ioc.actualName(s);
|
8873
|
+
}
|
8721
8874
|
// If equation already exists, return its modifier
|
8722
8875
|
const id = UI.nameToID(s);
|
8723
8876
|
if(this.modifiers.hasOwnProperty(id)) return this.modifiers[id];
|
@@ -8941,15 +9094,33 @@ class ChartVariable {
|
|
8941
9094
|
}
|
8942
9095
|
|
8943
9096
|
get displayName() {
|
8944
|
-
// Returns the name
|
8945
|
-
//
|
9097
|
+
// Returns the display name for this variable. This is the name of
|
9098
|
+
// the Linny-R entity and its attribute, followed by its scale factor
|
9099
|
+
// unless it equals 1 (no scaling).
|
8946
9100
|
const sf = (this.scale_factor === 1 ? '' :
|
8947
9101
|
` (x${VM.sig4Dig(this.scale_factor)})`);
|
8948
|
-
//Display name of equation is just the equations dataset selector.
|
9102
|
+
// Display name of equation is just the equations dataset selector.
|
8949
9103
|
if(this.object instanceof DatasetModifier) {
|
8950
9104
|
let eqn = this.object.selector;
|
9105
|
+
// If for this variable the `wildcard_index` property has been set,
|
9106
|
+
// this indicates that it is a Wildcard selector or a method, and
|
9107
|
+
// that the specified result vector should be used.
|
8951
9108
|
if(this.wildcard_index !== false) {
|
8952
|
-
|
9109
|
+
// NOTE: A wildcard index (a number) can also indicate that this
|
9110
|
+
// variable is a method, so check for a leading colon.
|
9111
|
+
if(eqn.startsWith(':')) {
|
9112
|
+
// For methods, use "entity name or prefix: method" as variable
|
9113
|
+
// name, so first get the method object prefix, expand it if
|
9114
|
+
// it identifies a specific model entity, and then append the
|
9115
|
+
// method name (leading colon replaced by the prefixer ": ").
|
9116
|
+
const
|
9117
|
+
mop = this.object.expression.method_object_list[this.wildcard_index],
|
9118
|
+
obj = MODEL.objectByID(mop);
|
9119
|
+
eqn = (obj ? obj.displayName : (mop || '')) +
|
9120
|
+
UI.PREFIXER + eqn.substring(1);
|
9121
|
+
} else {
|
9122
|
+
eqn = eqn.replace('??', this.wildcard_index);
|
9123
|
+
}
|
8953
9124
|
}
|
8954
9125
|
return eqn + sf;
|
8955
9126
|
}
|
@@ -9319,7 +9490,7 @@ class Chart {
|
|
9319
9490
|
|
9320
9491
|
addVariable(n, a) {
|
9321
9492
|
// Adds variable [entity name `n`|attribute `a`] to the chart unless it
|
9322
|
-
// is already in the variable list
|
9493
|
+
// is already in the variable list.
|
9323
9494
|
let dn = n + UI.OA_SEPARATOR + a;
|
9324
9495
|
// Adapt display name for special cases
|
9325
9496
|
if(n === UI.EQUATIONS_DATASET_NAME) {
|
@@ -9339,31 +9510,26 @@ class Chart {
|
|
9339
9510
|
return null;
|
9340
9511
|
}
|
9341
9512
|
const eq = obj instanceof DatasetModifier;
|
9342
|
-
|
9343
|
-
|
9344
|
-
|
9345
|
-
|
9346
|
-
//
|
9347
|
-
// for each vector in the wildcard vector set of the equation
|
9513
|
+
// No equation and no attribute specified? Then assume default.
|
9514
|
+
if(!eq && a === '') a = obj.defaultAttribute;
|
9515
|
+
if(eq && (n.indexOf('??') >= 0 || obj.expression.isMethod)) {
|
9516
|
+
// Special case: for wildcard equations and methods, add dummy
|
9517
|
+
// variables for each vector in the wildcard vector set of the
|
9348
9518
|
// expression.
|
9349
9519
|
const
|
9350
|
-
vlist = [],
|
9351
9520
|
clr = this.nextAvailableDefaultColor,
|
9352
9521
|
indices = Object.keys(obj.expression.wildcard_vectors);
|
9353
9522
|
for(let i = 0; i < indices.length; i++) {
|
9354
9523
|
const v = new ChartVariable(this);
|
9355
|
-
v.setProperties(
|
9524
|
+
v.setProperties(obj, dn, false, clr);
|
9356
9525
|
v.wildcard_index = parseInt(indices[i]);
|
9357
9526
|
this.variables.push(v);
|
9358
|
-
vlist.push(v);
|
9359
9527
|
}
|
9360
|
-
|
9361
|
-
|
9362
|
-
|
9528
|
+
} else {
|
9529
|
+
const v = new ChartVariable(this);
|
9530
|
+
v.setProperties(obj, a, false, this.nextAvailableDefaultColor, 1, 1);
|
9531
|
+
this.variables.push(v);
|
9363
9532
|
}
|
9364
|
-
const v = new ChartVariable(this);
|
9365
|
-
v.setProperties(obj, a, false, this.nextAvailableDefaultColor, 1, 1);
|
9366
|
-
this.variables.push(v);
|
9367
9533
|
return this.variables.length - 1;
|
9368
9534
|
}
|
9369
9535
|
|
@@ -10767,8 +10933,13 @@ class ExperimentRun {
|
|
10767
10933
|
}
|
10768
10934
|
// Calculate number of vectors/outcomes/equations to store.
|
10769
10935
|
this.oc_list = MODEL.outcomes;
|
10770
|
-
// NOTE: All equations are also considered to be outcomes
|
10771
|
-
|
10936
|
+
// NOTE: All equations are also considered to be outcomes EXCEPT
|
10937
|
+
// methods (selectors starting with a colon).
|
10938
|
+
this.eq_list = [];
|
10939
|
+
const eml = Object.keys(MODEL.equations_dataset.modifiers);
|
10940
|
+
for(let i = 0; i < eml.length; i++) {
|
10941
|
+
if(!eml[i].startsWith(':')) this.eq_list.push(eml[i]);
|
10942
|
+
}
|
10772
10943
|
const
|
10773
10944
|
cv = this.experiment.variables.length,
|
10774
10945
|
oc = this.oc_list.length,
|
@@ -10807,7 +10978,7 @@ class ExperimentRun {
|
|
10807
10978
|
// NOTE: This stores results only for "active" selectors (current run).
|
10808
10979
|
this.results.push(new ExperimentRunResult(this, MODEL.outcomes[oi]));
|
10809
10980
|
this.step++;
|
10810
|
-
UI.setProgressNeedle(this.step / this.steps);
|
10981
|
+
UI.setProgressNeedle(this.step / this.steps, '#d00080');
|
10811
10982
|
setTimeout((x) => x.addOutcomeResults(oi + 1), 0, this);
|
10812
10983
|
} else {
|
10813
10984
|
this.addEquationResults(0);
|
@@ -10822,7 +10993,7 @@ class ExperimentRun {
|
|
10822
10993
|
this.results.push(
|
10823
10994
|
new ExperimentRunResult(this, MODEL.equations_dataset, k));
|
10824
10995
|
this.step++;
|
10825
|
-
UI.setProgressNeedle(this.step / this.steps);
|
10996
|
+
UI.setProgressNeedle(this.step / this.steps, '#2000d0');
|
10826
10997
|
setTimeout((x) => x.addEquationResults(ei + 1), 0, this);
|
10827
10998
|
} else {
|
10828
10999
|
// Register when this result was stored.
|
@@ -11544,18 +11715,18 @@ class Experiment {
|
|
11544
11715
|
return i;
|
11545
11716
|
}
|
11546
11717
|
}
|
11547
|
-
// NOTE:
|
11718
|
+
// NOTE: Variables are stored first, outcomes second, equations last,
|
11548
11719
|
// *while numbering continues*, hence index is position in unsorted
|
11549
11720
|
// variable list, or position in outcome list, where this method
|
11550
|
-
// takes into account that experiments store ONE modifier expression
|
11551
|
-
// outcome, and ALL equations
|
11721
|
+
// takes into account that experiments store ONE modifier expression
|
11722
|
+
// per outcome, and ALL equations except methods.
|
11552
11723
|
const oci = MODEL.outcomeNames.indexOf(dn);
|
11553
11724
|
if(oci >= 0) return oci + this.variables.length;
|
11554
11725
|
return -1;
|
11555
11726
|
}
|
11556
11727
|
|
11557
11728
|
differences(x) {
|
11558
|
-
// Return "dictionary" of differences, or NULL if none
|
11729
|
+
// Return "dictionary" of differences, or NULL if none.
|
11559
11730
|
const d = differences(this, x, UI.MC.EXPERIMENT_PROPS);
|
11560
11731
|
/*
|
11561
11732
|
@@TO DO: add diffs for array properties:
|