linny-r 1.6.6 → 1.6.8

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]]);
@@ -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
@@ -4525,6 +4526,21 @@ class VirtualMachine {
4525
4526
  }
4526
4527
  }
4527
4528
 
4529
+ severestIssue(list, result) {
4530
+ // Returns severest exception code or +/- INFINITY in `list`, or the
4531
+ // result of the computation that involves the elements of `list`.
4532
+ let issue = 0;
4533
+ for(let i = 0; i < list.length; i++) {
4534
+ if(list[i] <= VM.MINUS_INFINITY) {
4535
+ issue = Math.min(list[i], issue);
4536
+ } else if(list[i] >= VM.PLUS_INFINITY) {
4537
+ issue = Math.max(list[i], issue);
4538
+ }
4539
+ }
4540
+ if(issue) return issue;
4541
+ return result;
4542
+ }
4543
+
4528
4544
  calculateDependentVariables(block) {
4529
4545
  // Calculate the values of all model variables that depend on the
4530
4546
  // values of the decision variables output by the solver.
@@ -4533,25 +4549,29 @@ class VirtualMachine {
4533
4549
  // optimization period, hence start by calculating the offset `bb`
4534
4550
  // being the first time step of this block.
4535
4551
  // Blocks are numbered 1, 2, ...
4536
- const bb = (block - 1) * MODEL.block_length + 1;
4552
+ const
4553
+ bb = (block - 1) * MODEL.block_length + 1,
4554
+ cbl = this.actualBlockLength(block);
4537
4555
 
4538
4556
  // FIRST: Calculate the actual flows on links.
4539
- let b, bt, p, pl, ld;
4557
+ let b, bt, p, pl, ld, ci;
4540
4558
  for(let l in MODEL.links) if(MODEL.links.hasOwnProperty(l) &&
4541
4559
  !MODEL.ignored_entities[l]) {
4542
4560
  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
4561
+ // NOTE: Flow is determined by the process node, or in case
4562
+ // of a P -> P data link by the FROM product node.
4545
4563
  p = (l.to_node instanceof Process ? l.to_node : l.from_node);
4546
4564
  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!
4565
+ // Iterate over all time steps in this chunk.
4566
+ for(let i = 0; i < cbl; i++) {
4567
+ // NOTE: Flows may have a delay!
4550
4568
  ld = l.actualDelay(b);
4551
4569
  bt = b - ld;
4552
- // NOTE: use non-zero level here to ignore non-zero values that
4570
+ // NOTE: Block index may fall beyond actual chunk length.
4571
+ ci = i - ld;
4572
+ // NOTE: Use non-zero level here to ignore non-zero values that
4553
4573
  // are very small relative to the bounds on the process
4554
- // (typically values below the non-zero tolerance of the solver)
4574
+ // (typically values below the non-zero tolerance of the solver).
4555
4575
  pl = p.nonZeroLevel(bt);
4556
4576
  if(l.multiplier === VM.LM_SPINNING_RESERVE) {
4557
4577
  pl = (pl > VM.NEAR_ZERO ? p.upper_bound.result(bt) - pl : 0);
@@ -4560,30 +4580,60 @@ class VirtualMachine {
4560
4580
  } else if(l.multiplier === VM.LM_ZERO) {
4561
4581
  pl = (Math.abs(pl) < VM.NEAR_ZERO ? 1 : 0);
4562
4582
  } else if(l.multiplier === VM.LM_STARTUP) {
4563
- // NOTE: ignore level; only check whether the start-up variable is set
4583
+ // NOTE: For start-up, first commit and shut-down, the level
4584
+ // can be ignored, as it suffices to check whether time step
4585
+ // `bt` occurs in the list of start-up time steps.
4564
4586
  pl = (p.start_ups.indexOf(bt) < 0 ? 0 : 1);
4565
4587
  } else if(l.multiplier === VM.LM_FIRST_COMMIT) {
4566
- // NOTE: ignore level; only check whether FIRST start-up occurred at bt
4588
+ // NOTE: Here, check whether FIRST start-up occurred at `bt`.
4589
+ // This means that `bt` must be the *first* value in the list.
4567
4590
  pl = (p.start_ups.indexOf(bt) === 0 ? 1 : 0);
4568
4591
  } else if(l.multiplier === VM.LM_SHUTDOWN) {
4569
- // NOTE: ignore level; only check whether the shut_down variable is set
4592
+ // Similar to STARTUP, but now look in the shut-down list.
4570
4593
  pl = (p.shut_downs.indexOf(bt) < 0 ? 0 : 1);
4571
4594
  } else if(l.multiplier === VM.LM_INCREASE) {
4572
- pl -= p.actualLevel(bt - 1);
4595
+ const ppl = p.actualLevel(bt - 1);
4596
+ pl = this.severestIssue([pl, ppl], pl - ppl);
4573
4597
  } 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);
4598
+ // Level for `bt` counts as first value.
4599
+ let count = 1;
4600
+ // NOTE: Link delay may be < 0!
4601
+ if(ld < 0) {
4602
+ // NOTE: Actual levels beyond the chunk length are undefined,
4603
+ // and should be ignored while summing / averaging.
4604
+ if(ci >= cbl) pl = 0;
4605
+ // If so, take sum over t, t+1, ..., t+(d-1).
4606
+ for(let j = ld + 1; j <= 0; j++) {
4607
+ // Again: only consider levels up to the end of the chunk.
4608
+ if(ci - j < cbl) {
4609
+ const spl = p.actualLevel(b - j);
4610
+ pl = this.severestIssue([pl, spl], pl + spl);
4611
+ count++;
4612
+ }
4613
+ }
4614
+ } else {
4615
+ // If d > 0, take sum over t, t-1, ..., t-(d-1).
4616
+ for(let j = 0; j < ld; j++) {
4617
+ // NOTE: Actual levels before t=0 are considered equal to
4618
+ // the initial level, and hence should NOT be ignored.
4619
+ const spl = p.actualLevel(b - j);
4620
+ pl = this.severestIssue([pl, spl], pl + spl);
4621
+ count++;
4622
+ }
4576
4623
  }
4577
- if(l.multiplier === VM.LM_MEAN && ld > 0) {
4578
- pl /= (ld + 1);
4624
+ if(l.multiplier === VM.LM_MEAN && count > 1) {
4625
+ // Average if more than 1 values have been summed.
4626
+ pl = this.keepException(pl, pl / count);
4579
4627
  }
4580
4628
  } else if(l.multiplier === VM.LM_THROUGHPUT) {
4581
4629
  // NOTE: calculate throughput on basis of levels and rates,
4582
4630
  // as not all actual flows may have been computed yet
4583
4631
  pl = 0;
4584
4632
  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));
4633
+ const
4634
+ ipl = p.inputs[j].from_node.actualLevel(bt),
4635
+ rr = p.inputs[j].relative_rate.result(bt);
4636
+ pl = this.severestIssue([pl, ipl, rr], pl + ipl * rr);
4587
4637
  }
4588
4638
  } else if(l.multiplier === VM.LM_PEAK_INC) {
4589
4639
  // Actual flow over "peak increase" link is zero unless...
@@ -4591,27 +4641,25 @@ class VirtualMachine {
4591
4641
  // first time step, then "block peak increase"...
4592
4642
  pl = p.b_peak_inc[block];
4593
4643
  } else if(i === MODEL.block_length) {
4594
- // or first step of look-ahead, then "additional increase"
4644
+ // ... or first step of look-ahead, then "additional increase".
4595
4645
  pl = p.la_peak_inc[block];
4596
4646
  } else {
4597
4647
  pl = 0;
4598
4648
  }
4599
4649
  }
4600
- // Preserve special values such as INF, UNDEFINED and VM error codes
4601
- if(pl <= VM.MINUS_INFINITY || pl > VM.PLUS_INFINITY) {
4602
- l.actual_flow[b] = pl;
4603
- } else {
4604
- const af = pl * l.relative_rate.result(bt);
4605
- l.actual_flow[b] = (Math.abs(af) > VM.NEAR_ZERO ? af : 0);
4606
- }
4650
+ // Preserve special values such as INF, UNDEFINED and VM error codes.
4651
+ const
4652
+ rr = l.relative_rate.result(bt),
4653
+ af = this.severestIssue([pl, rr], rr * pl);
4654
+ l.actual_flow[b] = (Math.abs(af) > VM.NEAR_ZERO ? af : 0);
4607
4655
  b++;
4608
4656
  }
4609
4657
  }
4610
4658
 
4611
- // THEN: calculate cash flows one step at a time because of delays
4659
+ // THEN: Calculate cash flows one step at a time because of delays.
4612
4660
  b = bb;
