linny-r 1.6.1 → 1.6.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/package.json +1 -1
- package/server.js +27 -6
- package/static/index.html +18 -0
- package/static/linny-r.css +39 -2
- package/static/scripts/linny-r-ctrl.js +8 -1
- package/static/scripts/linny-r-gui-chart-manager.js +80 -16
- package/static/scripts/linny-r-gui-controller.js +3 -3
- package/static/scripts/linny-r-gui-equation-manager.js +19 -0
- package/static/scripts/linny-r-gui-experiment-manager.js +14 -3
- package/static/scripts/linny-r-gui-expression-editor.js +1 -1
- package/static/scripts/linny-r-gui-monitor.js +1 -0
- package/static/scripts/linny-r-gui-paper.js +25 -23
- package/static/scripts/linny-r-model.js +134 -82
- package/static/scripts/linny-r-utils.js +15 -1
- package/static/scripts/linny-r-vm.js +248 -58
@@ -394,11 +394,12 @@ class Expression {
|
|
394
394
|
vmi[0](this, vmi[1]);
|
395
395
|
this.program_counter++;
|
396
396
|
}
|
397
|
-
// Stack should now have length 1.
|
397
|
+
// Stack should now have length 1. If not, report error unless the
|
398
|
+
// length is due to some other error.
|
398
399
|
if(this.stack.length > 1) {
|
399
|
-
v[t] = VM.OVERFLOW;
|
400
|
+
if(v[t] > VM.ERROR) v[t] = VM.OVERFLOW;
|
400
401
|
} else if(this.stack.length < 1) {
|
401
|
-
v[t] = VM.UNDERFLOW;
|
402
|
+
if(v[t] > VM.ERROR) v[t] = VM.UNDERFLOW;
|
402
403
|
} else {
|
403
404
|
v[t] = this.stack.pop();
|
404
405
|
}
|
@@ -1394,9 +1395,13 @@ class ExpressionParser {
|
|
1394
1395
|
'-- number is:', this.context_number,
|
1395
1396
|
'\nTRACE: Expression:', obj.expression.text);
|
1396
1397
|
// Use the context number as "selector" parameter of the VMI.
|
1397
|
-
|
1398
|
-
|
1399
|
-
|
1398
|
+
const arg0 = (by_reference ?
|
1399
|
+
// If equation is "by reference", use VMI_push_entity
|
1400
|
+
// while passing the context number as extra parameter.
|
1401
|
+
{r: obj.dataset, a: obj.selector, cn: this.context_number} :
|
1402
|
+
// Otherwise, use VMI_push_dataset_modifier.
|
1403
|
+
{d: obj.dataset, s: this.context_number, x: obj.expression});
|
1404
|
+
return [arg0, anchor1, offset1, anchor2, offset2];
|
1400
1405
|
}
|
1401
1406
|
}
|
1402
1407
|
}
|
@@ -2452,7 +2457,7 @@ class VirtualMachine {
|
|
2452
2457
|
if(n <= this.BAD_REF) return [true, '#REF?'];
|
2453
2458
|
if(n <= this.ARRAY_INDEX) return [true, '#INDEX!'];
|
2454
2459
|
if(n <= this.BAD_CALC) return [true, '#VALUE!'];
|
2455
|
-
if(n <= this.DIV_ZERO) return [true, '#
|
2460
|
+
if(n <= this.DIV_ZERO) return [true, '#DIV/0!'];
|
2456
2461
|
if(n <= this.CYCLIC) return [true, '#CYCLE!'];
|
2457
2462
|
// Any other number less than or equal to 10^30 is considered as
|
2458
2463
|
// minus infinity.
|
@@ -2472,7 +2477,7 @@ class VirtualMachine {
|
|
2472
2477
|
// Return number `n` formatted so as to show 2-3 significant digits
|
2473
2478
|
// NOTE: as `n` should be a number, a warning sign will typically
|
2474
2479
|
// indicate a bug in the software.
|
2475
|
-
if(n === undefined) return '\u26A0'; // Warning sign
|
2480
|
+
if(n === undefined || isNaN(n)) return '\u26A0'; // Warning sign
|
2476
2481
|
const sv = this.specialValue(n);
|
2477
2482
|
// If `n` has a special value, return its representation.
|
2478
2483
|
if(sv[0]) return sv[1];
|
@@ -2968,10 +2973,10 @@ class VirtualMachine {
|
|
2968
2973
|
if(vcnt == 0) return '(no variables)';
|
2969
2974
|
let l = '';
|
2970
2975
|
for(let i = 0; i < vcnt; i++) {
|
2971
|
-
const
|
2972
|
-
|
2973
|
-
|
2974
|
-
|
2976
|
+
const
|
2977
|
+
obj = this.variables[i][1],
|
2978
|
+
v = 'X' + (i+1).toString().padStart(z, '0') + ' ' + obj.displayName,
|
2979
|
+
p = (obj instanceof Process && obj.pace > 1 ? ' 1/' + obj.pace : '');
|
2975
2980
|
l += v + ' [' + this.variables[i][0] + p + ']\n';
|
2976
2981
|
}
|
2977
2982
|
if(this.chunk_variables.length > 0) {
|
@@ -4767,7 +4772,7 @@ class VirtualMachine {
|
|
4767
4772
|
// is calibrated for 1000 VMI instructions.
|
4768
4773
|
this.tsl = Math.ceil(CONFIGURATION.progress_needle_interval *
|
4769
4774
|
1000 / this.code.length);
|
4770
|
-
if(
|
4775
|
+
if(abl > this.tsl * 5) {
|
4771
4776
|
UI.setMessage('Constructing the Simplex tableau');
|
4772
4777
|
UI.setProgressNeedle(0);
|
4773
4778
|
this.show_progress = true;
|
@@ -5545,7 +5550,7 @@ Solver status = ${json.status}`);
|
|
5545
5550
|
this.round_times.length = 0;
|
5546
5551
|
this.solver_times[bnr - 1] = time;
|
5547
5552
|
const ssecs = this.round_secs.reduce((a, b) => a + b, 0);
|
5548
|
-
this.solver_secs[bnr - 1] = (ssecs ? VM.sig4Dig(ssecs) : '');
|
5553
|
+
this.solver_secs[bnr - 1] = (ssecs ? VM.sig4Dig(ssecs) : '0');
|
5549
5554
|
this.round_secs.length = 0;
|
5550
5555
|
MONITOR.addProgressBlock(bnr, issue, time);
|
5551
5556
|
}
|
@@ -6265,13 +6270,19 @@ function VMI_push_wildcard_entity(x, args) {
|
|
6265
6270
|
} else {
|
6266
6271
|
// Select the first entity in `ee` that matches the wildcard vector
|
6267
6272
|
// index of the expression `x` being executed.
|
6273
|
+
if(x.wildcard_vector_index === false && x.isWildcardExpression &&
|
6274
|
+
MODEL.running_experiment) {
|
6275
|
+
// If no wildcard vector index, try to infer it.
|
6276
|
+
x.wildcard_vector_index = matchingNumberInList(
|
6277
|
+
MODEL.running_experiment.activeCombination, x.attribute);
|
6278
|
+
}
|
6268
6279
|
nn = nn.replace('#', x.wildcard_vector_index);
|
6269
6280
|
for(let i = 0; !obj && i < el.length; i++) {
|
6270
6281
|
if(el[i].name === nn) obj = el[i];
|
6271
6282
|
}
|
6272
6283
|
// If no match, then this indicates a bad reference.
|
6273
6284
|
if(!obj) {
|
6274
|
-
console.log(`ERROR: no match for "${nn}" in eligible entity list`, el);
|
6285
|
+
console.log(`ERROR: no match for "${nn}" in eligible entity list`, el, x);
|
6275
6286
|
x.push(VM.BAD_REF);
|
6276
6287
|
return;
|
6277
6288
|
}
|
@@ -6281,7 +6292,8 @@ function VMI_push_wildcard_entity(x, args) {
|
|
6281
6292
|
// called with the appropriate parameters.
|
6282
6293
|
const attr = args[0].a || obj.defaultAttribute;
|
6283
6294
|
if(args[0].br) {
|
6284
|
-
VMI_push_entity(x, {r: obj, a: attr}
|
6295
|
+
VMI_push_entity(x, [{r: obj, a: attr},
|
6296
|
+
args[1], args[2], args[3], args[4]]);
|
6285
6297
|
return;
|
6286
6298
|
}
|
6287
6299
|
// Otherwise, if the entity is a dataset modifier, this must be an
|
@@ -6824,7 +6836,7 @@ function VMI_mul(x) {
|
|
6824
6836
|
|
6825
6837
|
function VMI_div(x) {
|
6826
6838
|
// Pops the top number on the stack and divides the new top number
|
6827
|
-
// by it. In case of division by zero, the top is replaced by #
|
6839
|
+
// by it. In case of division by zero, the top is replaced by #DIV/0!
|
6828
6840
|
const d = x.pop();
|
6829
6841
|
if(d !== false) {
|
6830
6842
|
if(DEBUGGING) console.log('DIV (' + d.join(', ') + ')');
|
@@ -6838,7 +6850,7 @@ function VMI_div(x) {
|
|
6838
6850
|
|
6839
6851
|
function VMI_mod(x) {
|
6840
6852
|
// Pops the top number on the stack, divides the new top number by it
|
6841
|
-
// (if non-zero, or it pushes error code #
|
6853
|
+
// (if non-zero, or it pushes error code #DIV/0!), takes the fraction
|
6842
6854
|
// part, and multiplies this with the divider; in other words, it
|
6843
6855
|
// performs a "floating point MOD operation"
|
6844
6856
|
const d = x.pop();
|
@@ -7159,39 +7171,41 @@ function VMI_jump(x, index) {
|
|
7159
7171
|
}
|
7160
7172
|
|
7161
7173
|
function VMI_jump_if_false(x, index) {
|
7162
|
-
//
|
7163
|
-
// VM.UNDEFINED)
|
7164
|
-
// as the counter is ALWAYS increased by 1 after calling a VMI function
|
7174
|
+
// Test the top number A on the stack, and if A is FALSE (zero or
|
7175
|
+
// VM.UNDEFINED) set the program counter of the VM to `index` minus 1,
|
7176
|
+
// as the counter is ALWAYS increased by 1 after calling a VMI function.
|
7165
7177
|
const r = x.top(true);
|
7166
7178
|
if(DEBUGGING) console.log(`JUMP-IF-FALSE (${r}, ${index})`);
|
7167
7179
|
if(r === 0 || r === VM.UNDEFINED || r === false) {
|
7168
7180
|
// Only jump on FALSE, leaving the stack "as is", so that in case
|
7169
|
-
// of no THEN the expression result equals the IF condition value
|
7181
|
+
// of no THEN, the expression result equals the IF condition value.
|
7170
7182
|
// NOTE: Also do this on a stack error (r === false)
|
7171
7183
|
x.program_counter = index - 1;
|
7172
7184
|
} else {
|
7173
|
-
// Remove the value from the stack
|
7185
|
+
// Remove the value from the stack.
|
7174
7186
|
x.stack.pop();
|
7175
7187
|
}
|
7176
7188
|
}
|
7177
7189
|
|
7178
7190
|
function VMI_pop_false(x) {
|
7179
|
-
//
|
7180
|
-
// VM.UNDEFINED (but this is not checked)
|
7191
|
+
// Remove the top value from the stack, which should be 0 or
|
7192
|
+
// VM.UNDEFINED (but this is not checked).
|
7181
7193
|
const r = x.stack.pop();
|
7182
7194
|
if(DEBUGGING) console.log(`POP-FALSE (${r})`);
|
7183
7195
|
}
|
7184
7196
|
|
7185
7197
|
function VMI_if_then(x) {
|
7186
7198
|
// NO operation -- as of version 1.0.14, this function only serves as
|
7187
|
-
// operator symbol
|
7188
|
-
|
7199
|
+
// placeholder in operator symbol arrays. The parser should no longer
|
7200
|
+
// code this, so its execution would indicate an error.
|
7201
|
+
console.log('WARNING: IF-THEN instruction is obsolete', x);
|
7189
7202
|
}
|
7190
7203
|
|
7191
7204
|
function VMI_if_else(x) {
|
7192
7205
|
// NO operation -- as of version 1.0.14, this function only serves as
|
7193
|
-
// operator symbol
|
7194
|
-
|
7206
|
+
// placeholder in operator symbol arrays. The parser should no longer
|
7207
|
+
// code this, so its execution would indicate an error.
|
7208
|
+
console.log('WARNING: IF-ELSE instruction is obsolete', x);
|
7195
7209
|
}
|
7196
7210
|
|
7197
7211
|
//
|
@@ -7424,7 +7438,7 @@ function VMI_set_bounds(args) {
|
|
7424
7438
|
}
|
7425
7439
|
}
|
7426
7440
|
|
7427
|
-
function VMI_clear_coefficients(
|
7441
|
+
function VMI_clear_coefficients() {
|
7428
7442
|
if(DEBUGGING) console.log('clear_coefficients');
|
7429
7443
|
VM.coefficients = {};
|
7430
7444
|
VM.cash_in_coefficients = {};
|
@@ -7555,7 +7569,7 @@ function VMI_add_var_to_weighted_sum_coefficients(args) {
|
|
7555
7569
|
VM.sig4Dig(w) + ' * ' + v.variableName + ' (t = ' + t + ')');
|
7556
7570
|
}
|
7557
7571
|
for(let i = 0; i <= d; i++) {
|
7558
|
-
|
7572
|
+
let r = v.result(t);
|
7559
7573
|
if(args.length > 3) r /= (d + 1);
|
7560
7574
|
if(k <= 0) {
|
7561
7575
|
// See NOTE in VMI_add_const_to_coefficient instruction
|
@@ -7779,7 +7793,7 @@ function VMI_add_throughput_to_coefficient(args) {
|
|
7779
7793
|
}
|
7780
7794
|
}
|
7781
7795
|
|
7782
|
-
function VMI_set_objective(
|
7796
|
+
function VMI_set_objective() {
|
7783
7797
|
// Copies the coefficients to the vector for the objective function
|
7784
7798
|
if(DEBUGGING) console.log('set_objective');
|
7785
7799
|
for(let i in VM.coefficients) if(Number(i)) {
|
@@ -7843,7 +7857,7 @@ function VMI_set_add_constraints_flag(args) {
|
|
7843
7857
|
(VM.add_constraints_flag ? 'TRUE' : 'FALSE') + ')');
|
7844
7858
|
}
|
7845
7859
|
|
7846
|
-
function VMI_toggle_add_constraints_flag(
|
7860
|
+
function VMI_toggle_add_constraints_flag() {
|
7847
7861
|
// Toggles the VM's "add constraints" flag
|
7848
7862
|
VM.add_constraints_flag = !VM.add_constraints_flag;
|
7849
7863
|
if(DEBUGGING) console.log('toggle_add_constraints_flag (now ' +
|
@@ -7956,7 +7970,7 @@ function VMI_add_bound_line_constraint(args) {
|
|
7956
7970
|
VM.coefficients[w[i]] = 1;
|
7957
7971
|
}
|
7958
7972
|
VM.rhs = 1;
|
7959
|
-
VMI_add_constraint(VM.EQ)
|
7973
|
+
VMI_add_constraint(VM.EQ);
|
7960
7974
|
// Add constraint (2):
|
7961
7975
|
VMI_clear_coefficients();
|
7962
7976
|
VM.coefficients[VM.offset + vix] = 1;
|
@@ -7964,7 +7978,7 @@ function VMI_add_bound_line_constraint(args) {
|
|
7964
7978
|
VM.coefficients[w[i]] = -x[i];
|
7965
7979
|
}
|
7966
7980
|
// No need to set RHS as it is already reset to 0
|
7967
|
-
VMI_add_constraint(VM.EQ)
|
7981
|
+
VMI_add_constraint(VM.EQ);
|
7968
7982
|
// Add constraint (3):
|
7969
7983
|
VMI_clear_coefficients();
|
7970
7984
|
VM.coefficients[VM.offset + viy] = 1;
|
@@ -8105,23 +8119,24 @@ const
|
|
8105
8119
|
// *** API section for custom operators ***
|
8106
8120
|
//
|
8107
8121
|
|
8108
|
-
// Custom operators are typically used to implement computations on model
|
8109
|
-
// that cannot be coded (efficiently) using standard expressions.
|
8110
|
-
// The first custom operator in this section demonstrates by example how
|
8111
|
-
// operators can be added.
|
8122
|
+
// Custom operators are typically used to implement computations on model
|
8123
|
+
// results that cannot be coded (efficiently) using standard expressions.
|
8124
|
+
// The first custom operator in this section demonstrates by example how
|
8125
|
+
// custom operators can be added.
|
8112
8126
|
|
8113
8127
|
// Custom operators should preferably have a short alphanumeric string as
|
8114
|
-
// their identifying symbol. Custom operators are monadic and reducing,
|
8115
|
-
// they must have a grouping as operand. The number of required
|
8116
|
-
// be checked at run time by the VM instruction for this
|
8128
|
+
// their identifying symbol. Custom operators are monadic and reducing,
|
8129
|
+
// i.e., they must have a grouping as operand. The number of required
|
8130
|
+
// arguments must be checked at run time by the VM instruction for this
|
8131
|
+
// operator.
|
8117
8132
|
|
8118
8133
|
// Each custom operator must have its own Virtual Machine instruction
|
8119
8134
|
|
8120
8135
|
function VMI_profitable_units(x) {
|
8121
|
-
//
|
8122
|
-
// number of profitable units having a standard capacity (number),
|
8123
|
-
// level (vector) of the process that represents multiple such
|
8124
|
-
// marginal cost (constant) and the market price (vector)
|
8136
|
+
// Replace the argument list that should be at the top of the stack by
|
8137
|
+
// the number of profitable units having a standard capacity (number),
|
8138
|
+
// given the level (vector) of the process that represents multiple such
|
8139
|
+
// units, the marginal cost (constant) and the market price (vector).
|
8125
8140
|
const d = x.top();
|
8126
8141
|
// Check whether the top stack element is a grouping of the correct size
|
8127
8142
|
// that contains arguments of the correct type
|
@@ -8134,7 +8149,7 @@ function VMI_profitable_units(x) {
|
|
8134
8149
|
d[3].entity.attributeExpression(d[3].attribute)) &&
|
8135
8150
|
(d.length === 4 || (typeof d[4] === 'number' &&
|
8136
8151
|
(d.length === 5 || typeof d[5] === 'number')))) {
|
8137
|
-
// Valid parameters => get the data required for computation
|
8152
|
+
// Valid parameters => get the data required for computation.
|
8138
8153
|
const
|
8139
8154
|
mup = d[0].entity, // the multi-unit process
|
8140
8155
|
ub = mup.upper_bound.result(0), // NOTE: UB is assumed to be static
|
@@ -8145,7 +8160,7 @@ function VMI_profitable_units(x) {
|
|
8145
8160
|
pt = (d.length > 4 ? d[4] : 0), // the profit threshold (0 by default)
|
8146
8161
|
// the time horizon (by default the length of the simulation period)
|
8147
8162
|
nt = (d.length > 5 ? d[5] : MODEL.end_period - MODEL.start_period + 1);
|
8148
|
-
// Handle exceptional values of `uc` and `mc
|
8163
|
+
// Handle exceptional values of `uc` and `mc`.
|
8149
8164
|
if(uc <= VM.BEYOND_MINUS_INFINITY || mc <= VM.BEYOND_MINUS_INFINITY) {
|
8150
8165
|
x.retop(Math.min(uc, mc));
|
8151
8166
|
return;
|
@@ -8155,16 +8170,16 @@ function VMI_profitable_units(x) {
|
|
8155
8170
|
return;
|
8156
8171
|
}
|
8157
8172
|
|
8158
|
-
// NOTE: NPU is not time-dependent => result is stored in cache
|
8159
|
-
// As expressions may contain several NPU operators, create a unique
|
8160
|
-
// based on its parameters
|
8173
|
+
// NOTE: NPU is not time-dependent => result is stored in cache.
|
8174
|
+
// As expressions may contain several NPU operators, create a unique
|
8175
|
+
// key based on its parameters.
|
8161
8176
|
const cache_key = ['npu', mup.code, ub, uc, mc, mpe.code, mpa, pt].join('_');
|
8162
8177
|
if(x.cache[cache_key]) {
|
8163
8178
|
x.retop(x.cache[cache_key]);
|
8164
8179
|
return;
|
8165
8180
|
}
|
8166
8181
|
|
8167
|
-
// mp can be a single value, a vector, or an expression
|
8182
|
+
// `mp` can be a single value, a vector, or an expression.
|
8168
8183
|
let mp = mpe.attributeValue(mpa);
|
8169
8184
|
if(mp === null) {
|
8170
8185
|
mp = mpe.attributeExpression(mpa);
|
@@ -8179,8 +8194,8 @@ function VMI_profitable_units(x) {
|
|
8179
8194
|
nu = Math.ceil(ub / uc), // Number of units
|
8180
8195
|
r = [];
|
8181
8196
|
if(mp && mp instanceof Expression) {
|
8182
|
-
// NOTE:
|
8183
|
-
mp.compute();
|
8197
|
+
// NOTE: An expression may not have been (fully) computed yet.
|
8198
|
+
mp.compute(0);
|
8184
8199
|
if(mp.isStatic) {
|
8185
8200
|
mp = mp.result(0);
|
8186
8201
|
} else {
|
@@ -8276,11 +8291,11 @@ function VMI_highest_cumulative_consecutive_deviation(x) {
|
|
8276
8291
|
e = d[0].entity,
|
8277
8292
|
a = d[0].attribute;
|
8278
8293
|
let vector = e.attributeValue(a);
|
8279
|
-
// NOTE:
|
8294
|
+
// NOTE: Equations can also be passed by reference.
|
8280
8295
|
if(e === MODEL.equations_dataset) {
|
8281
8296
|
const x = e.modifiers[a].expression;
|
8282
|
-
// NOTE: an expression may not have been (fully) computed yet
|
8283
|
-
x.compute();
|
8297
|
+
// NOTE: an expression may not have been (fully) computed yet.
|
8298
|
+
x.compute(0);
|
8284
8299
|
if(!x.isStatic) {
|
8285
8300
|
const nt = MODEL.end_period - MODEL.start_period + 1;
|
8286
8301
|
for(let t = 1; t <= nt; t++) x.result(t);
|
@@ -8390,6 +8405,181 @@ DYNAMIC_SYMBOLS.push('hccd');
|
|
8390
8405
|
// Add to this list only if operation makes an expression level-based
|
8391
8406
|
// LEVEL_BASED_CODES.push(VMI_...);
|
8392
8407
|
|
8408
|
+
function correlation_or_slope(x, c_or_s) {
|
8409
|
+
// Replaces the argument list that should be at the top of the stack by
|
8410
|
+
// either Spearman's correlation (r) or the slope (b) of the regression
|
8411
|
+
// line y = a + bx for the two vectors X and Y that are passed as the
|
8412
|
+
// two arguments of this function. Reason to combine these two statistics
|
8413
|
+
// in one function is because the required operations are very similar.
|
8414
|
+
// NOTES:
|
8415
|
+
// (1) This function codes for two different operators and therefore
|
8416
|
+
// is a helper function. The two operators must each have their
|
8417
|
+
// own VM instruction -- see immediately after this function.
|
8418
|
+
// (2) String `c_or_s` must be either 'correl' or 'slope'.
|
8419
|
+
// (3) The operands for this function must be vectors, not numbers,
|
8420
|
+
// so in the Linny-R expression they must be passed "by reference".
|
8421
|
+
const
|
8422
|
+
d = x.top(),
|
8423
|
+
vmi = c_or_s;
|
8424
|
+
// Check whether the top stack element is a grouping of two variables.
|
8425
|
+
if(d instanceof Array && d.length === 2 &&
|
8426
|
+
typeof d[0] === 'object' && d[0].hasOwnProperty('entity') &&
|
8427
|
+
typeof d[1] === 'object' && d[1].hasOwnProperty('entity')) {
|
8428
|
+
// Convert the two variables to vectors.
|
8429
|
+
const vector = {x: {}, y: {}};
|
8430
|
+
for(let k in vector) if(vector.hasOwnProperty(k)) {
|
8431
|
+
const
|
8432
|
+
i = ['x', 'y'].indexOf(k),
|
8433
|
+
e = d[i].entity,
|
8434
|
+
a = d[i].attribute;
|
8435
|
+
vector[k].e = e;
|
8436
|
+
vector[k].a = a;
|
8437
|
+
vector[k].v = e.attributeValue(a);
|
8438
|
+
vector[k].name = e.displayName + (a ? '|' + a : '');
|
8439
|
+
vector[k].id = e.identifier;
|
8440
|
+
// NOTE: Equations can also be passed by reference.
|
8441
|
+
if(e === MODEL.equations_dataset) {
|
8442
|
+
const eq = e.modifiers[UI.nameToID(a)].expression;
|
8443
|
+
// Level-based equations require that the model has run.
|
8444
|
+
if(eq.is_level_based && !MODEL.solved) {
|
8445
|
+
x.retop(VM.NOT_COMPUTED);
|
8446
|
+
return;
|
8447
|
+
}
|
8448
|
+
// NOTE: An equation may not have been (fully) computed yet.
|
8449
|
+
eq.compute(0, x.wildcard_vector_index);
|
8450
|
+
if(!eq.isStatic) {
|
8451
|
+
const nt = MODEL.end_period - MODEL.start_period + 1;
|
8452
|
+
for(let t = 1; t <= nt; t++) eq.result(t, x.wildcard_vector_index);
|
8453
|
+
}
|
8454
|
+
vector[k].v = eq.vector;
|
8455
|
+
}
|
8456
|
+
}
|
8457
|
+
// If either operand is level-based, return "not computed" if the
|
8458
|
+
// model has not been run yet.
|
8459
|
+
if((VM.level_based_attr.indexOf(vector.x.a) >= 0 ||
|
8460
|
+
VM.level_based_attr.indexOf(vector.y.a) >= 0) &&
|
8461
|
+
!MODEL.solved) {
|
8462
|
+
x.retop(VM.NOT_COMPUTED);
|
8463
|
+
return;
|
8464
|
+
}
|
8465
|
+
if(Array.isArray(vector.x.v) && Array.isArray(vector.y.v)) {
|
8466
|
+
// Valid parameters => compute the terms used in the formulas
|
8467
|
+
// for correlation (r) and regression (slope and intercept)
|
8468
|
+
// NOTE: Statistics are not time-dependent, so the result is stored
|
8469
|
+
// in the expression's cache. As expressions may contain several
|
8470
|
+
// correl and slope operators, create a unique key based on the
|
8471
|
+
// operator name and its two operands.
|
8472
|
+
const cache_key = [vmi, vector.x.id, vector.x.a,
|
8473
|
+
vector.y.id, vector.y.a].join('_');
|
8474
|
+
if(x.cache[cache_key]) {
|
8475
|
+
x.retop(x.cache[cache_key]);
|
8476
|
+
return;
|
8477
|
+
}
|
8478
|
+
if(true||DEBUGGING) {
|
8479
|
+
console.log(`-- ${vmi}(${vector.x.name}, ${vector.y.name})`);
|
8480
|
+
}
|
8481
|
+
// NOTE: Vectors should have equal length.
|
8482
|
+
const N = Math.min(vector.x.v.length, vector.y.v.length);
|
8483
|
+
if(!N) {
|
8484
|
+
// No data => result should be "division by zero"
|
8485
|
+
x.retop(VM.DIV_ZERO);
|
8486
|
+
return;
|
8487
|
+
}
|
8488
|
+
// Calculate dsq = N*variance for X and Y.
|
8489
|
+
for(let k in vector) if(vector.hasOwnProperty(k)) {
|
8490
|
+
let sum = 0;
|
8491
|
+
// NOTE: Ignore first element of vector (t=0).
|
8492
|
+
for(let i = 1; i < N; i++) {
|
8493
|
+
const v = vector[k].v[i];
|
8494
|
+
// Handle exceptional values in vector.
|
8495
|
+
if(v <= VM.BEYOND_MINUS_INFINITY || v >= VM.BEYOND_PLUS_INFINITY) {
|
8496
|
+
x.retop(v);
|
8497
|
+
return;
|
8498
|
+
}
|
8499
|
+
sum += v;
|
8500
|
+
}
|
8501
|
+
vector[k].sum = sum;
|
8502
|
+
const mean = sum / N;
|
8503
|
+
vector[k].mean = mean;
|
8504
|
+
let dsq = 0;
|
8505
|
+
// NOTE: Ignore first element of vector (t=0).
|
8506
|
+
for(let i = 1; i < N; i++) {
|
8507
|
+
const d = vector[k].v[i] - mean;
|
8508
|
+
dsq += d * d;
|
8509
|
+
}
|
8510
|
+
vector[k].dsq = dsq;
|
8511
|
+
}
|
8512
|
+
// Divisor is sqrt(dsqX * dsqY). If zero, return #DIV/0
|
8513
|
+
const divisor = Math.sqrt(vector.x.dsq * vector.y.dsq);
|
8514
|
+
if(divisor < VM.NEAR_ZERO) {
|
8515
|
+
x.retop(VM.DIV_ZERO);
|
8516
|
+
return;
|
8517
|
+
}
|
8518
|
+
// Calculate N*covariance of X and Y.
|
8519
|
+
let covar = 0;
|
8520
|
+
// NOTE: Ignore first element of vector (t=0).
|
8521
|
+
for(let i = 1; i < N; i++) {
|
8522
|
+
covar += (vector.x.v[i] - vector.x.mean) * (vector.y.v[i] - vector.y.mean);
|
8523
|
+
}
|
8524
|
+
// Correlation = covarXY / sqrt(dsqX * dsqY), slope = covarXY / dsqX.
|
8525
|
+
// NOTE: dsqX will be non-zero (or divisor would have been zero).
|
8526
|
+
const result = covar / (vmi === 'correl' ? divisor : vector.x.dsq);
|
8527
|
+
// Store the result in the expression's cache.
|
8528
|
+
x.cache[cache_key] = result;
|
8529
|
+
// Push the result onto the stack.
|
8530
|
+
x.retop(result);
|
8531
|
+
return;
|
8532
|
+
}
|
8533
|
+
}
|
8534
|
+
// Fall-trough indicates error
|
8535
|
+
if(DEBUGGING) console.log(vmi + ': invalid parameter(s)\n', d);
|
8536
|
+
x.retop(VM.PARAMS);
|
8537
|
+
}
|
8538
|
+
|
8539
|
+
// NOTE: Separate function for each operator: VMI_correl and VMI_slope.
|
8540
|
+
|
8541
|
+
function VMI_correlation(x) {
|
8542
|
+
correlation_or_slope(x, 'correl');
|
8543
|
+
}
|
8544
|
+
|
8545
|
+
// Add the custom operator instruction to the global lists
|
8546
|
+
// NOTE: All custom operators are monadic (priority 9) and reducing
|
8547
|
+
OPERATORS.push('correl');
|
8548
|
+
MONADIC_OPERATORS.push('correl');
|
8549
|
+
ACTUAL_SYMBOLS.push('correl');
|
8550
|
+
OPERATOR_CODES.push(VMI_correlation);
|
8551
|
+
MONADIC_CODES.push(VMI_correlation);
|
8552
|
+
REDUCING_CODES.push(VMI_correlation);
|
8553
|
+
SYMBOL_CODES.push(VMI_correlation);
|
8554
|
+
PRIORITIES.push(9);
|
8555
|
+
// Add to this list only if operation makes an expression dynamic
|
8556
|
+
// DYNAMIC_SYMBOLS.push('...');
|
8557
|
+
// Add to this list only if operation makes an expression random
|
8558
|
+
// RANDOM_CODES.push(VMI_...);
|
8559
|
+
// Add to this list only if operation makes an expression level-based
|
8560
|
+
// LEVEL_BASED_CODES.push(VMI_...);
|
8561
|
+
|
8562
|
+
function VMI_slope(x) {
|
8563
|
+
correlation_or_slope(x, 'slope');
|
8564
|
+
}
|
8565
|
+
|
8566
|
+
// Add the custom operator instruction to the global lists
|
8567
|
+
// NOTE: All custom operators are monadic (priority 9) and reducing
|
8568
|
+
OPERATORS.push('slope');
|
8569
|
+
MONADIC_OPERATORS.push('slope');
|
8570
|
+
ACTUAL_SYMBOLS.push('slope');
|
8571
|
+
OPERATOR_CODES.push(VMI_slope);
|
8572
|
+
MONADIC_CODES.push(VMI_slope);
|
8573
|
+
REDUCING_CODES.push(VMI_slope);
|
8574
|
+
SYMBOL_CODES.push(VMI_slope);
|
8575
|
+
PRIORITIES.push(9);
|
8576
|
+
// Add to this list only if operation makes an expression dynamic
|
8577
|
+
// DYNAMIC_SYMBOLS.push('...');
|
8578
|
+
// Add to this list only if operation makes an expression random
|
8579
|
+
// RANDOM_CODES.push(VMI_...);
|
8580
|
+
// Add to this list only if operation makes an expression level-based
|
8581
|
+
// LEVEL_BASED_CODES.push(VMI_...);
|
8582
|
+
|
8393
8583
|
/*** END of custom operator API section ***/
|
8394
8584
|
|
8395
8585
|
///////////////////////////////////////////////////////////////////////
|
@@ -8399,4 +8589,4 @@ if(NODE) module.exports = {
|
|
8399
8589
|
Expression: Expression,
|
8400
8590
|
ExpressionParser: ExpressionParser,
|
8401
8591
|
VirtualMachine: VirtualMachine
|
8402
|
-
}
|
8592
|
+
};
|