linny-r 2.0.9 → 2.0.11
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 +6 -2
- package/static/images/solve-not-same-changed.png +0 -0
- package/static/images/solve-not-same-not-changed.png +0 -0
- package/static/images/solve-same-changed.png +0 -0
- package/static/images/solve-same-not-changed.png +0 -0
- package/static/index.html +81 -11
- package/static/linny-r.css +250 -7
- package/static/scripts/linny-r-ctrl.js +76 -13
- package/static/scripts/linny-r-gui-chart-manager.js +13 -12
- package/static/scripts/linny-r-gui-constraint-editor.js +4 -4
- package/static/scripts/linny-r-gui-controller.js +416 -43
- package/static/scripts/linny-r-gui-dataset-manager.js +122 -93
- package/static/scripts/linny-r-gui-experiment-manager.js +22 -12
- package/static/scripts/linny-r-gui-expression-editor.js +26 -12
- package/static/scripts/linny-r-gui-finder.js +190 -5
- package/static/scripts/linny-r-gui-repository-browser.js +18 -0
- package/static/scripts/linny-r-model.js +192 -84
- package/static/scripts/linny-r-utils.js +13 -2
- package/static/scripts/linny-r-vm.js +137 -95
@@ -671,30 +671,8 @@ class ExpressionParser {
|
|
671
671
|
//
|
672
672
|
if(owner) {
|
673
673
|
this.context_number = owner.numberContext;
|
674
|
-
|
675
|
-
|
676
|
-
// For links and constraints, it depends:
|
677
|
-
const
|
678
|
-
fn = owner.from_node.displayName,
|
679
|
-
tn = owner.to_node.displayName;
|
680
|
-
if(fn.indexOf(UI.PREFIXER) >= 0) {
|
681
|
-
if(tn.indexOf(UI.PREFIXER) >= 0) {
|
682
|
-
// If both nodes are prefixed, use the longest prefix that these
|
683
|
-
// nodes have in common.
|
684
|
-
this.owner_prefix = UI.sharedPrefix(fn, tn) + UI.PREFIXER;
|
685
|
-
} else {
|
686
|
-
// Use the FROM node prefix.
|
687
|
-
this.owner_prefix = UI.completePrefix(fn);
|
688
|
-
}
|
689
|
-
} else if(tn.indexOf(UI.PREFIXER) >= 0) {
|
690
|
-
// Use the TO node prefix.
|
691
|
-
this.owner_prefix = UI.completePrefix(tn);
|
692
|
-
}
|
693
|
-
} else if(owner === MODEL.equations_dataset) {
|
694
|
-
this.owner_prefix = UI.completePrefix(attribute);
|
695
|
-
} else {
|
696
|
-
this.owner_prefix = UI.completePrefix(owner.displayName);
|
697
|
-
}
|
674
|
+
this.owner_prefix = UI.entityPrefix(
|
675
|
+
owner === MODEL.equations_dataset ? attribute : owner.displayName);
|
698
676
|
if(owner instanceof Dataset) {
|
699
677
|
this.dataset = owner;
|
700
678
|
// The attribute (if specified) is a dataset modifier selector.
|
@@ -848,30 +826,33 @@ class ExpressionParser {
|
|
848
826
|
}
|
849
827
|
}
|
850
828
|
}
|
851
|
-
//
|
852
|
-
// Specifier format: {method$title|run} where method and title are
|
853
|
-
// optional
|
829
|
+
// Experiment result specifier (optional) must be leading and braced.
|
830
|
+
// Specifier format: {method$title|run} where method$ and title| are
|
831
|
+
// optional. The run specifier may be a # followed by a run number, or
|
832
|
+
// a comma- or space-separated list of selectors.
|
833
|
+
// NOTE: # in title or run is NOT seen as a wildcard.
|
854
834
|
if(name.startsWith('{')) {
|
855
835
|
s = name.split('}');
|
856
836
|
if(s.length > 1) {
|
857
|
-
// Brace pair => interpret it as experiment result reference
|
837
|
+
// Brace pair => interpret it as experiment result reference.
|
858
838
|
const x = {
|
859
839
|
x: false, // experiment
|
860
840
|
r: false, // run number
|
861
841
|
v: false, // variable; if parametrized {n: name seg's, p: indices}
|
862
842
|
s: '', // statistic
|
863
|
-
m: '', // method
|
843
|
+
m: '', // method
|
864
844
|
p: false, // periodic
|
865
845
|
nr: false // run number range
|
866
846
|
};
|
867
|
-
// NOTE:
|
847
|
+
// NOTE: Name should then be in the experiment's variable list.
|
848
|
+
// This will be checked later, after validating the run specifier.
|
868
849
|
name = s[1].trim();
|
869
850
|
s = s[0].substring(1);
|
870
|
-
// Check for scaling method
|
871
|
-
// NOTE:
|
851
|
+
// Check for a time scaling method (used only for dataset run results).
|
852
|
+
// NOTE: Simply ignore $ unless it indicates a valid method.
|
872
853
|
const msep = s.indexOf('$');
|
873
854
|
if(msep <= 5) {
|
874
|
-
// Be tolerant as to case
|
855
|
+
// Be tolerant as to case.
|
875
856
|
let method = s.substring(0, msep).toUpperCase();
|
876
857
|
if(method.endsWith('P')) {
|
877
858
|
x.p = true;
|
@@ -879,70 +860,89 @@ class ExpressionParser {
|
|
879
860
|
}
|
880
861
|
if(['ABS', 'MEAN', 'SUM', 'MAX', ''].indexOf(method) >= 0) {
|
881
862
|
x.m = method;
|
882
|
-
s = s.substring(msep + 1);
|
863
|
+
s = s.substring(msep + 1).trim();
|
883
864
|
}
|
884
865
|
}
|
885
|
-
s
|
886
|
-
let
|
887
|
-
|
888
|
-
s = s
|
889
|
-
if(s.length >
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
//
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
//
|
905
|
-
|
906
|
-
//
|
907
|
-
//
|
908
|
-
const range = rn.substring(1);
|
909
|
-
// Call rangeToList only to validate the range syntax.
|
910
|
-
if(rangeToList(range)) {
|
911
|
-
x.nr = range;
|
912
|
-
this.is_static = false;
|
913
|
-
this.log('dynamic because experiment run number range');
|
914
|
-
} else {
|
915
|
-
msg = `Invalid experiment run number range "${range}"`;
|
916
|
-
}
|
866
|
+
// Now `s` may still have format title|run specifier.
|
867
|
+
let x_title = '',
|
868
|
+
run_spec = '';
|
869
|
+
s = s.split('|');
|
870
|
+
if(s.length > 2) {
|
871
|
+
msg = `Experiment result specifier may contain only one "|"`;
|
872
|
+
} else {
|
873
|
+
if(s.length == 2) {
|
874
|
+
run_spec = s.pop().trim();
|
875
|
+
x_title = s[0].trim();
|
876
|
+
} else {
|
877
|
+
// No vertical bar => no title, only the run specifier.
|
878
|
+
run_spec = s[0].trim();
|
879
|
+
}
|
880
|
+
// Run specifier can start with a # sign...
|
881
|
+
if(!run_spec.startsWith('#')) {
|
882
|
+
// ... and if not, it is assumed to be a list of modifier selectors
|
883
|
+
// that will identify (during problem set-up) a specific run.
|
884
|
+
// NOTE: Permit selectors to be separated by any combination
|
885
|
+
// of commas, semicolons and spaces.
|
886
|
+
x.r = run_spec.split(/[\,\;\/\s]+/g);
|
887
|
+
// NOTE: The VMI instruction accepts `x.r` to be a list of selectors
|
888
|
+
// or an integer number.
|
917
889
|
} else {
|
918
|
-
//
|
919
|
-
|
920
|
-
|
921
|
-
|
890
|
+
// If the specifier does start with a #, trim it...
|
891
|
+
run_spec = run_spec.substring(1);
|
892
|
+
// ... and then
|
893
|
+
// NOTE: Special notation for run numbers to permit modelers
|
894
|
+
// to chart results as if run numbers are on the time axis
|
895
|
+
// (with a given step size). The chart will be made as usual,
|
896
|
+
// i.e., plot a point for each time step t, but the value v[t]
|
897
|
+
// will then stay the same for the time interval that corresponds
|
898
|
+
// to simulation period length / number of runs.
|
899
|
+
// NOTE: This will fail to produce a meaningful chart when the
|
900
|
+
// simulation period is small compared to the number of runs.
|
901
|
+
if(run_spec.startsWith('n')) {
|
902
|
+
// #n may be followed by a range, or this range defaults to
|
903
|
+
// 0 - last run number. Of this range, the i-th number will
|
904
|
+
// be used, where i is computes as:
|
905
|
+
// floor(current time step * number of runs / period length)
|
906
|
+
const range = run_spec.substring(1);
|
907
|
+
// Call rangeToList only to validate the range syntax.
|
908
|
+
if(rangeToList(range)) {
|
909
|
+
x.nr = range;
|
910
|
+
this.is_static = false;
|
911
|
+
this.log('dynamic because experiment run number range');
|
912
|
+
} else {
|
913
|
+
msg = `Invalid experiment run number range "${range}"`;
|
914
|
+
}
|
922
915
|
} else {
|
923
|
-
// Explicit run number
|
924
|
-
|
916
|
+
// Explicit run number is specified.
|
917
|
+
const n = parseInt(run_spec);
|
918
|
+
if(isNaN(n)) {
|
919
|
+
msg = `Invalid experiment run number "${run_spec}"`;
|
920
|
+
} else {
|
921
|
+
// NOTE: Negative run numbers are acceptable.
|
922
|
+
x.r = n;
|
923
|
+
}
|
925
924
|
}
|
926
925
|
}
|
927
926
|
}
|
928
|
-
// NOTE:
|
929
|
-
|
930
|
-
|
931
|
-
// NOTE: title cannot be parametrized with a # wildcard
|
932
|
-
const n = MODEL.indexOfExperiment(s);
|
927
|
+
// NOTE: Experiment title cannot be parametrized with a # wildcard.
|
928
|
+
if(x_title) {
|
929
|
+
const n = MODEL.indexOfExperiment(x_title);
|
933
930
|
if(n < 0) {
|
934
|
-
msg = `Unknown experiment "${
|
931
|
+
msg = `Unknown experiment "${x_title}"`;
|
935
932
|
} else {
|
936
933
|
x.x = MODEL.experiments[n];
|
937
934
|
}
|
938
935
|
}
|
936
|
+
// END of code for parsing an experiment result specifier.
|
937
|
+
// Now proceed with parsing the variable name.
|
938
|
+
|
939
939
|
// Variable name may start with a (case insensitive) statistic
|
940
|
-
// specifier such as SUM or MEAN
|
940
|
+
// specifier such as SUM or MEAN.
|
941
941
|
s = name.split('$');
|
942
942
|
if(s.length > 1) {
|
943
943
|
const stat = s[0].trim().toUpperCase();
|
944
|
-
// NOTE:
|
945
|
-
// variable name) unless it is preceded by a valid statistic
|
944
|
+
// NOTE: Simply ignore $ (i.e., consider it as part of the
|
945
|
+
// variable name) unless it is preceded by a valid statistic.
|
946
946
|
if(VM.outcome_statistics.indexOf(stat) >= 0) {
|
947
947
|
x.s = stat;
|
948
948
|
name = s[1].trim();
|
@@ -951,7 +951,13 @@ class ExpressionParser {
|
|
951
951
|
// Variable name may start with a colon to denote that the owner
|
952
952
|
// prefix should be added.
|
953
953
|
name = UI.colonPrefixedName(name, this.owner_prefix);
|
954
|
-
|
954
|
+
// First check whether name refers to a valid attribute of an
|
955
|
+
// existing model entity.
|
956
|
+
const check = MODEL.validVariable(name);
|
957
|
+
if(check !== true) {
|
958
|
+
// If not TRUE, check will be an error message.
|
959
|
+
msg = check;
|
960
|
+
} else if(x.x) {
|
955
961
|
// Look up name in experiment outcomes list.
|
956
962
|
x.v = x.x.resultIndex(name);
|
957
963
|
if(x.v < 0 && name.indexOf('#') >= 0 &&
|
@@ -989,7 +995,26 @@ class ExpressionParser {
|
|
989
995
|
if(x.r === false && x.t === false) {
|
990
996
|
msg = 'Experiment run not specified';
|
991
997
|
} else if(x.v === false) {
|
992
|
-
|
998
|
+
// NOTE: Variable may not be defined as outcome of any experiment.
|
999
|
+
// This will be handled at runtime by VMI_push_run_result, but
|
1000
|
+
// it will be helpful to notify modelers at compile time when an
|
1001
|
+
// experiment is running, and also when they are editing an
|
1002
|
+
// expression (so when a modal dialog is showing).
|
1003
|
+
const
|
1004
|
+
notice = `No experiments have variable "${name}" as result`,
|
1005
|
+
tm = UI.topModal;
|
1006
|
+
// NOTE: Only notify when expression-editing modals are showing.
|
1007
|
+
if(tm) {
|
1008
|
+
const mid = tm.id.replace('-modal', '');
|
1009
|
+
if(['actor', 'note', 'link', 'boundline-data', 'process',
|
1010
|
+
'product', 'equation', 'expression'].indexOf(mid) >= 0) {
|
1011
|
+
UI.notify(notice);
|
1012
|
+
}
|
1013
|
+
}
|
1014
|
+
if(MODEL.running_experiment) {
|
1015
|
+
// Log message only for block 1.
|
1016
|
+
VM.logMessage(1, VM.WARNING + notice);
|
1017
|
+
}
|
993
1018
|
}
|
994
1019
|
}
|
995
1020
|
if(msg) {
|
@@ -2311,6 +2336,17 @@ class VirtualMachine {
|
|
2311
2336
|
P: 'process',
|
2312
2337
|
Q: 'product'
|
2313
2338
|
};
|
2339
|
+
// Reverse lookup for entity letter codes.
|
2340
|
+
this.entity_letter_codes = {
|
2341
|
+
actor: 'A',
|
2342
|
+
constraint: 'B',
|
2343
|
+
cluster: 'C',
|
2344
|
+
dataset: 'D',
|
2345
|
+
equation: 'E',
|
2346
|
+
link: 'L',
|
2347
|
+
process: 'P',
|
2348
|
+
product: 'Q'
|
2349
|
+
};
|
2314
2350
|
this.entity_letters = 'ABCDELPQ';
|
2315
2351
|
// Standard attributes of Linny-R entities.
|
2316
2352
|
this.attribute_names = {
|
@@ -7145,7 +7181,7 @@ function VMI_push_dataset_modifier(x, args) {
|
|
7145
7181
|
|
7146
7182
|
|
7147
7183
|
function VMI_push_run_result(x, args) {
|
7148
|
-
// NOTE:
|
7184
|
+
// NOTE: The first argument specifies the experiment run results:
|
7149
7185
|
// x: experiment object (FALSE indicates: use current experiment)
|
7150
7186
|
// r: integer number, or selector list
|
7151
7187
|
// v: variable index (integer number), or identifier (string)
|
@@ -7156,12 +7192,13 @@ function VMI_push_run_result(x, args) {
|
|
7156
7192
|
// t: if integer t > 0, use floor(current time step / t) as run number
|
7157
7193
|
const
|
7158
7194
|
rrspec = args[0],
|
7159
|
-
// NOTE:
|
7160
|
-
// a dataset modifier, use the time scale of the dataset, not of the
|
7161
|
-
// model, because the dataset vector is scaled to the model time scale
|
7195
|
+
// NOTE: When *expression* `x` for which this instruction is executed
|
7196
|
+
// is a dataset modifier, use the time scale of the dataset, not of the
|
7197
|
+
// model, because the dataset vector is scaled to the model time scale.
|
7162
7198
|
model_dt = MODEL.timeStepDuration;
|
7163
|
-
// NOTE:
|
7164
|
-
// better
|
7199
|
+
// NOTE: Run result can now default to UNDEFINED, because the VM now handles
|
7200
|
+
// exceptional values better: no call stack dump on "undefined" etc., but
|
7201
|
+
// only on real errors.
|
7165
7202
|
let v = rrspec.dv || VM.UNDEFINED;
|
7166
7203
|
if(rrspec && rrspec.hasOwnProperty('x')) {
|
7167
7204
|
let xp = rrspec.x,
|
@@ -7170,12 +7207,14 @@ function VMI_push_run_result(x, args) {
|
|
7170
7207
|
if(xp === false) xp = MODEL.running_experiment;
|
7171
7208
|
if(xp instanceof Experiment) {
|
7172
7209
|
if(Array.isArray(rn)) {
|
7210
|
+
// Let the running experiment infer run number from selector list `rn`
|
7211
|
+
// and its own "active combination" of selectors.
|
7173
7212
|
rn = xp.matchingCombinationIndex(rn);
|
7174
7213
|
} else if(rn < 0) {
|
7175
|
-
// Relative run number: use current run # + r (first run has number 0)
|
7214
|
+
// Relative run number: use current run # + r (first run has number 0).
|
7176
7215
|
rn += xp.active_combination_index;
|
7177
7216
|
} else if(rrspec.nr !== false) {
|
7178
|
-
// Run number inferred from local time step of expression
|
7217
|
+
// Run number inferred from local time step of expression.
|
7179
7218
|
const
|
7180
7219
|
rl = VM.nr_of_time_steps,
|
7181
7220
|
range = rangeToList(rrspec.nr, xp.runs.length - 1);
|
@@ -7186,9 +7225,9 @@ function VMI_push_run_result(x, args) {
|
|
7186
7225
|
rn = (ri < l ? range[ri] : range[l - 1]);
|
7187
7226
|
}
|
7188
7227
|
}
|
7189
|
-
// If variable is passed as identifier, get its index for the experiment
|
7228
|
+
// If variable is passed as identifier, get its index for the experiment.
|
7190
7229
|
if(typeof rri === 'string') rri = xp.resultIndex(rri);
|
7191
|
-
// Then proceed only if run number and result index both make sense
|
7230
|
+
// Then proceed only if run number and result index both make sense.
|
7192
7231
|
const run_count = (xp.completed ? xp.runs.length :
|
7193
7232
|
xp.active_combination_index);
|
7194
7233
|
if(rn !== false && rn >= 0 && rn < run_count) {
|
@@ -7233,11 +7272,14 @@ function VMI_push_run_result(x, args) {
|
|
7233
7272
|
} else {
|
7234
7273
|
// No statistic => return the vector for local time step
|
7235
7274
|
// using here, too, the delta-time-modifier to adjust the offsets
|
7236
|
-
// for different time steps per experiment
|
7275
|
+
// for different time steps per experiment.
|
7237
7276
|
const tot = twoOffsetTimeStep(x.step[x.step.length - 1],
|
7238
7277
|
args[1], args[2], args[3], args[4], dtm, x);
|
7239
7278
|
// Scale the (midpoint) time step (at current model run time scale)
|
7240
|
-
// to the experiment run time scale and get the run result value
|
7279
|
+
// to the experiment run time scale and get the run result value.
|
7280
|
+
// NOTE: the .m property specifies the time scaling method, and
|
7281
|
+
// the .p property whether the run result vector should be used as
|
7282
|
+
// a periodic time series.
|
7241
7283
|
v = rr.valueAtModelTime(tot[0], model_dt, rrspec.m, rrspec.p);
|
7242
7284
|
if(DEBUGGING) {
|
7243
7285
|
const trc = ['push run result: ', xp.title,
|