linny-r 1.6.5 → 1.6.7

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.
@@ -3199,25 +3199,25 @@ class VirtualMachine {
3199
3199
  }
3200
3200
 
3201
3201
  // Now all variables that get a tableau column in each time step have
3202
- // been defined; next step is to add "chunk variables"
3202
+ // been defined; next step is to add "chunk variables".
3203
3203
  let cvi = 0;
3204
- // Add *two* chunk variables for processes having a peak increase link
3204
+ // Add *two* chunk variables for processes having a peak increase link.
3205
3205
  for(i = 0; i < process_keys.length; i++) {
3206
3206
  k = process_keys[i];
3207
3207
  p = MODEL.processes[k];
3208
3208
  if(!MODEL.ignored_entities[k] && p.needsMaximumData) {
3209
- // "peak increase" for block
3209
+ // First variable: "peak increase" for block.
3210
3210
  p.peak_inc_var_index = cvi;
3211
3211
  this.chunk_variables.push(['b-peak', p]);
3212
3212
  cvi++;
3213
- // additional "peak increase" for the look-ahead period
3214
- // NOTE: no need to record the second index as it wil allways be
3215
- // equal to block peak index + 1
3213
+ // Additional "peak increase" for the look-ahead period.
3214
+ // NOTE: No need to record the second index as it wil allways be
3215
+ // equal to block peak index + 1.
3216
3216
  this.chunk_variables.push(['la-peak', p]);
3217
3217
  cvi++;
3218
3218
  }
3219
3219
  }
3220
- // Do likewise for such products
3220
+ // Do likewise for such products.
3221
3221
  for(i = 0; i < product_keys.length; i++) {
3222
3222
  k = product_keys[i];
3223
3223
  p = MODEL.products[k];
@@ -3230,12 +3230,13 @@ class VirtualMachine {
3230
3230
  }
3231
3231
  }
3232
3232
 
3233
- // Now *all* variables have been defined; next step is to set their bounds
3233
+ // Now *all* variables have been defined. The next step is to set
3234
+ // their bounds.
3234
3235
 
3235
- // NOTE: chunk variables of node `p` have LB = 0 and UB = UB of `p`;
3236
- // this is effectuated by the VM "set bounds" instructions at run time
3236
+ // NOTE: Chunk variables of node `p` have LB = 0 and UB = UB of `p`.
3237
+ // This is effectuated by the VM "set bounds" instructions at run time.
3237
3238
 
3238
- // NOTE: under normal assumptions (all processes having LB >= 0), bounds on
3239
+ // NOTE: Under normal assumptions (all processes having LB >= 0), bounds on
3239
3240
  // actor cash flow variables need NOT be set because cash IN and cash OUT
3240
3241
  // will then always be >= 0 (solver's default bounds).
3241
3242
  // However, Linny-R does not prohibit negative bounds on processes, nor
