linny-r 1.1.17 → 1.1.19
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/package.json +1 -1
- package/server.js +1 -1
- package/static/linny-r.css +5 -0
- package/static/scripts/linny-r-gui.js +28 -20
- package/static/scripts/linny-r-model.js +13 -7
- package/static/scripts/linny-r-vm.js +156 -90
package/package.json
CHANGED
package/server.js
CHANGED
@@ -91,7 +91,7 @@ function getVersionInfo() {
|
|
91
91
|
info.latest = 0;
|
92
92
|
}
|
93
93
|
if(!info.latest) {
|
94
|
-
console.log(connectionErrorText('Could not connect to https://
|
94
|
+
console.log(connectionErrorText('Could not connect to https://registry.npmjs.org/'));
|
95
95
|
} else if(!info.up_to_date) {
|
96
96
|
console.log('UPDATE: Version ' + info.latest + ' was released on ' +
|
97
97
|
info.latest_time.toString());
|
package/static/linny-r.css
CHANGED
@@ -3482,13 +3482,17 @@ class GUIController extends Controller {
|
|
3482
3482
|
const
|
3483
3483
|
dlg = e.target.id.split('-')[0],
|
3484
3484
|
tde = document.getElementById(dlg + '-dlg'),
|
3485
|
-
|
3485
|
+
was_hidden = this.hidden(tde.id);
|
3486
|
+
let mgr = tde.getAttribute('data-manager');
|
3487
|
+
if(mgr) mgr = window[mgr];
|
3486
3488
|
// NOTE: prevent modeler from viewing charts while an experiment is running
|
3487
|
-
if(dlg === 'chart' &&
|
3489
|
+
if(dlg === 'chart' && was_hidden && MODEL.running_experiment) {
|
3488
3490
|
UI.notify(UI.NOTICE.NO_CHARTS);
|
3491
|
+
mgr.visible = false;
|
3489
3492
|
return;
|
3490
3493
|
}
|
3491
3494
|
this.toggle(tde.id);
|
3495
|
+
if(mgr) mgr.visible = was_hidden;
|
3492
3496
|
// Open at position after last drag (recorded in DOM data attributes)
|
3493
3497
|
let t = tde.getAttribute('data-top'),
|
3494
3498
|
l = tde.getAttribute('data-left');
|
@@ -3506,15 +3510,14 @@ class GUIController extends Controller {
|
|
3506
3510
|
this.reorderDialogs();
|
3507
3511
|
// Update the diagram if its manager has been specified
|
3508
3512
|
if(mgr) {
|
3509
|
-
|
3510
|
-
|
3511
|
-
if(mgr ===
|
3513
|
+
mgr.visible = true;
|
3514
|
+
mgr.updateDialog();
|
3515
|
+
if(mgr === DOCUMENTATION_MANAGER) {
|
3512
3516
|
if(this.info_line.innerHTML.length === 0) {
|
3513
|
-
|
3514
|
-
|
3515
|
-
|
3516
|
-
|
3517
|
-
DOCUMENTATION_MANAGER.edit_btn.classList.add('disab');
|
3517
|
+
mgr.title.innerHTML = 'About Linny-R';
|
3518
|
+
mgr.viewer.innerHTML = mgr.about_linny_r;
|
3519
|
+
mgr.edit_btn.classList.remove('enab');
|
3520
|
+
mgr.edit_btn.classList.add('disab');
|
3518
3521
|
}
|
3519
3522
|
UI.drawDiagram(MODEL);
|
3520
3523
|
}
|
@@ -3527,10 +3530,10 @@ class GUIController extends Controller {
|
|
3527
3530
|
this.reorderDialogs();
|
3528
3531
|
}
|
3529
3532
|
if(mgr) {
|
3530
|
-
|
3531
|
-
if(mgr ===
|
3532
|
-
|
3533
|
-
|
3533
|
+
mgr.visible = true;
|
3534
|
+
if(mgr === DOCUMENTATION_MANAGER) {
|
3535
|
+
mgr.visible = false;
|
3536
|
+
mgr.title.innerHTML = 'Documentation';
|
3534
3537
|
UI.drawDiagram(MODEL);
|
3535
3538
|
}
|
3536
3539
|
}
|
@@ -5499,7 +5502,8 @@ class GUIController extends Controller {
|
|
5499
5502
|
a1 = document.getElementById('link-arrow-1'),
|
5500
5503
|
a2 = document.getElementById('link-arrow-2'),
|
5501
5504
|
lm = document.getElementById('link-multiplier').value,
|
5502
|
-
d = document.getElementById('link-D')
|
5505
|
+
d = document.getElementById('link-D'),
|
5506
|
+
deb = document.getElementById('link-D-x');
|
5503
5507
|
// NOTE: selector value is a string, not a number
|
5504
5508
|
if(lm === '0') {
|
5505
5509
|
// Default link symbol is a solid arrow
|
@@ -5517,10 +5521,15 @@ class GUIController extends Controller {
|
|
5517
5521
|
d.style.color = 'gray';
|
5518
5522
|
d.style.backgroundColor = 'inherit';
|
5519
5523
|
d.value = '0';
|
5524
|
+
// Also disable its "edit expression" button
|
5525
|
+
deb.classList.remove('enab');
|
5526
|
+
deb.classList.add('disab');
|
5520
5527
|
} else {
|
5521
5528
|
d.disabled = false;
|
5522
5529
|
d.style.color = 'black';
|
5523
5530
|
d.style.backgroundColor = 'white';
|
5531
|
+
deb.classList.remove('disab');
|
5532
|
+
deb.classList.add('enab');
|
5524
5533
|
}
|
5525
5534
|
}
|
5526
5535
|
|
@@ -8065,7 +8074,8 @@ class Repository {
|
|
8065
8074
|
if(include) {
|
8066
8075
|
// Include module into current model
|
8067
8076
|
REPOSITORY_BROWSER.promptForInclusion(
|
8068
|
-
this.name, this.module_names[n],
|
8077
|
+
this.name, this.module_names[n],
|
8078
|
+
parseXML(data.replace(/%23/g, '#')));
|
8069
8079
|
} else {
|
8070
8080
|
if(UI.loadModelFromXML(data)) {
|
8071
8081
|
UI.notify(`Model <tt>${this.module_names[n]}</tt> ` +
|
@@ -12003,7 +12013,8 @@ class GUIExperimentManager extends ExperimentManager {
|
|
12003
12013
|
ss = VM.sig2Dig(r.solver_seconds),
|
12004
12014
|
ssp = (rdt < VM.NEAR_ZERO ? '' :
|
12005
12015
|
' (' + Math.round(r.solver_seconds * 100 / rdt) + '%)'),
|
12006
|
-
w = (r.warning_count > 0 ?
|
12016
|
+
w = (r.warning_count > 0 ?
|
12017
|
+
' ' + pluralS(r.warning_count, 'warning') + '. ' : '');
|
12007
12018
|
cell.title = ['Run #', i, ' (', r.time_steps, ' time steps of ',
|
12008
12019
|
r.time_step_duration, ' h) took ', rdts, ' s. Solver used ', ss, ' s',
|
12009
12020
|
ssp, '.', w, (rr ? `
|
@@ -14792,6 +14803,3 @@ if (MODEL.focal_cluster === fc) {
|
|
14792
14803
|
}
|
14793
14804
|
}
|
14794
14805
|
} // END of class UndoStack
|
14795
|
-
|
14796
|
-
|
14797
|
-
|
@@ -2603,9 +2603,14 @@ class LinnyRModel {
|
|
2603
2603
|
this.cleanVector(p.cash_out, 0, 0);
|
2604
2604
|
// NOTE: `start_ups` is a list of time steps when start-up occurred
|
2605
2605
|
p.start_ups.length = 0;
|
2606
|
-
// NOTE: `
|
2607
|
-
//
|
2608
|
-
p.
|
2606
|
+
// NOTE: `b_peak_inc` records the peak increase for each block,
|
2607
|
+
// so at t=0 (block *before* block #1) this is the initial level
|
2608
|
+
p.b_peak_inc = [p.level[0]];
|
2609
|
+
// `la_peak_inc` records the additional peak increase in the
|
2610
|
+
// look-ahead period
|
2611
|
+
p.la_peak_inc = [p.level[0]];
|
2612
|
+
// b_peak[b] records peak level value up to and including block b
|
2613
|
+
p.b_peak = [p.level[0]];
|
2609
2614
|
}
|
2610
2615
|
for(obj in this.products) if(this.products.hasOwnProperty(obj)) {
|
2611
2616
|
p = this.products[obj];
|
@@ -2623,9 +2628,10 @@ class LinnyRModel {
|
|
2623
2628
|
this.cleanVector(p.highest_cost_price, VM.UNDEFINED);
|
2624
2629
|
if(p.is_buffer) this.cleanVector(p.stock_price, VM.UNDEFINED);
|
2625
2630
|
p.start_ups.length = 0;
|
2626
|
-
// NOTE:
|
2627
|
-
|
2628
|
-
p.
|
2631
|
+
// NOTE: peak increase also applies to products
|
2632
|
+
p.b_peak_inc = [p.level[0]];
|
2633
|
+
p.la_peak_inc = [p.level[0]];
|
2634
|
+
|
2629
2635
|
}
|
2630
2636
|
for(obj in this.links) if(this.links.hasOwnProperty(obj)) {
|
2631
2637
|
l = this.links[obj];
|
@@ -7265,7 +7271,7 @@ class Link {
|
|
7265
7271
|
IO_CONTEXT.addedLink(this);
|
7266
7272
|
// Contextualize the rate and delay expressions
|
7267
7273
|
IO_CONTEXT.rewrite(this.relative_rate);
|
7268
|
-
IO_CONTEXT.rewrite(this.
|
7274
|
+
IO_CONTEXT.rewrite(this.flow_delay);
|
7269
7275
|
}
|
7270
7276
|
}
|
7271
7277
|
|
@@ -1471,6 +1471,9 @@ class VirtualMachine {
|
|
1471
1471
|
// Base penalty of 10 is high relative to the (scaled) coefficients of the
|
1472
1472
|
// cash flows in the objective function (typically +/- 1)
|
1473
1473
|
this.BASE_PENALTY = 10;
|
1474
|
+
// Peak variable penalty is added to make solver choose the *smallest*
|
1475
|
+
// value that is greater than or equal to X[t] for all t as "peak value"
|
1476
|
+
this.PEAK_VAR_PENALTY = 0.01;
|
1474
1477
|
|
1475
1478
|
// NOTE: the VM uses numbers >> +INF to denote special computation results
|
1476
1479
|
this.EXCEPTION = 1e+36; // to test for any exceptional value
|
@@ -2130,7 +2133,7 @@ class VirtualMachine {
|
|
2130
2133
|
p.start_up_count_var_index = -1;
|
2131
2134
|
p.suc_on_var_index = -1;
|
2132
2135
|
p.first_commit_var_index = -1;
|
2133
|
-
p.
|
2136
|
+
p.peak_inc_var_index = -1;
|
2134
2137
|
if(p instanceof Product) {
|
2135
2138
|
p.stock_LE_slack_var_index = -1;
|
2136
2139
|
p.stock_GE_slack_var_index = -1;
|
@@ -2183,10 +2186,11 @@ class VirtualMachine {
|
|
2183
2186
|
const
|
2184
2187
|
type = tuple[0],
|
2185
2188
|
obj = tuple[1];
|
2186
|
-
if(type
|
2189
|
+
if(type.indexOf('-peak') > 0) {
|
2187
2190
|
// Peak level variables have an array as node property
|
2188
2191
|
const c = Math.trunc(t / this.block_length);
|
2189
|
-
return obj.
|
2192
|
+
if(type.startsWith('b')) return obj.b_peak_inc[c];
|
2193
|
+
return obj.la_peak_inc[c];
|
2190
2194
|
}
|
2191
2195
|
const prior_level = obj.actualLevel(t);
|
2192
2196
|
if(type === 'OO') return prior_level > 0 ? 1 : 0;
|
@@ -2206,7 +2210,7 @@ class VirtualMachine {
|
|
2206
2210
|
return prior_level;
|
2207
2211
|
}
|
2208
2212
|
|
2209
|
-
|
2213
|
+
variablesLegend(block) {
|
2210
2214
|
// Returns a string with each variable code and full name on a separate line
|
2211
2215
|
const
|
2212
2216
|
vcnt = this.variables.length,
|
@@ -2222,11 +2226,14 @@ class VirtualMachine {
|
|
2222
2226
|
l += v + ' [' + this.variables[i][0] + p + ']\n';
|
2223
2227
|
}
|
2224
2228
|
if(this.chunk_variables.length > 0) {
|
2229
|
+
// NOTE: chunk offset for last block may be lower than standard
|
2230
|
+
const chof = (block >= this.nr_of_blocks ? this.chunk_offset :
|
2231
|
+
this.cols * this.chunk_length + 1);
|
2225
2232
|
for(let i = 0; i < this.chunk_variables.length; i++) {
|
2226
2233
|
const
|
2227
2234
|
obj = this.chunk_variables[i][1],
|
2228
2235
|
// NOTE: chunk offset takes into account that indices are 0-based
|
2229
|
-
cvi =
|
2236
|
+
cvi = chof + i;
|
2230
2237
|
let v = (this.solver_name === 'lp_solve' ?
|
2231
2238
|
'C' + cvi : 'X' + cvi.toString().padStart(z, '0'));
|
2232
2239
|
v += ' '.slice(v.length) + obj.displayName;
|
@@ -2440,15 +2447,20 @@ class VirtualMachine {
|
|
2440
2447
|
|
2441
2448
|
// Now all variables that get a tableau column in each time step have
|
2442
2449
|
// been defined; next step is to add "chunk variables"
|
2443
|
-
// NOTE: chunk variables are "free", i.e., have no bound constraints
|
2444
2450
|
let cvi = 0;
|
2445
|
-
// Add chunk
|
2451
|
+
// Add *two* chunk variables for processes having a peak increase link
|
2446
2452
|
for(i = 0; i < process_keys.length; i++) {
|
2447
2453
|
k = process_keys[i];
|
2448
2454
|
p = MODEL.processes[k];
|
2449
2455
|
if(!MODEL.ignored_entities[k] && p.needsMaximumData) {
|
2450
|
-
|
2451
|
-
|
2456
|
+
// "peak increase" for block
|
2457
|
+
p.peak_inc_var_index = cvi;
|
2458
|
+
this.chunk_variables.push(['b-peak', p]);
|
2459
|
+
cvi++;
|
2460
|
+
// additional "peak increase" for the look-ahead period
|
2461
|
+
// NOTE: no need to record the second index as it wil allways be
|
2462
|
+
// equal to block peak index + 1
|
2463
|
+
this.chunk_variables.push(['la-peak', p]);
|
2452
2464
|
cvi++;
|
2453
2465
|
}
|
2454
2466
|
}
|
@@ -2457,14 +2469,19 @@ class VirtualMachine {
|
|
2457
2469
|
k = product_keys[i];
|
2458
2470
|
p = MODEL.products[k];
|
2459
2471
|
if(!MODEL.ignored_entities[k] && p.needsMaximumData) {
|
2460
|
-
p.
|
2461
|
-
this.chunk_variables.push(['peak', p]);
|
2472
|
+
p.peak_inc_var_index = cvi;
|
2473
|
+
this.chunk_variables.push(['b-peak', p]);
|
2474
|
+
cvi++;
|
2475
|
+
this.chunk_variables.push(['la-peak', p]);
|
2462
2476
|
cvi++;
|
2463
2477
|
}
|
2464
2478
|
}
|
2465
2479
|
|
2466
2480
|
// Now *all* variables have been defined; next step is to set their bounds
|
2467
2481
|
|
2482
|
+
// NOTE: chunk variables of node `p` have LB = 0 and UB = UB of `p`;
|
2483
|
+
// this is effectuated by the VM "set bounds" instructions at run time
|
2484
|
+
|
2468
2485
|
// NOTE: under normal assumptions (all processes having LB >= 0), bounds on
|
2469
2486
|
// actor cash flow variables need NOT be set because cash IN and cash OUT
|
2470
2487
|
// will then always be >= 0 (solver's default bounds).
|
@@ -2668,12 +2685,13 @@ class VirtualMachine {
|
|
2668
2685
|
VM.PRODUCE, VM.SPIN_RES, p.on_off_var_index, l.flow_delay, vi,
|
2669
2686
|
l.from_node.upper_bound, tnpx, l.relative_rate]]);
|
2670
2687
|
} else if(l.multiplier === VM.LM_PEAK_INC) {
|
2671
|
-
// "peak increase"
|
2672
|
-
// of the
|
2673
|
-
//
|
2688
|
+
// NOTE: "peak increase" may be > 0 only in the first time step
|
2689
|
+
// of the block being optimized, and in the first step of the
|
2690
|
+
// look-ahead period (if peak rises in that period), and will
|
2691
|
+
// be 0 in all other time steps; the VM instruction handles this
|
2674
2692
|
// NOTE: delay is always 0 for this link flow
|
2675
2693
|
this.code.push([VMI_update_cash_coefficient, [
|
2676
|
-
VM.PRODUCE, VM.PEAK_INC, p.
|
2694
|
+
VM.PRODUCE, VM.PEAK_INC, p.peak_inc_var_index, 0,
|
2677
2695
|
tnpx, l.relative_rate]]);
|
2678
2696
|
} else if(tnpx.isStatic && l.relative_rate.isStatic) {
|
2679
2697
|
// If link rate and product price are static, only add the variable
|
@@ -2850,7 +2868,7 @@ class VirtualMachine {
|
|
2850
2868
|
} else if(l.multiplier === VM.LM_SHUTDOWN) {
|
2851
2869
|
vi = fn.shut_down_var_index;
|
2852
2870
|
} else if(l.multiplier === VM.LM_PEAK_INC) {
|
2853
|
-
vi = fn.
|
2871
|
+
vi = fn.peak_inc_var_index;
|
2854
2872
|
} else {
|
2855
2873
|
vi = fn.level_var_index;
|
2856
2874
|
}
|
@@ -3089,22 +3107,23 @@ class VirtualMachine {
|
|
3089
3107
|
(to prevent that FC[t] = 1 when SO[t-1] = 1 and SO[t] = 1, i.e.,
|
3090
3108
|
SC was already > 0)
|
3091
3109
|
|
3092
|
-
To calculate the
|
3093
|
-
"chunk
|
3094
|
-
each time step.
|
3095
|
-
|
3096
|
-
|
3097
|
-
|
3098
|
-
|
3099
|
-
|
3100
|
-
(
|
3101
|
-
|
3102
|
-
|
3103
|
-
|
3104
|
-
|
3105
|
-
|
3106
|
-
|
3107
|
-
|
3110
|
+
To calculate the peak increase values, we need two continuous
|
3111
|
+
"chunk variables", i.e., only 1 tableau column per chunk, not 1 for
|
3112
|
+
each time step. These variables BPI and CPI will compute the highest
|
3113
|
+
value (for all t in the block (B) and for the chunk (C)) of the
|
3114
|
+
difference L[t] - block peak (BP) of previous block. This requires
|
3115
|
+
one equation for every t = 1, ..., block length:
|
3116
|
+
(n) L[t] - BPI[b] <= BP[b-1] (where b denotes the block number)
|
3117
|
+
plus one equation for every t = block length + 1 to chunk length:
|
3118
|
+
(o) L[t] - BPI[b] - CPI[b] <= BP[b-1]
|
3119
|
+
This ensures that CPI is the *additional* increase in the look-ahead
|
3120
|
+
Then use BPI[b] in first time step if block, and CPI[b] at first
|
3121
|
+
time step of the look-ahead period to compute the actual flow for
|
3122
|
+
the "peak increase" links. For all other time steps this AF equals 0.
|
3123
|
+
|
3124
|
+
NOTE: These constraints alone set the lower bound for BPI and CPI, so
|
3125
|
+
these variables can take on higher values. The modeler must ensure
|
3126
|
+
that there is a cost associated with the actual flow, not a revenue.
|
3108
3127
|
*/
|
3109
3128
|
// NOTE: as of 20 June 2021, binary attributes of products are also computed
|
3110
3129
|
const pp_nodes = [];
|
@@ -3350,12 +3369,12 @@ class VirtualMachine {
|
|
3350
3369
|
// Check whether constraints (n) through (p) need to be added
|
3351
3370
|
// to compute the peak level for a block of time steps
|
3352
3371
|
// NOTE: this is independent of the binary variables!
|
3353
|
-
if(p.
|
3372
|
+
if(p.peak_inc_var_index >= 0) {
|
3354
3373
|
this.code.push(
|
3355
3374
|
// One special instruction implements this operation, as part
|
3356
3375
|
// of it must be performed only at block time = 0
|
3357
|
-
[
|
3358
|
-
[p.level_var_index, p.
|
3376
|
+
[VMI_add_peak_increase_constraints,
|
3377
|
+
[p.level_var_index, p.peak_inc_var_index]]
|
3359
3378
|
);
|
3360
3379
|
}
|
3361
3380
|
}
|
@@ -3648,13 +3667,17 @@ class VirtualMachine {
|
|
3648
3667
|
b++;
|
3649
3668
|
}
|
3650
3669
|
}
|
3651
|
-
// Get values of peak
|
3670
|
+
// Get values of peak increase variables from solution vector
|
3652
3671
|
// NOTE: computed offset takes into account that chunk variable list
|
3653
3672
|
// is zero-based!
|
3654
3673
|
const offset = this.cols * abl;
|
3655
3674
|
for(let i = 0; i < ncv; i++) {
|
3656
3675
|
const p = this.chunk_variables[i][1];
|
3657
|
-
p.
|
3676
|
+
p.b_peak_inc[block] = x[offset + i];
|
3677
|
+
i++;
|
3678
|
+
p.la_peak_inc[block] = x[offset + i];
|
3679
|
+
// Compute the peak from the peak increase
|
3680
|
+
p.b_peak[block] = p.b_peak[block - 1] + p.b_peak_inc[block];
|
3658
3681
|
}
|
3659
3682
|
// Add warning to messages if slack has been used
|
3660
3683
|
// NOTE: only check after the last round has been evaluated
|
@@ -3764,11 +3787,13 @@ class VirtualMachine {
|
|
3764
3787
|
p.inputs[j].relative_rate.result(bt));
|
3765
3788
|
}
|
3766
3789
|
} else if(l.multiplier === VM.LM_PEAK_INC) {
|
3767
|
-
// Actual flow
|
3790
|
+
// Actual flow over "peak increase" link is zero unless...
|
3768
3791
|
if(i === 0) {
|
3769
|
-
//
|
3770
|
-
pl =
|
3771
|
-
|
3792
|
+
// first time step, then "block peak increase"...
|
3793
|
+
pl = p.b_peak_inc[block];
|
3794
|
+
} else if(i === MODEL.block_length) {
|
3795
|
+
// or first step of look-ahead, then "additional increase"
|
3796
|
+
pl = p.la_peak_inc[block];
|
3772
3797
|
} else {
|
3773
3798
|
pl = 0;
|
3774
3799
|
}
|
@@ -6207,6 +6232,7 @@ function VMI_set_const_bounds(args) {
|
|
6207
6232
|
// `args`: [var_index, number, number]
|
6208
6233
|
const
|
6209
6234
|
vi = args[0],
|
6235
|
+
vbl = VM.variables[vi - 1][1],
|
6210
6236
|
k = VM.offset + vi,
|
6211
6237
|
r = VM.round_letters.indexOf(VM.round_sequence[VM.current_round]),
|
6212
6238
|
// Optional fourth parameter indicates whether the solver's
|
@@ -6217,7 +6243,6 @@ function VMI_set_const_bounds(args) {
|
|
6217
6243
|
u,
|
6218
6244
|
fixed = (vi in VM.fixed_var_indices[r - 1]);
|
6219
6245
|
if(fixed) {
|
6220
|
-
const vbl = VM.variables[vi - 1][1];
|
6221
6246
|
// Set both bounds equal to the level set in the previous round, or to 0
|
6222
6247
|
// if this is the first round
|
6223
6248
|
if(VM.current_round) {
|
@@ -6239,8 +6264,8 @@ function VMI_set_const_bounds(args) {
|
|
6239
6264
|
}
|
6240
6265
|
// NOTE: to check, add this to the condition below: fixed !== ''
|
6241
6266
|
if(DEBUGGING) {
|
6242
|
-
console.log(['set_const_bounds [', k, ']
|
6243
|
-
', UB = ', VM.sig4Dig(u), fixed].join(''));
|
6267
|
+
console.log(['set_const_bounds [', k, '] ', vbl.displayName, ' t = ', VM.t,
|
6268
|
+
' LB = ', VM.sig4Dig(l), ', UB = ', VM.sig4Dig(u), fixed].join(''));
|
6244
6269
|
}
|
6245
6270
|
// NOTE: since the VM vectors for lower bounds and upper bounds are
|
6246
6271
|
// initialized with default values (0 for LB, +INF for UB), there is
|
@@ -6248,6 +6273,21 @@ function VMI_set_const_bounds(args) {
|
|
6248
6273
|
if(l !== 0 || u < inf_val) {
|
6249
6274
|
VM.lower_bounds[k] = l;
|
6250
6275
|
VM.upper_bounds[k] = u;
|
6276
|
+
// If associated node is FROM-node of a "peak increase" link, then
|
6277
|
+
// the "peak increase" variables of this node must have the highest
|
6278
|
+
// UB of the node (for all t in this block, hence MAX) MINUS their
|
6279
|
+
// peak level in previous block
|
6280
|
+
if(vbl.peak_inc_var_index >= 0) {
|
6281
|
+
u = Math.max(0, u - vbl.b_peak[VM.block_count - 1]);
|
6282
|
+
const
|
6283
|
+
cvi = VM.chunk_offset + vbl.peak_inc_var_index,
|
6284
|
+
// Check if peak UB already set for previous t
|
6285
|
+
piub = VM.upper_bounds[cvi];
|
6286
|
+
// If so, use the highest value
|
6287
|
+
if(piub) u = Math.max(piub, u);
|
6288
|
+
VM.upper_bounds[cvi] = u;
|
6289
|
+
VM.upper_bounds[cvi + 1] = u;
|
6290
|
+
}
|
6251
6291
|
}
|
6252
6292
|
}
|
6253
6293
|
|
@@ -6255,6 +6295,7 @@ function VMI_set_var_bounds(args) {
|
|
6255
6295
|
// `args`: [var_index, expression, expression]
|
6256
6296
|
const
|
6257
6297
|
vi = args[0],
|
6298
|
+
vbl = VM.variables[vi - 1][1],
|
6258
6299
|
k = VM.offset + vi,
|
6259
6300
|
r = VM.round_letters.indexOf(VM.round_sequence[VM.current_round]),
|
6260
6301
|
// Optional fourth parameter indicates whether the solver's
|
@@ -6265,7 +6306,6 @@ function VMI_set_var_bounds(args) {
|
|
6265
6306
|
u,
|
6266
6307
|
fixed = (vi in VM.fixed_var_indices[r - 1]);
|
6267
6308
|
if(fixed) {
|
6268
|
-
const vbl = VM.variables[vi - 1][1];
|
6269
6309
|
// Set both bounds equal to the level set in the previous round, or to 0
|
6270
6310
|
// if this is the first round
|
6271
6311
|
if(VM.current_round) {
|
@@ -6289,10 +6329,20 @@ function VMI_set_var_bounds(args) {
|
|
6289
6329
|
if(Math.abs(l) > VM.NEAR_ZERO || u !== inf_val) {
|
6290
6330
|
VM.lower_bounds[k] = l;
|
6291
6331
|
VM.upper_bounds[k] = u;
|
6332
|
+
// Check for peak increase -- see comments in VMI_set_const_bound
|
6333
|
+
if(vbl.peak_inc_var_index >= 0) {
|
6334
|
+
u = Math.max(0, u - vbl.b_peak[VM.block_count - 1]);
|
6335
|
+
const
|
6336
|
+
cvi = VM.chunk_offset + vbl.peak_inc_var_index,
|
6337
|
+
piub = VM.upper_bounds[cvi];
|
6338
|
+
if(piub) u = Math.max(piub, u);
|
6339
|
+
VM.upper_bounds[cvi] = u;
|
6340
|
+
VM.upper_bounds[cvi + 1] = u;
|
6341
|
+
}
|
6292
6342
|
}
|
6293
6343
|
if(DEBUGGING) {
|
6294
|
-
console.log(['set_var_bounds [', k, ']
|
6295
|
-
', UB = ',
|
6344
|
+
console.log(['set_var_bounds [', k, '] ', vbl.displayName, ' t = ', VM.t,
|
6345
|
+
' LB = ', l, ', UB = ', u, fixed].join(''));
|
6296
6346
|
}
|
6297
6347
|
}
|
6298
6348
|
|
@@ -6534,19 +6584,20 @@ function VMI_update_cash_coefficient(args) {
|
|
6534
6584
|
if((type === VM.ONE_C && args.length === 6) ||
|
6535
6585
|
(type === VM.TWO_X && args.length === 7)) d++;
|
6536
6586
|
}
|
6537
|
-
|
6538
|
-
|
6539
|
-
|
6540
|
-
|
6541
|
-
|
6542
|
-
|
6543
|
-
t = VM.t - d;
|
6587
|
+
// `k` is the tableau column index of the variable that affects the CF
|
6588
|
+
let k = (type === VM.PEAK_INC ? VM.chunk_offset + vi :
|
6589
|
+
VM.offset + vi - d*VM.cols);
|
6590
|
+
// NOTE: delay > 0 affects only which variable is to be used,
|
6591
|
+
// not the expressions for rates or prices!
|
6592
|
+
const t = VM.t - d;
|
6544
6593
|
// NOTE: this instruction is used only for objective function
|
6545
6594
|
// coefficients; previously computed decision variables can be ignored
|
6546
6595
|
if(k <= 0) return;
|
6547
6596
|
// NOTE: peak increase can generate cash only at the first time
|
6548
|
-
// step of a block
|
6549
|
-
|
6597
|
+
// step of a block (when VM.offset = 0) and at the first time step
|
6598
|
+
// of the look-ahead period (when VM.offset = block length)
|
6599
|
+
if(type === VM.PEAK_INC &&
|
6600
|
+
VM.offset > 0 && VM.offset !== MODEL.block_length) return;
|
6550
6601
|
// First compute the result to be processed
|
6551
6602
|
let r = 0;
|
6552
6603
|
if(type === VM.ONE_C) {
|
@@ -6591,11 +6642,13 @@ function VMI_update_cash_coefficient(args) {
|
|
6591
6642
|
if(flow === VM.CONSUME) r = -r;
|
6592
6643
|
if(DEBUGGING) {
|
6593
6644
|
const vbl = (vi <= this.cols ? VM.variables[vi - 1] :
|
6594
|
-
VM.chunk_variables[vi - this.cols]); //@@@ TO MAKE CORRECT!
|
6645
|
+
VM.chunk_variables[vi - this.cols]); //@@@ TO MAKE CORRECT FOR chunk vars!
|
6595
6646
|
console.log(['update_cash_coefficient [', k, ']: ', vbl[0], ' ',
|
6596
6647
|
vbl[1].displayName, ' (t = ', t, ') ', VM.CF_CONSTANTS[type], ' ',
|
6597
6648
|
VM.CF_CONSTANTS[flow], ' r = ', VM.sig4Dig(r)].join(''));
|
6598
6649
|
}
|
6650
|
+
// Use look-ahead peak increase when offset > 0
|
6651
|
+
if(type === VM.PEAK_INC && VM.offset) k++;
|
6599
6652
|
// Then update the cash flow: cash IN if r > 0, otherwise cash OUT
|
6600
6653
|
if(r > 0) {
|
6601
6654
|
if(k in VM.cash_in_coefficients) {
|
@@ -6654,6 +6707,18 @@ function VMI_set_objective(empty) {
|
|
6654
6707
|
for(let i in VM.coefficients) if(Number(i)) {
|
6655
6708
|
VM.objective[i] = VM.coefficients[i];
|
6656
6709
|
}
|
6710
|
+
// NOTE: For peak increase to function properly, the peak variables
|
6711
|
+
// must have a small penalty in the objective function
|
6712
|
+
if(VM.chunk_variables.length > 0) {
|
6713
|
+
for(let i = 0; i < VM.chunk_variables.length; i++) {
|
6714
|
+
const vn = VM.chunk_variables[i][0];
|
6715
|
+
if(vn.indexOf('peak') > 0) {
|
6716
|
+
// NOTE: chunk offset takes into account that indices are 0-based
|
6717
|
+
VM.objective[VM.chunk_offset + i] = -VM.PEAK_VAR_PENALTY;
|
6718
|
+
if(vn.startsWith('b')) VM.objective[VM.chunk_offset + i] -= VM.PEAK_VAR_PENALTY;
|
6719
|
+
}
|
6720
|
+
}
|
6721
|
+
}
|
6657
6722
|
}
|
6658
6723
|
|
6659
6724
|
function VMI_set_const_rhs(c) {
|
@@ -6832,60 +6897,61 @@ function VMI_add_bound_line_constraint(args) {
|
|
6832
6897
|
VMI_add_constraint(bl.type);
|
6833
6898
|
}
|
6834
6899
|
|
6835
|
-
function
|
6836
|
-
// Adds
|
6900
|
+
function VMI_add_peak_increase_constraints(args) {
|
6901
|
+
// Adds constraints to compute peak increase for current block and
|
6902
|
+
// for current block + look-ahead
|
6837
6903
|
const
|
6838
6904
|
vi = args[0], // tableau column of L[t]
|
6839
6905
|
cvi = args[1], // tableau column of peak
|
6840
6906
|
lci = VM.offset + vi,
|
6841
|
-
|
6907
|
+
cbici = VM.chunk_offset + cvi,
|
6908
|
+
cvbl = VM.chunk_variables[cvi][1];
|
6842
6909
|
if(DEBUGGING) {
|
6843
6910
|
console.log('add_peak_level_constraints (t = ' + VM.t + ')',
|
6844
6911
|
VM.variables[vi - 1][0], VM.variables[vi - 1][1].displayName,
|
6845
|
-
VM.chunk_variables[cvi][0],
|
6912
|
+
VM.chunk_variables[cvi][0], cvbl.displayName);
|
6913
|
+
}
|
6914
|
+
// For t = 1 to block length, add constraint to compute block peak increase
|
6915
|
+
if(VM.offset < MODEL.block_length * VM.cols) {
|
6916
|
+
// (n) L[t] - BPI[b] <= BP[b-1] (where b denotes the block number)
|
6917
|
+
VMI_clear_coefficients();
|
6918
|
+
VM.coefficients[lci] = 1;
|
6919
|
+
VM.coefficients[cbici] = -1;
|
6920
|
+
// Set RHS to highest level computed in previous blocks
|
6921
|
+
VM.rhs = cvbl.b_peak[VM.block_count - 1];
|
6922
|
+
VMI_add_constraint(VM.LE);
|
6923
|
+
return;
|
6846
6924
|
}
|
6847
|
-
//
|
6925
|
+
// For every t = block length + 1 to chunk length:
|
6848
6926
|
VMI_clear_coefficients();
|
6927
|
+
// (o) L[t] - BPI[b] - CPI[b] <= BP[b-1]
|
6849
6928
|
VM.coefficients[lci] = 1;
|
6850
|
-
VM.coefficients[
|
6851
|
-
//
|
6929
|
+
VM.coefficients[cbici] = -1;
|
6930
|
+
// NOTE: next index always points to LA peak increase
|
6931
|
+
VM.coefficients[cbici + 1] = -1;
|
6932
|
+
// Set RHS to highest level computed in previous blocks
|
6933
|
+
VM.rhs = cvbl.b_peak[VM.block_count - 1];
|
6852
6934
|
VMI_add_constraint(VM.LE);
|
6853
|
-
|
6854
|
-
// NOTE: the other two constraints need to be defined only for bt = 0
|
6855
|
-
if(VM.offset > 0) return;
|
6856
|
-
VMI_clear_coefficients();
|
6857
|
-
// (o) peak[b] >= peak[b-1]
|
6858
|
-
VM.coefficients[plci] = 1;
|
6859
|
-
// Set RHS to peak computed in previous block
|
6860
|
-
VM.rhs = VM.chunk_variables[cvi][1].peak_level[VM.block_count - 1];
|
6861
|
-
VMI_add_constraint(VM.GE);
|
6862
|
-
// NOTE: these constraints will ensure that peak >= all L[t] in the
|
6863
|
-
// chunk, but this does not prevent peak from taking a value higher
|
6864
|
-
// than the highest L[t]; preventing this would require defining as
|
6865
|
-
// many binary variables as there are time steps in the chunk.
|
6866
6935
|
}
|
6867
6936
|
|
6868
6937
|
function VMI_add_peak_increase_at_t_0(args) {
|
6869
|
-
// This operation should result in adding
|
6938
|
+
// This operation should result in adding peak increase[b] * link rate
|
6870
6939
|
// to the product level for which a constraint is being defined.
|
6871
|
-
// This means that the coefficient for
|
6872
|
-
// equal the link rate
|
6873
|
-
//
|
6874
|
-
|
6875
|
-
// NOTE: only execute this operation at block time = 0
|
6876
|
-
if(VM.offset > 0) return;
|
6940
|
+
// This means that the coefficient for (B or LA) peak increase[b] must
|
6941
|
+
// equal the link rate.
|
6942
|
+
// NOTE: only execute this operation at start of block or of LA period
|
6943
|
+
if(VM.offset && VM.offset !== MODEL.block_length * VM.cols) return;
|
6877
6944
|
const
|
6878
|
-
cvi = args[0],
|
6879
|
-
|
6880
|
-
prev_pl = obj.peak_level[VM.block_count - 1],
|
6945
|
+
cvi = args[0] + (VM.offset ? 1 : 0),
|
6946
|
+
tpl = VM.chunk_variables[cvi],
|
6881
6947
|
rr = args[1].result(VM.t);
|
6882
6948
|
if(DEBUGGING) {
|
6883
6949
|
console.log('VMI_add_peak_increase_at_t_0 (t = ' + VM.t + ')',
|
6884
|
-
|
6885
|
-
'previous peak =', prev_pl);
|
6950
|
+
tpl[0], tpl[1].displayName);
|
6886
6951
|
}
|
6887
6952
|
VM.coefficients[VM.chunk_offset + cvi] = rr;
|
6888
|
-
|
6953
|
+
// NOTE: no "add constraint" as this instruction is only part of the
|
6954
|
+
// series of coefficient-setting instructions
|
6889
6955
|
}
|
6890
6956
|
|
6891
6957
|
// NOTE: the global constants below are not defined in linny-r-globals.js
|