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.
@@ -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
- return [
1398
- {d: obj.dataset, s: this.context_number, x: obj.expression},
1399
- anchor1, offset1, anchor2, offset2];
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, '#DIV0!'];
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(true||abl > this.tsl * 5) {
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
- get actualBlockLength() {
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(this.block_count < this.nr_of_blocks) return this.chunk_length;
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
- this.numeric_issue = '';
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 #DIV0!
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 #DIV0!), takes the fraction
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
- // Tests the top number A of the stack, and if A is FALSE (zero or
7162
- // VM.UNDEFINED) sets the program counter of the VM to `index` minus 1,
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
- // Removes the top value from the stack, which should be 0 or
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, and its executions would indicate an error
7187
- console.log('WARNING: this IF-THEN instruction is obsolete!');
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, and its executions would indicate an error
7193
- console.log('WARNING: this IF-THEN instruction is obsolete!');
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(empty) {
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
- const r = v.result(t);
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(empty) {
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(empty) {
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 results
8108
- // that cannot be coded (efficiently) using standard expressions.
8109
- // The first custom operator in this section demonstrates by example how custom
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, i.e.,
8114
- // they must have a grouping as operand. The number of required arguments must
8115
- // be checked at run time by the VM instruction for this operator.
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
- // Replaces the argument list that should be at the top of the stack by the
8121
- // number of profitable units having a standard capacity (number), given the
8122
- // level (vector) of the process that represents multiple such units, the
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 key
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: an expression may not have been (fully) computed yet
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: equations can also be passed by reference
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
+ };