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