linny-r 1.6.8 → 1.7.1

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.
@@ -2328,7 +2328,13 @@ class VirtualMachine {
2328
2328
  // Statistics that can be calculated for outcomes and experiment run results
2329
2329
  this.outcome_statistics =
2330
2330
  ['LAST', 'MAX', 'MEAN', 'MIN', 'N', 'NZ', 'SD', 'SUM', 'VAR'];
2331
- }
2331
+ this.solver_names = {
2332
+ gurobi: 'Gurobi',
2333
+ cplex: 'CPLEX',
2334
+ scip: 'SCIP',
2335
+ lp_solve: 'LP_solve'
2336
+ };
2337
+ }
2332
2338
 
2333
2339
  reset() {
2334
2340
  // Reset the virtual machine so that it can execute the model again.
@@ -2384,8 +2390,13 @@ class VirtualMachine {
2384
2390
  this.issue_list.length = 0;
2385
2391
  this.issue_index = -1;
2386
2392
  UI.updateIssuePanel();
2387
- // NOTE: Special tracking of potential solver license errors.
2393
+ // Special tracking of potential solver license errors.
2388
2394
  this.license_expired = 0;
2395
+ // Variables that will be decided by the solver again in the next
2396
+ // block must be "fixated" when, due to a negative link delay, they
2397
+ // would have consequences for the previous block (and these will be
2398
+ // ignored).
2399
+ this.variables_to_fixate = {};
2389
2400
  // Reset solver result arrays.
2390
2401
  this.round_times.length = 0;
2391
2402
  this.solver_times.length = 0;
@@ -3264,18 +3275,18 @@ class VirtualMachine {
3264
3275
  if(!MODEL.ignored_entities[k]) {
3265
3276
  p = MODEL.processes[k];
3266
3277
  lbx = p.lower_bound;
3267
- // NOTE: if UB = LB, set UB to LB only if LB is defined,
3278
+ // NOTE: If UB = LB, set UB to LB only if LB is defined,
3268
3279
  // because LB expressions default to -INF while UB expressions
3269
- // default to + INF
3280
+ // default to +INF.
3270
3281
  ubx = (p.equal_bounds && lbx.defined ? lbx : p.upper_bound);
3271
3282
  if(lbx.isStatic) lbx = lbx.result(0);
3272
3283
  if(ubx.isStatic) ubx = ubx.result(0);
3273
- // NOTE: pass TRUE as fourth parameter to indicate that +INF
3284
+ // NOTE: Pass TRUE as fourth parameter to indicate that +INF
3274
3285
  // and -INF can be coded as the infinity values used by the
3275
3286
  // solver, rather than the Linny-R values used to detect
3276
- // unbounded problems
3287
+ // unbounded problems.
3277
3288
  this.code.push([VMI_set_bounds, [p.level_var_index, lbx, ubx, true]]);
3278
- // Add level variable index to "fixed" list for specified rounds
3289
+ // Add level variable index to "fixed" list for specified rounds.
3279
3290
  const rf = p.actor.round_flags;
3280
3291
  if(rf != 0) {
3281
3292
  // Note: 32-bit integer `b` is used for bit-wise AND
@@ -4305,7 +4316,7 @@ class VirtualMachine {
4305
4316
  // Return floating point number `n`, or +INF or -INF if the absolute
4306
4317
  // value of `n` is relatively (!) close to the VM infinity constants
4307
4318
  // (since the solver may return imprecise values of such magnitude).
4308
- if(n > 0.5 * VM.PLUS_INFINITY && n < VM.BEYOND_PLUS_INFINITY) {
4319
+ if(n > 0.5 * VM.PLUS_INFINITY && n < VM.BEYOND_PLUS_INFINITY) {
4309
4320
  return VM.PLUS_INFINITY;
4310
4321
  }
4311
4322
  if(n < 0.5 * VM.MINUS_INFINITY && n > VM.BEYOND_MINUS_INFINITY) {
@@ -4358,26 +4369,26 @@ class VirtualMachine {
4358
4369
  ncv_msg + ' is not a multiple of # columns', this.cols);
4359
4370
  // Assume no warnings or errors.
4360
4371
  this.error_count = 0;
4361
- // Set cash flows for all actors
4362
- // NOTE: all cash IN and cash OUT values should normally be non-negative,
4372
+ // Set cash flows for all actors.
4373
+ // NOTE: All cash IN and cash OUT values should normally be non-negative,
4363
4374
  // but since Linny-R permits negative lower bounds on processes, and also
4364
4375
  // negative link rates, cash flows may become negative. If that occurs,
4365
4376
  // the modeler should be warned.
4366
4377
  for(let o in MODEL.actors) if(MODEL.actors.hasOwnProperty(o)) {
4367
4378
  const a = MODEL.actors[o];
4368
- // NOTE: `b` is the index to be used for the vectors
4379
+ // NOTE: `b` is the index to be used for the vectors.
4369
4380
  let b = bb;
4370
- // Iterate over all time steps in this block
4371
- // NOTE: -1 because indices start at 1, but list is zero-based
4381
+ // Iterate over all time steps in this block.
4382
+ // NOTE: -1 because indices start at 1, but list is zero-based.
4372
4383
  let j = -1;
4373
4384
  for(let i = 0; i < abl; i++) {
4374
- // NOTE: cash coefficients computed by the solver must be scaled back
4385
+ // NOTE: Cash coefficients computed by the solver must be scaled back.
4375
4386
  a.cash_in[b] = this.checkForInfinity(
4376
4387
  x[a.cash_in_var_index + j] * this.cash_scalar);
4377
4388
  a.cash_out[b] = this.checkForInfinity(
4378
4389
  x[a.cash_out_var_index + j] * this.cash_scalar);
4379
4390
  a.cash_flow[b] = a.cash_in[b] - a.cash_out[b];
4380
- // Count occurrences of a negative cash flow (threshold -0.5 cent)
4391
+ // Count occurrences of a negative cash flow (threshold -0.5 cent).
4381
4392
  if(a.cash_in[b] < -0.005) {
4382
4393
  this.logMessage(block, `${this.WARNING}(t=${b}${round}) ` +
4383
4394
  a.displayName + ' cash IN = ' + a.cash_in[b].toPrecision(2));
@@ -4386,13 +4397,13 @@ class VirtualMachine {
4386
4397
  this.logMessage(block, `${this.WARNING}(t=${b}${round}) ` +
4387
4398
  a.displayName + ' cash IN = ' + a.cash_out[b].toPrecision(2));
4388
4399
  }
4389
- // Advance column offset in tableau by the # cols per time step
4400
+ // Advance column offset in tableau by the # cols per time step.
4390
4401
  j += this.cols;
4391
- // Advance to the next time step in this block
4402
+ // Advance to the next time step in this block.
4392
4403
  b++;
4393
4404
  }
4394
4405
  }
4395
- // Set production levels and start-up moments for all processes
4406
+ // Set production levels and start-up moments for all processes.
4396
4407
  for(let o in MODEL.processes) if(MODEL.processes.hasOwnProperty(o) &&
4397
4408
  !MODEL.ignored_entities[o]) {
4398
4409
  const
@@ -4553,6 +4564,8 @@ class VirtualMachine {
4553
4564
  bb = (block - 1) * MODEL.block_length + 1,
4554
4565
  cbl = this.actualBlockLength(block);
4555
4566
 
4567
+ // Start with an empty list of variables to "fixate" in the next block.
4568
+ this.variables_to_fixate = {};
4556
4569
  // FIRST: Calculate the actual flows on links.
4557
4570
  let b, bt, p, pl, ld, ci;
4558
4571
  for(let l in MODEL.links) if(MODEL.links.hasOwnProperty(l) &&
@@ -4567,6 +4580,22 @@ class VirtualMachine {
4567
4580
  // NOTE: Flows may have a delay!
4568
4581
  ld = l.actualDelay(b);
4569
4582
  bt = b - ld;
4583
+ // If delay < 0 AND this results in a block time beyond the
4584
+ // block length, this means that the level of the FROM node
4585
+ // must be "fixated" in the next block.
4586
+ const nbt = bt - bb - MODEL.block_length + 1;
4587
+ // NOTE: `nbt` (next block time) cannot be beyond the look-ahead
4588
+ // period, as for those time steps the levels are still undefined,
4589
+ // NOR can it be later than the duration of the (negative) delay
4590
+ // on the link.
4591
+ if(ld < 0 && nbt > 0 && nbt <= MODEL.look_ahead && nbt <= -ld) {
4592
+ this.addNodeToFixate(l.from_node, nbt,
4593
+ // NOTE: Use the level at time `bt` (i.e., in the future)
4594
+ // because that is the optimal level computed for this chunk
4595
+ // (in its look-ahead period) that should be maintained in
4596
+ // the next block.
4597
+ l.from_node.nonZeroLevel(bt));
4598
+ }
4570
4599
  // NOTE: Block index may fall beyond actual chunk length.
4571
4600
  ci = i - ld;
4572
4601
  // NOTE: Use non-zero level here to ignore non-zero values that
@@ -5021,7 +5050,7 @@ class VirtualMachine {
5021
5050
  let rem = (MODEL.runLength - MODEL.look_ahead) % MODEL.block_length;
5022
5051
  // If this remainder equals 0, the last block is a full chunk.
5023
5052
  if(rem === 0) return this.chunk_length;
5024
- // Otherwise, the last block if remainder + look-ahead time steps.
5053
+ // Otherwise, the last block has remainder + look-ahead time steps.
5025
5054
  return rem + MODEL.look_ahead;
5026
5055
  }
5027
5056
 
@@ -5051,6 +5080,33 @@ class VirtualMachine {
5051
5080
  return this.chunk_length * this.cols + this.chunk_variables.length;
5052
5081
  }
5053
5082
 
5083
+ addNodeToFixate(n, bt, level) {
5084
+ // Record that level of node `n` must be fixated for block time `bt`
5085
+ // in the next block by setting it to the specified level.
5086
+ const
5087
+ vi = n.level_var_index,
5088
+ oo = n.on_off_var_index,
5089
+ iz = n.is_zero_var_index;
5090
+ if(!this.variables_to_fixate.hasOwnProperty(vi)) {
5091
+ this.variables_to_fixate[vi] = {};
5092
+ }
5093
+ this.variables_to_fixate[vi][bt] = level;
5094
+ if(oo >= 0) {
5095
+ if(!this.variables_to_fixate.hasOwnProperty(oo)) {
5096
+ this.variables_to_fixate[oo] = {};
5097
+ }
5098
+ this.variables_to_fixate[oo][bt] = (
5099
+ Math.abs(level) < VM.NEAR_ZERO ? 0 : 1);
5100
+ }
5101
+ if(iz >= 0) {
5102
+ if(!this.variables_to_fixate.hasOwnProperty(iz)) {
5103
+ this.variables_to_fixate[iz] = {};
5104
+ }
5105
+ this.variables_to_fixate[iz][bt] = (
5106
+ Math.abs(level) < VM.NEAR_ZERO ? 1 : 0);
5107
+ }
5108
+ }
5109
+
5054
5110
  writeLpFormat(cplex=false) {
5055
5111
  // NOTE: Up to version 1.5.6, actual block length of last block used
5056
5112
  // to be shorter than the chunk length so as not to go beyond the
@@ -5694,6 +5750,37 @@ Solver status = ${json.status}`);
5694
5750
  this.logMessage(this.block_count, 'Zero columns -- nothing to solve');
5695
5751
  return;
5696
5752
  }
5753
+ // If negative delays require "fixating" variables for some number
5754
+ // of time steps, this must be logged in the monitor.
5755
+ const keys = Object.keys(this.variables_to_fixate);
5756
+ if(keys.length) {
5757
+ const msg = ['NOTE: Due to negative link delays, levels for ' +
5758
+ pluralS(keys.length, 'variable') + ' are pre-set:'];
5759
+ for(let i = 0; i < keys.length; i++) {
5760
+ const
5761
+ vi = parseInt(keys[i]),
5762
+ // NOTE: Subtract 1 because variable list is zero-based.
5763
+ vbl = this.variables[vi - 1],
5764
+ fv = this.variables_to_fixate[vi],
5765
+ fvk = Object.keys(fv),
5766
+ fvl = [];
5767
+ // Add constraints that fixate the levels directly to the tableau.
5768
+ for(let i = 0; i < fvk.length; i++) {
5769
+ const
5770
+ bt = fvk[i],
5771
+ pl = fv[bt],
5772
+ k = (bt - 1) * VM.cols + vi,
5773
+ row = {};
5774
+ row[k] = 1;
5775
+ VM.matrix.push(row);
5776
+ VM.right_hand_side.push(pl);
5777
+ VM.constraint_types.push(VM.EQ);
5778
+ fvl.push(pl + ' for bt=' + bt);
5779
+ }
5780
+ msg.push(`- ${vbl[1].displayName} [${vbl[0]}]: ${fvl.join(', ')}`);
5781
+ }
5782
+ this.logMessage(this.block_count, msg.join('\n'));
5783
+ }
5697
5784
  this.logMessage(this.block_count,
5698
5785
  'Creating model for block #' + this.blockWithRound);
5699
5786
  this.cbl = CONFIGURATION.progress_needle_interval * 200;
@@ -5719,7 +5806,7 @@ Solver status = ${json.status}`);
5719
5806
  }
5720
5807
 
5721
5808
  submitFile() {
5722
- // Prepares to POST the model file (LP or MPS) to the Linny-R server.
5809
+ // Prepare to POST the model file (LP or MPS) to the Linny-R server.
5723
5810
  // NOTE: The tableau is no longer needed, so free up its memory.
5724
5811
  this.resetTableau();
5725
5812
  if(this.numeric_issue) {
@@ -5772,6 +5859,13 @@ Solver status = ${json.status}`);
5772
5859
  solveModel() {
5773
5860
  // Start the sequence of data loading, model translation, solving
5774
5861
  // consecutive blocks, and finally calculating dependent variables.
5862
+ // NOTE: Do this only if the model defines a MILP problem, i.e.,
5863
+ // contains at least one process or product.
5864
+ if(!(Object.keys(MODEL.processes).length ||
5865
+ Object.keys(MODEL.products).length)) {
5866
+ UI.notify('Nothing to solve');
5867
+ return;
5868
+ }
5775
5869
  const n = MODEL.loading_datasets.length;
5776
5870
  if(n > 0) {
5777
5871
  // Still within reasonable time? (3 seconds per dataset)
@@ -5780,7 +5874,7 @@ Solver status = ${json.status}`);
5780
5874
  UI.setMessage(`Waiting for ${pluralS(n, 'dataset')} to load`);
5781
5875
  // Decrease the remaining time to wait (half second units)
5782
5876
  MODEL.max_time_to_load--;
5783
- // Try again after half a second
5877
+ // Try again after half a second.
5784
5878
  setTimeout(() => VM.solveModel(), 500);
5785
5879
  return;
5786
5880
  } else {
@@ -7453,19 +7547,19 @@ function VMI_set_bounds(args) {
7453
7547
  k = VM.offset + vi,
7454
7548
  r = VM.round_letters.indexOf(VM.round_sequence[VM.current_round]),
7455
7549
  // Optional fourth parameter indicates whether the solver's
7456
- // infinity values should be used
7550
+ // infinity values should be used.
7457
7551
  solver_inf = args.length > 3 && args[3],
7458
7552
  inf_val = (solver_inf ? VM.SOLVER_PLUS_INFINITY : VM.PLUS_INFINITY);
7459
7553
  let l,
7460
7554
  u,
7461
7555
  fixed = (vi in VM.fixed_var_indices[r - 1]);
7462
7556
  if(fixed) {
7463
- // Set both bounds equal to the level set in the previous round, or to 0
7464
- // if this is the first round
7557
+ // Set both bounds equal to the level set in the previous round,
7558
+ // or to 0 if this is the first round.
7465
7559
  if(VM.current_round) {
7466
7560
  l = vbl.actualLevel(VM.t);
7467
- // QUICK PATCH! should resolve that small non-zero process levels
7468
- // computed in prior round make problem infeasible
7561
+ // QUICK PATCH! Should resolve that small non-zero process levels
7562
+ // computed in prior round make problem infeasible.
7469
7563
  if(l < 0.0005) l = 0;
7470
7564
  } else {
7471
7565
  l = 0;
@@ -7473,7 +7567,7 @@ function VMI_set_bounds(args) {
7473
7567
  u = l;
7474
7568
  fixed = ' (FIXED ' + vbl.displayName + ')';
7475
7569
  } else {
7476
- // Set bounds as specified by the two arguments
7570
+ // Set bounds as specified by the two arguments.
7477
7571
  l = args[1];
7478
7572
  if(l instanceof Expression) l = l.result(VM.t);
7479
7573
  if(l === VM.UNDEFINED) l = 0;
@@ -7486,22 +7580,22 @@ function VMI_set_bounds(args) {
7486
7580
  }
7487
7581
  fixed = '';
7488
7582
  }
7489
- // NOTE: to see in the console whether fixing across rounds works, insert
7490
- // "fixed !== '' || " before DEBUGGING below
7583
+ // NOTE: To see in the console whether fixing across rounds works, insert
7584
+ // "fixed !== '' || " before DEBUGGING below.
7491
7585
  if(DEBUGGING) {
7492
7586
  console.log(['set_bounds [', k, '] ', vbl.displayName, ' t = ', VM.t,
7493
7587
  ' LB = ', VM.sig4Dig(l), ', UB = ', VM.sig4Dig(u), fixed].join(''));
7494
7588
  }
7495
- // NOTE: since the VM vectors for lower bounds and upper bounds are
7589
+ // NOTE: Since the VM vectors for lower bounds and upper bounds are
7496
7590
  // initialized with default values (0 for LB, +INF for UB), there is
7497
- // no need to set them
7591
+ // no need to set them.
7498
7592
  if(l !== 0 || u < inf_val) {
7499
7593
  VM.lower_bounds[k] = l;
7500
7594
  VM.upper_bounds[k] = u;
7501
7595
  // If associated node is FROM-node of a "peak increase" link, then
7502
7596
  // the "peak increase" variables of this node must have the highest
7503
7597
  // UB of the node (for all t in this block, hence MAX) MINUS their
7504
- // peak level in previous block
7598
+ // peak level in previous block.
7505
7599
  if(vbl.peak_inc_var_index >= 0) {
7506
7600
  u = Math.max(0, u - vbl.b_peak[VM.block_count - 1]);
7507
7601
  const
@@ -7546,8 +7640,8 @@ function VMI_add_const_to_coefficient(args) {
7546
7640
  console.log(`add_const_to_coefficient [${k}]: ${VM.sig4Dig(n)}`);
7547
7641
  }
7548
7642
  // A negative delay may result in a variable index beyond the tableau
7549
- // column range. Such "future variables" should be ignored.
7550
- if(d < 0 && k > VM.chunk_offset) return;
7643
+ // column range. Such "future variables" should always be ignored.
7644
+ if(k > VM.chunk_offset) return;
7551
7645
  if(k <= 0) {
7552
7646
  // NOTE: If `k` falls PRIOR to the start of the block being solved,
7553
7647
  // this means that the value of the decision variable X for which the
@@ -7563,7 +7657,7 @@ function VMI_add_const_to_coefficient(args) {
7563
7657
  if(DEBUGGING) {
7564
7658
  console.log(`--lookup[${k}]: ${vbl[0]} ${vbl[1].displayName} @ ${t} = ${pv}`);
7565
7659
  }
7566
- // NOTE: special cases for binary variables!
7660
+ // NOTE: Special cases for binary variables!
7567
7661
  VM.rhs -= pv * n;
7568
7662
  } else if(k in VM.coefficients) {
7569
7663
  VM.coefficients[k] += n;
@@ -7573,7 +7667,7 @@ function VMI_add_const_to_coefficient(args) {
7573
7667
  }
7574
7668
 
7575
7669
  function VMI_add_const_to_sum_coefficients(args) {
7576
- // NOTE: used to implement data links with SUM multiplier
7670
+ // NOTE: Used to implement data links with SUM multiplier.
7577
7671
  // `args`: [var_index, number, delay (, 1)]
7578
7672
  const vi = args[0];
7579
7673
  let d = args[2].object.actualDelay(VM.t),
@@ -7592,11 +7686,10 @@ function VMI_add_const_to_sum_coefficients(args) {
7592
7686
  d = -d;
7593
7687
  }
7594
7688
  for(let i = 0; i <= d; i++) {
7595
- // A negative delay may result in a variable index beyond the tableau
7596
- // column range. Such "future variables" should be ignored.
7689
+ // Variables beyond the chunk length should be ignored.
7597
7690
  if(k > VM.chunk_offset) return;
7598
7691
  if(k <= 0) {
7599
- // See NOTE in VMI_add_const_to_coefficient instruction
7692
+ // See NOTE in VMI_add_const_to_coefficient instruction.
7600
7693
  const vbl = VM.variables[vi - 1];
7601
7694
  if(DEBUGGING) {
7602
7695
  console.log('--lookup[' + k + ']: ' + vbl[0] + ' ' + vbl[1].displayName);
@@ -7618,24 +7711,23 @@ function VMI_add_var_to_coefficient(args) {
7618
7711
  let d = 0;
7619
7712
  if(args.length > 2 && args[2] instanceof Expression) {
7620
7713
  d = args[2].object.actualDelay(VM.t);
7621
- // 4th argument = 1 indicates "delay + 1"
7714
+ // 4th argument = 1 indicates "delay + 1".
7622
7715
  if(args.length > 3 && args[3]) d++;
7623
7716
  }
7624
7717
  const
7625
7718
  k = VM.offset + vi - d*VM.cols,
7626
7719
  t = VM.t - d;
7627
7720
  let r = args[1].result(t);
7628
- // Optional 5th parameter is a constant multiplier
7721
+ // Optional 5th parameter is a constant multiplier.
7629
7722
  if(args.length > 4) r *= args[4];
7630
7723
  if(DEBUGGING) {
7631
7724
  console.log('add_var_to_coefficient [' + k + ']: ' +
7632
7725
  args[1].variableName + ' (t = ' + t + ')');
7633
7726
  }
7634
- // A negative delay may result in a variable index beyond the tableau
7635
- // column range. Such "future variables" should be ignored.
7727
+ // Ignore "future variables".
7636
7728
  if(k > VM.chunk_offset) return;
7637
7729
  if(k <= 0) {
7638
- // See NOTE in VMI_add_const_to_coefficient instruction
7730
+ // See NOTE in VMI_add_const_to_coefficient instruction.
7639
7731
  const vbl = VM.variables[vi - 1];
7640
7732
  if(DEBUGGING) {
7641
7733
  console.log('--lookup[' + k + ']: ' + vbl[0] + ' ' + vbl[1].displayName);
@@ -7668,8 +7760,7 @@ function VMI_add_var_to_weighted_sum_coefficients(args) {
7668
7760
  d = -d;
7669
7761
  }
7670
7762
  for(let i = 0; i <= d; i++) {
7671
- // A negative delay may result in a variable index beyond the tableau
7672
- // column range. Such "future variables" should be ignored.
7763
+ // Ignore "future variables".
7673
7764
  if(k > VM.chunk_offset) return;
7674
7765
  let r = v.result(t);
7675
7766
  if(args.length > 3) r /= (d + 1);
@@ -7707,8 +7798,7 @@ function VMI_subtract_const_from_coefficient(args) {
7707
7798
  if(DEBUGGING) {
7708
7799
  console.log('subtract_const_from_coefficient [' + k + ']: ' + VM.sig4Dig(n));
7709
7800
  }
7710
- // A negative delay may result in a variable index beyond the tableau
7711
- // column range. Such "future variables" should be ignored.
7801
+ // Ignore "future variables".
7712
7802
  if(k > VM.chunk_offset) return;
7713
7803
  if(k <= 0) {
7714
7804
  // See NOTE in VMI_add_const_to_coefficient instruction
@@ -7750,11 +7840,10 @@ function VMI_subtract_var_from_coefficient(args) {
7750
7840
  console.log('subtract_var_from_coefficient [' + k + ']: ' +
7751
7841
  args[1].variableName + ' (t = ' + t + ')');
7752
7842
  }
7753
- // A negative delay may result in a variable index beyond the tableau
7754
- // column range. Such "future variables" should be ignored.
7843
+ // Ignore "future variables".
7755
7844
  if(k > VM.chunk_offset) return;
7756
7845
  if(k <= 0) {
7757
- // See NOTE in VMI_add_const_to_coefficient instruction
7846
+ // See NOTE in VMI_add_const_to_coefficient instruction.
7758
7847
  const vbl = VM.variables[vi - 1];
7759
7848
  if(DEBUGGING) {
7760
7849
  console.log('--lookup[' + k + ']: ' + vbl[0] + ' ' + vbl[1].displayName);
@@ -7790,30 +7879,30 @@ function VMI_update_cash_coefficient(args) {
7790
7879
  // NOTE: delay > 0 affects only which variable is to be used,
7791
7880
  // not the expressions for rates or prices!
7792
7881
  const t = VM.t - d;
7793
- // NOTE: this instruction is used only for objective function
7794
- // coefficients; previously computed decision variables and variables
7882
+ // NOTE: This instruction is used only for objective function
7883
+ // coefficients. Previously computed decision variables and variables
7795
7884
  // beyond the tableau column range (when delay < 0) can be ignored.
7796
7885
  if(k <= 0 || k > VM.chunk_offset) return;
7797
- // NOTE: peak increase can generate cash only at the first time
7886
+ // NOTE: Peak increase can generate cash only at the first time
7798
7887
  // step of a block (when VM.offset = 0) and at the first time step
7799
- // of the look-ahead period (when VM.offset = block length)
7888
+ // of the look-ahead period (when VM.offset = block length).
7800
7889
  if(type === VM.PEAK_INC &&
7801
7890
  VM.offset > 0 && VM.offset !== MODEL.block_length) return;
7802
- // First compute the result to be processed
7891
+ // First compute the result to be processed.
7803
7892
  let r = 0;
7804
7893
  if(type === VM.ONE_C) {
7805
7894
  r = args[4];
7806
7895
  } else if(type === VM.TWO_X || type === VM.PEAK_INC) {
7807
- // NOTE: "peak increase" always passes two expressions
7896
+ // NOTE: "peak increase" always passes two expressions.
7808
7897
  r = args[4].result(VM.t) * args[5].result(VM.t);
7809
7898
  } else if(type === VM.THREE_X) {
7810
7899
  r = args[4].result(VM.t) * args[5].result(VM.t) * args[6].result(VM.t);
7811
7900
  } else if(type === VM.SPIN_RES) {
7812
- // "spinning reserve" equals UB - level if level > 0, or 0
7901
+ // "spinning reserve" equals UB - level if level > 0, or 0.
7813
7902
  // The cash flow then equals ON/OFF*UB*price*rate - level*price*rate.
7814
7903
  // The ON/OFF variable index is passed as third argument, hence `plvi`
7815
7904
  // (process level variable index) as first extra parameter, plus three
7816
- // expressions (UB, price, rate)
7905
+ // expressions (UB, price, rate).
7817
7906
  const
7818
7907
  plvi = args[4],
7819
7908
  // NOTE: column of second variable will be relative to same offset
@@ -7821,9 +7910,9 @@ function VMI_update_cash_coefficient(args) {
7821
7910
  ub = args[5].result(VM.t),
7822
7911
  price_rate = args[6].result(VM.t) * args[7].result(VM.t);
7823
7912
  r = ub * price_rate;
7824
- // NOTE: the sign of r determines whether this spinning reserve will
7825
- // generate cash IN or cash OUT; the *subtracted* part hence be ADDED
7826
- // if r > 0, and SUBTRACTED if r < 0 (unlike the "primary" part r itself)
7913
+ // NOTE: The sign of r determines whether this spinning reserve will
7914
+ // generate cash IN or cash OUT. The *subtracted* part hence be ADDED
7915
+ // if r > 0, and SUBTRACTED if r < 0 (unlike the "primary" part r itself).
7827
7916
  if(r > 0) {
7828
7917
  if(plk in VM.cash_in_coefficients) {
7829
7918
  VM.cash_in_coefficients[plk] += price_rate;
@@ -7838,8 +7927,8 @@ function VMI_update_cash_coefficient(args) {
7838
7927
  }
7839
7928
  }
7840
7929
  }
7841
- // NOTE: for spinning reserve and highest increment, flow will always
7842
- // be PRODUCE
7930
+ // NOTE: For spinning reserve and highest increment, flow will always
7931
+ // be PRODUCE.
7843
7932
  if(flow === VM.CONSUME) r = -r;
7844
7933
  if(DEBUGGING) {
7845
7934
  const vbl = (vi <= this.cols ? VM.variables[vi - 1] :
@@ -7848,9 +7937,9 @@ function VMI_update_cash_coefficient(args) {
7848
7937
  vbl[1].displayName, ' (t = ', t, ') ', VM.CF_CONSTANTS[type], ' ',
7849
7938
  VM.CF_CONSTANTS[flow], ' r = ', VM.sig4Dig(r)].join(''));
7850
7939
  }
7851
- // Use look-ahead peak increase when offset > 0
7940
+ // Use look-ahead peak increase when offset > 0.
7852
7941
  if(type === VM.PEAK_INC && VM.offset) k++;
7853
- // Then update the cash flow: cash IN if r > 0, otherwise cash OUT
7942
+ // Then update the cash flow: cash IN if r > 0, otherwise cash OUT .
7854
7943
  if(r > 0) {
7855
7944
  if(k in VM.cash_in_coefficients) {
7856
7945
  VM.cash_in_coefficients[k] -= r;
@@ -7858,7 +7947,7 @@ function VMI_update_cash_coefficient(args) {
7858
7947
  VM.cash_in_coefficients[k] = -r;
7859
7948
  }
7860
7949
  } else if(r < 0) {
7861
- // NOTE: Test for r < 0 because no action is needed if r = 0
7950
+ // NOTE: Test for r < 0 because no action is needed if r = 0.
7862
7951
  if(k in VM.cash_out_coefficients) {
7863
7952
  VM.cash_out_coefficients[k] += r;
7864
7953
  } else {
@@ -7868,7 +7957,7 @@ function VMI_update_cash_coefficient(args) {
7868
7957
  }
7869
7958
 
7870
7959
  function VMI_add_throughput_to_coefficient(args) {
7871
- // Special instruction to deal with throughput calculation
7960
+ // Special instruction to deal with throughput calculation.
7872
7961
  // Function: to add the contribution of variable X to the level of
7873
7962
  // variable Z when Z depends (a.o.) on the throughput of variable Y, i.e.,
7874
7963
  // X --(r2,d2)--> Y --(r1,d1)--> Z
@@ -7878,8 +7967,9 @@ function VMI_add_throughput_to_coefficient(args) {
7878
7967
  vi = args[0],
7879
7968
  d1 = args[2].object.actualDelay(VM.t),
7880
7969
  d2 = (args[4] ? args[4].object.actualDelay(VM.t) : 0),
7881
- k = VM.offset + vi - (d1 + d2)*VM.cols,
7882
- t = VM.t - d1 - d2,
7970
+ dsum = d1 + d2,
7971
+ k = VM.offset + vi - dsum * VM.cols,
7972
+ t = VM.t - dsum,
7883
7973
  // Compute the value to be added to the coefficient
7884
7974
  v = args[1].result(VM.t) * args[3].result(VM.t - d1);
7885
7975
  if(DEBUGGING) {
@@ -7887,8 +7977,7 @@ function VMI_add_throughput_to_coefficient(args) {
7887
7977
  args[1].variableName + ' * ' + args[3].variableName +
7888
7978
  ' (t = ' + VM.t + ')');
7889
7979
  }
7890
- // A negative delay may result in a variable index beyond the tableau
7891
- // column range. Such "future variables" should be ignored.
7980
+ // Ignore "future variables".
7892
7981
  if(k > VM.chunk_offset) return;
7893
7982
  if(k <= 0) {
7894
7983
  const vbl = VM.variables[vi - 1];
@@ -8587,7 +8676,7 @@ function correlation_or_slope(x, c_or_s) {
8587
8676
  x.retop(x.cache[cache_key]);
8588
8677
  return;
8589
8678
  }
8590
- if(true||DEBUGGING) {
8679
+ if(DEBUGGING) {
8591
8680
  console.log(`-- ${vmi}(${vector.x.name}, ${vector.y.name})`);
8592
8681
  }
8593
8682
  // NOTE: Vectors should have equal length.