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