linny-r 1.6.0 → 1.6.2
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/scripts/linny-r-gui-controller.js +47 -35
- package/static/scripts/linny-r-gui-documentation-manager.js +36 -32
- package/static/scripts/linny-r-gui-equation-manager.js +10 -7
- package/static/scripts/linny-r-gui-experiment-manager.js +14 -3
- package/static/scripts/linny-r-gui-finder.js +1 -0
- package/static/scripts/linny-r-gui-paper.js +25 -23
- package/static/scripts/linny-r-milp.js +18 -15
- package/static/scripts/linny-r-model.js +143 -91
- package/static/scripts/linny-r-utils.js +33 -8
- package/static/scripts/linny-r-vm.js +250 -60
@@ -1394,9 +1394,13 @@ class ExpressionParser {
|
|
1394
1394
|
'-- number is:', this.context_number,
|
1395
1395
|
'\nTRACE: Expression:', obj.expression.text);
|
1396
1396
|
// Use the context number as "selector" parameter of the VMI.
|
1397
|
-
|
1398
|
-
|
1399
|
-
|
1397
|
+
const arg0 = (by_reference ?
|
1398
|
+
// If equation is "by reference", use VMI_push_entity
|
1399
|
+
// while passing the context number as extra parameter.
|
1400
|
+
{r: obj.dataset, a: obj.selector, cn: this.context_number} :
|
1401
|
+
// Otherwise, use VMI_push_dataset_modifier.
|
1402
|
+
{d: obj.dataset, s: this.context_number, x: obj.expression});
|
1403
|
+
return [arg0, anchor1, offset1, anchor2, offset2];
|
1400
1404
|
}
|
1401
1405
|
}
|
1402
1406
|
}
|
@@ -2452,7 +2456,7 @@ class VirtualMachine {
|
|
2452
2456
|
if(n <= this.BAD_REF) return [true, '#REF?'];
|
2453
2457
|
if(n <= this.ARRAY_INDEX) return [true, '#INDEX!'];
|
2454
2458
|
if(n <= this.BAD_CALC) return [true, '#VALUE!'];
|
2455
|
-
if(n <= this.DIV_ZERO) return [true, '#
|
2459
|
+
if(n <= this.DIV_ZERO) return [true, '#DIV/0!'];
|
2456
2460
|
if(n <= this.CYCLIC) return [true, '#CYCLE!'];
|
2457
2461
|
// Any other number less than or equal to 10^30 is considered as
|
2458
2462
|
// minus infinity.
|
@@ -4319,7 +4323,7 @@ class VirtualMachine {
|
|
4319
4323
|
// `cbl` is the cropped block length (applies only to last block).
|
4320
4324
|
let bb = (block - 1) * MODEL.block_length + 1,
|
4321
4325
|
abl = this.chunk_length,
|
4322
|
-
cbl = this.actualBlockLength;
|
4326
|
+
cbl = this.actualBlockLength(block);
|
4323
4327
|
// For the last block, crop the actual block length so it does not
|
4324
4328
|
// extend beyond the simulation period (these results should be ignored).
|
4325
4329
|
// If no results computed, preserve those already computed for the
|
@@ -4761,13 +4765,13 @@ class VirtualMachine {
|
|
4761
4765
|
|
4762
4766
|
setupBlock() {
|
4763
4767
|
if(DEBUGGING) this.logCode();
|
4764
|
-
const abl = this.actualBlockLength;
|
4768
|
+
const abl = this.actualBlockLength(this.block_count);
|
4765
4769
|
// NOTE: Tableau segment length is the number of time steps between
|
4766
4770
|
// updates of the progress needle. The default progress needle interval
|
4767
4771
|
// is calibrated for 1000 VMI instructions.
|
4768
4772
|
this.tsl = Math.ceil(CONFIGURATION.progress_needle_interval *
|
4769
4773
|
1000 / this.code.length);
|
4770
|
-
if(
|
4774
|
+
if(abl > this.tsl * 5) {
|
4771
4775
|
UI.setMessage('Constructing the Simplex tableau');
|
4772
4776
|
UI.setProgressNeedle(0);
|
4773
4777
|
this.show_progress = true;
|
@@ -4955,12 +4959,12 @@ class VirtualMachine {
|
|
4955
4959
|
setTimeout(() => VM.solveBlock(), 0);
|
4956
4960
|
}
|
4957
4961
|
|
4958
|
-
|
4962
|
+
actualBlockLength(block) {
|
4959
4963
|
// The actual block length is the number of time steps to be considered
|
4960
4964
|
// by the solver; the abl of the last block is likely to be shorter
|
4961
4965
|
// than the standard, as it should not go beyond the end time plus
|
4962
4966
|
// look-ahead.
|
4963
|
-
if(
|
4967
|
+
if(block < this.nr_of_blocks) return this.chunk_length;
|
4964
4968
|
// Last block length equals remainder of simulation period divided
|
4965
4969
|
// by block length.
|
4966
4970
|
let rem = (MODEL.runLength - MODEL.look_ahead) % MODEL.block_length;
|
@@ -5005,7 +5009,7 @@ class VirtualMachine {
|
|
5005
5009
|
// behavior can still be generated by limiting time series length to
|
5006
5010
|
// the simulation period.
|
5007
5011
|
const
|
5008
|
-
abl = this.actualBlockLength,
|
5012
|
+
abl = this.actualBlockLength(this.block_count),
|
5009
5013
|
// Get the number digits for variable names
|
5010
5014
|
z = this.columnsInBlock.toString().length,
|
5011
5015
|
// LP_solve uses semicolon as separator between equations
|
@@ -5240,7 +5244,7 @@ class VirtualMachine {
|
|
5240
5244
|
// instead of row-based, hence for each column a separate string list.
|
5241
5245
|
// NOTE: Columns are numbered from 1 to N, hence a dummy list for c=0.
|
5242
5246
|
const
|
5243
|
-
abl = this.actualBlockLength,
|
5247
|
+
abl = this.actualBlockLength(this.block_count),
|
5244
5248
|
cols = [[]],
|
5245
5249
|
rhs = [];
|
5246
5250
|
let nrow = this.matrix.length,
|
@@ -5248,7 +5252,7 @@ class VirtualMachine {
|
|
5248
5252
|
c,
|
5249
5253
|
p,
|
5250
5254
|
r;
|
5251
|
-
|
5255
|
+
this.numeric_issue = '';
|
5252
5256
|
this.lines = '';
|
5253
5257
|
for(c = 1; c <= ncol; c++) cols.push([]);
|
5254
5258
|
this.decimals = Math.max(nrow, ncol).toString().length;
|
@@ -5422,7 +5426,7 @@ class VirtualMachine {
|
|
5422
5426
|
// Add the SOS section.
|
5423
5427
|
if(this.sos_var_indices.length > 0) {
|
5424
5428
|
this.lines += 'SOS\n';
|
5425
|
-
const abl = this.actualBlockLength;
|
5429
|
+
const abl = this.actualBlockLength(this.block_count);
|
5426
5430
|
let sos = 1;
|
5427
5431
|
for(let j = 0; j < abl; j++) {
|
5428
5432
|
for(let i = 0; i < this.sos_var_indices.length; i++) {
|
@@ -5545,7 +5549,7 @@ Solver status = ${json.status}`);
|
|
5545
5549
|
this.round_times.length = 0;
|
5546
5550
|
this.solver_times[bnr - 1] = time;
|
5547
5551
|
const ssecs = this.round_secs.reduce((a, b) => a + b, 0);
|
5548
|
-
this.solver_secs[bnr - 1] = (ssecs ? VM.sig4Dig(ssecs) : '');
|
5552
|
+
this.solver_secs[bnr - 1] = (ssecs ? VM.sig4Dig(ssecs) : '0');
|
5549
5553
|
this.round_secs.length = 0;
|
5550
5554
|
MONITOR.addProgressBlock(bnr, issue, time);
|
5551
5555
|
}
|
@@ -5594,7 +5598,7 @@ Solver status = ${json.status}`);
|
|
5594
5598
|
const
|
5595
5599
|
bwr = this.blockWithRound,
|
5596
5600
|
fromt = (this.block_count - 1) * MODEL.block_length + 1,
|
5597
|
-
abl = this.actualBlockLength;
|
5601
|
+
abl = this.actualBlockLength(this.block_count);
|
5598
5602
|
MONITOR.updateBlockNumber(bwr);
|
5599
5603
|
// NOTE: Add blank line to message to visually separate rounds.
|
5600
5604
|
this.logMessage(this.block_count, ['\nSetting up block #', bwr,
|
@@ -5626,7 +5630,8 @@ Solver status = ${json.status}`);
|
|
5626
5630
|
}
|
5627
5631
|
// Generate lines of code in format that should be accepted by solver.
|
5628
5632
|
if(this.solver_name === 'gurobi') {
|
5629
|
-
this.writeMPSFormat();
|
5633
|
+
//this.writeMPSFormat();
|
5634
|
+
this.writeLpFormat(true);
|
5630
5635
|
} else if(this.solver_name === 'scip' || this.solver_name === 'cplex') {
|
5631
5636
|
// NOTE: The CPLEX LP format that is also used by SCIP differs from
|
5632
5637
|
// the LP_solve format that was used by the first versions of Linny-R.
|
@@ -6264,13 +6269,19 @@ function VMI_push_wildcard_entity(x, args) {
|
|
6264
6269
|
} else {
|
6265
6270
|
// Select the first entity in `ee` that matches the wildcard vector
|
6266
6271
|
// index of the expression `x` being executed.
|
6272
|
+
if(x.wildcard_vector_index === false && x.isWildcardExpression &&
|
6273
|
+
MODEL.running_experiment) {
|
6274
|
+
// If no wildcard vector index, try to infer it.
|
6275
|
+
x.wildcard_vector_index = matchingNumberInList(
|
6276
|
+
MODEL.running_experiment.activeCombination, x.attribute);
|
6277
|
+
}
|
6267
6278
|
nn = nn.replace('#', x.wildcard_vector_index);
|
6268
6279
|
for(let i = 0; !obj && i < el.length; i++) {
|
6269
6280
|
if(el[i].name === nn) obj = el[i];
|
6270
6281
|
}
|
6271
6282
|
// If no match, then this indicates a bad reference.
|
6272
6283
|
if(!obj) {
|
6273
|
-
console.log(`ERROR: no match for "${nn}" in eligible entity list`, el);
|
6284
|
+
console.log(`ERROR: no match for "${nn}" in eligible entity list`, el, x);
|
6274
6285
|
x.push(VM.BAD_REF);
|
6275
6286
|
return;
|
6276
6287
|
}
|
@@ -6280,7 +6291,8 @@ function VMI_push_wildcard_entity(x, args) {
|
|
6280
6291
|
// called with the appropriate parameters.
|
6281
6292
|
const attr = args[0].a || obj.defaultAttribute;
|
6282
6293
|
if(args[0].br) {
|
6283
|
-
VMI_push_entity(x, {r: obj, a: attr}
|
6294
|
+
VMI_push_entity(x, [{r: obj, a: attr},
|
6295
|
+
args[1], args[2], args[3], args[4]]);
|
6284
6296
|
return;
|
6285
6297
|
}
|
6286
6298
|
// Otherwise, if the entity is a dataset modifier, this must be an
|
@@ -6823,7 +6835,7 @@ function VMI_mul(x) {
|
|
6823
6835
|
|
6824
6836
|
function VMI_div(x) {
|
6825
6837
|
// Pops the top number on the stack and divides the new top number
|
6826
|
-
// by it. In case of division by zero, the top is replaced by #
|
6838
|
+
// by it. In case of division by zero, the top is replaced by #DIV/0!
|
6827
6839
|
const d = x.pop();
|
6828
6840
|
if(d !== false) {
|
6829
6841
|
if(DEBUGGING) console.log('DIV (' + d.join(', ') + ')');
|
@@ -6837,7 +6849,7 @@ function VMI_div(x) {
|
|
6837
6849
|
|
6838
6850
|
function VMI_mod(x) {
|
6839
6851
|
// Pops the top number on the stack, divides the new top number by it
|
6840
|
-
// (if non-zero, or it pushes error code #
|
6852
|
+
// (if non-zero, or it pushes error code #DIV/0!), takes the fraction
|
6841
6853
|
// part, and multiplies this with the divider; in other words, it
|
6842
6854
|
// performs a "floating point MOD operation"
|
6843
6855
|
const d = x.pop();
|
@@ -7158,39 +7170,41 @@ function VMI_jump(x, index) {
|
|
7158
7170
|
}
|
7159
7171
|
|
7160
7172
|
function VMI_jump_if_false(x, index) {
|
7161
|
-
//
|
7162
|
-
// VM.UNDEFINED)
|
7163
|
-
// as the counter is ALWAYS increased by 1 after calling a VMI function
|
7173
|
+
// Test the top number A on the stack, and if A is FALSE (zero or
|
7174
|
+
// VM.UNDEFINED) set the program counter of the VM to `index` minus 1,
|
7175
|
+
// as the counter is ALWAYS increased by 1 after calling a VMI function.
|
7164
7176
|
const r = x.top(true);
|
7165
7177
|
if(DEBUGGING) console.log(`JUMP-IF-FALSE (${r}, ${index})`);
|
7166
7178
|
if(r === 0 || r === VM.UNDEFINED || r === false) {
|
7167
7179
|
// Only jump on FALSE, leaving the stack "as is", so that in case
|
7168
|
-
// of no THEN the expression result equals the IF condition value
|
7180
|
+
// of no THEN, the expression result equals the IF condition value.
|
7169
7181
|
// NOTE: Also do this on a stack error (r === false)
|
7170
7182
|
x.program_counter = index - 1;
|
7171
7183
|
} else {
|
7172
|
-
// Remove the value from the stack
|
7184
|
+
// Remove the value from the stack.
|
7173
7185
|
x.stack.pop();
|
7174
7186
|
}
|
7175
7187
|
}
|
7176
7188
|
|
7177
7189
|
function VMI_pop_false(x) {
|
7178
|
-
//
|
7179
|
-
// VM.UNDEFINED (but this is not checked)
|
7190
|
+
// Remove the top value from the stack, which should be 0 or
|
7191
|
+
// VM.UNDEFINED (but this is not checked).
|
7180
7192
|
const r = x.stack.pop();
|
7181
7193
|
if(DEBUGGING) console.log(`POP-FALSE (${r})`);
|
7182
7194
|
}
|
7183
7195
|
|
7184
7196
|
function VMI_if_then(x) {
|
7185
7197
|
// NO operation -- as of version 1.0.14, this function only serves as
|
7186
|
-
// operator symbol
|
7187
|
-
|
7198
|
+
// placeholder in operator symbol arrays. The parser should no longer
|
7199
|
+
// code this, so its execution would indicate an error.
|
7200
|
+
console.log('WARNING: IF-THEN instruction is obsolete', x);
|
7188
7201
|
}
|
7189
7202
|
|
7190
7203
|
function VMI_if_else(x) {
|
7191
7204
|
// NO operation -- as of version 1.0.14, this function only serves as
|
7192
|
-
// operator symbol
|
7193
|
-
|
7205
|
+
// placeholder in operator symbol arrays. The parser should no longer
|
7206
|
+
// code this, so its execution would indicate an error.
|
7207
|
+
console.log('WARNING: IF-ELSE instruction is obsolete', x);
|
7194
7208
|
}
|
7195
7209
|
|
7196
7210
|
//
|
@@ -7423,7 +7437,7 @@ function VMI_set_bounds(args) {
|
|
7423
7437
|
}
|
7424
7438
|
}
|
7425
7439
|
|
7426
|
-
function VMI_clear_coefficients(
|
7440
|
+
function VMI_clear_coefficients() {
|
7427
7441
|
if(DEBUGGING) console.log('clear_coefficients');
|
7428
7442
|
VM.coefficients = {};
|
7429
7443
|
VM.cash_in_coefficients = {};
|
@@ -7554,7 +7568,7 @@ function VMI_add_var_to_weighted_sum_coefficients(args) {
|
|
7554
7568
|
VM.sig4Dig(w) + ' * ' + v.variableName + ' (t = ' + t + ')');
|
7555
7569
|
}
|
7556
7570
|
for(let i = 0; i <= d; i++) {
|
7557
|
-
|
7571
|
+
let r = v.result(t);
|
7558
7572
|
if(args.length > 3) r /= (d + 1);
|
7559
7573
|
if(k <= 0) {
|
7560
7574
|
// See NOTE in VMI_add_const_to_coefficient instruction
|
@@ -7778,7 +7792,7 @@ function VMI_add_throughput_to_coefficient(args) {
|
|
7778
7792
|
}
|
7779
7793
|
}
|
7780
7794
|
|
7781
|
-
function VMI_set_objective(
|
7795
|
+
function VMI_set_objective() {
|
7782
7796
|
// Copies the coefficients to the vector for the objective function
|
7783
7797
|
if(DEBUGGING) console.log('set_objective');
|
7784
7798
|
for(let i in VM.coefficients) if(Number(i)) {
|
@@ -7842,7 +7856,7 @@ function VMI_set_add_constraints_flag(args) {
|
|
7842
7856
|
(VM.add_constraints_flag ? 'TRUE' : 'FALSE') + ')');
|
7843
7857
|
}
|
7844
7858
|
|
7845
|
-
function VMI_toggle_add_constraints_flag(
|
7859
|
+
function VMI_toggle_add_constraints_flag() {
|
7846
7860
|
// Toggles the VM's "add constraints" flag
|
7847
7861
|
VM.add_constraints_flag = !VM.add_constraints_flag;
|
7848
7862
|
if(DEBUGGING) console.log('toggle_add_constraints_flag (now ' +
|
@@ -7955,7 +7969,7 @@ function VMI_add_bound_line_constraint(args) {
|
|
7955
7969
|
VM.coefficients[w[i]] = 1;
|
7956
7970
|
}
|
7957
7971
|
VM.rhs = 1;
|
7958
|
-
VMI_add_constraint(VM.EQ)
|
7972
|
+
VMI_add_constraint(VM.EQ);
|
7959
7973
|
// Add constraint (2):
|
7960
7974
|
VMI_clear_coefficients();
|
7961
7975
|
VM.coefficients[VM.offset + vix] = 1;
|
@@ -7963,7 +7977,7 @@ function VMI_add_bound_line_constraint(args) {
|
|
7963
7977
|
VM.coefficients[w[i]] = -x[i];
|
7964
7978
|
}
|
7965
7979
|
// No need to set RHS as it is already reset to 0
|
7966
|
-
VMI_add_constraint(VM.EQ)
|
7980
|
+
VMI_add_constraint(VM.EQ);
|
7967
7981
|
// Add constraint (3):
|
7968
7982
|
VMI_clear_coefficients();
|
7969
7983
|
VM.coefficients[VM.offset + viy] = 1;
|
@@ -8104,23 +8118,24 @@ const
|
|
8104
8118
|
// *** API section for custom operators ***
|
8105
8119
|
//
|
8106
8120
|
|
8107
|
-
// Custom operators are typically used to implement computations on model
|
8108
|
-
// that cannot be coded (efficiently) using standard expressions.
|
8109
|
-
// The first custom operator in this section demonstrates by example how
|
8110
|
-
// operators can be added.
|
8121
|
+
// Custom operators are typically used to implement computations on model
|
8122
|
+
// results that cannot be coded (efficiently) using standard expressions.
|
8123
|
+
// The first custom operator in this section demonstrates by example how
|
8124
|
+
// custom operators can be added.
|
8111
8125
|
|
8112
8126
|
// Custom operators should preferably have a short alphanumeric string as
|
8113
|
-
// their identifying symbol. Custom operators are monadic and reducing,
|
8114
|
-
// they must have a grouping as operand. The number of required
|
8115
|
-
// be checked at run time by the VM instruction for this
|
8127
|
+
// their identifying symbol. Custom operators are monadic and reducing,
|
8128
|
+
// i.e., they must have a grouping as operand. The number of required
|
8129
|
+
// arguments must be checked at run time by the VM instruction for this
|
8130
|
+
// operator.
|
8116
8131
|
|
8117
8132
|
// Each custom operator must have its own Virtual Machine instruction
|
8118
8133
|
|
8119
8134
|
function VMI_profitable_units(x) {
|
8120
|
-
//
|
8121
|
-
// number of profitable units having a standard capacity (number),
|
8122
|
-
// level (vector) of the process that represents multiple such
|
8123
|
-
// marginal cost (constant) and the market price (vector)
|
8135
|
+
// Replace the argument list that should be at the top of the stack by
|
8136
|
+
// the number of profitable units having a standard capacity (number),
|
8137
|
+
// given the level (vector) of the process that represents multiple such
|
8138
|
+
// units, the marginal cost (constant) and the market price (vector).
|
8124
8139
|
const d = x.top();
|
8125
8140
|
// Check whether the top stack element is a grouping of the correct size
|
8126
8141
|
// that contains arguments of the correct type
|
@@ -8133,7 +8148,7 @@ function VMI_profitable_units(x) {
|
|
8133
8148
|
d[3].entity.attributeExpression(d[3].attribute)) &&
|
8134
8149
|
(d.length === 4 || (typeof d[4] === 'number' &&
|
8135
8150
|
(d.length === 5 || typeof d[5] === 'number')))) {
|
8136
|
-
// Valid parameters => get the data required for computation
|
8151
|
+
// Valid parameters => get the data required for computation.
|
8137
8152
|
const
|
8138
8153
|
mup = d[0].entity, // the multi-unit process
|
8139
8154
|
ub = mup.upper_bound.result(0), // NOTE: UB is assumed to be static
|
@@ -8144,7 +8159,7 @@ function VMI_profitable_units(x) {
|
|
8144
8159
|
pt = (d.length > 4 ? d[4] : 0), // the profit threshold (0 by default)
|
8145
8160
|
// the time horizon (by default the length of the simulation period)
|
8146
8161
|
nt = (d.length > 5 ? d[5] : MODEL.end_period - MODEL.start_period + 1);
|
8147
|
-
// Handle exceptional values of `uc` and `mc
|
8162
|
+
// Handle exceptional values of `uc` and `mc`.
|
8148
8163
|
if(uc <= VM.BEYOND_MINUS_INFINITY || mc <= VM.BEYOND_MINUS_INFINITY) {
|
8149
8164
|
x.retop(Math.min(uc, mc));
|
8150
8165
|
return;
|
@@ -8154,16 +8169,16 @@ function VMI_profitable_units(x) {
|
|
8154
8169
|
return;
|
8155
8170
|
}
|
8156
8171
|
|
8157
|
-
// NOTE: NPU is not time-dependent => result is stored in cache
|
8158
|
-
// As expressions may contain several NPU operators, create a unique
|
8159
|
-
// based on its parameters
|
8172
|
+
// NOTE: NPU is not time-dependent => result is stored in cache.
|
8173
|
+
// As expressions may contain several NPU operators, create a unique
|
8174
|
+
// key based on its parameters.
|
8160
8175
|
const cache_key = ['npu', mup.code, ub, uc, mc, mpe.code, mpa, pt].join('_');
|
8161
8176
|
if(x.cache[cache_key]) {
|
8162
8177
|
x.retop(x.cache[cache_key]);
|
8163
8178
|
return;
|
8164
8179
|
}
|
8165
8180
|
|
8166
|
-
// mp can be a single value, a vector, or an expression
|
8181
|
+
// `mp` can be a single value, a vector, or an expression.
|
8167
8182
|
let mp = mpe.attributeValue(mpa);
|
8168
8183
|
if(mp === null) {
|
8169
8184
|
mp = mpe.attributeExpression(mpa);
|
@@ -8178,8 +8193,8 @@ function VMI_profitable_units(x) {
|
|
8178
8193
|
nu = Math.ceil(ub / uc), // Number of units
|
8179
8194
|
r = [];
|
8180
8195
|
if(mp && mp instanceof Expression) {
|
8181
|
-
// NOTE:
|
8182
|
-
mp.compute();
|
8196
|
+
// NOTE: An expression may not have been (fully) computed yet.
|
8197
|
+
mp.compute(0);
|
8183
8198
|
if(mp.isStatic) {
|
8184
8199
|
mp = mp.result(0);
|
8185
8200
|
} else {
|
@@ -8275,11 +8290,11 @@ function VMI_highest_cumulative_consecutive_deviation(x) {
|
|
8275
8290
|
e = d[0].entity,
|
8276
8291
|
a = d[0].attribute;
|
8277
8292
|
let vector = e.attributeValue(a);
|
8278
|
-
// NOTE:
|
8293
|
+
// NOTE: Equations can also be passed by reference.
|
8279
8294
|
if(e === MODEL.equations_dataset) {
|
8280
8295
|
const x = e.modifiers[a].expression;
|
8281
|
-
// NOTE: an expression may not have been (fully) computed yet
|
8282
|
-
x.compute();
|
8296
|
+
// NOTE: an expression may not have been (fully) computed yet.
|
8297
|
+
x.compute(0);
|
8283
8298
|
if(!x.isStatic) {
|
8284
8299
|
const nt = MODEL.end_period - MODEL.start_period + 1;
|
8285
8300
|
for(let t = 1; t <= nt; t++) x.result(t);
|
@@ -8389,6 +8404,181 @@ DYNAMIC_SYMBOLS.push('hccd');
|
|
8389
8404
|
// Add to this list only if operation makes an expression level-based
|
8390
8405
|
// LEVEL_BASED_CODES.push(VMI_...);
|
8391
8406
|
|
8407
|
+
function correlation_or_slope(x, c_or_s) {
|
8408
|
+
// Replaces the argument list that should be at the top of the stack by
|
8409
|
+
// either Spearman's correlation (r) or the slope (b) of the regression
|
8410
|
+
// line y = a + bx for the two vectors X and Y that are passed as the
|
8411
|
+
// two arguments of this function. Reason to combine these two statistics
|
8412
|
+
// in one function is because the required operations are very similar.
|
8413
|
+
// NOTES:
|
8414
|
+
// (1) This function codes for two different operators and therefore
|
8415
|
+
// is a helper function. The two operators must each have their
|
8416
|
+
// own VM instruction -- see immediately after this function.
|
8417
|
+
// (2) String `c_or_s` must be either 'correl' or 'slope'.
|
8418
|
+
// (3) The operands for this function must be vectors, not numbers,
|
8419
|
+
// so in the Linny-R expression they must be passed "by reference".
|
8420
|
+
const
|
8421
|
+
d = x.top(),
|
8422
|
+
vmi = c_or_s;
|
8423
|
+
// Check whether the top stack element is a grouping of two variables.
|
8424
|
+
if(d instanceof Array && d.length === 2 &&
|
8425
|
+
typeof d[0] === 'object' && d[0].hasOwnProperty('entity') &&
|
8426
|
+
typeof d[1] === 'object' && d[1].hasOwnProperty('entity')) {
|
8427
|
+
// Convert the two variables to vectors.
|
8428
|
+
const vector = {x: {}, y: {}};
|
8429
|
+
for(let k in vector) if(vector.hasOwnProperty(k)) {
|
8430
|
+
const
|
8431
|
+
i = ['x', 'y'].indexOf(k),
|
8432
|
+
e = d[i].entity,
|
8433
|
+
a = d[i].attribute;
|
8434
|
+
vector[k].e = e;
|
8435
|
+
vector[k].a = a;
|
8436
|
+
vector[k].v = e.attributeValue(a);
|
8437
|
+
vector[k].name = e.displayName + (a ? '|' + a : '');
|
8438
|
+
vector[k].id = e.identifier;
|
8439
|
+
// NOTE: Equations can also be passed by reference.
|
8440
|
+
if(e === MODEL.equations_dataset) {
|
8441
|
+
const eq = e.modifiers[UI.nameToID(a)].expression;
|
8442
|
+
// Level-based equations require that the model has run.
|
8443
|
+
if(eq.is_level_based && !MODEL.solved) {
|
8444
|
+
x.retop(VM.NOT_COMPUTED);
|
8445
|
+
return;
|
8446
|
+
}
|
8447
|
+
// NOTE: An equation may not have been (fully) computed yet.
|
8448
|
+
eq.compute(0, x.wildcard_vector_index);
|
8449
|
+
if(!eq.isStatic) {
|
8450
|
+
const nt = MODEL.end_period - MODEL.start_period + 1;
|
8451
|
+
for(let t = 1; t <= nt; t++) eq.result(t, x.wildcard_vector_index);
|
8452
|
+
}
|
8453
|
+
vector[k].v = eq.vector;
|
8454
|
+
}
|
8455
|
+
}
|
8456
|
+
// If either operand is level-based, return "not computed" if the
|
8457
|
+
// model has not been run yet.
|
8458
|
+
if((VM.level_based_attr.indexOf(vector.x.a) >= 0 ||
|
8459
|
+
VM.level_based_attr.indexOf(vector.y.a) >= 0) &&
|
8460
|
+
!MODEL.solved) {
|
8461
|
+
x.retop(VM.NOT_COMPUTED);
|
8462
|
+
return;
|
8463
|
+
}
|
8464
|
+
if(Array.isArray(vector.x.v) && Array.isArray(vector.y.v)) {
|
8465
|
+
// Valid parameters => compute the terms used in the formulas
|
8466
|
+
// for correlation (r) and regression (slope and intercept)
|
8467
|
+
// NOTE: Statistics are not time-dependent, so the result is stored
|
8468
|
+
// in the expression's cache. As expressions may contain several
|
8469
|
+
// correl and slope operators, create a unique key based on the
|
8470
|
+
// operator name and its two operands.
|
8471
|
+
const cache_key = [vmi, vector.x.id, vector.x.a,
|
8472
|
+
vector.y.id, vector.y.a].join('_');
|
8473
|
+
if(x.cache[cache_key]) {
|
8474
|
+
x.retop(x.cache[cache_key]);
|
8475
|
+
return;
|
8476
|
+
}
|
8477
|
+
if(true||DEBUGGING) {
|
8478
|
+
console.log(`-- ${vmi}(${vector.x.name}, ${vector.y.name})`);
|
8479
|
+
}
|
8480
|
+
// NOTE: Vectors should have equal length.
|
8481
|
+
const N = Math.min(vector.x.v.length, vector.y.v.length);
|
8482
|
+
if(!N) {
|
8483
|
+
// No data => result should be "division by zero"
|
8484
|
+
x.retop(VM.DIV_ZERO);
|
8485
|
+
return;
|
8486
|
+
}
|
8487
|
+
// Calculate dsq = N*variance for X and Y.
|
8488
|
+
for(let k in vector) if(vector.hasOwnProperty(k)) {
|
8489
|
+
let sum = 0;
|
8490
|
+
// NOTE: Ignore first element of vector (t=0).
|
8491
|
+
for(let i = 1; i < N; i++) {
|
8492
|
+
const v = vector[k].v[i];
|
8493
|
+
// Handle exceptional values in vector.
|
8494
|
+
if(v <= VM.BEYOND_MINUS_INFINITY || v >= VM.BEYOND_PLUS_INFINITY) {
|
8495
|
+
x.retop(v);
|
8496
|
+
return;
|
8497
|
+
}
|
8498
|
+
sum += v;
|
8499
|
+
}
|
8500
|
+
vector[k].sum = sum;
|
8501
|
+
const mean = sum / N;
|
8502
|
+
vector[k].mean = mean;
|
8503
|
+
let dsq = 0;
|
8504
|
+
// NOTE: Ignore first element of vector (t=0).
|
8505
|
+
for(let i = 1; i < N; i++) {
|
8506
|
+
const d = vector[k].v[i] - mean;
|
8507
|
+
dsq += d * d;
|
8508
|
+
}
|
8509
|
+
vector[k].dsq = dsq;
|
8510
|
+
}
|
8511
|
+
// Divisor is sqrt(dsqX * dsqY). If zero, return #DIV/0
|
8512
|
+
const divisor = Math.sqrt(vector.x.dsq * vector.y.dsq);
|
8513
|
+
if(divisor < VM.NEAR_ZERO) {
|
8514
|
+
x.retop(VM.DIV_ZERO);
|
8515
|
+
return;
|
8516
|
+
}
|
8517
|
+
// Calculate N*covariance of X and Y.
|
8518
|
+
let covar = 0;
|
8519
|
+
// NOTE: Ignore first element of vector (t=0).
|
8520
|
+
for(let i = 1; i < N; i++) {
|
8521
|
+
covar += (vector.x.v[i] - vector.x.mean) * (vector.y.v[i] - vector.y.mean);
|
8522
|
+
}
|
8523
|
+
// Correlation = covarXY / sqrt(dsqX * dsqY), slope = covarXY / dsqX.
|
8524
|
+
// NOTE: dsqX will be non-zero (or divisor would have been zero).
|
8525
|
+
const result = covar / (vmi === 'correl' ? divisor : vector.x.dsq);
|
8526
|
+
// Store the result in the expression's cache.
|
8527
|
+
x.cache[cache_key] = result;
|
8528
|
+
// Push the result onto the stack.
|
8529
|
+
x.retop(result);
|
8530
|
+
return;
|
8531
|
+
}
|
8532
|
+
}
|
8533
|
+
// Fall-trough indicates error
|
8534
|
+
if(DEBUGGING) console.log(vmi + ': invalid parameter(s)\n', d);
|
8535
|
+
x.retop(VM.PARAMS);
|
8536
|
+
}
|
8537
|
+
|
8538
|
+
// NOTE: Separate function for each operator: VMI_correl and VMI_slope.
|
8539
|
+
|
8540
|
+
function VMI_correlation(x) {
|
8541
|
+
correlation_or_slope(x, 'correl');
|
8542
|
+
}
|
8543
|
+
|
8544
|
+
// Add the custom operator instruction to the global lists
|
8545
|
+
// NOTE: All custom operators are monadic (priority 9) and reducing
|
8546
|
+
OPERATORS.push('correl');
|
8547
|
+
MONADIC_OPERATORS.push('correl');
|
8548
|
+
ACTUAL_SYMBOLS.push('correl');
|
8549
|
+
OPERATOR_CODES.push(VMI_correlation);
|
8550
|
+
MONADIC_CODES.push(VMI_correlation);
|
8551
|
+
REDUCING_CODES.push(VMI_correlation);
|
8552
|
+
SYMBOL_CODES.push(VMI_correlation);
|
8553
|
+
PRIORITIES.push(9);
|
8554
|
+
// Add to this list only if operation makes an expression dynamic
|
8555
|
+
// DYNAMIC_SYMBOLS.push('...');
|
8556
|
+
// Add to this list only if operation makes an expression random
|
8557
|
+
// RANDOM_CODES.push(VMI_...);
|
8558
|
+
// Add to this list only if operation makes an expression level-based
|
8559
|
+
// LEVEL_BASED_CODES.push(VMI_...);
|
8560
|
+
|
8561
|
+
function VMI_slope(x) {
|
8562
|
+
correlation_or_slope(x, 'slope');
|
8563
|
+
}
|
8564
|
+
|
8565
|
+
// Add the custom operator instruction to the global lists
|
8566
|
+
// NOTE: All custom operators are monadic (priority 9) and reducing
|
8567
|
+
OPERATORS.push('slope');
|
8568
|
+
MONADIC_OPERATORS.push('slope');
|
8569
|
+
ACTUAL_SYMBOLS.push('slope');
|
8570
|
+
OPERATOR_CODES.push(VMI_slope);
|
8571
|
+
MONADIC_CODES.push(VMI_slope);
|
8572
|
+
REDUCING_CODES.push(VMI_slope);
|
8573
|
+
SYMBOL_CODES.push(VMI_slope);
|
8574
|
+
PRIORITIES.push(9);
|
8575
|
+
// Add to this list only if operation makes an expression dynamic
|
8576
|
+
// DYNAMIC_SYMBOLS.push('...');
|
8577
|
+
// Add to this list only if operation makes an expression random
|
8578
|
+
// RANDOM_CODES.push(VMI_...);
|
8579
|
+
// Add to this list only if operation makes an expression level-based
|
8580
|
+
// LEVEL_BASED_CODES.push(VMI_...);
|
8581
|
+
|
8392
8582
|
/*** END of custom operator API section ***/
|
8393
8583
|
|
8394
8584
|
///////////////////////////////////////////////////////////////////////
|
@@ -8398,4 +8588,4 @@ if(NODE) module.exports = {
|
|
8398
8588
|
Expression: Expression,
|
8399
8589
|
ExpressionParser: ExpressionParser,
|
8400
8590
|
VirtualMachine: VirtualMachine
|
8401
|
-
}
|
8591
|
+
};
|