4613
- for(let i = 0; i < this.chunk_length; i++) {
4614
- // Initialize cumulative cash flows for clusters
4661
+ for(let i = 0; i < cbl; i++) {
4662
+ // Initialize cumulative cash flows for clusters.
4615
4663
  for(let o in MODEL.clusters) if(MODEL.clusters.hasOwnProperty(o) &&
4616
4664
  !MODEL.ignored_entities[o]) {
4617
4665
  const c = MODEL.clusters[o];
@@ -4619,14 +4667,14 @@ class VirtualMachine {
4619
4667
  c.cash_out[b] = 0;
4620
4668
  c.cash_flow[b] = 0;
4621
4669
  }
4622
- // NOTE: cash flows ONLY result from processes
4670
+ // NOTE: Cash flows ONLY result from processes.
4623
4671
  for(let o in MODEL.processes) if(MODEL.processes.hasOwnProperty(o) &&
4624
4672
  !MODEL.ignored_entities[o]) {
4625
4673
  const p = MODEL.processes[o];
4626
4674
  let ci = 0, co = 0;
4627
4675
  // INPUT links from priced products generate cash OUT...
4628
4676
  for(let j = 0; j < p.inputs.length; j++) {
4629
- // NOTE: input links do NOT have a delay
4677
+ // NOTE: Input links do NOT have a delay.
4630
4678
  const l = p.inputs[j],
4631
4679
  af = l.actual_flow[b],
4632
4680
  fnp = l.from_node.price;
@@ -4634,7 +4682,7 @@ class VirtualMachine {
4634
4682
  const pp = fnp.result(b);
4635
4683
  if(pp > 0 && pp < VM.PLUS_INFINITY) {
4636
4684
  co += pp * af;
4637
- // ... unless the product price is negative; then cash IN
4685
+ // ... unless the product price is negative; then cash IN.
4638
4686
  } else if(pp < 0 && pp > VM.MINUS_INFINITY) {
4639
4687
  ci -= pp * af;
4640
4688
  }
@@ -4642,27 +4690,27 @@ class VirtualMachine {
4642
4690
  }
4643
4691
  // OUTPUT links to priced products generate cash IN ...
4644
4692
  for(let j = 0; j < p.outputs.length; j++) {
4645
- // NOTE: actual flows already consider delay
4693
+ // NOTE: Actual flows already consider delay!
4646
4694
  const l = p.outputs[j],
4647
4695
  ld = l.actualDelay(b),
4648
4696
  af = l.actual_flow[b],
4649
4697
  tnp = l.to_node.price;
4650
4698
  if(af > VM.NEAR_ZERO && tnp.defined) {
4651
- // NOTE: to get the correct price, again consider delays
4699
+ // NOTE: To get the correct price, again consider delays.
4652
4700
  const pp = tnp.result(b - ld);
4653
4701
  if(pp > 0 && pp < VM.PLUS_INFINITY) {
4654
4702
  ci += pp * af;
4655
- // ... unless the product price is negative; then cash OUT
4703
+ // ... unless the product price is negative; then cash OUT.
4656
4704
  } else if(pp < 0 && pp > VM.MINUS_INFINITY) {
4657
4705
  co -= pp * af;
4658
4706
  }
4659
4707
  }
4660
4708
  }
4661
- // Cash flows of process p are now known
4709
+ // Cash flows of process p are now known.
4662
4710
  p.cash_in[b] = ci;
4663
4711
  p.cash_out[b] = co;
4664
4712
  p.cash_flow[b] = ci - co;
4665
- // Also add these flows to all parent clusters of the process
4713
+ // Also add these flows to all parent clusters of the process.
4666
4714
  let c = p.cluster;
4667
4715
  while(c) {
4668
4716
  c.cash_in[b] += ci;
@@ -4674,50 +4722,51 @@ class VirtualMachine {
4674
4722
  b++;
4675
4723
  }
4676
4724
 
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
4725
+ // THEN: If cost prices should be inferred, calculate them one step
4726
+ // at a time because of delays, and also because expressions may refer
4727
+ // to values for earlier time steps.
4680
4728
  if(MODEL.infer_cost_prices) {
4681
4729
  b = bb;
4682
- for(let i = 0; i < this.chunk_length; i++) {
4730
+ for(let i = 0; i < cbl; i++) {
4683
4731
  if(!MODEL.calculateCostPrices(b)) {
4684
4732
  this.logMessage(block, `${this.WARNING}(t=${b}) ` +
4685
4733
  'Invalid cost prices due to negative flow(s)');
4686
4734
  }
4687
- // move to the next time step of the block
4735
+ // Move on to the next time step of the block.
4688
4736
  b++;
4689
4737
  }
4690
4738
  }
4691
4739
 
4692
- // THEN: reset all datasets that serve as "formulas"
4740
+ // THEN: Reset all datasets that are outcomes or serve as "formulas".
4693
4741
  for(let o in MODEL.datasets) if(MODEL.datasets.hasOwnProperty(o)) {
4694
4742
  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) {
4743
+ // NOTE: Assume that datasets having modifiers but no data serve as
4744
+ // "formulas", i.e., expressions to be calculated AFTER a model run.
4745
+ // This will automatically include the equations dataset.
4746
+ if(ds.outcome || ds.data.length === 0) {
4698
4747
  for(let m in ds.modifiers) if(ds.modifiers.hasOwnProperty(m)) {
4699
4748
  ds.modifiers[m].expression.reset();
4700
4749
  }
4701
4750
  }
4702
4751
  }
4703
4752
 
4704
- // THEN: reset the vectors of all chart variables
4753
+ // THEN: Reset the vectors of all chart variables.
4705
4754
  for(let i = 0; i < MODEL.charts.length; i++) {
4706
4755
  MODEL.charts[i].resetVectors();
4707
4756
  }
4708
4757
 
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
4758
+ // Update the chart dialog if it is visible.
4759
+ // NOTE: Do NOT do this while an experiment is running, as this may
4760
+ // interfere with storing the run results.
4712
4761
  if(!MODEL.running_experiment) {
4713
4762
  if(CHART_MANAGER.visible) CHART_MANAGER.updateDialog();
4714
4763
  }
4715
4764
 
4716
- // NOTE: add a blank line to separate from next round (if any)
4765
+ // NOTE: Add a blank line to separate from next round (if any).
4717
4766
  this.logMessage(block,
4718
4767
  `Calculating dependent variables took ${this.elapsedTime} seconds.\n`);
4719
4768
 
4720
- // FINALLY: reset the vectors of all note colors
4769
+ // FINALLY: Reset the vectors of all note colors.
4721
4770
  for(let o in MODEL.clusters) if(MODEL.clusters.hasOwnProperty(o)) {
4722
4771
  const c = MODEL.clusters[o];
4723
4772
  for(let i = 0; i < c.notes.length; i++) {
@@ -4871,7 +4920,8 @@ class VirtualMachine {
4871
4920
  this.stopSolving();
4872
4921
  return;
4873
4922
  }
4874
- // NOTE: save an additional call when less than 20% of a segment would remain
4923
+ // NOTE: Save an additional call when less than 20% of a segment would
4924
+ // remain.
4875
4925
  var l;
4876
4926
  const next_start = (start + this.tsl * 1.2 < abl ? start + this.tsl : abl);
4877
4927
  for(let i = start; i < next_start; i++) {
@@ -4879,16 +4929,16 @@ class VirtualMachine {
4879
4929
  l = this.code.length;
4880
4930
  for(let j = 0; j < l; j++) {
4881
4931
  this.IP = j;
4882
- // Execute the instruction, which has form [function, argument list]
4932
+ // Execute the instruction, which has form [function, argument list].
4883
4933
  const instr = this.code[j];
4884
4934
  instr[0](instr[1]);
4885
- // Trace the result when debugging
4935
+ // Trace the result when debugging.
4886
4936
  this.logTrace([(' ' + j).slice(-5), ': coeff = ',
4887
4937
  JSON.stringify(this.coefficients), '; rhs = ', this.rhs].join(''));
4888
4938
  }
4889
4939
  this.logTrace('STOP executing block code');
4890
- // Add constraints for paced process variables
4891
- // NOTE: this is effectuated by *executing* VM instructions
4940
+ // Add constraints for paced process variables.
4941
+ // NOTE: This is effectuated by *executing* VM instructions.
4892
4942
  for(let j in this.paced_var_indices) if(Number(j)) {
4893
4943
  const
4894
4944
  // p is the pace (number of time steps)
@@ -5011,13 +5061,13 @@ class VirtualMachine {
5011
5061
  // the simulation period.
5012
5062
  const
5013
5063
  abl = this.actualBlockLength(this.block_count),
5014
- // Get the number digits for variable names
5064
+ // Get the number digits for variable names.
5015
5065
  z = this.columnsInBlock.toString().length,
5016
- // LP_solve uses semicolon as separator between equations
5066
+ // LP_solve uses semicolon as separator between equations.
5017
5067
  EOL = (cplex ? '\n' : ';\n'),
5018
5068
  // Local function that returns variable symbol (e.g. X001) with
5019
5069
  // its coefficient if specified (e.g., -0.123 X001) in the
5020
- // most compact notation
5070
+ // most compact notation.
5021
5071
  vbl = (index, c=false) => {
5022
5072
  const v = 'X' + index.toString().padStart(z, '0');
5023
5073
  if(c === false) return v; // Only the symbol
@@ -5025,11 +5075,11 @@ class VirtualMachine {
5025
5075
  if(c < 0) return ` ${c} ${v}`; // Number had minus sign
5026
5076
  if(c === 1) return ` +${v}`; // No coefficient needed
5027
5077
  return ` +${c} ${v}`; // Prefix coefficient with +
5028
- // NOTE: this may return +0 X001
5078
+ // NOTE: This may return +0 X001.
5029
5079
  };
5030
5080
 
5031
5081
  this.numeric_issue = '';
5032
- // First add the objective (always MAXimize)
5082
+ // First add the objective (always MAXimize).
5033
5083
  if(cplex) {
5034
5084
  this.lines = 'Maximize\n';
5035
5085
  } else {
@@ -5038,19 +5088,19 @@ class VirtualMachine {
5038
5088
  let c,
5039
5089
  p,
5040
5090
  line = '';
5041
- // NOTE: iterate over ALL columns to maintain variable order
5091
+ // NOTE: Iterate over ALL columns to maintain variable order.
5042
5092
  let n = abl * this.cols + this.chunk_variables.length;
5043
5093
  for(p = 1; p <= n; p++) {
5044
5094
  if(this.objective.hasOwnProperty(p)) {
5045
5095
  c = this.objective[p];
5046
- // Check for numeric issues
5096
+ // Check for numeric issues.
5047
5097
  if (c < VM.MINUS_INFINITY || c > VM.PLUS_INFINITY) {
5048
5098
  this.setNumericIssue(c, p, 'objective function coefficient');
5049
5099
  break;
5050
5100
  }
5051
5101
  line += vbl(p, c);
5052
5102
  }
5053
- // Keep lines under approx. 110 chars
5103
+ // Keep lines under approx. 110 chars.
5054
5104
  if(line.length >= 100) {
5055
5105
  this.lines += line + '\n';
5056
5106
  line = '';
@@ -5058,7 +5108,7 @@ class VirtualMachine {
5058
5108
  }
5059
5109
  this.lines += line + EOL;
5060
5110
  line = '';
5061
- // Add the row constraints
5111
+ // Add the row constraints.
5062
5112
  if(cplex) {
5063
5113
  this.lines += '\nSubject To\n';
5064
5114
  } else {
@@ -5074,7 +5124,7 @@ class VirtualMachine {
5074
5124
  break;
5075
5125
  }
5076
5126
  line += vbl(p, c);
5077
- // Keep lines under approx. 110 chars
5127
+ // Keep lines under approx. 110 chars.
5078
5128
  if(line.length >= 100) {
5079
5129
  this.lines += line + '\n';
5080
5130
  line = '';
@@ -5085,7 +5135,7 @@ class VirtualMachine {
5085
5135
  this.constraint_symbols[this.constraint_types[r]] + ' ' + c + EOL;
5086
5136
  line = '';
5087
5137
  }
5088
- // Add the variable bounds
5138
+ // Add the variable bounds.
5089
5139
  if(cplex) {
5090
5140
  this.lines += '\nBounds\n';
5091
5141
  } else {
@@ -5097,7 +5147,7 @@ class VirtualMachine {
5097
5147
  ub = null;
5098
5148
  if(this.lower_bounds.hasOwnProperty(p)) {
5099
5149
  lb = this.lower_bounds[p];
5100
- // NOTE: for bounds, use the SOLVER values for +/- Infinity
5150
+ // NOTE: For bounds, use the SOLVER values for +/- Infinity.
5101
5151
  if (lb < VM.SOLVER_MINUS_INFINITY || lb > VM.SOLVER_PLUS_INFINITY) {
5102
5152
  this.setNumericIssue(lb, p, 'lower bound');
5103
5153
  break;
@@ -5114,56 +5164,71 @@ class VirtualMachine {
5114
5164
  if(lb === ub) {
5115
5165
  if(lb !== null) line = ` ${vbl(p)} = ${lb}`;
5116
5166
  } else {
5117
- // NOTE: by default, lower bound of variables is 0
5167
+ // NOTE: By default, lower bound of variables is 0.
5118
5168
  line = ` ${vbl(p)}`;
5119
5169
  if(cplex) {
5120
- // Explicitly denote free variables
5170
+ // Explicitly denote free variables.
5121
5171
  if(lb === null && ub === null && !this.is_binary[p]) {
5122
5172
  line += ' free';
5123
5173
  } else {
5124
- // Separate lines for LB and UB if specified
5174
+ // Separate lines for LB and UB if specified.
5125
5175
  if(ub !== null) line += ' <= ' + ub;
5126
5176
  if(lb !== null && lb !== 0) line += `\n ${vbl(p)} >= ${lb}`;
5127
5177
  }
5128
5178
  } else {
5129
- // Bounds can be specified on a single line: lb <= X001 <= ub
5179
+ // Bounds can be specified on a single line: lb <= X001 <= ub.
5130
5180
  if(lb !== null && lb !== 0) line = lb + ' <= ' + line;
5131
5181
  if(ub !== null) line += ' <= ' + ub;
5132
5182
  }
5133
5183
  }
5134
5184
  if(line) this.lines += line + EOL;
5135
5185
  }
5136
- // Add the special variable types
5186
+ // Add the special variable types.
5137
5187
  if(cplex) {
5138
5188
  line = '';
5139
- let scv = 0;
5189
+ let scv = 0,
5190
+ vcnt = 0;
5140
5191
  for(let i in this.is_binary) if(Number(i)) {
5141
5192
  line += ' ' + vbl(i);
5142
5193
  scv++;
5143
- // Max. 10 variables per line
5144
- if(scv >= 10) line += '\n';
5194
+ vcnt++;
5195
+ // Max. 10 variables per line.
5196
+ if(vcnt >= 10) {
5197
+ line += '\n';
5198
+ vcnt = 0;
5199
+ }
5145
5200
  }
5146
5201
  if(scv) {
5147
5202
  this.lines += `Binary\n${line}\n`;
5148
5203
  line = '';
5149
5204
  scv = 0;
5205
+ vcnt = 0;
5150
5206
  }
5151
5207
  for(let i in this.is_integer) if(Number(i)) {
5152
5208
  line += ' ' + vbl(i);
5153
5209
  scv++;
5154
- // Max. 10 variables per line
5155
- if(scv >= 10) line += '\n';
5210
+ vcnt++;
5211
+ // Max. 10 variables per line.
5212
+ if(vcnt >= 10) {
5213
+ line += '\n';
5214
+ vcnt = 0;
5215
+ }
5156
5216
  }
5157
5217
  if(scv) {
5158
5218
  this.lines += `General\n${line}\n`;
5159
5219
  line = '';
5160
5220
  scv = 0;
5221
+ vcnt = 0;
5161
5222
  }
5162
5223
  for(let i in this.is_semi_continuous) if(Number(i)) {
5163
5224
  line += ' '+ vbl(i);
5164
5225
  scv++;
5165
- // Max. 10 variables per line
5166
- if(scv >= 10) line += '\n';
5226
+ vcnt++;
5227
+ // Max. 10 variables per line.
5228
+ if(vcnt >= 10) {
5229
+ line += '\n';
5230
+ vcnt = 0;
5231
+ }
5167
5232
  }
5168
5233
  if(scv) {
5169
5234
  this.lines += `Semi-continuous\n${line}\n`;
@@ -5191,7 +5256,7 @@ class VirtualMachine {
5191
5256
  this.lines += 'End';
5192
5257
  } else {
5193
5258
  // NOTE: LP_solve does not differentiate between binary and integer,
5194
- // so for binary variables, the constraint <= 1 must be added
5259
+ // so for binary variables, the constraint <= 1 must be added.
5195
5260
  const v_set = [];
5196
5261
  for(let i in this.is_binary) if(Number(i)) {
5197
5262
  const v = vbl(i);
@@ -5200,12 +5265,12 @@ class VirtualMachine {
5200
5265
  }
5201
5266
  for(let i in this.is_integer) if(Number(i)) v_set.push(vbl(i));
5202
5267
  if(v_set.length > 0) this.lines += 'int ' + v_set.join(', ') + ';\n';
5203
- // Clear the INT variable list
5268
+ // Clear the INT variable list.
5204
5269
  v_set.length = 0;
5205
- // Add the semi-continuous variables
5270
+ // Add the semi-continuous variables.
5206
5271
  for(let i in this.is_semi_continuous) if(Number(i)) v_set.push(vbl(i));
5207
5272
  if(v_set.length > 0) this.lines += 'sec ' + v_set.join(', ') + ';\n';
5208
- // Add the SOS section
5273
+ // Add the SOS section.
5209
5274
  if(this.sos_var_indices.length > 0) {
5210
5275
  this.lines += 'sos\n';
5211
5276
  let sos = 1;
@@ -5258,7 +5323,7 @@ class VirtualMachine {
5258
5323
  for(c = 1; c <= ncol; c++) cols.push([]);
5259
5324
  this.decimals = Math.max(nrow, ncol).toString().length;
5260
5325
  this.lines += 'NAME block-' + this.blockWithRound + '\nROWS\n';
5261
- // Start with the "free" row that will be the objective function
5326
+ // Start with the "free" row that will be the objective function.
5262
5327
  this.lines += ' N OBJ\n';
5263
5328
  for(r = 0; r < nrow; r++) {
5264
5329
  const
@@ -5268,7 +5333,7 @@ class VirtualMachine {
5268
5333
  ' ' + row_lbl + '\n';
5269
5334
  for(p in row) if (row.hasOwnProperty(p)) {
5270
5335
  c = row[p];
5271
- // Check for numeric issues
5336
+ // Check for numeric issues.
5272
5337
  if(c === undefined || c < VM.SOLVER_MINUS_INFINITY ||
5273
5338
  c > VM.SOLVER_PLUS_INFINITY) {
5274
5339
  this.setNumericIssue(c, p, 'constraint');
@@ -5287,47 +5352,47 @@ class VirtualMachine {
5287
5352
  rhs.push(' B ' + row_lbl + ' ' + c);
5288
5353
  }
5289
5354
  }
5290
- // The objective function is a row like those for the constraints
5355
+ // The objective function is a row like those for the constraints.
5291
5356
  for(p in this.objective) if(this.objective.hasOwnProperty(p)) {
5292
5357
  c = this.objective[p];
5293
5358
  if(c === null || c < VM.MINUS_INFINITY || c > VM.PLUS_INFINITY) {
5294
5359
  this.setNumericIssue(c, p, 'objective function coefficient');
5295
5360
  break;
5296
5361
  }
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
5362
+ // NOTE: MPS assumes MINimization, hence negate all coefficients.
5363
+ // NOTE: JavaScript differentiates between 0 and -0, so add 0 to
5364
+ // prevent creating the special numeric value -0.
5300
5365
  cols[p].push('OBJ ' + (-c + 0));
5301
5366
  }
5302
- // Abort if any invalid coefficient was detected
5367
+ // Abort if any invalid coefficient was detected.
5303
5368
  if(this.numeric_issue) {
5304
5369
  this.hideSetUpOrWriteProgress();
5305
5370
  this.stopSolving();
5306
5371
  return;
5307
5372
  }
5308
- // Add the columns section
5373
+ // Add the columns section.
5309
5374
  this.lines += 'COLUMNS\n';
5310
5375
  for(c = 1; c <= ncol; c++) {
5311
5376
  const col_lbl = ' X' + c.toString().padStart(this.decimals, '0') + ' ';
5312
- // NOTE: if processes have no in- or outgoing links their decision
5377
+ // NOTE: If processes have no in- or outgoing links their decision
5313
5378
  // variable does not occur in any constraint, and this may cause
5314
5379
  // problems for solvers that cannot handle columns having a blank
5315
5380
  // row name (e.g., CPLEX). To prevent errors, these columns are
5316
- // given coefficient 0 in the OBJ row
5381
+ // given coefficient 0 in the OBJ row.
5317
5382
  if(cols[c].length) {
5318
5383
  this.lines += col_lbl + cols[c].join('\n' + col_lbl) + '\n';
5319
5384
  } else {
5320
5385
  this.lines += col_lbl + ' OBJ 0\n';
5321
5386
  }
5322
5387
  }
5323
- // Free up memory
5388
+ // Free up memory.
5324
5389
  cols.length = 0;
5325
- // Add the RHS section
5390
+ // Add the RHS section.
5326
5391
  this.lines += 'RHS\n' + rhs.join('\n') + '\n';
5327
5392
  rhs.length = 0;
5328
- // Add the BOUNDS section
5393
+ // Add the BOUNDS section.
5329
5394
  this.lines += 'BOUNDS\n';
5330
- // NOTE: start at column number 1 (not 0)
5395
+ // NOTE: Start at column number 1, not 0.
5331
5396
  setTimeout((c, n) => VM.showMPSProgress(c, n), 0, 1, ncol);
5332
5397
  }
5333
5398
 
@@ -5338,7 +5403,7 @@ class VirtualMachine {
5338
5403
  return;
5339
5404
  }
5340
5405
  if(this.show_progress) {
5341
- // NOTE: display 1 block more progress, or the bar never reaches 100%
5406
+ // NOTE: Display 1 block more progress, or the bar never reaches 100%.
5342
5407
  UI.setProgressNeedle((next_col + this.cbl) / ncol);
5343
5408
  }
5344
5409
  setTimeout((c, n) => VM.writeMPSColumns(c, n), 0, next_col, ncol);
@@ -5356,7 +5421,7 @@ class VirtualMachine {
5356
5421
  ub = null;
5357
5422
  if(this.lower_bounds.hasOwnProperty(p)) {
5358
5423
  lb = this.lower_bounds[p];
5359
- // NOTE: for bounds, use the SOLVER values for +/- Infinity
5424
+ // NOTE: For bounds, use the SOLVER values for +/- Infinity.
5360
5425
  if(lb < VM.SOLVER_MINUS_INFINITY || lb > VM.PLUS_INFINITY) {
5361
5426
  this.setNumericIssue(lb, p, 'lower bound');
5362
5427
  break;
@@ -5392,7 +5457,7 @@ class VirtualMachine {
5392
5457
  } else if(lb !== null && lb === ub && !semic) {
5393
5458
  this.lines += ' FX' + bnd + lb + '\n';
5394
5459
  } else {
5395
- // Assume "standard" bounds
5460
+ // Assume "standard" bounds.
5396
5461
  lbc = ' LO';
5397
5462
  ubc = ' UP';
5398
5463
  if(p in this.is_integer) {
@@ -5403,7 +5468,7 @@ class VirtualMachine {
5403
5468
  } else if(semic) {
5404
5469
  ubc = ' SC';
5405
5470
  }
5406
- // NOTE: by default, lower bound of variables is 0
5471
+ // NOTE: by default, lower bound of variables is 0.
5407
5472
  if(lb !== null && lb !== 0 || lbc !== ' LO') {
5408
5473
  this.lines += lbc + bnd + lb + '\n';
5409
5474
  }
@@ -5412,7 +5477,7 @@ class VirtualMachine {
5412
5477
  }
5413
5478
  }
5414
5479
  }
5415
- // Abort if any invalid coefficient was detected
5480
+ // Abort if any invalid coefficient was detected.
5416
5481
  if(this.numeric_issue) this.submitFile();
5417
5482
  if(next_col <= ncol) {
5418
5483
  setTimeout((c, n) => VM.showMPSProgress(c, n), 0, next_col, ncol);
@@ -5535,24 +5600,33 @@ Solver status = ${json.status}`);
5535
5600
  // levels and stock level), but do NOT overwrite "look-ahead" levels
5536
5601
  // if this block was not solved (indicated by the 4th parameter that
5537
5602
  // tests the status).
5538
- // NOTE: Appropriate status codes are solver-dependent.
5539
- this.setLevels(bnr, rl, json.data.x,
5540
- this.noSolutionStatus.indexOf(json.status) >= 0);
5541
- // NOTE: Post-process levels only AFTER the last round!
5542
- if(rl === this.lastRound) {
5543
- // Calculate data for all other dependent variables.
5544
- this.calculateDependentVariables(bnr);
5545
- // Add progress bar segment only now, knowing status AND slack use.
5546
- const issue = json.status !== 0 || this.error_count > 0;
5547
- if(issue) this.block_issues++;
5548
- // NOTE: in case of multiple rounds, use the sum of the round times.
5549
- const time = this.round_times.reduce((a, b) => a + b, 0);
5550
- this.round_times.length = 0;
5551
- this.solver_times[bnr - 1] = time;
5552
- const ssecs = this.round_secs.reduce((a, b) => a + b, 0);
5553
- this.solver_secs[bnr - 1] = (ssecs ? VM.sig4Dig(ssecs) : '0');
5554
- this.round_secs.length = 0;
5555
- MONITOR.addProgressBlock(bnr, issue, time);
5603
+ try {
5604
+ this.setLevels(bnr, rl, json.data.x,
5605
+ // NOTE: Appropriate status codes are solver-dependent.
5606
+ this.noSolutionStatus.indexOf(json.status) >= 0);
5607
+ // NOTE: Post-process levels only AFTER the last round!
5608
+ if(rl === this.lastRound) {
5609
+ // Calculate data for all other dependent variables.
5610
+ this.calculateDependentVariables(bnr);
5611
+ // Add progress bar segment only now, knowing status AND slack use.
5612
+ const issue = json.status !== 0 || this.error_count > 0;
5613
+ if(issue) this.block_issues++;
5614
+ // NOTE: in case of multiple rounds, use the sum of the round times.
5615
+ const time = this.round_times.reduce((a, b) => a + b, 0);
5616
+ this.round_times.length = 0;
5617
+ this.solver_times[bnr - 1] = time;
5618
+ const ssecs = this.round_secs.reduce((a, b) => a + b, 0);
5619
+ this.solver_secs[bnr - 1] = (ssecs ? VM.sig4Dig(ssecs) : '0');
5620
+ this.round_secs.length = 0;
5621
+ MONITOR.addProgressBlock(bnr, issue, time);
5622
+ }
5623
+ } catch(err) {
5624
+ const msg = `ERROR while processing solver data for block ${bnr}: ${err}`;
5625
+ console.log(msg);
5626
+ MONITOR.logMessage(bnr, msg);
5627
+ UI.alert(msg);
5628
+ this.stopSolving();
5629
+ this.halted = true;
5556
5630
  }
5557
5631
  // Free up memory.
5558
5632
  json = null;
@@ -5631,7 +5705,6 @@ Solver status = ${json.status}`);
5631
5705
  }
5632
5706
  // Generate lines of code in format that should be accepted by solver.
5633
5707
  if(this.solver_name === 'gurobi') {
5634
- //this.writeMPSFormat();
5635
5708
  this.writeLpFormat(true);
5636
5709
  } else if(this.solver_name === 'scip' || this.solver_name === 'cplex') {
5637
5710
  // NOTE: The CPLEX LP format that is also used by SCIP differs from
@@ -5697,8 +5770,8 @@ Solver status = ${json.status}`);
5697
5770
  }
5698
5771
 
5699
5772
  solveModel() {
5700
- // Starts the sequence of data loading, model translation, solving
5701
- // consecutive blocks, and finally calculating dependent variables
5773
+ // Start the sequence of data loading, model translation, solving
5774
+ // consecutive blocks, and finally calculating dependent variables.
5702
5775
  const n = MODEL.loading_datasets.length;
5703
5776
  if(n > 0) {
5704
5777
  // Still within reasonable time? (3 seconds per dataset)
@@ -5711,7 +5784,7 @@ Solver status = ${json.status}`);
5711
5784
  setTimeout(() => VM.solveModel(), 500);
5712
5785
  return;
5713
5786
  } else {
5714
- // Wait no longer, but warn user that data may be incomplete
5787
+ // Wait no longer, but warn user that data may be incomplete.
5715
5788
  const dsl = [];
5716
5789
  for(let i = 0; i < MODEL.loading_datasets.length; i++) {
5717
5790
  dsl.push(MODEL.loading_datasets[i].displayName);
@@ -5729,7 +5802,7 @@ Solver status = ${json.status}`);
5729
5802
  }
5730
5803
 
5731
5804
  halt() {
5732
- // Aborts solving process (prevents submitting next block)
5805
+ // Abort solving process. This prevents submitting the next block.
5733
5806
  UI.waitToStop();
5734
5807
  this.halted = true;
5735
5808
  }
@@ -6701,20 +6774,21 @@ function VMI_push_statistic(x, args) {
6701
6774
  }
6702
6775
 
6703
6776
  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
6777
+ // Replace one of the two top numbers on the stack by the other if the
6778
+ // one is undefined.
6779
+ // NOTE: pop(TRUE) denotes that "undefined" should be ignored as issue.
6780
+ const d = x.pop(true);
6707
6781
  if(d !== false) {
6708
6782
  if(DEBUGGING) console.log('REPLACE UNDEFINED (' + d.join(', ') + ')');
6709
6783
  x.retop(d[0] === VM.UNDEFINED ? d[1] : d[0]);
6710
6784
  }
6711
6785
  }
6712
6786
 
6713
- // NOTE: when the VM computes logical OR, AND and NOT, any non-zero number
6714
- // is interpreted as TRUE
6787
+ // NOTE: When the VM computes logical OR, AND and NOT, any non-zero number
6788
+ // is interpreted as TRUE.
6715
6789
 
6716
6790
  function VMI_or(x) {
6717
- // Performs a logical OR on the two top numbers on the stack
6791
+ // Perform a logical OR on the two top numbers on the stack.
6718
6792
  const d = x.pop();
6719
6793
  if(d !== false) {
6720
6794
  if(DEBUGGING) console.log('OR (' + d.join(', ') + ')');
@@ -6723,7 +6797,7 @@ function VMI_or(x) {
6723
6797
  }
6724
6798
 
6725
6799
  function VMI_and(x) {
6726
- // Performs a logical AND on the two top numbers on the stack
6800
+ // Perform a logical AND on the two top numbers on the stack.
6727
6801
  const d = x.pop();
6728
6802
  if(d !== false) {
6729
6803
  if(DEBUGGING) console.log('AND (' + d.join(', ') + ')');
@@ -6732,7 +6806,7 @@ function VMI_and(x) {
6732
6806
  }
6733
6807
 
6734
6808
  function VMI_not(x) {
6735
- // Performs a logical NOT on the top number of the stack
6809
+ // Perform a logical NOT on the top number of the stack.
6736
6810
  const d = x.top();
6737
6811
  if(d !== false) {
6738
6812
  if(DEBUGGING) console.log('NOT ' + d);
@@ -6741,7 +6815,7 @@ function VMI_not(x) {
6741
6815
  }
6742
6816
 
6743
6817
  function VMI_abs(x) {
6744
- // Replaces the top number of the stack by its absolute value
6818
+ // Replace the top number of the stack by its absolute value.
6745
6819
  const d = x.top();
6746
6820
  if(d !== false) {
6747
6821
  if(DEBUGGING) console.log('ABS ' + d);
@@ -6750,7 +6824,7 @@ function VMI_abs(x) {
6750
6824
  }
6751
6825
 
6752
6826
  function VMI_eq(x) {
6753
- // Tests equality of the two top numbers on the stack
6827
+ // Test equality of the two top numbers on the stack.
6754
6828
  const d = x.pop();
6755
6829
  if(d !== false) {
6756
6830
  if(DEBUGGING) console.log('EQ (' + d.join(', ') + ')');
@@ -6759,7 +6833,7 @@ function VMI_eq(x) {
6759
6833
  }
6760
6834
 
6761
6835
  function VMI_ne(x) {
6762
- // Tests inequality of the two top numbers on the stack
6836
+ // Test inequality of the two top numbers on the stack.
6763
6837
  const d = x.pop();
6764
6838
  if(d !== false) {
6765
6839
  if(DEBUGGING) console.log('NE (' + d.join(', ') + ')');
@@ -6768,7 +6842,7 @@ function VMI_ne(x) {
6768
6842
  }
6769
6843
 
6770
6844
  function VMI_lt(x) {
6771
- // Tests whether second number on the stack is less than the top number
6845
+ // Test whether second number on the stack is less than the top number.
6772
6846
  const d = x.pop();
6773
6847
  if(d !== false) {
6774
6848
  if(DEBUGGING) console.log('LT (' + d.join(', ') + ')');
@@ -6777,7 +6851,7 @@ function VMI_lt(x) {
6777
6851
  }
6778
6852
 
6779
6853
  function VMI_gt(x) {
6780
- // Tests whether second number on the stack is greater than the top number
6854
+ // Test whether second number on the stack is greater than the top number.
6781
6855
  const d = x.pop();
6782
6856
  if(d !== false) {
6783
6857
  if(DEBUGGING) console.log('GT (' + d.join(', ') + ')');
@@ -6786,8 +6860,8 @@ function VMI_gt(x) {
6786
6860
  }
6787
6861
 
6788
6862
  function VMI_le(x) {
6789
- // Tests whether second number on the stack is less than, or equal to,
6790
- // the top number
6863
+ // Test whether second number on the stack is less than, or equal to,
6864
+ // the top number.
6791
6865
  const d = x.pop();
6792
6866
  if(d !== false) {
6793
6867
  if(DEBUGGING) console.log('LE (' + d.join(', ') + ')');
@@ -6796,8 +6870,8 @@ function VMI_le(x) {
6796
6870
  }
6797
6871
 
6798
6872
  function VMI_ge(x) {
6799
- // Tests whether second number on the stack is greater than, or equal to,
6800
- // the top number
6873
+ // Test whether second number on the stack is greater than, or equal to,
6874
+ // the top number.
6801
6875
  const d = x.pop();
6802
6876
  if(d !== false) {
6803
6877
  if(DEBUGGING) console.log('LE (' + d.join(', ') + ')');
@@ -6806,7 +6880,7 @@ function VMI_ge(x) {
6806
6880
  }
6807
6881
 
6808
6882
  function VMI_add(x) {
6809
- // Pops the top number on the stack and adds it to the new top number
6883
+ // Pop the top number on the stack, and add it to the new top number.
6810
6884
  const d = x.pop();
6811
6885
  if(d !== false) {
6812
6886
  if(DEBUGGING) console.log('ADD (' + d.join(', ') + ')');
@@ -6815,8 +6889,8 @@ function VMI_add(x) {
6815
6889
  }
6816
6890
 
6817
6891
  function VMI_sub(x) {
6818
- // Pops the top number on the stack and subtracts it from the new
6819
- // top number
6892
+ // Pop the top number on the stack, and subtract it from the new
6893
+ // top number.
6820
6894
  const d = x.pop();
6821
6895
  if(d !== false) {
6822
6896
  if(DEBUGGING) console.log('SUB (' + d.join(', ') + ')');
@@ -6825,7 +6899,7 @@ function VMI_sub(x) {
6825
6899
  }
6826
6900
 
6827
6901
  function VMI_mul(x) {
6828
- // Pops the top number on the stack and multiplies it with the new
6902
+ // Pop the top number on the stack, and multiply it with the new
6829
6903
  // top number
6830
6904
  const d = x.pop();
6831
6905
  if(d !== false) {
@@ -6835,8 +6909,8 @@ function VMI_mul(x) {
6835
6909
  }
6836
6910
 
6837
6911
  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!
6912
+ // Pop the top number on the stack, and divide the new top number
6913
+ // by it. In case of division by zero, replace the top by #DIV/0!
6840
6914
  const d = x.pop();
6841
6915
  if(d !== false) {
6842
6916
  if(DEBUGGING) console.log('DIV (' + d.join(', ') + ')');
@@ -6849,23 +6923,23 @@ function VMI_div(x) {
6849
6923
  }
6850
6924
 
6851
6925
  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"
6926
+ // Perform a "floating point MOD operation" as explained below.
6927
+ // Pop the top number on the stack. If zero, push error code #DIV/0!.
6928
+ // Otherwise, proceed: divide the new top number by the divisor, take
6929
+ // the fraction part, and multiply this with the divisor.
6856
6930
  const d = x.pop();
6857
6931
  if(d !== false) {
6858
6932
  if(DEBUGGING) console.log('DIV (' + d.join(', ') + ')');
6859
6933
  if(Math.abs(d[1]) <= VM.NEAR_ZERO) {
6860
6934
  x.retop(VM.DIV_ZERO);
6861
6935
  } else {
6862
- x.retop(d[0] % d[1]); // % is the modulo operator in JavaScript
6936
+ x.retop(d[0] % d[1]); // % is the modulo operator in JavaScript.
6863
6937
  }
6864
6938
  }
6865
6939
  }
6866
6940
 
6867
6941
  function VMI_negate(x) {
6868
- // Performs a negation on the top number of the stack
6942
+ // Perform a negation on the top number of the stack.
6869
6943
  const d = x.top();
6870
6944
  if(d !== false) {
6871
6945
  if(DEBUGGING) console.log('NEG ' + d);
@@ -6874,8 +6948,8 @@ function VMI_negate(x) {
6874
6948
  }
6875
6949
 
6876
6950
  function VMI_power(x) {
6877
- // Pops the top number on the stack and raises the new top number
6878
- // to its power
6951
+ // Pop the top number on the stack, and raise the new top number
6952
+ // to its power.
6879
6953
  const d = x.pop();
6880
6954
  if(d !== false) {
6881
6955
  if(DEBUGGING) console.log('POWER (' + d.join(', ') + ')');
@@ -6884,8 +6958,8 @@ function VMI_power(x) {
6884
6958
  }
6885
6959
 
6886
6960
  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
6961
+ // Replace the top number of the stack by its square root, or by
6962
+ // error code #VALUE! if the top number is negative.
6889
6963
  const d = x.top();
6890
6964
  if(d !== false) {
6891
6965
  if(DEBUGGING) console.log('SQRT ' + d);
@@ -6898,7 +6972,7 @@ function VMI_sqrt(x) {
6898
6972
  }
6899
6973
 
6900
6974
  function VMI_sin(x) {
6901
- // Replaces the top number X of the stack by sin(X)
6975
+ // Replace the top number X of the stack by sin(X).
6902
6976
  const d = x.top();
6903
6977
  if(d !== false) {
6904
6978
  if(DEBUGGING) console.log('SIN ' + d);
@@ -6907,7 +6981,7 @@ function VMI_sin(x) {
6907
6981
  }
6908
6982
 
6909
6983
  function VMI_cos(x) {
6910
- // Replaces the top number X of the stack by cos(X)
6984
+ // Replace the top number X of the stack by cos(X).
6911
6985
  const d = x.top();
6912
6986
  if(d !== false) {
6913
6987
  if(DEBUGGING) console.log('COS ' + d);
@@ -6916,7 +6990,7 @@ function VMI_cos(x) {
6916
6990
  }
6917
6991
 
6918
6992
  function VMI_atan(x) {
6919
- // Replaces the top number X of the stack by atan(X)
6993
+ // Replace the top number X of the stack by atan(X).
6920
6994
  const d = x.top();
6921
6995
  if(d !== false) {
6922
6996
  if(DEBUGGING) console.log('ATAN ' + d);
@@ -6925,8 +6999,8 @@ function VMI_atan(x) {
6925
6999
  }
6926
7000
 
6927
7001
  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
7002
+ // Replace the top number X of the stack by ln(X), or by error
7003
+ // code #VALUE! if X is negative.
6930
7004
  const d = x.top();
6931
7005
  if(d !== false) {
6932
7006
  if(DEBUGGING) console.log('LN ' + d);
@@ -6939,7 +7013,7 @@ function VMI_ln(x) {
6939
7013
  }
6940
7014
 
6941
7015
  function VMI_exp(x) {
6942
- // Replaces the top number X of the stack by exp(X)
7016
+ // Replace the top number X of the stack by exp(X).
6943
7017
  const d = x.top();
6944
7018
  if(d !== false) {
6945
7019
  if(DEBUGGING) console.log('EXP ' + d);
@@ -6948,7 +7022,7 @@ function VMI_exp(x) {
6948
7022
  }
6949
7023
 
6950
7024
  function VMI_log(x) {
6951
- // Pops the top number B from the stack and replaces the new top
7025
+ // Pop the top number B from the stack, and replace the new top
6952
7026
  // number A by A log B. NOTE: x = A log B <=> x = ln(B) / ln(A)
6953
7027
  let d = x.pop();
6954
7028
  if(d !== false) {
@@ -6963,7 +7037,7 @@ function VMI_log(x) {
6963
7037
  }
6964
7038
 
6965
7039
  function VMI_round(x) {
6966
- // Replaces the top number X of the stack by round(X)
7040
+ // Replace the top number X of the stack by round(X).
6967
7041
  const d = x.top();
6968
7042
  if(d !== false) {
6969
7043
  if(DEBUGGING) console.log('ROUND ' + d);
@@ -6972,7 +7046,7 @@ function VMI_round(x) {
6972
7046
  }
6973
7047
 
6974
7048
  function VMI_int(x) {
6975
- // Replaces the top number X of the stack by its integer part
7049
+ // Replace the top number X of the stack by its integer part.
6976
7050
  const d = x.top();
6977
7051
  if(d !== false) {
6978
7052
  if(DEBUGGING) console.log('INT ' + d);
@@ -6981,7 +7055,7 @@ function VMI_int(x) {
6981
7055
  }
6982
7056
 
6983
7057
  function VMI_fract(x) {
6984
- // Replaces the top number X of the stack by its fraction part
7058
+ // Replace the top number X of the stack by its fraction part.
6985
7059
  const d = x.top();
6986
7060
  if(d !== false) {
6987
7061
  if(DEBUGGING) console.log('FRACT ' + d);
@@ -6990,9 +7064,9 @@ function VMI_fract(x) {
6990
7064
  }
6991
7065
 
6992
7066
  function VMI_exponential(x) {
6993
- // Replaces the top number X of the stack by a random number from the
7067
+ // Replace the top number X of the stack by a random number from the
6994
7068
  // negative exponential distribution with parameter X (so X is the lambda,
6995
- // and the mean will be 1/X)
7069
+ // and the mean will be 1/X).
6996
7070
  const d = x.top();
6997
7071
  if(d !== false) {
6998
7072
  const a = randomExponential(d);
@@ -7002,8 +7076,8 @@ function VMI_exponential(x) {
7002
7076
  }
7003
7077
 
7004
7078
  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)
7079
+ // Replace the top number X of the stack by a random number from the
7080
+ // poisson distribution with parameter X (so X is the mean value lambda).
7007
7081
  const d = x.top();
7008
7082
  if(d !== false) {
7009
7083
  const a = randomPoisson(d);
@@ -7013,8 +7087,9 @@ function VMI_poisson(x) {
7013
7087
  }
7014
7088
 
7015
7089
  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]
7090
+ // Replace the top list (!) A of the stack by Bin(A[0], A[1]), i.e.,
7091
+ // a random number from the binomial distribution with n = A[0] and
7092
+ // p = A[1].
7018
7093
  const d = x.top();
7019
7094
  if(d !== false) {
7020
7095
  if(d instanceof Array && d.length === 2) {
@@ -7029,8 +7104,9 @@ function VMI_binomial(x) {
7029
7104
  }
7030
7105
 
7031
7106
  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]
7107
+ // Replace the top list (!) A of the stack by N(A[0], A[1]), i.e.,
7108
+ // a random number from the normal distribution with mu = A[0] and
7109
+ // sigma = A[1].
7034
7110
  const d = x.top();
7035
7111
  if(d !== false) {
7036
7112
  if(d instanceof Array && d.length === 2) {
@@ -7045,8 +7121,9 @@ function VMI_normal(x) {
7045
7121
  }
7046
7122
 
7047
7123
  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]
7124
+ // Replace the top list (!) A of the stack by Weibull(A[0], A[1]), i.e.,
7125
+ // a random number from the Weibull distribution with lambda = A[0]
7126
+ // and k = A[1].
7050
7127
  const d = x.top();
7051
7128
  if(d !== false) {
7052
7129
  if(d instanceof Array && d.length === 2) {
@@ -7061,10 +7138,10 @@ function VMI_weibull(x) {
7061
7138
  }
7062
7139
 
7063
7140
  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
7141
+ // Replaces the top list (!) A of the stack by Tri(A[0], A[1]), A[2]),
7142
+ // i.e., a random number from the triangular distribution with a = A[0],
7143
+ // b = A[1], and c = A[2]. NOTE: if only 2 parameters are passed, c is
7144
+ // assumed to equal (a + b) / 2.
7068
7145
  const d = x.top();
7069
7146
  if(d !== false) {
7070
7147
  if(d instanceof Array && (d.length === 2 || d.length === 3)) {
@@ -7079,14 +7156,15 @@ function VMI_triangular(x) {
7079
7156
  }
7080
7157
 
7081
7158
  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
7159
+ // Replace the top list (!) A of the stack by the net present value (NPV)
7160
+ // of the arguments in A. A[0] is the interest rate r, A[1] is the number
7161
+ // of time periods n. If A has only 1 or 2 elements, the NPV is 0.
7162
+ // If A has 3 elements, A[2] is the constant cash flow C, and the NPV is
7163
+ // the sum (for t = 0 to n-1) of C/(1+r)^t. If A has N>2 elements, A[2]
7164
+ // through A[N] are considered as a cash flow time series C0, C1, ..., CN-2
7165
+ // that is then discounted.
7166
+ // NOTE: If A is not a list, A considered to be the single argument, and
7167
+ // is hence replaced by 0.
7090
7168
  const d = x.top();
7091
7169
  if(d !== false) {
7092
7170
  if(d instanceof Array && d.length > 2) {
@@ -7119,8 +7197,8 @@ function VMI_npv(x) {
7119
7197
  }
7120
7198
 
7121
7199
  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
7200
+ // Replace the top list (!) A of the stack by the lowest value in this
7201
+ // list. If A is not a list, A is left on the stack.
7124
7202
  const d = x.top();
7125
7203
  if(d !== false && d instanceof Array) {
7126
7204
  if(DEBUGGING) console.log('MIN (' + d.join(', ') + ')');
@@ -7131,8 +7209,8 @@ function VMI_min(x) {
7131
7209
  }
7132
7210
 
7133
7211
  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
7212
+ // Replace the top list (!) A of the stack by the highest value in this
7213
+ // list. If A is not a list, A is left on the stack.
7136
7214
  const d = x.top();
7137
7215
  if(d !== false && d instanceof Array) {
7138
7216
  if(DEBUGGING) console.log('MAX (' + d.join(', ') + ')');
@@ -7143,9 +7221,9 @@ function VMI_max(x) {
7143
7221
  }
7144
7222
 
7145
7223
  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
7224
+ // Pop the top number B from the stack, and then replace the new top
7225
+ // element A by [A, B] if A is a number, or add B to A if A is a list
7226
+ // of numbers (!), or concatenate if A and B both are lists.
7149
7227
  const d = x.pop();
7150
7228
  if(d !== false) {
7151
7229
  if(DEBUGGING) console.log('CONCAT (' + d.join(', ') + ')');
@@ -7164,8 +7242,8 @@ function VMI_concat(x) {
7164
7242
  }
7165
7243
 
7166
7244
  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
7245
+ // Set the program counter of the VM to `index` minus 1, as the
7246
+ // counter is ALWAYS increased by 1 after calling a VMI function.
7169
7247
  if(DEBUGGING) console.log('JUMP ' + index);
7170
7248
  x.program_counter = index - 1;
7171
7249
  }
@@ -7179,7 +7257,7 @@ function VMI_jump_if_false(x, index) {
7179
7257
  if(r === 0 || r === VM.UNDEFINED || r === false) {
7180
7258
  // Only jump on FALSE, leaving the stack "as is", so that in case
7181
7259
  // of no THEN, the expression result equals the IF condition value.
7182
- // NOTE: Also do this on a stack error (r === false)
7260
+ // NOTE: Also do this on a stack error (r === false).
7183
7261
  x.program_counter = index - 1;
7184
7262
  } else {
7185
7263
  // Remove the value from the stack.
@@ -7209,22 +7287,22 @@ function VMI_if_else(x) {
7209
7287
  }
7210
7288
 
7211
7289
  //
7212
- // Functions that implement random numbers from specific distribution
7290
+ // Functions that implement random numbers from specific distribution.
7213
7291
  //
7214
7292
 
7215
7293
  function randomExponential(lambda) {
7216
- // Returns a random number drawn from a Exp(lambda) distribution
7294
+ // Return a random number drawn from a Exp(lambda) distribution.
7217
7295
  return -Math.log(Math.random()) / lambda;
7218
7296
  }
7219
7297
 
7220
7298
  function randomWeibull(lambda, k) {
7221
- // Returns a random number drawn from a Weibull(lambda, k) distribution
7299
+ // Return a random number drawn from a Weibull(lambda, k) distribution.
7222
7300
  if(Math.abs(k) < VM.NEAR_ZERO) return VM.DIV_ZERO;
7223
7301
  return lambda * Math.pow(-Math.log(Math.random()), 1.0 / k);
7224
7302
  }
7225
7303
 
7226
7304
  function randomTriangular(a, b, c=0.5*(a + b)) {
7227
- // Returns a random number drawn from a Triangular(a, b, c) distribution
7305
+ // Return a random number drawn from a Triangular(a, b, c) distribution.
7228
7306
  const u = Math.random(), b_a = b - a, c_a = c - a;
7229
7307
  if(u < c_a / b_a) {
7230
7308
  return a + Math.sqrt(u * b_a * c_a);
@@ -7234,8 +7312,8 @@ function randomTriangular(a, b, c=0.5*(a + b)) {
7234
7312
  }
7235
7313
 
7236
7314
  function randomNormal(mean, std) {
7237
- // Returns a random number drawn from a N(mean, standard deviation)
7238
- // distribution
7315
+ // Return a random number drawn from a N(mean, standard deviation)
7316
+ // distribution.
7239
7317
  const
7240
7318
  a1 = -39.6968302866538, a2 = 220.946098424521, a3 = -275.928510446969,
7241
7319
  a4 = 138.357751867269, a5 = -30.6647980661472, a6 = 2.50662827745924,
@@ -7273,11 +7351,11 @@ function randomBinomial(n, p) {
7273
7351
  }
7274
7352
  }
7275
7353
 
7276
- // Global array as cache for computation of factorial numbers
7354
+ // Global array as cache for computation of factorial numbers.
7277
7355
  const FACTORIALS = [0, 1];
7278
7356
 
7279
7357
  function factorial(n) {
7280
- // Fast factorial function using pre-calculated values up to n = 100
7358
+ // Fast factorial function using pre-calculated values up to n = 100.
7281
7359
  const l = FACTORIALS.length;
7282
7360
  if(n < l) return FACTORIALS[n];
7283
7361
  let f = FACTORIALS[l - 1];
@@ -7290,7 +7368,7 @@ function factorial(n) {
7290
7368
 
7291
7369
  function randomPoisson(lambda) {
7292
7370
  if(lambda < 30) {
7293
- // Use Knuth's algorithm
7371
+ // Use Knuth's algorithm.
7294
7372
  const L = Math.exp(-lambda);
7295
7373
  let k = 0, p = 1;
7296
7374
  do {
@@ -7299,8 +7377,8 @@ function randomPoisson(lambda) {
7299
7377
  } while(p > L);
7300
7378
  return k - 1;
7301
7379
  } else {
7302
- // Use "method PA" from Atkinson, A.C. (1979). The Computer Generation of
7303
- // Poisson Random Variables, Journal of the Royal Statistical Society
7380
+ // Use "method PA" from Atkinson, A.C. (1979). The Computer Generation
7381
+ // of Poisson Random Variables, Journal of the Royal Statistical Society
7304
7382
  // Series C (Applied Statistics), 28(1): 29-35.
7305
7383
  const c = 0.767 - 3.36 / lambda,
7306
7384
  beta = Math.PI / Math.sqrt(3.0 * lambda),
@@ -7467,14 +7545,18 @@ function VMI_add_const_to_coefficient(args) {
7467
7545
  if(DEBUGGING) {
7468
7546
  console.log(`add_const_to_coefficient [${k}]: ${VM.sig4Dig(n)}`);
7469
7547
  }
7548
+ // 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;
7470
7551
  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
7552
+ // NOTE: If `k` falls PRIOR to the start of the block being solved,
7553
+ // this means that the value of the decision variable X for which the
7554
+ // coefficient C is to be set by this instruction has been calculated
7555
+ // while solving a previous block. Since the value of X is known,
7556
+ // adding n to C is implemented as subtracting n*X from the right hand
7557
+ // side of the constraint.
7558
+ // NOTE: Subtract 1 from index `vi` because VM.variables is a 0-based
7559
+ // array.
7478
7560
  const
7479
7561
  vbl = VM.variables[vi - 1],
7480
7562
  pv = VM.priorValue(vbl, t);
@@ -7493,10 +7575,9 @@ function VMI_add_const_to_coefficient(args) {
7493
7575
  function VMI_add_const_to_sum_coefficients(args) {
7494
7576
  // NOTE: used to implement data links with SUM multiplier
7495
7577
  // `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,
7578
+ const vi = args[0];
7579
+ let d = args[2].object.actualDelay(VM.t),
7580
+ k = VM.offset + vi - d * VM.cols,
7500
7581
  t = VM.t - d,
7501
7582
  n = args[1];
7502
7583
  if(args.length > 3) n /= (d + 1);
@@ -7504,7 +7585,16 @@ function VMI_add_const_to_sum_coefficients(args) {
7504
7585
  console.log('add_const_to_sum_coefficients [' + k + ']: ' +
7505
7586
  VM.sig4Dig(n) + '; delay = ' + d);
7506
7587
  }
7588
+ // NOTE: When delay is negative, start at time t, not t - d.
7589
+ if(d < 0) {
7590
+ k = VM.offset + vi;
7591
+ t = VM.t;
7592
+ d = -d;
7593
+ }
7507
7594
  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.
7597
+ if(k > VM.chunk_offset) return;
7508
7598
  if(k <= 0) {
7509
7599
  // See NOTE in VMI_add_const_to_coefficient instruction
7510
7600
  const vbl = VM.variables[vi - 1];
@@ -7541,6 +7631,9 @@ function VMI_add_var_to_coefficient(args) {
7541
7631
  console.log('add_var_to_coefficient [' + k + ']: ' +
7542
7632
  args[1].variableName + ' (t = ' + t + ')');
7543
7633
  }
7634
+ // A negative delay may result in a variable index beyond the tableau
7635
+ // column range. Such "future variables" should be ignored.
7636
+ if(k > VM.chunk_offset) return;
7544
7637
  if(k <= 0) {
7545
7638
  // See NOTE in VMI_add_const_to_coefficient instruction
7546
7639
  const vbl = VM.variables[vi - 1];
@@ -7560,15 +7653,24 @@ function VMI_add_var_to_weighted_sum_coefficients(args) {
7560
7653
  // `args`: [var_index, number, delay (, 1)]
7561
7654
  const
7562
7655
  vi = args[0],
7563
- v = args[1],
7564
- d = args[2].object.actualDelay(VM.t);
7565
- let k = VM.offset + vi - d * VM.cols,
7656
+ v = args[1];
7657
+ let d = args[2].object.actualDelay(VM.t),
7658
+ k = VM.offset + vi - d * VM.cols,
7566
7659
  t = VM.t - d;
7567
7660
  if(DEBUGGING) {
7568
7661
  console.log('add_var_to_weighted_sum_coefficients [' + k + ']: ' +
7569
7662
  VM.sig4Dig(w) + ' * ' + v.variableName + ' (t = ' + t + ')');
7570
7663
  }
7664
+ // NOTE: When delay is negative, start at time t, not t - d.
7665
+ if(d < 0) {
7666
+ k = VM.offset + vi;
7667
+ t = VM.t;
7668
+ d = -d;
7669
+ }
7571
7670
  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.
7673
+ if(k > VM.chunk_offset) return;
7572
7674
  let r = v.result(t);
7573
7675
  if(args.length > 3) r /= (d + 1);
7574
7676
  if(k <= 0) {
@@ -7605,6 +7707,9 @@ function VMI_subtract_const_from_coefficient(args) {
7605
7707
  if(DEBUGGING) {
7606
7708
  console.log('subtract_const_from_coefficient [' + k + ']: ' + VM.sig4Dig(n));
7607
7709
  }
7710
+ // A negative delay may result in a variable index beyond the tableau
7711
+ // column range. Such "future variables" should be ignored.
7712
+ if(k > VM.chunk_offset) return;
7608
7713
  if(k <= 0) {
7609
7714
  // See NOTE in VMI_add_const_to_coefficient instruction
7610
7715
  const vbl = VM.variables[vi - 1];
@@ -7645,6 +7750,9 @@ function VMI_subtract_var_from_coefficient(args) {
7645
7750
  console.log('subtract_var_from_coefficient [' + k + ']: ' +
7646
7751
  args[1].variableName + ' (t = ' + t + ')');
7647
7752
  }
7753
+ // A negative delay may result in a variable index beyond the tableau
7754
+ // column range. Such "future variables" should be ignored.
7755
+ if(k > VM.chunk_offset) return;
7648
7756
  if(k <= 0) {
7649
7757
  // See NOTE in VMI_add_const_to_coefficient instruction
7650
7758
  const vbl = VM.variables[vi - 1];
@@ -7683,8 +7791,9 @@ function VMI_update_cash_coefficient(args) {
7683
7791
  // not the expressions for rates or prices!
7684
7792
  const t = VM.t - d;
7685
7793
  // NOTE: this instruction is used only for objective function
7686
- // coefficients; previously computed decision variables can be ignored
7687
- if(k <= 0) return;
7794
+ // coefficients; previously computed decision variables and variables
7795
+ // beyond the tableau column range (when delay < 0) can be ignored.
7796
+ if(k <= 0 || k > VM.chunk_offset) return;
7688
7797
  // NOTE: peak increase can generate cash only at the first time
7689
7798
  // step of a block (when VM.offset = 0) and at the first time step
7690
7799
  // of the look-ahead period (when VM.offset = block length)
@@ -7778,6 +7887,9 @@ function VMI_add_throughput_to_coefficient(args) {
7778
7887
  args[1].variableName + ' * ' + args[3].variableName +
7779
7888
  ' (t = ' + VM.t + ')');
7780
7889
  }
7890
+ // A negative delay may result in a variable index beyond the tableau
7891
+ // column range. Such "future variables" should be ignored.
7892
+ if(k > VM.chunk_offset) return;
7781
7893
  if(k <= 0) {
7782
7894
  const vbl = VM.variables[vi - 1];
7783
7895
  if(DEBUGGING) {
@@ -7866,13 +7978,13 @@ function VMI_toggle_add_constraints_flag() {
7866
7978
 
7867
7979
  function VMI_add_constraint(ct) {
7868
7980
  // 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
7981
+ // RHS to the RHS vector, and `ct` to the constraint type vector.
7982
+ // NOTE: Constraint is NOT added when the "add constraints flag" is FALSE.
7871
7983
  if(DEBUGGING) console.log('add_constraint: ' + VM.constraint_codes[ct]);
7872
7984
  if(VM.add_constraints_flag) {
7873
7985
  const row = {};
7874
7986
  for(let i in VM.coefficients) if(Number(i)) {
7875
- // Do not add (near)zero coefficients to the matrix
7987
+ // Do not add (near)zero coefficients to the matrix.
7876
7988
  const c = VM.coefficients[i];
7877
7989
  if(Math.abs(c) >= VM.NEAR_ZERO) {
7878
7990
  row[i] = c;