@@ -3243,10 +3244,10 @@ class VirtualMachine {
3243
3244
  // cash OUT of all actors are both allowed to become negative.
3244
3245
  for(i = 0; i < actor_keys.length; i++) {
3245
3246
  const a = MODEL.actors[actor_keys[i]];
3246
- // NOTE: add fourth parameter TRUE to signal that the SOLVER's
3247
+ // NOTE: Add fourth parameter TRUE to signal that the SOLVER's
3247
3248
  // infinity constants should be used, as this is likely to be more
3248
3249
  // efficient, while cash flows are inferred properties and will not
3249
- // result in an "unbounded problem" error message from the solver
3250
+ // result in an "unbounded problem" error message from the solver.
3250
3251
  this.code.push(
3251
3252
  [VMI_set_bounds, [a.cash_in_var_index,
3252
3253
  VM.MINUS_INFINITY, VM.PLUS_INFINITY, true]],
@@ -3255,9 +3256,9 @@ class VirtualMachine {
3255
3256
  );
3256
3257
  }
3257
3258
 
3258
- // NEXT: Define the bounds for all production level variables
3259
- // NOTE: the VM instructions check dynamically whether the variable index
3260
- // is listed as "fixed" for the round that is being solved
3259
+ // NEXT: Define the bounds for all production level variables.
3260
+ // NOTE: The VM instructions check dynamically whether the variable
3261
+ // index is listed as "fixed" for the round that is being solved.
3261
3262
  for(i = 0; i < process_keys.length; i++) {
3262
3263
  k = process_keys[i];
3263
3264
  if(!MODEL.ignored_entities[k]) {
@@ -3411,13 +3412,13 @@ class VirtualMachine {
3411
3412
  } else if(l.multiplier === VM.LM_ZERO) {
3412
3413
  vi = p.is_zero_var_index;
3413
3414
  }
3414
- // NOTE: "throughput", "spinning reserve" and "peak increase" are
3415
- // special cases that send a different parameter list
3415
+ // NOTE: "throughput", "spinning reserve" and "peak increase"
3416
+ // are special cases that send a different parameter list.
3416
3417
  if(l.multiplier === VM.LM_THROUGHPUT) {
3417
3418
  // When throughput is read from process Y, calculation
3418
3419
  // is simple: no delays, so the flow over link `l`
3419
3420
  // equals the (sum of all Ri) times the level of Y
3420
- // times the rate of `l`
3421
+ // times the rate of `l`.
3421
3422
  for(k = 0; k < l.from_node.inputs.length; j++) {
3422
3423
  ll = l.from_node.inputs[k];
3423
3424
  // NOTE: no attempt for efficiency -- assume that
@@ -3441,7 +3442,7 @@ class VirtualMachine {
3441
3442
  // of the block being optimized, and in the first step of the
3442
3443
  // look-ahead period (if peak rises in that period), and will
3443
3444
  // be 0 in all other time steps; the VM instruction handles this
3444
- // NOTE: delay is always 0 for this link flow
3445
+ // NOTE: Delay is always 0 for this link flow.
3445
3446
  this.code.push([VMI_update_cash_coefficient, [
3446
3447
  VM.PRODUCE, VM.PEAK_INC, p.peak_inc_var_index, 0,
3447
3448
  tnpx, l.relative_rate]]);
@@ -4295,7 +4296,7 @@ class VirtualMachine {
4295
4296
  cv = this.chunk_variables[ci - this.chunk_offset];
4296
4297
  }
4297
4298
  // NOTE: Do not scale the coefficient of the cash variable.
4298
- if(!cv[0].startsWith('C')) cc[ci] *= m;
4299
+ if(cv && !cv[0].startsWith('C')) cc[ci] *= m;
4299
4300
  }
4300
4301
  }
4301
4302
  }
@@ -4399,22 +4400,22 @@ class VirtualMachine {
4399
4400
  has_OO = (p.on_off_var_index >= 0),
4400
4401
  has_SU = (p.start_up_var_index >= 0),
4401
4402
  has_SD = (p.shut_down_var_index >= 0);
4402
- // Clear all start-ups and shut-downs at t >= bb
4403
+ // Clear all start-ups and shut-downs at t >= bb.
4403
4404
  if(has_SU) p.resetStartUps(bb);
4404
4405
  if(has_SD) p.resetShutDowns(bb);
4405
- // NOTE: `b` is the index to be used for the vectors
4406
+ // NOTE: `b` is the index to be used for the vectors.
4406
4407
  let b = bb;
4407
- // Iterate over all time steps in this block
4408
- // NOTE: -1 because indices start at 1, but list is zero-based
4408
+ // Iterate over all time steps in this block.
4409
+ // NOTE: -1 because indices start at 1, but list is zero-based.
4409
4410
  let j = -1;
4410
4411
  for(let i = 0; i < abl; i++) {
4411
4412
  p.level[b] = this.checkForInfinity(x[p.level_var_index + j]);
4412
4413
  // @@TO DO: If ON/OFF is relevant, check whether it is correctly inferred
4413
4414
  if(has_OO) {
4414
4415
  if(has_SU) {
4415
- // NOTE: some solvers (Gurobi!) may return real numbers instead of
4416
+ // NOTE: Some solvers (Gurobi!) may return real numbers instead of
4416
4417
  // integers, typically near-zero or near-one, so only consider
4417
- // values near 1 to indicate start-up
4418
+ // values near 1 to indicate start-up.
4418
4419
  if(x[p.start_up_var_index + j] > 0.999) {
4419
4420
  p.start_ups.push(b);
4420
4421
  }
@@ -4425,13 +4426,13 @@ class VirtualMachine {
4425
4426
  }
4426
4427
  }
4427
4428
  }
4428
- // Advance column offset in tableau by the # cols per time step
4429
+ // Advance column offset in tableau by the # cols per time step.
4429
4430
  j += this.cols;
4430
- // Advance to the next time step in this block
4431
+ // Advance to the next time step in this block.
4431
4432
  b++;
4432
4433
  }
4433
4434
  }
4434
- // Set stock levels for all products
4435
+ // Set stock levels for all products.
4435
4436
  for(let o in MODEL.products) if(MODEL.products.hasOwnProperty(o) &&
4436
4437
  !MODEL.ignored_entities[o]) {
4437
4438
  const
@@ -4439,23 +4440,23 @@ class VirtualMachine {
4439
4440
  has_OO = (p.on_off_var_index >= 0),
4440
4441
  has_SU = (p.start_up_var_index >= 0),
4441
4442
  has_SD = (p.shut_down_var_index >= 0);
4442
- // Clear all start-ups and shut-downs at t >= bb
4443
+ // Clear all start-ups and shut-downs at t >= bb.
4443
4444
  if(has_SU) p.resetStartUps(bb);
4444
4445
  if(has_SD) p.resetShutDowns(bb);
4445
4446
  let b = bb;
4446
- // Iterate over all time steps in this block
4447
+ // Iterate over all time steps in this block.
4447
4448
  let j = -1;
4448
4449
  for(let i = 0; i < abl; i++) {
4449
4450
  p.level[b] = this.checkForInfinity(x[p.level_var_index + j]);
4450
4451
  // @@TO DO: If ON/OFF is relevant, check whether it is correctly inferred
4451
4452
  if(has_OO) {
4452
- // Check if start-up variable is set (see NOTE above)
4453
+ // Check if start-up variable is set (see NOTE above).
4453
4454
  if(has_SU) {
4454
4455
  if(x[p.start_up_var_index + j] > 0.999) {
4455
4456
  p.start_ups.push(b);
4456
4457
  }
4457
4458
  }
4458
- // Same for shut-down variable
4459
+ // Same for shut-down variable.
4459
4460
  if(has_SD) {
4460
4461
  if(x[p.shut_down_var_index + j] > 0.999) {
4461
4462
  p.shut_downs.push(b);
@@ -4475,11 +4476,11 @@ class VirtualMachine {
4475
4476
  p.b_peak_inc[block] = x[offset + i];
4476
4477
  i++;
4477
4478
  p.la_peak_inc[block] = x[offset + i];
4478
- // Compute the peak from the peak increase
4479
+ // Compute the peak from the peak increase.
4479
4480
  p.b_peak[block] = p.b_peak[block - 1] + p.b_peak_inc[block];
4480
4481
  }
4481
- // Add warning to messages if slack has been used
4482
- // NOTE: only check after the last round has been evaluated
4482
+ // Add warning to messages if slack has been used.
4483
+ // NOTE: Only check after the last round has been evaluated.
4483
4484
  if(round === this.lastRound) {
4484
4485
  let b = bb;
4485
4486
  // Iterate over all time steps in this block
@@ -4533,25 +4534,27 @@ class VirtualMachine {
4533
4534
  // optimization period, hence start by calculating the offset `bb`
4534
4535
  // being the first time step of this block.
4535
4536
  // Blocks are numbered 1, 2, ...
4536
- const bb = (block - 1) * MODEL.block_length + 1;
4537
+ const
4538
+ bb = (block - 1) * MODEL.block_length + 1,
4539
+ cbl = this.actualBlockLength(block);
4537
4540
 
4538
4541
  // FIRST: Calculate the actual flows on links.
4539
4542
  let b, bt, p, pl, ld;
4540
4543
  for(let l in MODEL.links) if(MODEL.links.hasOwnProperty(l) &&
4541
4544
  !MODEL.ignored_entities[l]) {
4542
4545
  l = MODEL.links[l];
4543
- // NOTE: flow is determined by the process node, or in case
4544
- // of a P -> P data link by the FROM product node
4546
+ // NOTE: Flow is determined by the process node, or in case
4547
+ // of a P -> P data link by the FROM product node.
4545
4548
  p = (l.to_node instanceof Process ? l.to_node : l.from_node);
4546
4549
  b = bb;
4547
- // Iterate over all time steps in this chunk
4548
- for(let i = 0; i < this.chunk_length; i++) {
4549
- // NOTE: flows may have a delay!
4550
+ // Iterate over all time steps in this chunk.
4551
+ for(let i = 0; i < cbl; i++) {
4552
+ // NOTE: Flows may have a delay!
4550
4553
  ld = l.actualDelay(b);
4551
4554
  bt = b - ld;
4552
- // NOTE: use non-zero level here to ignore non-zero values that
4555
+ // NOTE: Use non-zero level here to ignore non-zero values that
4553
4556
  // are very small relative to the bounds on the process
4554
- // (typically values below the non-zero tolerance of the solver)
4557
+ // (typically values below the non-zero tolerance of the solver).
4555
4558
  pl = p.nonZeroLevel(bt);
4556
4559
  if(l.multiplier === VM.LM_SPINNING_RESERVE) {
4557
4560
  pl = (pl > VM.NEAR_ZERO ? p.upper_bound.result(bt) - pl : 0);
@@ -4560,30 +4563,56 @@ class VirtualMachine {
4560
4563
  } else if(l.multiplier === VM.LM_ZERO) {
4561
4564
  pl = (Math.abs(pl) < VM.NEAR_ZERO ? 1 : 0);
4562
4565
  } else if(l.multiplier === VM.LM_STARTUP) {
4563
- // NOTE: ignore level; only check whether the start-up variable is set
4566
+ // NOTE: For start-up, first commit and shut-down, the level
4567
+ // can be ignored, as it suffices to check whether time step
4568
+ // `bt` occurs in the list of start-up time steps.
4564
4569
  pl = (p.start_ups.indexOf(bt) < 0 ? 0 : 1);
4565
4570
  } else if(l.multiplier === VM.LM_FIRST_COMMIT) {
4566
- // NOTE: ignore level; only check whether FIRST start-up occurred at bt
4571
+ // NOTE: Here, check whether FIRST start-up occurred at `bt`.
4572
+ // This means that `bt` must be the *first* value in the list.
4567
4573
  pl = (p.start_ups.indexOf(bt) === 0 ? 1 : 0);
4568
4574
  } else if(l.multiplier === VM.LM_SHUTDOWN) {
4569
- // NOTE: ignore level; only check whether the shut_down variable is set
4575
+ // Similar to STARTUP, but now look in the shut-down list.
4570
4576
  pl = (p.shut_downs.indexOf(bt) < 0 ? 0 : 1);
4571
4577
  } else if(l.multiplier === VM.LM_INCREASE) {
4572
- pl -= p.actualLevel(bt - 1);
4578
+ pl = VM.keepException(pl, pl - p.actualLevel(bt - 1));
4573
4579
  } else if(l.multiplier === VM.LM_SUM || l.multiplier === VM.LM_MEAN) {
4574
- for(let j = 0; j < ld; j++) {
4575
- pl += p.actualLevel(b - j);
4580
+ // Level for `bt` counts as first value.
4581
+ let count = 1;
4582
+ // NOTE: Link delay may be < 0!
4583
+ if(ld < 0) {
4584
+ // NOTE: Actual levels beyond end of period are undefined,
4585
+ // and should be ignored while summing / averaging.
4586
+ if(bt >= p.level.length) pl = 0;
4587
+ // If so, take sum over t, t+1, ..., t+(d-1).
4588
+ for(let j = ld + 1; j <= 0; j++) {
4589
+ // Again: ignore levels beyond end of period.
4590
+ if(b - j < p.level.length) {
4591
+ pl = VM.keepException(pl, pl + p.actualLevel(b - j));
4592
+ count++;
4593
+ }
4594
+ }
4595
+ } else {
4596
+ // If d > 0, take sum over t, t-1, ..., t-(d-1).
4597
+ for(let j = 0; j < ld; j++) {
4598
+ // NOTE: Actual levels before t=0 are considered equal to
4599
+ // the initial level, and hence should NOT be ignored.
4600
+ pl = VM.keepException(pl, pl + p.actualLevel(b - j));
4601
+ count++;
4602
+ }
4576
4603
  }
4577
- if(l.multiplier === VM.LM_MEAN && ld > 0) {
4578
- pl /= (ld + 1);
4604
+ if(l.multiplier === VM.LM_MEAN && count > 1) {
4605
+ // Average if more than 1 values have been summed.
4606
+ pl = VM.keepException(pl, pl / count);
4579
4607
  }
4580
4608
  } else if(l.multiplier === VM.LM_THROUGHPUT) {
4581
4609
  // NOTE: calculate throughput on basis of levels and rates,
4582
4610
  // as not all actual flows may have been computed yet
4583
4611
  pl = 0;
4584
4612
  for(let j = 0; j < p.inputs.length; j++) {
4585
- pl += (p.inputs[j].from_node.actualLevel(bt) *
4586
- p.inputs[j].relative_rate.result(bt));
4613
+ pl = VM.keepException(pl,
4614
+ pl + (p.inputs[j].from_node.actualLevel(bt) *
4615
+ p.inputs[j].relative_rate.result(bt)));
4587
4616
  }
4588
4617
  } else if(l.multiplier === VM.LM_PEAK_INC) {
4589
4618
  // Actual flow over "peak increase" link is zero unless...
@@ -4591,27 +4620,32 @@ class VirtualMachine {
4591
4620
  // first time step, then "block peak increase"...
4592
4621
  pl = p.b_peak_inc[block];
4593
4622
  } else if(i === MODEL.block_length) {
4594
- // or first step of look-ahead, then "additional increase"
4623
+ // ... or first step of look-ahead, then "additional increase".
4595
4624
  pl = p.la_peak_inc[block];
4596
4625
  } else {
4597
4626
  pl = 0;
4598
4627
  }
4599
4628
  }
4600
- // Preserve special values such as INF, UNDEFINED and VM error codes
4629
+ // Preserve special values such as INF, UNDEFINED and VM error codes.
4601
4630
  if(pl <= VM.MINUS_INFINITY || pl > VM.PLUS_INFINITY) {
4602
4631
  l.actual_flow[b] = pl;
4603
4632
  } else {
4604
- const af = pl * l.relative_rate.result(bt);
4605
- l.actual_flow[b] = (Math.abs(af) > VM.NEAR_ZERO ? af : 0);
4633
+ const rr = l.relative_rate.result(bt);
4634
+ if(rr <= VM.MINUS_INFINITY || rr > VM.PLUS_INFINITY) {
4635
+ l.actual_flow[b] = rr;
4636
+ } else {
4637
+ const af = rr * pl;
4638
+ l.actual_flow[b] = (Math.abs(af) > VM.NEAR_ZERO ? af : 0);
4639
+ }
4606
4640
  }
4607
4641
  b++;
4608
4642
  }
4609
4643
  }
4610
4644
 
4611
- // THEN: calculate cash flows one step at a time because of delays
4645
+ // THEN: Calculate cash flows one step at a time because of delays.
4612
4646
  b = bb;
4613
- for(let i = 0; i < this.chunk_length; i++) {
4614
- // Initialize cumulative cash flows for clusters
4647
+ for(let i = 0; i < cbl; i++) {
4648
+ // Initialize cumulative cash flows for clusters.
4615
4649
  for(let o in MODEL.clusters) if(MODEL.clusters.hasOwnProperty(o) &&
4616
4650
  !MODEL.ignored_entities[o]) {
4617
4651
  const c = MODEL.clusters[o];
@@ -4619,14 +4653,14 @@ class VirtualMachine {
4619
4653
  c.cash_out[b] = 0;
4620
4654
  c.cash_flow[b] = 0;
4621
4655
  }
4622
- // NOTE: cash flows ONLY result from processes
4656
+ // NOTE: Cash flows ONLY result from processes.
4623
4657
  for(let o in MODEL.processes) if(MODEL.processes.hasOwnProperty(o) &&
4624
4658
  !MODEL.ignored_entities[o]) {
4625
4659
  const p = MODEL.processes[o];
4626
4660
  let ci = 0, co = 0;
4627
4661
  // INPUT links from priced products generate cash OUT...
4628
4662
  for(let j = 0; j < p.inputs.length; j++) {
4629
- // NOTE: input links do NOT have a delay
4663
+ // NOTE: Input links do NOT have a delay.
4630
4664
  const l = p.inputs[j],
4631
4665
  af = l.actual_flow[b],
4632
4666
  fnp = l.from_node.price;
@@ -4634,7 +4668,7 @@ class VirtualMachine {
4634
4668
  const pp = fnp.result(b);
4635
4669
  if(pp > 0 && pp < VM.PLUS_INFINITY) {
4636
4670
  co += pp * af;
4637
- // ... unless the product price is negative; then cash IN
4671
+ // ... unless the product price is negative; then cash IN.
4638
4672
  } else if(pp < 0 && pp > VM.MINUS_INFINITY) {
4639
4673
  ci -= pp * af;
4640
4674
  }
@@ -4642,27 +4676,27 @@ class VirtualMachine {
4642
4676
  }
4643
4677
  // OUTPUT links to priced products generate cash IN ...
4644
4678
  for(let j = 0; j < p.outputs.length; j++) {
4645
- // NOTE: actual flows already consider delay
4679
+ // NOTE: Actual flows already consider delay!
4646
4680
  const l = p.outputs[j],
4647
4681
  ld = l.actualDelay(b),
4648
4682
  af = l.actual_flow[b],
4649
4683
  tnp = l.to_node.price;
4650
4684
  if(af > VM.NEAR_ZERO && tnp.defined) {
4651
- // NOTE: to get the correct price, again consider delays
4685
+ // NOTE: To get the correct price, again consider delays.
4652
4686
  const pp = tnp.result(b - ld);
4653
4687
  if(pp > 0 && pp < VM.PLUS_INFINITY) {
4654
4688
  ci += pp * af;
4655
- // ... unless the product price is negative; then cash OUT
4689
+ // ... unless the product price is negative; then cash OUT.
4656
4690
  } else if(pp < 0 && pp > VM.MINUS_INFINITY) {
4657
4691
  co -= pp * af;
4658
4692
  }
4659
4693
  }
4660
4694
  }
4661
- // Cash flows of process p are now known
4695
+ // Cash flows of process p are now known.
4662
4696
  p.cash_in[b] = ci;
4663
4697
  p.cash_out[b] = co;
4664
4698
  p.cash_flow[b] = ci - co;
4665
- // Also add these flows to all parent clusters of the process
4699
+ // Also add these flows to all parent clusters of the process.
4666
4700
  let c = p.cluster;
4667
4701
  while(c) {
4668
4702
  c.cash_in[b] += ci;
@@ -4674,50 +4708,51 @@ class VirtualMachine {
4674
4708
  b++;
4675
4709
  }
4676
4710
 
4677
- // THEN: if cost prices should be inferred, calculate them one step at a
4678
- // time because of delays, and also because expressions may refer to values
4679
- // for earlier time steps
4711
+ // THEN: If cost prices should be inferred, calculate them one step
4712
+ // at a time because of delays, and also because expressions may refer
4713
+ // to values for earlier time steps.
4680
4714
  if(MODEL.infer_cost_prices) {
4681
4715
  b = bb;
4682
- for(let i = 0; i < this.chunk_length; i++) {
4716
+ for(let i = 0; i < cbl; i++) {
4683
4717
  if(!MODEL.calculateCostPrices(b)) {
4684
4718
  this.logMessage(block, `${this.WARNING}(t=${b}) ` +
4685
4719
  'Invalid cost prices due to negative flow(s)');
4686
4720
  }
4687
- // move to the next time step of the block
4721
+ // Move on to the next time step of the block.
4688
4722
  b++;
4689
4723
  }
4690
4724
  }
4691
4725
 
4692
- // THEN: reset all datasets that serve as "formulas"
4726
+ // THEN: Reset all datasets that are outcomes or serve as "formulas".
4693
4727
  for(let o in MODEL.datasets) if(MODEL.datasets.hasOwnProperty(o)) {
4694
4728
  const ds = MODEL.datasets[o];
4695
- // NOTE: assume that datasets having modifiers but no data serve as
4696
- // "formulas", i.e., expressions to be calculated AFTER a model run
4697
- if(ds.data.length === 0) {
4729
+ // NOTE: Assume that datasets having modifiers but no data serve as
4730
+ // "formulas", i.e., expressions to be calculated AFTER a model run.
4731
+ // This will automatically include the equations dataset.
4732
+ if(ds.outcome || ds.data.length === 0) {
4698
4733
  for(let m in ds.modifiers) if(ds.modifiers.hasOwnProperty(m)) {
4699
4734
  ds.modifiers[m].expression.reset();
4700
4735
  }
4701
4736
  }
4702
4737
  }
4703
4738
 
4704
- // THEN: reset the vectors of all chart variables
4739
+ // THEN: Reset the vectors of all chart variables.
4705
4740
  for(let i = 0; i < MODEL.charts.length; i++) {
4706
4741
  MODEL.charts[i].resetVectors();
4707
4742
  }
4708
4743
 
4709
- // Update the chart dialog if it is visible
4710
- // NOTE: do NOT do this while an experiment is running, as this may
4711
- // interfere with storing the run results
4744
+ // Update the chart dialog if it is visible.
4745
+ // NOTE: Do NOT do this while an experiment is running, as this may
4746
+ // interfere with storing the run results.
4712
4747
  if(!MODEL.running_experiment) {
4713
4748
  if(CHART_MANAGER.visible) CHART_MANAGER.updateDialog();
4714
4749
  }
4715
4750
 
4716
- // NOTE: add a blank line to separate from next round (if any)
4751
+ // NOTE: Add a blank line to separate from next round (if any).
4717
4752
  this.logMessage(block,
4718
4753
  `Calculating dependent variables took ${this.elapsedTime} seconds.\n`);
4719
4754
 
4720
- // FINALLY: reset the vectors of all note colors
4755
+ // FINALLY: Reset the vectors of all note colors.
4721
4756
  for(let o in MODEL.clusters) if(MODEL.clusters.hasOwnProperty(o)) {
4722
4757
  const c = MODEL.clusters[o];
4723
4758
  for(let i = 0; i < c.notes.length; i++) {
@@ -4871,7 +4906,8 @@ class VirtualMachine {
4871
4906
  this.stopSolving();
4872
4907
  return;
4873
4908
  }
4874
- // NOTE: save an additional call when less than 20% of a segment would remain
4909
+ // NOTE: Save an additional call when less than 20% of a segment would
4910
+ // remain.
4875
4911
  var l;
4876
4912
  const next_start = (start + this.tsl * 1.2 < abl ? start + this.tsl : abl);
4877
4913
  for(let i = start; i < next_start; i++) {
@@ -4879,16 +4915,16 @@ class VirtualMachine {
4879
4915
  l = this.code.length;
4880
4916
  for(let j = 0; j < l; j++) {
4881
4917
  this.IP = j;
4882
- // Execute the instruction, which has form [function, argument list]
4918
+ // Execute the instruction, which has form [function, argument list].
4883
4919
  const instr = this.code[j];
4884
4920
  instr[0](instr[1]);
4885
- // Trace the result when debugging
4921
+ // Trace the result when debugging.
4886
4922
  this.logTrace([(' ' + j).slice(-5), ': coeff = ',
4887
4923
  JSON.stringify(this.coefficients), '; rhs = ', this.rhs].join(''));
4888
4924
  }
4889
4925
  this.logTrace('STOP executing block code');
4890
- // Add constraints for paced process variables
4891
- // NOTE: this is effectuated by *executing* VM instructions
4926
+ // Add constraints for paced process variables.
4927
+ // NOTE: This is effectuated by *executing* VM instructions.
4892
4928
  for(let j in this.paced_var_indices) if(Number(j)) {
4893
4929
  const
4894
4930
  // p is the pace (number of time steps)
@@ -5011,13 +5047,13 @@ class VirtualMachine {
5011
5047
  // the simulation period.
5012
5048
  const
5013
5049
  abl = this.actualBlockLength(this.block_count),
5014
- // Get the number digits for variable names
5050
+ // Get the number digits for variable names.
5015
5051
  z = this.columnsInBlock.toString().length,
5016
- // LP_solve uses semicolon as separator between equations
5052
+ // LP_solve uses semicolon as separator between equations.
5017
5053
  EOL = (cplex ? '\n' : ';\n'),
5018
5054
  // Local function that returns variable symbol (e.g. X001) with
5019
5055
  // its coefficient if specified (e.g., -0.123 X001) in the
5020
- // most compact notation
5056
+ // most compact notation.
5021
5057
  vbl = (index, c=false) => {
5022
5058
  const v = 'X' + index.toString().padStart(z, '0');
5023
5059
  if(c === false) return v; // Only the symbol
@@ -5025,11 +5061,11 @@ class VirtualMachine {
5025
5061
  if(c < 0) return ` ${c} ${v}`; // Number had minus sign
5026
5062
  if(c === 1) return ` +${v}`; // No coefficient needed
5027
5063
  return ` +${c} ${v}`; // Prefix coefficient with +
5028
- // NOTE: this may return +0 X001
5064
+ // NOTE: This may return +0 X001.
5029
5065
  };
5030
5066
 
5031
5067
  this.numeric_issue = '';
5032
- // First add the objective (always MAXimize)
5068
+ // First add the objective (always MAXimize).
5033
5069
  if(cplex) {
5034
5070
  this.lines = 'Maximize\n';
5035
5071
  } else {
@@ -5038,19 +5074,19 @@ class VirtualMachine {
5038
5074
  let c,
5039
5075
  p,
5040
5076
  line = '';
5041
- // NOTE: iterate over ALL columns to maintain variable order
5077
+ // NOTE: Iterate over ALL columns to maintain variable order.
5042
5078
  let n = abl * this.cols + this.chunk_variables.length;
5043
5079
  for(p = 1; p <= n; p++) {
5044
5080
  if(this.objective.hasOwnProperty(p)) {
5045
5081
  c = this.objective[p];
5046
- // Check for numeric issues
5082
+ // Check for numeric issues.
5047
5083
  if (c < VM.MINUS_INFINITY || c > VM.PLUS_INFINITY) {
5048
5084
  this.setNumericIssue(c, p, 'objective function coefficient');
5049
5085
  break;
5050
5086
  }
5051
5087
  line += vbl(p, c);
5052
5088
  }
5053
- // Keep lines under approx. 110 chars
5089
+ // Keep lines under approx. 110 chars.
5054
5090
  if(line.length >= 100) {
5055
5091
  this.lines += line + '\n';
5056
5092
  line = '';
@@ -5058,7 +5094,7 @@ class VirtualMachine {
5058
5094
  }
5059
5095
  this.lines += line + EOL;
5060
5096
  line = '';
5061
- // Add the row constraints
5097
+ // Add the row constraints.
5062
5098
  if(cplex) {
5063
5099
  this.lines += '\nSubject To\n';
5064
5100
  } else {
@@ -5074,7 +5110,7 @@ class VirtualMachine {
5074
5110
  break;
5075
5111
  }
5076
5112
  line += vbl(p, c);
5077
- // Keep lines under approx. 110 chars
5113
+ // Keep lines under approx. 110 chars.
5078
5114
  if(line.length >= 100) {
5079
5115
  this.lines += line + '\n';
5080
5116
  line = '';
@@ -5085,7 +5121,7 @@ class VirtualMachine {
5085
5121
  this.constraint_symbols[this.constraint_types[r]] + ' ' + c + EOL;
5086
5122
  line = '';
5087
5123
  }
5088
- // Add the variable bounds
5124
+ // Add the variable bounds.
5089
5125
  if(cplex) {
5090
5126
  this.lines += '\nBounds\n';
5091
5127
  } else {
@@ -5097,7 +5133,7 @@ class VirtualMachine {
5097
5133
  ub = null;
5098
5134
  if(this.lower_bounds.hasOwnProperty(p)) {
5099
5135
  lb = this.lower_bounds[p];
5100
- // NOTE: for bounds, use the SOLVER values for +/- Infinity
5136
+ // NOTE: For bounds, use the SOLVER values for +/- Infinity.
5101
5137
  if (lb < VM.SOLVER_MINUS_INFINITY || lb > VM.SOLVER_PLUS_INFINITY) {
5102
5138
  this.setNumericIssue(lb, p, 'lower bound');
5103
5139
  break;
@@ -5114,56 +5150,71 @@ class VirtualMachine {
5114
5150
  if(lb === ub) {
5115
5151
  if(lb !== null) line = ` ${vbl(p)} = ${lb}`;
5116
5152
  } else {
5117
- // NOTE: by default, lower bound of variables is 0
5153
+ // NOTE: By default, lower bound of variables is 0.
5118
5154
  line = ` ${vbl(p)}`;
5119
5155
  if(cplex) {
5120
- // Explicitly denote free variables
5156
+ // Explicitly denote free variables.
5121
5157
  if(lb === null && ub === null && !this.is_binary[p]) {
5122
5158
  line += ' free';
5123
5159
  } else {
5124
- // Separate lines for LB and UB if specified
5160
+ // Separate lines for LB and UB if specified.
5125
5161
  if(ub !== null) line += ' <= ' + ub;
5126
5162
  if(lb !== null && lb !== 0) line += `\n ${vbl(p)} >= ${lb}`;
5127
5163
  }
5128
5164
  } else {
5129
- // Bounds can be specified on a single line: lb <= X001 <= ub
5165
+ // Bounds can be specified on a single line: lb <= X001 <= ub.
5130
5166
  if(lb !== null && lb !== 0) line = lb + ' <= ' + line;
5131
5167
  if(ub !== null) line += ' <= ' + ub;
5132
5168
  }
5133
5169
  }
5134
5170
  if(line) this.lines += line + EOL;
5135
5171
  }
5136
- // Add the special variable types
5172
+ // Add the special variable types.
5137
5173
  if(cplex) {
5138
5174
  line = '';
5139
- let scv = 0;
5175
+ let scv = 0,
5176
+ vcnt = 0;
5140
5177
  for(let i in this.is_binary) if(Number(i)) {
5141
5178
  line += ' ' + vbl(i);
5142
5179
  scv++;
5143
- // Max. 10 variables per line
5144
- if(scv >= 10) line += '\n';
5180
+ vcnt++;
5181
+ // Max. 10 variables per line.
5182
+ if(vcnt >= 10) {
5183
+ line += '\n';
5184
+ vcnt = 0;
5185
+ }
5145
5186
  }
5146
5187
  if(scv) {
5147
5188
  this.lines += `Binary\n${line}\n`;
5148
5189
  line = '';
5149
5190
  scv = 0;
5191
+ vcnt = 0;
5150
5192
  }
5151
5193
  for(let i in this.is_integer) if(Number(i)) {
5152
5194
  line += ' ' + vbl(i);
5153
5195
  scv++;
5154
- // Max. 10 variables per line
5155
- if(scv >= 10) line += '\n';
5196
+ vcnt++;
5197
+ // Max. 10 variables per line.
5198
+ if(vcnt >= 10) {
5199
+ line += '\n';
5200
+ vcnt = 0;
5201
+ }
5156
5202
  }
5157
5203
  if(scv) {
5158
5204
  this.lines += `General\n${line}\n`;
5159
5205
  line = '';
5160
5206
  scv = 0;
5207
+ vcnt = 0;
5161
5208
  }
5162
5209
  for(let i in this.is_semi_continuous) if(Number(i)) {
5163
5210
  line += ' '+ vbl(i);
5164
5211
  scv++;
5165
- // Max. 10 variables per line
5166
- if(scv >= 10) line += '\n';
5212
+ vcnt++;
5213
+ // Max. 10 variables per line.
5214
+ if(vcnt >= 10) {
5215
+ line += '\n';
5216
+ vcnt = 0;
5217
+ }
5167
5218
  }
5168
5219
  if(scv) {
5169
5220
  this.lines += `Semi-continuous\n${line}\n`;
@@ -5191,7 +5242,7 @@ class VirtualMachine {
5191
5242
  this.lines += 'End';
5192
5243
  } else {
5193
5244
  // NOTE: LP_solve does not differentiate between binary and integer,
5194
- // so for binary variables, the constraint <= 1 must be added
5245
+ // so for binary variables, the constraint <= 1 must be added.
5195
5246
  const v_set = [];
5196
5247
  for(let i in this.is_binary) if(Number(i)) {
5197
5248
  const v = vbl(i);
@@ -5200,12 +5251,12 @@ class VirtualMachine {
5200
5251
  }
5201
5252
  for(let i in this.is_integer) if(Number(i)) v_set.push(vbl(i));
5202
5253
  if(v_set.length > 0) this.lines += 'int ' + v_set.join(', ') + ';\n';
5203
- // Clear the INT variable list
5254
+ // Clear the INT variable list.
5204
5255
  v_set.length = 0;
5205
- // Add the semi-continuous variables
5256
+ // Add the semi-continuous variables.
5206
5257
  for(let i in this.is_semi_continuous) if(Number(i)) v_set.push(vbl(i));
5207
5258
  if(v_set.length > 0) this.lines += 'sec ' + v_set.join(', ') + ';\n';
5208
- // Add the SOS section
5259
+ // Add the SOS section.
5209
5260
  if(this.sos_var_indices.length > 0) {
5210
5261
  this.lines += 'sos\n';
5211
5262
  let sos = 1;
@@ -5258,7 +5309,7 @@ class VirtualMachine {
5258
5309
  for(c = 1; c <= ncol; c++) cols.push([]);
5259
5310
  this.decimals = Math.max(nrow, ncol).toString().length;
5260
5311
  this.lines += 'NAME block-' + this.blockWithRound + '\nROWS\n';
5261
- // Start with the "free" row that will be the objective function
5312
+ // Start with the "free" row that will be the objective function.
5262
5313
  this.lines += ' N OBJ\n';
5263
5314
  for(r = 0; r < nrow; r++) {
5264
5315
  const
@@ -5268,7 +5319,7 @@ class VirtualMachine {
5268
5319
  ' ' + row_lbl + '\n';
5269
5320
  for(p in row) if (row.hasOwnProperty(p)) {
5270
5321
  c = row[p];
5271
- // Check for numeric issues
5322
+ // Check for numeric issues.
5272
5323
  if(c === undefined || c < VM.SOLVER_MINUS_INFINITY ||
5273
5324
  c > VM.SOLVER_PLUS_INFINITY) {
5274
5325
  this.setNumericIssue(c, p, 'constraint');
@@ -5287,47 +5338,47 @@ class VirtualMachine {
5287
5338
  rhs.push(' B ' + row_lbl + ' ' + c);
5288
5339
  }
5289
5340
  }
5290
- // The objective function is a row like those for the constraints
5341
+ // The objective function is a row like those for the constraints.
5291
5342
  for(p in this.objective) if(this.objective.hasOwnProperty(p)) {
5292
5343
  c = this.objective[p];
5293
5344
  if(c === null || c < VM.MINUS_INFINITY || c > VM.PLUS_INFINITY) {
5294
5345
  this.setNumericIssue(c, p, 'objective function coefficient');
5295
5346
  break;
5296
5347
  }
5297
- // NOTE: MPS assumes MINimization, hence negate all coefficients
5298
- // NOTE: JavaScript differentiates between 0 and -0, so add 0 to prevent
5299
- // creating the special numeric value -0
5348
+ // NOTE: MPS assumes MINimization, hence negate all coefficients.
5349
+ // NOTE: JavaScript differentiates between 0 and -0, so add 0 to
5350
+ // prevent creating the special numeric value -0.
5300
5351
  cols[p].push('OBJ ' + (-c + 0));
5301
5352
  }
5302
- // Abort if any invalid coefficient was detected
5353
+ // Abort if any invalid coefficient was detected.
5303
5354
  if(this.numeric_issue) {
5304
5355
  this.hideSetUpOrWriteProgress();
5305
5356
  this.stopSolving();
5306
5357
  return;
5307
5358
  }
5308
- // Add the columns section
5359
+ // Add the columns section.
5309
5360
  this.lines += 'COLUMNS\n';
5310
5361
  for(c = 1; c <= ncol; c++) {
5311
5362
  const col_lbl = ' X' + c.toString().padStart(this.decimals, '0') + ' ';
5312
- // NOTE: if processes have no in- or outgoing links their decision
5363
+ // NOTE: If processes have no in- or outgoing links their decision
5313
5364
  // variable does not occur in any constraint, and this may cause
5314
5365
  // problems for solvers that cannot handle columns having a blank
5315
5366
  // row name (e.g., CPLEX). To prevent errors, these columns are
5316
- // given coefficient 0 in the OBJ row
5367
+ // given coefficient 0 in the OBJ row.
5317
5368
  if(cols[c].length) {
5318
5369
  this.lines += col_lbl + cols[c].join('\n' + col_lbl) + '\n';
5319
5370
  } else {
5320
5371
  this.lines += col_lbl + ' OBJ 0\n';
5321
5372
  }
5322
5373
  }
5323
- // Free up memory
5374
+ // Free up memory.
5324
5375
  cols.length = 0;
5325
- // Add the RHS section
5376
+ // Add the RHS section.
5326
5377
  this.lines += 'RHS\n' + rhs.join('\n') + '\n';
5327
5378
  rhs.length = 0;
5328
- // Add the BOUNDS section
5379
+ // Add the BOUNDS section.
5329
5380
  this.lines += 'BOUNDS\n';
5330
- // NOTE: start at column number 1 (not 0)
5381
+ // NOTE: Start at column number 1, not 0.
5331
5382
  setTimeout((c, n) => VM.showMPSProgress(c, n), 0, 1, ncol);
5332
5383
  }
5333
5384
 
@@ -5338,7 +5389,7 @@ class VirtualMachine {
5338
5389
  return;
5339
5390
  }
5340
5391
  if(this.show_progress) {
5341
- // NOTE: display 1 block more progress, or the bar never reaches 100%
5392
+ // NOTE: Display 1 block more progress, or the bar never reaches 100%.
5342
5393
  UI.setProgressNeedle((next_col + this.cbl) / ncol);
5343
5394
  }
5344
5395
  setTimeout((c, n) => VM.writeMPSColumns(c, n), 0, next_col, ncol);
@@ -5356,7 +5407,7 @@ class VirtualMachine {
5356
5407
  ub = null;
5357
5408
  if(this.lower_bounds.hasOwnProperty(p)) {
5358
5409
  lb = this.lower_bounds[p];
5359
- // NOTE: for bounds, use the SOLVER values for +/- Infinity
5410
+ // NOTE: For bounds, use the SOLVER values for +/- Infinity.
5360
5411
  if(lb < VM.SOLVER_MINUS_INFINITY || lb > VM.PLUS_INFINITY) {
5361
5412
  this.setNumericIssue(lb, p, 'lower bound');
5362
5413
  break;
@@ -5392,7 +5443,7 @@ class VirtualMachine {
5392
5443
  } else if(lb !== null && lb === ub && !semic) {
5393
5444
  this.lines += ' FX' + bnd + lb + '\n';
5394
5445
  } else {
5395
- // Assume "standard" bounds
5446
+ // Assume "standard" bounds.
5396
5447
  lbc = ' LO';
5397
5448
  ubc = ' UP';
5398
5449
  if(p in this.is_integer) {
@@ -5403,7 +5454,7 @@ class VirtualMachine {
5403
5454
  } else if(semic) {
5404
5455
  ubc = ' SC';
5405
5456
  }
5406
- // NOTE: by default, lower bound of variables is 0
5457
+ // NOTE: by default, lower bound of variables is 0.
5407
5458
  if(lb !== null && lb !== 0 || lbc !== ' LO') {
5408
5459
  this.lines += lbc + bnd + lb + '\n';
5409
5460
  }
@@ -5412,7 +5463,7 @@ class VirtualMachine {
5412
5463
  }
5413
5464
  }
5414
5465
  }
5415
- // Abort if any invalid coefficient was detected
5466
+ // Abort if any invalid coefficient was detected.
5416
5467
  if(this.numeric_issue) this.submitFile();
5417
5468
  if(next_col <= ncol) {
5418
5469
  setTimeout((c, n) => VM.showMPSProgress(c, n), 0, next_col, ncol);
@@ -5631,7 +5682,6 @@ Solver status = ${json.status}`);
5631
5682
  }
5632
5683
  // Generate lines of code in format that should be accepted by solver.
5633
5684
  if(this.solver_name === 'gurobi') {
5634
- //this.writeMPSFormat();
5635
5685
  this.writeLpFormat(true);
5636
5686
  } else if(this.solver_name === 'scip' || this.solver_name === 'cplex') {
5637
5687
  // NOTE: The CPLEX LP format that is also used by SCIP differs from
@@ -5697,8 +5747,8 @@ Solver status = ${json.status}`);
5697
5747
  }
5698
5748
 
5699
5749
  solveModel() {
5700
- // Starts the sequence of data loading, model translation, solving
5701
- // consecutive blocks, and finally calculating dependent variables
5750
+ // Start the sequence of data loading, model translation, solving
5751
+ // consecutive blocks, and finally calculating dependent variables.
5702
5752
  const n = MODEL.loading_datasets.length;
5703
5753
  if(n > 0) {
5704
5754
  // Still within reasonable time? (3 seconds per dataset)
@@ -5711,7 +5761,7 @@ Solver status = ${json.status}`);
5711
5761
  setTimeout(() => VM.solveModel(), 500);
5712
5762
  return;
5713
5763
  } else {
5714
- // Wait no longer, but warn user that data may be incomplete
5764
+ // Wait no longer, but warn user that data may be incomplete.
5715
5765
  const dsl = [];
5716
5766
  for(let i = 0; i < MODEL.loading_datasets.length; i++) {
5717
5767
  dsl.push(MODEL.loading_datasets[i].displayName);
@@ -5729,7 +5779,7 @@ Solver status = ${json.status}`);
5729
5779
  }
5730
5780
 
5731
5781
  halt() {
5732
- // Aborts solving process (prevents submitting next block)
5782
+ // Abort solving process. This prevents submitting the next block.
5733
5783
  UI.waitToStop();
5734
5784
  this.halted = true;
5735
5785
  }
@@ -6701,20 +6751,21 @@ function VMI_push_statistic(x, args) {
6701
6751
  }
6702
6752
 
6703
6753
  function VMI_replace_undefined(x) {
6704
- // Replaces one of the two top numbers on the stack by the other if the one
6705
- // is undefined
6706
- const d = x.pop(true); // TRUE denotes that "undefined" should be ignored as issue
6754
+ // Replace one of the two top numbers on the stack by the other if the
6755
+ // one is undefined.
6756
+ // NOTE: pop(TRUE) denotes that "undefined" should be ignored as issue.
6757
+ const d = x.pop(true);
6707
6758
  if(d !== false) {
6708
6759
  if(DEBUGGING) console.log('REPLACE UNDEFINED (' + d.join(', ') + ')');
6709
6760
  x.retop(d[0] === VM.UNDEFINED ? d[1] : d[0]);
6710
6761
  }
6711
6762
  }
6712
6763
 
6713
- // NOTE: when the VM computes logical OR, AND and NOT, any non-zero number
6714
- // is interpreted as TRUE
6764
+ // NOTE: When the VM computes logical OR, AND and NOT, any non-zero number
6765
+ // is interpreted as TRUE.
6715
6766
 
6716
6767
  function VMI_or(x) {
6717
- // Performs a logical OR on the two top numbers on the stack
6768
+ // Perform a logical OR on the two top numbers on the stack.
6718
6769
  const d = x.pop();
6719
6770
  if(d !== false) {
6720
6771
  if(DEBUGGING) console.log('OR (' + d.join(', ') + ')');
@@ -6723,7 +6774,7 @@ function VMI_or(x) {
6723
6774
  }
6724
6775
 
6725
6776
  function VMI_and(x) {
6726
- // Performs a logical AND on the two top numbers on the stack
6777
+ // Perform a logical AND on the two top numbers on the stack.
6727
6778
  const d = x.pop();
6728
6779
  if(d !== false) {
6729
6780
  if(DEBUGGING) console.log('AND (' + d.join(', ') + ')');
@@ -6732,7 +6783,7 @@ function VMI_and(x) {
6732
6783
  }
6733
6784
 
6734
6785
  function VMI_not(x) {
6735
- // Performs a logical NOT on the top number of the stack
6786
+ // Perform a logical NOT on the top number of the stack.
6736
6787
  const d = x.top();
6737
6788
  if(d !== false) {
6738
6789
  if(DEBUGGING) console.log('NOT ' + d);
@@ -6741,7 +6792,7 @@ function VMI_not(x) {
6741
6792
  }
6742
6793
 
6743
6794
  function VMI_abs(x) {
6744
- // Replaces the top number of the stack by its absolute value
6795
+ // Replace the top number of the stack by its absolute value.
6745
6796
  const d = x.top();
6746
6797
  if(d !== false) {
6747
6798
  if(DEBUGGING) console.log('ABS ' + d);
@@ -6750,7 +6801,7 @@ function VMI_abs(x) {
6750
6801
  }
6751
6802
 
6752
6803
  function VMI_eq(x) {
6753
- // Tests equality of the two top numbers on the stack
6804
+ // Test equality of the two top numbers on the stack.
6754
6805
  const d = x.pop();
6755
6806
  if(d !== false) {
6756
6807
  if(DEBUGGING) console.log('EQ (' + d.join(', ') + ')');
@@ -6759,7 +6810,7 @@ function VMI_eq(x) {
6759
6810
  }
6760
6811
 
6761
6812
  function VMI_ne(x) {
6762
- // Tests inequality of the two top numbers on the stack
6813
+ // Test inequality of the two top numbers on the stack.
6763
6814
  const d = x.pop();
6764
6815
  if(d !== false) {
6765
6816
  if(DEBUGGING) console.log('NE (' + d.join(', ') + ')');
@@ -6768,7 +6819,7 @@ function VMI_ne(x) {
6768
6819
  }
6769
6820
 
6770
6821
  function VMI_lt(x) {
6771
- // Tests whether second number on the stack is less than the top number
6822
+ // Test whether second number on the stack is less than the top number.
6772
6823
  const d = x.pop();
6773
6824
  if(d !== false) {
6774
6825
  if(DEBUGGING) console.log('LT (' + d.join(', ') + ')');
@@ -6777,7 +6828,7 @@ function VMI_lt(x) {
6777
6828
  }
6778
6829
 
6779
6830
  function VMI_gt(x) {
6780
- // Tests whether second number on the stack is greater than the top number
6831
+ // Test whether second number on the stack is greater than the top number.
6781
6832
  const d = x.pop();
6782
6833
  if(d !== false) {
6783
6834
  if(DEBUGGING) console.log('GT (' + d.join(', ') + ')');
@@ -6786,8 +6837,8 @@ function VMI_gt(x) {
6786
6837
  }
6787
6838
 
6788
6839
  function VMI_le(x) {
6789
- // Tests whether second number on the stack is less than, or equal to,
6790
- // the top number
6840
+ // Test whether second number on the stack is less than, or equal to,
6841
+ // the top number.
6791
6842
  const d = x.pop();
6792
6843
  if(d !== false) {
6793
6844
  if(DEBUGGING) console.log('LE (' + d.join(', ') + ')');
@@ -6796,8 +6847,8 @@ function VMI_le(x) {
6796
6847
  }
6797
6848
 
6798
6849
  function VMI_ge(x) {
6799
- // Tests whether second number on the stack is greater than, or equal to,
6800
- // the top number
6850
+ // Test whether second number on the stack is greater than, or equal to,
6851
+ // the top number.
6801
6852
  const d = x.pop();
6802
6853
  if(d !== false) {
6803
6854
  if(DEBUGGING) console.log('LE (' + d.join(', ') + ')');
@@ -6806,7 +6857,7 @@ function VMI_ge(x) {
6806
6857
  }
6807
6858
 
6808
6859
  function VMI_add(x) {
6809
- // Pops the top number on the stack and adds it to the new top number
6860
+ // Pop the top number on the stack, and add it to the new top number.
6810
6861
  const d = x.pop();
6811
6862
  if(d !== false) {
6812
6863
  if(DEBUGGING) console.log('ADD (' + d.join(', ') + ')');
@@ -6815,8 +6866,8 @@ function VMI_add(x) {
6815
6866
  }
6816
6867
 
6817
6868
  function VMI_sub(x) {
6818
- // Pops the top number on the stack and subtracts it from the new
6819
- // top number
6869
+ // Pop the top number on the stack, and subtract it from the new
6870
+ // top number.
6820
6871
  const d = x.pop();
6821
6872
  if(d !== false) {
6822
6873
  if(DEBUGGING) console.log('SUB (' + d.join(', ') + ')');
@@ -6825,7 +6876,7 @@ function VMI_sub(x) {
6825
6876
  }
6826
6877
 
6827
6878
  function VMI_mul(x) {
6828
- // Pops the top number on the stack and multiplies it with the new
6879
+ // Pop the top number on the stack, and multiply it with the new
6829
6880
  // top number
6830
6881
  const d = x.pop();
6831
6882
  if(d !== false) {
@@ -6835,8 +6886,8 @@ function VMI_mul(x) {
6835
6886
  }
6836
6887
 
6837
6888
  function VMI_div(x) {
6838
- // Pops the top number on the stack and divides the new top number
6839
- // by it. In case of division by zero, the top is replaced by #DIV/0!
6889
+ // Pop the top number on the stack, and divide the new top number
6890
+ // by it. In case of division by zero, replace the top by #DIV/0!
6840
6891
  const d = x.pop();
6841
6892
  if(d !== false) {
6842
6893
  if(DEBUGGING) console.log('DIV (' + d.join(', ') + ')');
@@ -6849,23 +6900,23 @@ function VMI_div(x) {
6849
6900
  }
6850
6901
 
6851
6902
  function VMI_mod(x) {
6852
- // Pops the top number on the stack, divides the new top number by it
6853
- // (if non-zero, or it pushes error code #DIV/0!), takes the fraction
6854
- // part, and multiplies this with the divider; in other words, it
6855
- // performs a "floating point MOD operation"
6903
+ // Perform a "floating point MOD operation" as explained below.
6904
+ // Pop the top number on the stack. If zero, push error code #DIV/0!.
6905
+ // Otherwise, proceed: divide the new top number by the divisor, take
6906
+ // the fraction part, and multiply this with the divisor.
6856
6907
  const d = x.pop();
6857
6908
  if(d !== false) {
6858
6909
  if(DEBUGGING) console.log('DIV (' + d.join(', ') + ')');
6859
6910
  if(Math.abs(d[1]) <= VM.NEAR_ZERO) {
6860
6911
  x.retop(VM.DIV_ZERO);
6861
6912
  } else {
6862
- x.retop(d[0] % d[1]); // % is the modulo operator in JavaScript
6913
+ x.retop(d[0] % d[1]); // % is the modulo operator in JavaScript.
6863
6914
  }
6864
6915
  }
6865
6916
  }
6866
6917
 
6867
6918
  function VMI_negate(x) {
6868
- // Performs a negation on the top number of the stack
6919
+ // Perform a negation on the top number of the stack.
6869
6920
  const d = x.top();
6870
6921
  if(d !== false) {
6871
6922
  if(DEBUGGING) console.log('NEG ' + d);
@@ -6874,8 +6925,8 @@ function VMI_negate(x) {
6874
6925
  }
6875
6926
 
6876
6927
  function VMI_power(x) {
6877
- // Pops the top number on the stack and raises the new top number
6878
- // to its power
6928
+ // Pop the top number on the stack, and raise the new top number
6929
+ // to its power.
6879
6930
  const d = x.pop();
6880
6931
  if(d !== false) {
6881
6932
  if(DEBUGGING) console.log('POWER (' + d.join(', ') + ')');
@@ -6884,8 +6935,8 @@ function VMI_power(x) {
6884
6935
  }
6885
6936
 
6886
6937
  function VMI_sqrt(x) {
6887
- // Replaces the top number of the stack by its square root, or by
6888
- // error code #VALUE! if the top number is negative
6938
+ // Replace the top number of the stack by its square root, or by
6939
+ // error code #VALUE! if the top number is negative.
6889
6940
  const d = x.top();
6890
6941
  if(d !== false) {
6891
6942
  if(DEBUGGING) console.log('SQRT ' + d);
@@ -6898,7 +6949,7 @@ function VMI_sqrt(x) {
6898
6949
  }
6899
6950
 
6900
6951
  function VMI_sin(x) {
6901
- // Replaces the top number X of the stack by sin(X)
6952
+ // Replace the top number X of the stack by sin(X).
6902
6953
  const d = x.top();
6903
6954
  if(d !== false) {
6904
6955
  if(DEBUGGING) console.log('SIN ' + d);
@@ -6907,7 +6958,7 @@ function VMI_sin(x) {
6907
6958
  }
6908
6959
 
6909
6960
  function VMI_cos(x) {
6910
- // Replaces the top number X of the stack by cos(X)
6961
+ // Replace the top number X of the stack by cos(X).
6911
6962
  const d = x.top();
6912
6963
  if(d !== false) {
6913
6964
  if(DEBUGGING) console.log('COS ' + d);
@@ -6916,7 +6967,7 @@ function VMI_cos(x) {
6916
6967
  }
6917
6968
 
6918
6969
  function VMI_atan(x) {
6919
- // Replaces the top number X of the stack by atan(X)
6970
+ // Replace the top number X of the stack by atan(X).
6920
6971
  const d = x.top();
6921
6972
  if(d !== false) {
6922
6973
  if(DEBUGGING) console.log('ATAN ' + d);
@@ -6925,8 +6976,8 @@ function VMI_atan(x) {
6925
6976
  }
6926
6977
 
6927
6978
  function VMI_ln(x) {
6928
- // Replaces the top number X of the stack by ln(X), or by error
6929
- // code #VALUE! if X is negative
6979
+ // Replace the top number X of the stack by ln(X), or by error
6980
+ // code #VALUE! if X is negative.
6930
6981
  const d = x.top();
6931
6982
  if(d !== false) {
6932
6983
  if(DEBUGGING) console.log('LN ' + d);
@@ -6939,7 +6990,7 @@ function VMI_ln(x) {
6939
6990
  }
6940
6991
 
6941
6992
  function VMI_exp(x) {
6942
- // Replaces the top number X of the stack by exp(X)
6993
+ // Replace the top number X of the stack by exp(X).
6943
6994
  const d = x.top();
6944
6995
  if(d !== false) {
6945
6996
  if(DEBUGGING) console.log('EXP ' + d);
@@ -6948,7 +6999,7 @@ function VMI_exp(x) {
6948
6999
  }
6949
7000
 
6950
7001
  function VMI_log(x) {
6951
- // Pops the top number B from the stack and replaces the new top
7002
+ // Pop the top number B from the stack, and replace the new top
6952
7003
  // number A by A log B. NOTE: x = A log B <=> x = ln(B) / ln(A)
6953
7004
  let d = x.pop();
6954
7005
  if(d !== false) {
@@ -6963,7 +7014,7 @@ function VMI_log(x) {
6963
7014
  }
6964
7015
 
6965
7016
  function VMI_round(x) {
6966
- // Replaces the top number X of the stack by round(X)
7017
+ // Replace the top number X of the stack by round(X).
6967
7018
  const d = x.top();
6968
7019
  if(d !== false) {
6969
7020
  if(DEBUGGING) console.log('ROUND ' + d);
@@ -6972,7 +7023,7 @@ function VMI_round(x) {
6972
7023
  }
6973
7024
 
6974
7025
  function VMI_int(x) {
6975
- // Replaces the top number X of the stack by its integer part
7026
+ // Replace the top number X of the stack by its integer part.
6976
7027
  const d = x.top();
6977
7028
  if(d !== false) {
6978
7029
  if(DEBUGGING) console.log('INT ' + d);
@@ -6981,7 +7032,7 @@ function VMI_int(x) {
6981
7032
  }
6982
7033
 
6983
7034
  function VMI_fract(x) {
6984
- // Replaces the top number X of the stack by its fraction part
7035
+ // Replace the top number X of the stack by its fraction part.
6985
7036
  const d = x.top();
6986
7037
  if(d !== false) {
6987
7038
  if(DEBUGGING) console.log('FRACT ' + d);
@@ -6990,9 +7041,9 @@ function VMI_fract(x) {
6990
7041
  }
6991
7042
 
6992
7043
  function VMI_exponential(x) {
6993
- // Replaces the top number X of the stack by a random number from the
7044
+ // Replace the top number X of the stack by a random number from the
6994
7045
  // negative exponential distribution with parameter X (so X is the lambda,
6995
- // and the mean will be 1/X)
7046
+ // and the mean will be 1/X).
6996
7047
  const d = x.top();
6997
7048
  if(d !== false) {
6998
7049
  const a = randomExponential(d);
@@ -7002,8 +7053,8 @@ function VMI_exponential(x) {
7002
7053
  }
7003
7054
 
7004
7055
  function VMI_poisson(x) {
7005
- // Replaces the top number X of the stack by a random number from the
7006
- // poisson distribution with parameter X (so X is the mean value lambda)
7056
+ // Replace the top number X of the stack by a random number from the
7057
+ // poisson distribution with parameter X (so X is the mean value lambda).
7007
7058
  const d = x.top();
7008
7059
  if(d !== false) {
7009
7060
  const a = randomPoisson(d);
@@ -7013,8 +7064,9 @@ function VMI_poisson(x) {
7013
7064
  }
7014
7065
 
7015
7066
  function VMI_binomial(x) {
7016
- // Replaces the top list (!) A of the stack by Bin(A[0], A[1]), i.e., a random
7017
- // number from the binomial distribution with n = A[0] and p = A[1]
7067
+ // Replace the top list (!) A of the stack by Bin(A[0], A[1]), i.e.,
7068
+ // a random number from the binomial distribution with n = A[0] and
7069
+ // p = A[1].
7018
7070
  const d = x.top();
7019
7071
  if(d !== false) {
7020
7072
  if(d instanceof Array && d.length === 2) {
@@ -7029,8 +7081,9 @@ function VMI_binomial(x) {
7029
7081
  }
7030
7082
 
7031
7083
  function VMI_normal(x) {
7032
- // Replaces the top list (!) A of the stack by N(A[0], A[1]), i.e., a random
7033
- // number from the normal distribution with mu = A[0] and sigma = A[1]
7084
+ // Replace the top list (!) A of the stack by N(A[0], A[1]), i.e.,
7085
+ // a random number from the normal distribution with mu = A[0] and
7086
+ // sigma = A[1].
7034
7087
  const d = x.top();
7035
7088
  if(d !== false) {
7036
7089
  if(d instanceof Array && d.length === 2) {
@@ -7045,8 +7098,9 @@ function VMI_normal(x) {
7045
7098
  }
7046
7099
 
7047
7100
  function VMI_weibull(x) {
7048
- // Replaces the top list (!) A of the stack by Weibull(A[0], A[1]), i.e., a
7049
- // random number from the Weibull distribution with lambda = A[0] and k = A[1]
7101
+ // Replace the top list (!) A of the stack by Weibull(A[0], A[1]), i.e.,
7102
+ // a random number from the Weibull distribution with lambda = A[0]
7103
+ // and k = A[1].
7050
7104
  const d = x.top();
7051
7105
  if(d !== false) {
7052
7106
  if(d instanceof Array && d.length === 2) {
@@ -7061,10 +7115,10 @@ function VMI_weibull(x) {
7061
7115
  }
7062
7116
 
7063
7117
  function VMI_triangular(x) {
7064
- // Replaces the top list (!) A of the stack by Tri(A[0], A[1]), A[2]), i.e.,
7065
- // a random number from the triangular distribution with a = A[0], b = A[1],
7066
- // and c = A[2]. NOTE: if only 2 parameters are passed, c is assumed to equal
7067
- // (a + b) / 2
7118
+ // Replaces the top list (!) A of the stack by Tri(A[0], A[1]), A[2]),
7119
+ // i.e., a random number from the triangular distribution with a = A[0],
7120
+ // b = A[1], and c = A[2]. NOTE: if only 2 parameters are passed, c is
7121
+ // assumed to equal (a + b) / 2.
7068
7122
  const d = x.top();
7069
7123
  if(d !== false) {
7070
7124
  if(d instanceof Array && (d.length === 2 || d.length === 3)) {
@@ -7079,14 +7133,15 @@ function VMI_triangular(x) {
7079
7133
  }
7080
7134
 
7081
7135
  function VMI_npv(x) {
7082
- // Replaces the top list (!) A of the stack by the net present value (NPV)
7083
- // of the arguments in A. A[0] is the interest rate r, A[1] is the number of
7084
- // time periods n. If A has only 1 or 2 elements, the NPV is 0. If A has 3
7085
- // elements, A[2] is the constant cash flow C, and the NPV is the sum
7086
- // (for t = 0 to n-1) of C/(1+r)^t. If A has N>2 elements, A[2] through A[N]
7087
- // are considered as a cash flow time series C0, C1, ..., CN-2 that is then
7088
- // NOTE: if A is not a list, A considered to be the single argument, and is
7089
- // hence replaced by 0
7136
+ // Replace the top list (!) A of the stack by the net present value (NPV)
7137
+ // of the arguments in A. A[0] is the interest rate r, A[1] is the number
7138
+ // of time periods n. If A has only 1 or 2 elements, the NPV is 0.
7139
+ // If A has 3 elements, A[2] is the constant cash flow C, and the NPV is
7140
+ // the sum (for t = 0 to n-1) of C/(1+r)^t. If A has N>2 elements, A[2]
7141
+ // through A[N] are considered as a cash flow time series C0, C1, ..., CN-2
7142
+ // that is then discounted.
7143
+ // NOTE: If A is not a list, A considered to be the single argument, and
7144
+ // is hence replaced by 0.
7090
7145
  const d = x.top();
7091
7146
  if(d !== false) {
7092
7147
  if(d instanceof Array && d.length > 2) {
@@ -7119,8 +7174,8 @@ function VMI_npv(x) {
7119
7174
  }
7120
7175
 
7121
7176
  function VMI_min(x) {
7122
- // Replaces the top list (!) A of the stack by the lowest value in this list
7123
- // NOTE: if A is not a list, A is left on the stack
7177
+ // Replace the top list (!) A of the stack by the lowest value in this
7178
+ // list. If A is not a list, A is left on the stack.
7124
7179
  const d = x.top();
7125
7180
  if(d !== false && d instanceof Array) {
7126
7181
  if(DEBUGGING) console.log('MIN (' + d.join(', ') + ')');
@@ -7131,8 +7186,8 @@ function VMI_min(x) {
7131
7186
  }
7132
7187
 
7133
7188
  function VMI_max(x) {
7134
- // Replaces the top list (!) A of the stack by the highest value in this list
7135
- // NOTE: if A is not a list, A is left on the stack
7189
+ // Replace the top list (!) A of the stack by the highest value in this
7190
+ // list. If A is not a list, A is left on the stack.
7136
7191
  const d = x.top();
7137
7192
  if(d !== false && d instanceof Array) {
7138
7193
  if(DEBUGGING) console.log('MAX (' + d.join(', ') + ')');
@@ -7143,9 +7198,9 @@ function VMI_max(x) {
7143
7198
  }
7144
7199
 
7145
7200
  function VMI_concat(x) {
7146
- // Pops the top number B from the stack, and then replaces the new top
7147
- // element A by [A, B] if A is a number, or adds B to A is A is a list
7148
- // of numbers (!) or
7201
+ // Pop the top number B from the stack, and then replace the new top
7202
+ // element A by [A, B] if A is a number, or add B to A if A is a list
7203
+ // of numbers (!), or concatenate if A and B both are lists.
7149
7204
  const d = x.pop();
7150
7205
  if(d !== false) {
7151
7206
  if(DEBUGGING) console.log('CONCAT (' + d.join(', ') + ')');
@@ -7164,8 +7219,8 @@ function VMI_concat(x) {
7164
7219
  }
7165
7220
 
7166
7221
  function VMI_jump(x, index) {
7167
- // Sets the program counter of the VM to `index` minus 1, as the
7168
- // counter is ALWAYS increased by 1 after calling a VMI function
7222
+ // Set the program counter of the VM to `index` minus 1, as the
7223
+ // counter is ALWAYS increased by 1 after calling a VMI function.
7169
7224
  if(DEBUGGING) console.log('JUMP ' + index);
7170
7225
  x.program_counter = index - 1;
7171
7226
  }
@@ -7179,7 +7234,7 @@ function VMI_jump_if_false(x, index) {
7179
7234
  if(r === 0 || r === VM.UNDEFINED || r === false) {
7180
7235
  // Only jump on FALSE, leaving the stack "as is", so that in case
7181
7236
  // of no THEN, the expression result equals the IF condition value.
7182
- // NOTE: Also do this on a stack error (r === false)
7237
+ // NOTE: Also do this on a stack error (r === false).
7183
7238
  x.program_counter = index - 1;
7184
7239
  } else {
7185
7240
  // Remove the value from the stack.
@@ -7209,22 +7264,22 @@ function VMI_if_else(x) {
7209
7264
  }
7210
7265
 
7211
7266
  //
7212
- // Functions that implement random numbers from specific distribution
7267
+ // Functions that implement random numbers from specific distribution.
7213
7268
  //
7214
7269
 
7215
7270
  function randomExponential(lambda) {
7216
- // Returns a random number drawn from a Exp(lambda) distribution
7271
+ // Return a random number drawn from a Exp(lambda) distribution.
7217
7272
  return -Math.log(Math.random()) / lambda;
7218
7273
  }
7219
7274
 
7220
7275
  function randomWeibull(lambda, k) {
7221
- // Returns a random number drawn from a Weibull(lambda, k) distribution
7276
+ // Return a random number drawn from a Weibull(lambda, k) distribution.
7222
7277
  if(Math.abs(k) < VM.NEAR_ZERO) return VM.DIV_ZERO;
7223
7278
  return lambda * Math.pow(-Math.log(Math.random()), 1.0 / k);
7224
7279
  }
7225
7280
 
7226
7281
  function randomTriangular(a, b, c=0.5*(a + b)) {
7227
- // Returns a random number drawn from a Triangular(a, b, c) distribution
7282
+ // Return a random number drawn from a Triangular(a, b, c) distribution.
7228
7283
  const u = Math.random(), b_a = b - a, c_a = c - a;
7229
7284
  if(u < c_a / b_a) {
7230
7285
  return a + Math.sqrt(u * b_a * c_a);
@@ -7234,8 +7289,8 @@ function randomTriangular(a, b, c=0.5*(a + b)) {
7234
7289
  }
7235
7290
 
7236
7291
  function randomNormal(mean, std) {
7237
- // Returns a random number drawn from a N(mean, standard deviation)
7238
- // distribution
7292
+ // Return a random number drawn from a N(mean, standard deviation)
7293
+ // distribution.
7239
7294
  const
7240
7295
  a1 = -39.6968302866538, a2 = 220.946098424521, a3 = -275.928510446969,
7241
7296
  a4 = 138.357751867269, a5 = -30.6647980661472, a6 = 2.50662827745924,
@@ -7273,11 +7328,11 @@ function randomBinomial(n, p) {
7273
7328
  }
7274
7329
  }
7275
7330
 
7276
- // Global array as cache for computation of factorial numbers
7331
+ // Global array as cache for computation of factorial numbers.
7277
7332
  const FACTORIALS = [0, 1];
7278
7333
 
7279
7334
  function factorial(n) {
7280
- // Fast factorial function using pre-calculated values up to n = 100
7335
+ // Fast factorial function using pre-calculated values up to n = 100.
7281
7336
  const l = FACTORIALS.length;
7282
7337
  if(n < l) return FACTORIALS[n];
7283
7338
  let f = FACTORIALS[l - 1];
@@ -7290,7 +7345,7 @@ function factorial(n) {
7290
7345
 
7291
7346
  function randomPoisson(lambda) {
7292
7347
  if(lambda < 30) {
7293
- // Use Knuth's algorithm
7348
+ // Use Knuth's algorithm.
7294
7349
  const L = Math.exp(-lambda);
7295
7350
  let k = 0, p = 1;
7296
7351
  do {
@@ -7299,8 +7354,8 @@ function randomPoisson(lambda) {
7299
7354
  } while(p > L);
7300
7355
  return k - 1;
7301
7356
  } else {
7302
- // Use "method PA" from Atkinson, A.C. (1979). The Computer Generation of
7303
- // Poisson Random Variables, Journal of the Royal Statistical Society
7357
+ // Use "method PA" from Atkinson, A.C. (1979). The Computer Generation
7358
+ // of Poisson Random Variables, Journal of the Royal Statistical Society
7304
7359
  // Series C (Applied Statistics), 28(1): 29-35.
7305
7360
  const c = 0.767 - 3.36 / lambda,
7306
7361
  beta = Math.PI / Math.sqrt(3.0 * lambda),
@@ -7467,14 +7522,18 @@ function VMI_add_const_to_coefficient(args) {
7467
7522
  if(DEBUGGING) {
7468
7523
  console.log(`add_const_to_coefficient [${k}]: ${VM.sig4Dig(n)}`);
7469
7524
  }
7525
+ // A negative delay may result in a variable index beyond the tableau
7526
+ // column range. Such "future variables" should be ignored.
7527
+ if(d < 0 && k > VM.chunk_offset) return;
7470
7528
  if(k <= 0) {
7471
- // NOTE: if `k` falls PRIOR to the start of the block being solved, this
7472
- // means that the value of the decision variable X for which the coefficient
7473
- // C is to be set by this instruction has been calculated while solving a
7474
- // previous block. Since the value of X is known, adding n to C is
7475
- // implemented as subtracting n*X from the right hand side of the
7476
- // constraint.
7477
- // NOTE: subtract 1 from index vi because VM.variables is a 0-based array
7529
+ // NOTE: If `k` falls PRIOR to the start of the block being solved,
7530
+ // this means that the value of the decision variable X for which the
7531
+ // coefficient C is to be set by this instruction has been calculated
7532
+ // while solving a previous block. Since the value of X is known,
7533
+ // adding n to C is implemented as subtracting n*X from the right hand
7534
+ // side of the constraint.
7535
+ // NOTE: Subtract 1 from index `vi` because VM.variables is a 0-based
7536
+ // array.
7478
7537
  const
7479
7538
  vbl = VM.variables[vi - 1],
7480
7539
  pv = VM.priorValue(vbl, t);
@@ -7493,10 +7552,9 @@ function VMI_add_const_to_coefficient(args) {
7493
7552
  function VMI_add_const_to_sum_coefficients(args) {
7494
7553
  // NOTE: used to implement data links with SUM multiplier
7495
7554
  // `args`: [var_index, number, delay (, 1)]
7496
- const
7497
- vi = args[0],
7498
- d = args[2].object.actualDelay(VM.t);
7499
- let k = VM.offset + vi - d * VM.cols,
7555
+ const vi = args[0];
7556
+ let d = args[2].object.actualDelay(VM.t),
7557
+ k = VM.offset + vi - d * VM.cols,
7500
7558
  t = VM.t - d,
7501
7559
  n = args[1];
7502
7560
  if(args.length > 3) n /= (d + 1);
@@ -7504,7 +7562,16 @@ function VMI_add_const_to_sum_coefficients(args) {
7504
7562
  console.log('add_const_to_sum_coefficients [' + k + ']: ' +
7505
7563
  VM.sig4Dig(n) + '; delay = ' + d);
7506
7564
  }
7565
+ // NOTE: When delay is negative, start at time t, not t - d.
7566
+ if(d < 0) {
7567
+ k = VM.offset + vi;
7568
+ t = VM.t;
7569
+ d = -d;
7570
+ }
7507
7571
  for(let i = 0; i <= d; i++) {
7572
+ // A negative delay may result in a variable index beyond the tableau
7573
+ // column range. Such "future variables" should be ignored.
7574
+ if(k > VM.chunk_offset) return;
7508
7575
  if(k <= 0) {
7509
7576
  // See NOTE in VMI_add_const_to_coefficient instruction
7510
7577
  const vbl = VM.variables[vi - 1];
@@ -7541,6 +7608,9 @@ function VMI_add_var_to_coefficient(args) {
7541
7608
  console.log('add_var_to_coefficient [' + k + ']: ' +
7542
7609
  args[1].variableName + ' (t = ' + t + ')');
7543
7610
  }
7611
+ // A negative delay may result in a variable index beyond the tableau
7612
+ // column range. Such "future variables" should be ignored.
7613
+ if(k > VM.chunk_offset) return;
7544
7614
  if(k <= 0) {
7545
7615
  // See NOTE in VMI_add_const_to_coefficient instruction
7546
7616
  const vbl = VM.variables[vi - 1];
@@ -7560,15 +7630,24 @@ function VMI_add_var_to_weighted_sum_coefficients(args) {
7560
7630
  // `args`: [var_index, number, delay (, 1)]
7561
7631
  const
7562
7632
  vi = args[0],
7563
- v = args[1],
7564
- d = args[2].object.actualDelay(VM.t);
7565
- let k = VM.offset + vi - d * VM.cols,
7633
+ v = args[1];
7634
+ let d = args[2].object.actualDelay(VM.t),
7635
+ k = VM.offset + vi - d * VM.cols,
7566
7636
  t = VM.t - d;
7567
7637
  if(DEBUGGING) {
7568
7638
  console.log('add_var_to_weighted_sum_coefficients [' + k + ']: ' +
7569
7639
  VM.sig4Dig(w) + ' * ' + v.variableName + ' (t = ' + t + ')');
7570
7640
  }
7641
+ // NOTE: When delay is negative, start at time t, not t - d.
7642
+ if(d < 0) {
7643
+ k = VM.offset + vi;
7644
+ t = VM.t;
7645
+ d = -d;
7646
+ }
7571
7647
  for(let i = 0; i <= d; i++) {
7648
+ // A negative delay may result in a variable index beyond the tableau
7649
+ // column range. Such "future variables" should be ignored.
7650
+ if(k > VM.chunk_offset) return;
7572
7651
  let r = v.result(t);
7573
7652
  if(args.length > 3) r /= (d + 1);
7574
7653
  if(k <= 0) {
@@ -7605,6 +7684,9 @@ function VMI_subtract_const_from_coefficient(args) {
7605
7684
  if(DEBUGGING) {
7606
7685
  console.log('subtract_const_from_coefficient [' + k + ']: ' + VM.sig4Dig(n));
7607
7686
  }
7687
+ // A negative delay may result in a variable index beyond the tableau
7688
+ // column range. Such "future variables" should be ignored.
7689
+ if(k > VM.chunk_offset) return;
7608
7690
  if(k <= 0) {
7609
7691
  // See NOTE in VMI_add_const_to_coefficient instruction
7610
7692
  const vbl = VM.variables[vi - 1];
@@ -7645,6 +7727,9 @@ function VMI_subtract_var_from_coefficient(args) {
7645
7727
  console.log('subtract_var_from_coefficient [' + k + ']: ' +
7646
7728
  args[1].variableName + ' (t = ' + t + ')');
7647
7729
  }
7730
+ // A negative delay may result in a variable index beyond the tableau
7731
+ // column range. Such "future variables" should be ignored.
7732
+ if(k > VM.chunk_offset) return;
7648
7733
  if(k <= 0) {
7649
7734
  // See NOTE in VMI_add_const_to_coefficient instruction
7650
7735
  const vbl = VM.variables[vi - 1];
@@ -7683,8 +7768,9 @@ function VMI_update_cash_coefficient(args) {
7683
7768
  // not the expressions for rates or prices!
7684
7769
  const t = VM.t - d;
7685
7770
  // NOTE: this instruction is used only for objective function
7686
- // coefficients; previously computed decision variables can be ignored
7687
- if(k <= 0) return;
7771
+ // coefficients; previously computed decision variables and variables
7772
+ // beyond the tableau column range (when delay < 0) can be ignored.
7773
+ if(k <= 0 || k > VM.chunk_offset) return;
7688
7774
  // NOTE: peak increase can generate cash only at the first time
7689
7775
  // step of a block (when VM.offset = 0) and at the first time step
7690
7776
  // of the look-ahead period (when VM.offset = block length)
@@ -7778,6 +7864,9 @@ function VMI_add_throughput_to_coefficient(args) {
7778
7864
  args[1].variableName + ' * ' + args[3].variableName +
7779
7865
  ' (t = ' + VM.t + ')');
7780
7866
  }
7867
+ // A negative delay may result in a variable index beyond the tableau
7868
+ // column range. Such "future variables" should be ignored.
7869
+ if(k > VM.chunk_offset) return;
7781
7870
  if(k <= 0) {
7782
7871
  const vbl = VM.variables[vi - 1];
7783
7872
  if(DEBUGGING) {
@@ -7866,13 +7955,13 @@ function VMI_toggle_add_constraints_flag() {
7866
7955
 
7867
7956
  function VMI_add_constraint(ct) {
7868
7957
  // Appends the current coefficients as a row to the matrix, the current
7869
- // RHS to the RHS vector, and `ct` to the constraint type vector
7870
- // NOTE: constraint is NOT added when the "add constraints flag" is FALSE
7958
+ // RHS to the RHS vector, and `ct` to the constraint type vector.
7959
+ // NOTE: Constraint is NOT added when the "add constraints flag" is FALSE.
7871
7960
  if(DEBUGGING) console.log('add_constraint: ' + VM.constraint_codes[ct]);
7872
7961
  if(VM.add_constraints_flag) {
7873
7962
  const row = {};
7874
7963
  for(let i in VM.coefficients) if(Number(i)) {
7875
- // Do not add (near)zero coefficients to the matrix
7964
+ // Do not add (near)zero coefficients to the matrix.
7876
7965
  const c = VM.coefficients[i];
7877
7966
  if(Math.abs(c) >= VM.NEAR_ZERO) {
7878
7967
  row[i] = c;