linny-r 1.4.1 → 1.4.3
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 +69 -35
- package/package.json +1 -1
- package/server.js +114 -43
- package/static/images/octaeder.svg +993 -0
- package/static/index.html +22 -617
- package/static/linny-r.css +32 -378
- package/static/scripts/linny-r-ctrl.js +59 -5
- package/static/scripts/linny-r-gui.js +198 -56
- package/static/scripts/linny-r-model.js +127 -41
- package/static/scripts/linny-r-utils.js +32 -11
- package/static/scripts/linny-r-vm.js +40 -14
@@ -2940,24 +2940,72 @@ class LinnyRModel {
|
|
2940
2940
|
}
|
2941
2941
|
|
2942
2942
|
get outputData() {
|
2943
|
-
// Returns model results [data, statistics] in tab-separated format
|
2944
|
-
|
2945
|
-
|
2943
|
+
// Returns model results [data, statistics] in tab-separated format.
|
2944
|
+
const
|
2945
|
+
vbls = [],
|
2946
|
+
names = [],
|
2947
|
+
scale_re = /\s+\(x[0-9\.\,]+\)$/;
|
2948
|
+
// First create list of distinct variables used in charts.
|
2949
|
+
// NOTE: Also include those that are not "visible" in a chart.
|
2946
2950
|
for(let i = 0; i < this.charts.length; i++) {
|
2947
2951
|
const c = this.charts[i];
|
2948
2952
|
for(let j = 0; j < c.variables.length; j++) {
|
2949
|
-
|
2953
|
+
let v = c.variables[j],
|
2954
|
+
vn = v.displayName;
|
2955
|
+
// If variable is scaled, do not include it as such, but include
|
2956
|
+
// a new unscaled chart variable.
|
2957
|
+
if(vn.match(scale_re)) {
|
2958
|
+
vn = vn.replace(scale_re, '');
|
2959
|
+
// Add only if (now unscaled) variable has not been added already.
|
2960
|
+
if(names.indexOf(vn) < 0) {
|
2961
|
+
// NOTE: Chart variable object is used ony as adummy, so NULL
|
2962
|
+
// can be used as its "owner chart".
|
2963
|
+
const cv = new ChartVariable(null);
|
2964
|
+
cv.setProperties(v.object, v.attribute, false, '#000000');
|
2965
|
+
vbls.push(cv);
|
2966
|
+
names.push(uvn);
|
2967
|
+
}
|
2968
|
+
} else if(names.indexOf(vn) < 0) {
|
2969
|
+
// Keep track of the dataset and dataset modifier variables,
|
2970
|
+
// so they will not be added in the next FOR loop.
|
2971
|
+
vbls.push(v);
|
2972
|
+
names.push(vn);
|
2973
|
+
}
|
2974
|
+
}
|
2975
|
+
}
|
2976
|
+
// Add new variables for each outcome dataset and each equation that
|
2977
|
+
// is not a chart variable.
|
2978
|
+
for(let id in this.datasets) if(this.datasets.hasOwnProperty(id)) {
|
2979
|
+
const
|
2980
|
+
ds = this.datasets[id],
|
2981
|
+
eq = (ds === this.equations_dataset);
|
2982
|
+
if(ds.outcome || eq) {
|
2983
|
+
for(let ms in ds.modifiers) if(ds.modifiers.hasOwnProperty(ms)) {
|
2984
|
+
const
|
2985
|
+
dm = ds.modifiers[ms],
|
2986
|
+
n = dm.displayName;
|
2987
|
+
// Do not add if already in the list.
|
2988
|
+
if(names.indexOf(n) < 0) {
|
2989
|
+
// Here, too, NULL can be used as "owner chart".
|
2990
|
+
const cv = new ChartVariable(null);
|
2991
|
+
cv.setProperties(ds, dm.selector, false, '#000000');
|
2992
|
+
vbls.push(cv);
|
2993
|
+
}
|
2994
|
+
}
|
2950
2995
|
}
|
2951
2996
|
}
|
2952
|
-
//
|
2997
|
+
// Sort variables by their name.
|
2998
|
+
vbls.sort((a, b) => UI.compareFullNames(a.displayName, b.displayName));
|
2999
|
+
// Create a new chart as dummy, so without adding it to this model.
|
2953
3000
|
const c = new Chart();
|
2954
3001
|
for(let i = 0; i < vbls.length; i++) {
|
2955
3002
|
const v = vbls[i];
|
2956
3003
|
c.addVariable(v.object.displayName, v.attribute);
|
2957
3004
|
}
|
2958
|
-
|
3005
|
+
// NOTE: Call `draw` with FALSE to prevent display in the chart manager.
|
3006
|
+
c.draw(false);
|
3007
|
+
// After drawing, all variables and their statistics have been computed.
|
2959
3008
|
return [c.dataAsString, c.statisticsAsString];
|
2960
|
-
// @@@TO DO: also add statistics on "outcome" datasets
|
2961
3009
|
}
|
2962
3010
|
|
2963
3011
|
get listOfAllSelectors() {
|
@@ -5278,14 +5326,14 @@ class NodeBox extends ObjectWithXYWH {
|
|
5278
5326
|
MODEL.replaceEntityInExpressions(old_name, this.displayName);
|
5279
5327
|
MODEL.inferIgnoredEntities();
|
5280
5328
|
// NOTE: Renaming may affect the node's display size.
|
5281
|
-
if(this.resize())
|
5329
|
+
if(this.resize()) UI.drawSelection(MODEL);
|
5282
5330
|
// NOTE: Only TRUE indicates a successful (cosmetic) name change.
|
5283
5331
|
return true;
|
5284
5332
|
}
|
5285
5333
|
|
5286
5334
|
resize() {
|
5287
|
-
// Resizes this node; returns TRUE iff size has changed
|
5288
|
-
//
|
5335
|
+
// Resizes this node; returns TRUE iff size has changed.
|
5336
|
+
// Therefore, keep track of original width and height.
|
5289
5337
|
const
|
5290
5338
|
ow = this.width,
|
5291
5339
|
oh = this.height,
|
@@ -5341,12 +5389,13 @@ class NodeBox extends ObjectWithXYWH {
|
|
5341
5389
|
}
|
5342
5390
|
|
5343
5391
|
drawWithLinks() {
|
5344
|
-
// TO DO:
|
5392
|
+
// TO DO: Also draw relevant arrows when this is a cluster.
|
5345
5393
|
UI.drawObject(this);
|
5346
5394
|
if(this instanceof Cluster) return;
|
5347
|
-
//
|
5395
|
+
// Draw ALL arrows associated with this node.
|
5348
5396
|
const fc = this.cluster;
|
5349
|
-
|
5397
|
+
fc.categorizeEntities();
|
5398
|
+
// Make list of arrows that represent a link related to this node.
|
5350
5399
|
let a,
|
5351
5400
|
alist = [];
|
5352
5401
|
for(let j = 0; j < fc.arrows.length; j++) {
|
@@ -5362,7 +5411,7 @@ class NodeBox extends ObjectWithXYWH {
|
|
5362
5411
|
}
|
5363
5412
|
}
|
5364
5413
|
}
|
5365
|
-
//
|
5414
|
+
// Draw all arrows in this list.
|
5366
5415
|
for(let i = 0; i < alist.length; i++) UI.drawObject(alist[i]);
|
5367
5416
|
}
|
5368
5417
|
|
@@ -7290,6 +7339,7 @@ class Process extends Node {
|
|
7290
7339
|
a.LB = this.lower_bound.asAttribute;
|
7291
7340
|
a.UB = (this.equal_bounds ? a.LB : this.upper_bound.asAttribute);
|
7292
7341
|
a.IL = this.initial_level.asAttribute;
|
7342
|
+
a.LCF = this.pace_expression.asAttribute;
|
7293
7343
|
if(MODEL.solved) {
|
7294
7344
|
const t = MODEL.t;
|
7295
7345
|
a.L = this.level[t];
|
@@ -7323,7 +7373,7 @@ class Process extends Node {
|
|
7323
7373
|
// without comments or their (X, Y) position
|
7324
7374
|
n = MODEL.black_box_entities[id];
|
7325
7375
|
// `n` is just the name, so remove the actor name if it was added
|
7326
|
-
if(an) n = n.
|
7376
|
+
if(an) n = n.substring(0, n.lastIndexOf(an));
|
7327
7377
|
col = true;
|
7328
7378
|
cmnts = '';
|
7329
7379
|
x = 0;
|
@@ -7551,6 +7601,8 @@ class Product extends Node {
|
|
7551
7601
|
if(MODEL.infer_cost_prices) {
|
7552
7602
|
a.CP = this.cost_price[t];
|
7553
7603
|
a.HCP = this.highest_cost_price[t];
|
7604
|
+
// Highest cost price may be undefined if product has no inflows.
|
7605
|
+
if(a.HCP === VM.MINUS_INFINITY) a.HCP = '';
|
7554
7606
|
}
|
7555
7607
|
}
|
7556
7608
|
return a;
|
@@ -8829,10 +8881,11 @@ class ChartVariable {
|
|
8829
8881
|
this.non_zero_tally = 0;
|
8830
8882
|
this.exceptions = 0;
|
8831
8883
|
this.bin_tallies = [];
|
8884
|
+
this.wildcard_index = false;
|
8832
8885
|
}
|
8833
8886
|
|
8834
8887
|
setProperties(obj, attr, stck, clr, sf=1, lw=1, vis=true) {
|
8835
|
-
// Sets the defining properties for this chart variable
|
8888
|
+
// Sets the defining properties for this chart variable.
|
8836
8889
|
this.object = obj;
|
8837
8890
|
this.attribute = attr;
|
8838
8891
|
this.stacked = stck;
|
@@ -8844,12 +8897,18 @@ class ChartVariable {
|
|
8844
8897
|
|
8845
8898
|
get displayName() {
|
8846
8899
|
// Returns the name of the Linny-R entity and its attribute, followed
|
8847
|
-
// by its scale factor unless it equals 1 (no scaling)
|
8900
|
+
// by its scale factor unless it equals 1 (no scaling).
|
8848
8901
|
const sf = (this.scale_factor === 1 ? '' :
|
8849
8902
|
` (x${VM.sig4Dig(this.scale_factor)})`);
|
8850
|
-
// NOTE:
|
8851
|
-
if(this.object === MODEL.equations_dataset)
|
8852
|
-
|
8903
|
+
// NOTE: Display name of equation is just the equations dataset modifier.
|
8904
|
+
if(this.object === MODEL.equations_dataset) {
|
8905
|
+
let eqn = this.attribute;
|
8906
|
+
if(this.wildcard_index !== false) {
|
8907
|
+
eqn = eqn.replace('??', this.wildcard_index);
|
8908
|
+
}
|
8909
|
+
return eqn + sf;
|
8910
|
+
}
|
8911
|
+
// NOTE: Do not display the vertical bar if no attribute is specified.
|
8853
8912
|
if(!this.attribute) return this.object.displayName + sf;
|
8854
8913
|
return this.object.displayName + UI.OA_SEPARATOR + this.attribute + sf;
|
8855
8914
|
}
|
@@ -8919,7 +8978,7 @@ class ChartVariable {
|
|
8919
8978
|
}
|
8920
8979
|
|
8921
8980
|
computeVector() {
|
8922
|
-
// Compute vector for this variable (using run results if specified)
|
8981
|
+
// Compute vector for this variable (using run results if specified).
|
8923
8982
|
let xrun = null,
|
8924
8983
|
rr = null,
|
8925
8984
|
ri = this.chart.run_index;
|
@@ -8934,10 +8993,10 @@ class ChartVariable {
|
|
8934
8993
|
this.vector.length = 0;
|
8935
8994
|
}
|
8936
8995
|
}
|
8937
|
-
// Compute vector and statistics only if vector is still empty
|
8996
|
+
// Compute vector and statistics only if vector is still empty.
|
8938
8997
|
if(this.vector.length > 0) return;
|
8939
|
-
// NOTE: expression vectors start at t = 0 with initial values that
|
8940
|
-
// not be included in statistics
|
8998
|
+
// NOTE: expression vectors start at t = 0 with initial values that
|
8999
|
+
// should not be included in statistics.
|
8941
9000
|
let v,
|
8942
9001
|
av = null,
|
8943
9002
|
t_end;
|
@@ -8948,22 +9007,23 @@ class ChartVariable {
|
|
8948
9007
|
this.non_zero_tally = 0;
|
8949
9008
|
this.exceptions = 0;
|
8950
9009
|
if(rr) {
|
8951
|
-
// Use run results (time scaled) as "actual vector" `av` for this
|
9010
|
+
// Use run results (time scaled) as "actual vector" `av` for this
|
9011
|
+
// variable.
|
8952
9012
|
const tsteps = Math.ceil(this.chart.time_horizon / this.chart.time_scale);
|
8953
9013
|
av = [];
|
8954
|
-
// NOTE:
|
9014
|
+
// NOTE: `scaleDataToVector` expects "pure" data, so slice off v[0].
|
8955
9015
|
VM.scaleDataToVector(rr.vector.slice(1), av, xrun.time_step_duration,
|
8956
9016
|
this.chart.time_scale, tsteps, 1);
|
8957
9017
|
t_end = tsteps;
|
8958
9018
|
} else {
|
8959
9019
|
// Get the variable's own value (number, vector or expression)
|
8960
|
-
// Special case: when an experiment is running, variables that
|
8961
|
-
// dataset with no explicit modifier must recompute the
|
8962
|
-
// current experiment run combination
|
9020
|
+
// Special case: when an experiment is running, variables that
|
9021
|
+
// depict a dataset with no explicit modifier must recompute the
|
9022
|
+
// vector using the current experiment run combination.
|
8963
9023
|
if(MODEL.running_experiment &&
|
8964
9024
|
this.object instanceof Dataset && !this.attribute) {
|
8965
9025
|
// Check if dataset modifiers match the combination of selectors
|
8966
|
-
// for the active run
|
9026
|
+
// for the active run.
|
8967
9027
|
const mm = this.object.matchingModifiers(
|
8968
9028
|
MODEL.running_experiment.activeCombination);
|
8969
9029
|
// If so, use the first (the list should contain at most 1 selector)
|
@@ -8982,21 +9042,24 @@ class ChartVariable {
|
|
8982
9042
|
}
|
8983
9043
|
t_end = MODEL.end_period - MODEL.start_period + 1;
|
8984
9044
|
}
|
8985
|
-
// NOTE: when a chart combines run results with dataset vectors, the
|
8986
|
-
// may be longer than the # of time steps displayed in the chart
|
9045
|
+
// NOTE: when a chart combines run results with dataset vectors, the
|
9046
|
+
// latter may be longer than the # of time steps displayed in the chart.
|
8987
9047
|
t_end = Math.min(t_end, this.chart.total_time_steps);
|
8988
9048
|
this.N = t_end;
|
8989
9049
|
for(let t = 0; t <= t_end; t++) {
|
8990
|
-
// Get the result, store it, and incorporate it in statistics
|
9050
|
+
// Get the result, store it, and incorporate it in statistics.
|
8991
9051
|
if(!av) {
|
8992
9052
|
// Undefined attribute => zero (no error)
|
8993
9053
|
v = 0;
|
8994
9054
|
} else if(Array.isArray(av)) {
|
8995
|
-
// Attribute value is a vector
|
9055
|
+
// Attribute value is a vector.
|
9056
|
+
// NOTE: This vector may be shorter than t; then use 0.
|
8996
9057
|
v = (t < av.length ? av[t] : 0);
|
8997
9058
|
} else if(av instanceof Expression) {
|
8998
|
-
// Attribute value is an expression
|
8999
|
-
|
9059
|
+
// Attribute value is an expression. If this chart variable has
|
9060
|
+
// its wildcard vector index set, evaluate the expression with
|
9061
|
+
// this index as context number.
|
9062
|
+
v = av.result(t, this.wildcard_index);
|
9000
9063
|
} else {
|
9001
9064
|
// Attribute value must be a number
|
9002
9065
|
v = av;
|
@@ -9165,7 +9228,7 @@ class Chart {
|
|
9165
9228
|
|
9166
9229
|
variableIndexByName(n) {
|
9167
9230
|
for(let i = 0; i < this.variables.length; i++) {
|
9168
|
-
if(this.variables[i].displayName
|
9231
|
+
if(this.variables[i].displayName === n) return i;
|
9169
9232
|
}
|
9170
9233
|
return -1;
|
9171
9234
|
}
|
@@ -9190,20 +9253,39 @@ class Chart {
|
|
9190
9253
|
if(n === UI.EQUATIONS_DATASET_NAME) {
|
9191
9254
|
// For equations only the attribute (modifier selector)
|
9192
9255
|
dn = a;
|
9256
|
+
n = a;
|
9193
9257
|
} else if(!a) {
|
9194
9258
|
// If no attribute specified (=> dataset) only the entity name
|
9195
9259
|
dn = n;
|
9196
9260
|
}
|
9197
9261
|
let vi = this.variableIndexByName(dn);
|
9198
9262
|
if(vi >= 0) return vi;
|
9199
|
-
//
|
9263
|
+
// Check whether name refers to a Linny-R entity defined by the model.
|
9200
9264
|
let obj = MODEL.objectByName(n);
|
9201
9265
|
if(obj === null) {
|
9202
9266
|
UI.warn(`Unknown entity "${n}"`);
|
9203
9267
|
return null;
|
9204
|
-
}
|
9205
|
-
|
9268
|
+
}
|
9269
|
+
const eq = obj instanceof DatasetModifier;
|
9270
|
+
if(!eq) {
|
9271
|
+
// No equation and no attribute specified? Then assume default.
|
9206
9272
|
if(a === '') a = obj.defaultAttribute;
|
9273
|
+
} else if(n.indexOf('??') >= 0) {
|
9274
|
+
// Special case: for wildcard equations, add dummy variables
|
9275
|
+
// for each vector in the wildcard vector set of the equation
|
9276
|
+
// expression.
|
9277
|
+
const
|
9278
|
+
vlist = [],
|
9279
|
+
clr = this.nextAvailableDefaultColor,
|
9280
|
+
indices = Object.keys(obj.expression.wildcard_vectors);
|
9281
|
+
for(let i = 0; i < indices.length; i++) {
|
9282
|
+
const v = new ChartVariable(this);
|
9283
|
+
v.setProperties(MODEL.equations_dataset, dn, false, clr);
|
9284
|
+
v.wildcard_index = parseInt(indices[i]);
|
9285
|
+
this.variables.push(v);
|
9286
|
+
vlist.push(v);
|
9287
|
+
}
|
9288
|
+
return vlist;
|
9207
9289
|
}
|
9208
9290
|
const v = new ChartVariable(this);
|
9209
9291
|
v.setProperties(obj, a, false, this.nextAvailableDefaultColor, 1, 1);
|
@@ -9296,7 +9378,7 @@ class Chart {
|
|
9296
9378
|
return VM.sig2Dig(s / 8760) + 'y';
|
9297
9379
|
}
|
9298
9380
|
|
9299
|
-
draw() {
|
9381
|
+
draw(display=true) {
|
9300
9382
|
// NOTE: The SVG drawing area is fixed to be 500 pixels high, so that
|
9301
9383
|
// when saved as a file, it will (at 300 dpi) be about 2 inches high.
|
9302
9384
|
// Its width will equal its hight times the W/H-ratio of the chart
|
@@ -9471,6 +9553,10 @@ class Chart {
|
|
9471
9553
|
}
|
9472
9554
|
}
|
9473
9555
|
|
9556
|
+
// Now all vectors have been computed. If `display` is FALSE, this
|
9557
|
+
// indicates that data is used only to save model results.
|
9558
|
+
if(!display) return;
|
9559
|
+
|
9474
9560
|
// Define the bins when drawing as histogram
|
9475
9561
|
if(this.histogram) {
|
9476
9562
|
this.value_range = maxv - minv;
|
@@ -124,9 +124,9 @@ function msecToTime(msec) {
|
|
124
124
|
const ts = new Date(msec).toISOString().slice(11, -1).split('.');
|
125
125
|
let hms = ts[0], ms = ts[1];
|
126
126
|
// Trim zero hours and minutes
|
127
|
-
while(hms.startsWith('00:')) hms = hms.
|
127
|
+
while(hms.startsWith('00:')) hms = hms.substring(3);
|
128
128
|
// Trim leading zero on first number
|
129
|
-
if(hms.startsWith('00')) hms = hms.
|
129
|
+
if(hms.startsWith('00')) hms = hms.substring(1);
|
130
130
|
// Trim msec when minutes > 0
|
131
131
|
if(hms.indexOf(':') > 0) return hms;
|
132
132
|
// If < 1 second, return as milliseconds
|
@@ -173,7 +173,7 @@ function uniformDecimals(data) {
|
|
173
173
|
ss = v.split('e');
|
174
174
|
x = ss[1];
|
175
175
|
if(x.length < maxe) {
|
176
|
-
x = x[0] + '0' + x.
|
176
|
+
x = x[0] + '0' + x.substring(1);
|
177
177
|
}
|
178
178
|
data[i] = ss[0] + 'e' + x;
|
179
179
|
} else if(maxi > 3) {
|
@@ -186,9 +186,14 @@ function uniformDecimals(data) {
|
|
186
186
|
}
|
187
187
|
}
|
188
188
|
|
189
|
+
function capitalized(s) {
|
190
|
+
// Returns string `s` with its first letter capitalized.
|
191
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
192
|
+
}
|
193
|
+
|
189
194
|
function ellipsedText(text, n=50, m=10) {
|
190
|
-
// Returns `text` with ellipsis " ... " between its first `n` and
|
191
|
-
// characters
|
195
|
+
// Returns `text` with ellipsis " ... " between its first `n` and
|
196
|
+
// last `m` characters.
|
192
197
|
if(text.length <= n + m + 3) return text;
|
193
198
|
return text.slice(0, n) + ' \u2026 ' + text.slice(text.length - m);
|
194
199
|
}
|
@@ -478,6 +483,21 @@ function matchingNumber(m, s) {
|
|
478
483
|
return (n == m ? n : false);
|
479
484
|
}
|
480
485
|
|
486
|
+
function compareWithTailNumbers(s1, s2) {
|
487
|
+
// Returns 0 on equal, an integer < 0 if `s1` comes before `s2`, and
|
488
|
+
// an integer > 0 if `s2` comes before `s1`.
|
489
|
+
if(s1 === s2) return 0;
|
490
|
+
let tn1 = endsWithDigits(s1),
|
491
|
+
tn2 = endsWithDigits(s2);
|
492
|
+
if(tn1) s1 = s1.slice(0, -tn1.length);
|
493
|
+
if(tn2) s2 = s2.slice(0, -tn2.length);
|
494
|
+
let c = ciCompare(s1, s2);
|
495
|
+
if(c !== 0 || !(tn1 || tn2)) return c;
|
496
|
+
if(tn1 && tn2) return parseInt(tn1) - parseInt(tn2);
|
497
|
+
if(tn2) return -1;
|
498
|
+
return 1;
|
499
|
+
}
|
500
|
+
|
481
501
|
function compareSelectors(s1, s2) {
|
482
502
|
// Dataset selectors comparison is case-insensitive, and puts wildcards
|
483
503
|
// last, where * comes later than ?
|
@@ -492,7 +512,7 @@ function compareSelectors(s1, s2) {
|
|
492
512
|
star2 = s2.indexOf('*');
|
493
513
|
if(star1 >= 0) {
|
494
514
|
if(star2 < 0) return 1;
|
495
|
-
return s1
|
515
|
+
return ciCompare(s1, s2);
|
496
516
|
}
|
497
517
|
if(star2 >= 0) return -1;
|
498
518
|
// Replace ? by | because | has a higher ASCII value than all other chars
|
@@ -525,7 +545,7 @@ function compareSelectors(s1, s2) {
|
|
525
545
|
while(i >= 0 && s_1[i] === '-') i--;
|
526
546
|
// If trailing minuses, replace by as many spaces and add an exclamation point
|
527
547
|
if(i < n - 1) {
|
528
|
-
s_1 = s_1.
|
548
|
+
s_1 = s_1.substring(0, i);
|
529
549
|
while(s_1.length < n) s_1 += ' ';
|
530
550
|
s_1 += '!';
|
531
551
|
}
|
@@ -534,7 +554,7 @@ function compareSelectors(s1, s2) {
|
|
534
554
|
i = n - 1;
|
535
555
|
while(i >= 0 && s_2[i] === '-') i--;
|
536
556
|
if(i < n - 1) {
|
537
|
-
s_2 = s_2.
|
557
|
+
s_2 = s_2.substring(0, i);
|
538
558
|
while(s_2.length < n) s_2 += ' ';
|
539
559
|
s_2 += '!';
|
540
560
|
}
|
@@ -814,8 +834,9 @@ function stringToFloatArray(s) {
|
|
814
834
|
a = [];
|
815
835
|
while(i <= s.length) {
|
816
836
|
const
|
817
|
-
h = s.
|
818
|
-
r = h.
|
837
|
+
h = s.substring(i - 8, i),
|
838
|
+
r = h.substring(6, 2) + h.substring(4, 2) +
|
839
|
+
h.substring(2, 2) + h.substring(0, 2);
|
819
840
|
a.push(hexToFloat(r));
|
820
841
|
i += 8;
|
821
842
|
}
|
@@ -830,7 +851,7 @@ function hexToBytes(hex) {
|
|
830
851
|
// Converts a hex string to a Uint8Array
|
831
852
|
const bytes = [];
|
832
853
|
for(let i = 0; i < hex.length; i += 2) {
|
833
|
-
bytes.push(parseInt(hex.
|
854
|
+
bytes.push(parseInt(hex.substring(i, i + 2), 16));
|
834
855
|
}
|
835
856
|
return new Uint8Array(bytes);
|
836
857
|
}
|
@@ -713,14 +713,14 @@ class ExpressionParser {
|
|
713
713
|
// t (current time step, this is the default),
|
714
714
|
if('#cfijklnprst'.includes(offs[0].charAt(0))) {
|
715
715
|
anchor1 = offs[0].charAt(0);
|
716
|
-
offset1 = safeStrToInt(offs[0].
|
716
|
+
offset1 = safeStrToInt(offs[0].substring(1));
|
717
717
|
} else {
|
718
718
|
offset1 = safeStrToInt(offs[0]);
|
719
719
|
}
|
720
720
|
if(offs.length > 1) {
|
721
721
|
if('#cfijklnprst'.includes(offs[1].charAt(0))) {
|
722
722
|
anchor2 = offs[1].charAt(0);
|
723
|
-
offset2 = safeStrToInt(offs[1].
|
723
|
+
offset2 = safeStrToInt(offs[1].substring(1));
|
724
724
|
} else {
|
725
725
|
offset2 = safeStrToInt(offs[1]);
|
726
726
|
}
|
@@ -757,7 +757,7 @@ class ExpressionParser {
|
|
757
757
|
};
|
758
758
|
// NOTE: name should then be in the experiment's variable list
|
759
759
|
name = s[1].trim();
|
760
|
-
s = s[0].
|
760
|
+
s = s[0].substring(1);
|
761
761
|
// Check for scaling method
|
762
762
|
// NOTE: simply ignore $ unless it indicates a valid method
|
763
763
|
const msep = s.indexOf('$');
|
@@ -967,6 +967,10 @@ class ExpressionParser {
|
|
967
967
|
return false;
|
968
968
|
}
|
969
969
|
}
|
970
|
+
/*
|
971
|
+
// DEPRECATED -- Modeler can deal with this by smartly using AND
|
972
|
+
// clauses like "&x: &y:" to limit set to specific prefixes.
|
973
|
+
|
970
974
|
// Deal with "prefix inheritance" when pattern starts with a colon.
|
971
975
|
if(pat.startsWith(':') && this.owner_prefix) {
|
972
976
|
// Add a "must start with" AND condition to all OR clauses of the
|
@@ -979,6 +983,7 @@ class ExpressionParser {
|
|
979
983
|
}
|
980
984
|
pat = oc.join('|');
|
981
985
|
}
|
986
|
+
*/
|
982
987
|
// NOTE: For patterns, assume that # *always* denotes the context-
|
983
988
|
// sensitive number #, because if modelers wishes to include
|
984
989
|
// ANY number, they can make their pattern less selective.
|
@@ -1449,7 +1454,7 @@ class ExpressionParser {
|
|
1449
1454
|
this.los = 1;
|
1450
1455
|
this.error = 'Missing closing bracket \']\'';
|
1451
1456
|
} else {
|
1452
|
-
v = this.expr.
|
1457
|
+
v = this.expr.substring(this.pit + 1, i);
|
1453
1458
|
this.pit = i + 1;
|
1454
1459
|
// NOTE: Enclosing brackets are also part of this symbol
|
1455
1460
|
this.los = v.length + 2;
|
@@ -1467,7 +1472,7 @@ class ExpressionParser {
|
|
1467
1472
|
this.los = 1;
|
1468
1473
|
this.error = 'Unmatched quote';
|
1469
1474
|
} else {
|
1470
|
-
v = this.expr.
|
1475
|
+
v = this.expr.substring(this.pit + 1, i);
|
1471
1476
|
this.pit = i + 1;
|
1472
1477
|
// NOTE: Enclosing quotes are also part of this symbol
|
1473
1478
|
this.los = v.length + 2;
|
@@ -1520,7 +1525,7 @@ class ExpressionParser {
|
|
1520
1525
|
this.los++;
|
1521
1526
|
}
|
1522
1527
|
// ... but trim spaces from the symbol
|
1523
|
-
v = this.expr.
|
1528
|
+
v = this.expr.substring(this.pit, this.pit + this.los).trim();
|
1524
1529
|
// Ignore case
|
1525
1530
|
l = v.toLowerCase();
|
1526
1531
|
if(l === '#') {
|
@@ -2065,6 +2070,16 @@ class VirtualMachine {
|
|
2065
2070
|
P: this.process_attr,
|
2066
2071
|
Q: this.product_attr
|
2067
2072
|
};
|
2073
|
+
this.entity_attribute_names = {};
|
2074
|
+
for(let i = 0; i < this.entity_letters.length; i++) {
|
2075
|
+
const
|
2076
|
+
el = this.entity_letters.charAt(i),
|
2077
|
+
ac = this.attribute_codes[el];
|
2078
|
+
this.entity_attribute_names[el] = [];
|
2079
|
+
for(let j = 0; j < ac.length; j++) {
|
2080
|
+
this.entity_attribute_names[el].push(ac[j]);
|
2081
|
+
}
|
2082
|
+
}
|
2068
2083
|
// Level-based attributes are computed only AFTER optimization
|
2069
2084
|
this.level_based_attr = ['L', 'CP', 'HCP', 'CF', 'CI', 'CO', 'F', 'A'];
|
2070
2085
|
this.object_types = ['Process', 'Product', 'Cluster', 'Link', 'Constraint',
|
@@ -5929,7 +5944,9 @@ function VMI_push_dataset_modifier(x, args) {
|
|
5929
5944
|
// NOTE: Use the "local" time step for expression x, i.e., the top
|
5930
5945
|
// value of the expression's time step stack `x.step`.
|
5931
5946
|
tot = twoOffsetTimeStep(x.step[x.step.length - 1],
|
5932
|
-
args[1], args[2], args[3], args[4], 1, x)
|
5947
|
+
args[1], args[2], args[3], args[4], 1, x),
|
5948
|
+
// Record whether either anchor uses the context-sensitive number.
|
5949
|
+
hashtag_index = (args[1] === '#' || args[3] === '#');
|
5933
5950
|
// NOTE: Sanity check to facilitate debugging; if no dataset is provided,
|
5934
5951
|
// the script will still break at the LET statement below.
|
5935
5952
|
if(!ds) console.log('ERROR: VMI_push_dataset_modifier without dataset',
|
@@ -5947,7 +5964,7 @@ function VMI_push_dataset_modifier(x, args) {
|
|
5947
5964
|
t = t % obj.length;
|
5948
5965
|
if(t < 0) t += obj.length;
|
5949
5966
|
}
|
5950
|
-
if(
|
5967
|
+
if(hashtag_index) {
|
5951
5968
|
// NOTE: Add 1 because (parent) anchors are 1-based.
|
5952
5969
|
ds.parent_anchor = t + 1;
|
5953
5970
|
if(DEBUGGING) {
|
@@ -5991,12 +6008,21 @@ function VMI_push_dataset_modifier(x, args) {
|
|
5991
6008
|
if(t >= 0 && t < obj.length) {
|
5992
6009
|
v = obj[t];
|
5993
6010
|
} else if(ds.array && t >= obj.length) {
|
5994
|
-
//
|
5995
|
-
|
5996
|
-
VM.
|
5997
|
-
|
5998
|
-
|
5999
|
-
|
6011
|
+
// Ensure that value of t is human-readable.
|
6012
|
+
// NOTE: Add 1 to compensate for earlier t-- to make `t` zero-based.
|
6013
|
+
const index = VM.sig2Dig(t + 1);
|
6014
|
+
// Special case: index is undefined because # was undefined.
|
6015
|
+
if(hashtag_index && index === '\u2047') {
|
6016
|
+
// In such cases, return the default value of the dataset.
|
6017
|
+
v = ds.default_value;
|
6018
|
+
} else {
|
6019
|
+
// Set error value to indicate that array index is out of bounds.
|
6020
|
+
v = VM.ARRAY_INDEX;
|
6021
|
+
VM.out_of_bounds_array = ds.displayName;
|
6022
|
+
VM.out_of_bounds_msg = `Index ${index} not in array dataset ` +
|
6023
|
+
`${ds.displayName}, which has length ${obj.length}`;
|
6024
|
+
console.log(VM.out_of_bounds_msg);
|
6025
|
+
}
|
6000
6026
|
}
|
6001
6027
|
// Fall through: no change to `v` => dataset default value is pushed.
|
6002
6028
|
} else {
|