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
|
-
//
|
3214
|
-
// NOTE:
|
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
|
3233
|
+
// Now *all* variables have been defined. The next step is to set
|
3234
|
+
// their bounds.
|
3234
3235
|
|
3235
|
-
// NOTE:
|
3236
|
-
//
|
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:
|
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:
|
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:
|
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"
|
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:
|
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:
|
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:
|
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
|
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:
|
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 <
|
4549
|
-
// NOTE:
|
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:
|
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:
|
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:
|
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
|
-
//
|
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
|
-
|
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
|
-
|
4575
|
-
|
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 &&
|
4578
|
-
|
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
|
-
|
4586
|
-
|
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
|
-
|
4602
|
-
|
4603
|
-
|
4604
|
-
|
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:
|
4659
|
+
// THEN: Calculate cash flows one step at a time because of delays.
|
4612
4660
|
b = bb;
|
4613
|
-
for(let i = 0; 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:
|
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:
|
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:
|
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:
|
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:
|
4678
|
-
// time because of delays, and also because expressions may refer
|
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 <
|
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
|
-
//
|
4735
|
+
// Move on to the next time step of the block.
|
4688
4736
|
b++;
|
4689
4737
|
}
|
4690
4738
|
}
|
4691
4739
|
|
4692
|
-
// THEN:
|
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:
|
4696
|
-
// "formulas", i.e., expressions to be calculated AFTER a model run
|
4697
|
-
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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
|
-
|
5144
|
-
|
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
|
-
|
5155
|
-
|
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
|
-
|
5166
|
-
|
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
|
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:
|
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:
|
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:
|
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:
|
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
|
-
|
5539
|
-
|
5540
|
-
|
5541
|
-
|
5542
|
-
|
5543
|
-
|
5544
|
-
|
5545
|
-
|
5546
|
-
|
5547
|
-
|
5548
|
-
|
5549
|
-
|
5550
|
-
|
5551
|
-
|
5552
|
-
|
5553
|
-
|
5554
|
-
|
5555
|
-
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
6705
|
-
// is undefined
|
6706
|
-
|
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:
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
6839
|
-
// by it. In case of division by zero, the top
|
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
|
-
//
|
6853
|
-
//
|
6854
|
-
//
|
6855
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
7017
|
-
// number from the binomial distribution with n = A[0] and
|
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
|
-
//
|
7033
|
-
// number from the normal distribution with mu = A[0] and
|
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
|
-
//
|
7049
|
-
// random number from the Weibull distribution with lambda = A[0]
|
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]),
|
7065
|
-
// a random number from the triangular distribution with a = A[0],
|
7066
|
-
// and c = A[2]. NOTE: if only 2 parameters are passed, c is
|
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
|
-
//
|
7083
|
-
// of the arguments in A. A[0] is the interest rate r, A[1] is the number
|
7084
|
-
// time periods n. If A has only 1 or 2 elements, the NPV is 0.
|
7085
|
-
// elements, A[2] is the constant cash flow C, and the NPV is
|
7086
|
-
// (for t = 0 to n-1) of C/(1+r)^t. If A has N>2 elements, A[2]
|
7087
|
-
// are considered as a cash flow time series C0, C1, ..., CN-2
|
7088
|
-
//
|
7089
|
-
//
|
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
|
-
//
|
7123
|
-
//
|
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
|
-
//
|
7135
|
-
//
|
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
|
-
//
|
7147
|
-
// element A by [A, B] if A is a number, or
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
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:
|
7472
|
-
// means that the value of the decision variable X for which the
|
7473
|
-
// C is to be set by this instruction has been calculated
|
7474
|
-
// previous block. Since the value of X is known,
|
7475
|
-
// implemented as subtracting n*X from the right hand
|
7476
|
-
// constraint.
|
7477
|
-
// NOTE:
|
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
|
-
|
7498
|
-
|
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
|
-
|
7565
|
-
|
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
|
7687
|
-
|
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:
|
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;
|