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.
@@ -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: experiments store only ONE modifier result per run
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
- for(let k in dsm) if(dsm.hasOwnProperty(k)) olist.push(dsm[k].selector);
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
- // Returns a "dictionary" object with entities of the specified types
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
- // Returns a list of entities (of any type) having the specified attribute
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: ignore the equations dataset
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
- // NOTE: this routine is computationally intensive as it performs matches
481
- // on the display names of entities while iterating over all relevant sets
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
- for(let k in dict) if(dict.hasOwnProperty(k)) {
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" in a chart.
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 adummy, so NULL
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" like so:
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: ignore selectors of the equations dataset
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(tag.replace(/\s+/, '') === '[[#]]') {
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.warn(`Unknown model entity "${en}"`);
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) dimensionless => unit '1'.
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; represemting these lins by block arrows
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: identifier will be unique only for equations
8454
+ // NOTE: Identifier will be unique only for equations.
8320
8455
  return UI.nameToID(this.selector);
8321
8456
  }
8322
8457
  get displayName() {
8323
- // NOTE: when "displayed", dataset modifiers have their selector as name
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: for some reason, selector may become empty string, so prevent
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
- // Returns TRUE if this modifier contains wildcards
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
- // Returns the string to be used to evaluate #.
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
- // Returns TRUE if string `s` matches with the wildcard pattern of
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
- // Returns TRUE if all modifier expressions are static
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
- // Returns list of dataset modifiers with expressions that do not
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
- // Returns duration of 1 time step on the time scale of this dataset
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
- // Returns default value *scaled to the model time step*
8543
- // NOTE: scaling is only needed for the weighted sum method
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
- // Then prefix it when the IO context argument is defined
8720
- if(ioc) s = ioc.actualName(s);
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 of the Linny-R entity and its attribute, followed
8945
- // by its scale factor unless it equals 1 (no scaling).
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
- eqn = eqn.replace('??', this.wildcard_index);
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
- if(!eq) {
9343
- // No equation and no attribute specified? Then assume default.
9344
- if(a === '') a = obj.defaultAttribute;
9345
- } else if(n.indexOf('??') >= 0) {
9346
- // Special case: for wildcard equations, add dummy variables
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(MODEL.equations_dataset, dn, false, clr);
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
- return vlist;
9361
- // FALL-THROUGH: Equation name has no wildcard => use the equation
9362
- // object (= dataset modifier) as `obj`.
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
- this.eq_list = Object.keys(MODEL.equations_dataset.modifiers);
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: variables are stored first, outcomes second, equations last,
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 per
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: