linny-r 2.0.4 → 2.0.5
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/static/images/replace-data-product.png +0 -0
- package/static/images/replace-product.png +0 -0
- package/static/index.html +6 -1
- package/static/scripts/linny-r-gui-controller.js +60 -12
- package/static/scripts/linny-r-model.js +23 -15
- package/static/scripts/linny-r-vm.js +72 -7
package/package.json
CHANGED
Binary file
|
Binary file
|
package/static/index.html
CHANGED
@@ -378,7 +378,9 @@ and move the cursor over the status bar">
|
|
378
378
|
title="Add cluster">
|
379
379
|
<img id="note-btn" class="btn toggle enab sep" src="images/note.png"
|
380
380
|
title="Add note">
|
381
|
-
<img id="
|
381
|
+
<img id="replace-btn" class="btn disab" src="images/replace-product.png"
|
382
|
+
title="Replace selected product by some other product (Alt-P)">
|
383
|
+
<img id="clone-btn" class="btn disab" src="images/clone.png"
|
382
384
|
title="Copy selection (Ctrl-C) – Alt-click to clone (Alt-C)">
|
383
385
|
<img id="paste-btn" class="btn disab sep" src="images/paste.png"
|
384
386
|
title="Paste selection (Ctrl-V)">
|
@@ -1375,6 +1377,9 @@ NOTE: Products directly linked to such processes should have a proportional unit
|
|
1375
1377
|
<option id="link-shutdown" value="10">
|
1376
1378
|
▼ (shut-down: 1 if X[t-1] > 0 ∧ X[t] = 0, otherwise 0)
|
1377
1379
|
</option>
|
1380
|
+
<option id="link-slack" value="12">
|
1381
|
+
↥ (available capacity: UB - X[t])
|
1382
|
+
</option>
|
1378
1383
|
<option id="link-spinning" value="8">
|
1379
1384
|
⤴ (spinning reserve: UB - X[t] if X[t] > 0, otherwise 0)
|
1380
1385
|
</option>
|
@@ -418,7 +418,7 @@ class GUIController extends Controller {
|
|
418
418
|
// Initialize controller buttons.
|
419
419
|
this.node_btns = ['process', 'product', 'link', 'constraint',
|
420
420
|
'cluster', 'module', 'note'];
|
421
|
-
this.edit_btns = ['clone', 'paste', 'delete', 'undo', 'redo'];
|
421
|
+
this.edit_btns = ['replace', 'clone', 'paste', 'delete', 'undo', 'redo'];
|
422
422
|
this.model_btns = ['settings', 'save', 'repository', 'actors',
|
423
423
|
'dataset', 'equation', 'chart', 'sensitivity', 'experiment',
|
424
424
|
'diagram', 'savediagram', 'finder', 'monitor', 'tex', 'solve'];
|
@@ -599,6 +599,8 @@ class GUIController extends Controller {
|
|
599
599
|
UI.copySelection();
|
600
600
|
}
|
601
601
|
});
|
602
|
+
this.buttons.replace.addEventListener('click',
|
603
|
+
() => UI.replaceSelectedProduct());
|
602
604
|
this.buttons.paste.addEventListener('click',
|
603
605
|
() => UI.pasteSelection());
|
604
606
|
this.buttons['delete'].addEventListener('click',
|
@@ -827,7 +829,7 @@ class GUIController extends Controller {
|
|
827
829
|
this.modals.move.cancel.addEventListener('click',
|
828
830
|
() => UI.doNotMoveNode());
|
829
831
|
|
830
|
-
// The REPLACE dialog appears when a product is
|
832
|
+
// The REPLACE dialog appears when a product is Shift-Alt-clicked.
|
831
833
|
this.modals.replace.ok.addEventListener('click',
|
832
834
|
() => UI.replaceProduct());
|
833
835
|
this.modals.replace.cancel.addEventListener('click',
|
@@ -925,6 +927,9 @@ class GUIController extends Controller {
|
|
925
927
|
UNDO_STACK.clear();
|
926
928
|
// Autosaving should start anew.
|
927
929
|
AUTO_SAVE.setAutoSaveInterval();
|
930
|
+
// Finder dialog is closed, but may still display results for
|
931
|
+
// previous model.
|
932
|
+
FINDER.updateDialog();
|
928
933
|
// Signal success or failure.
|
929
934
|
return loaded;
|
930
935
|
}
|
@@ -1132,7 +1137,6 @@ class GUIController extends Controller {
|
|
1132
1137
|
'confirm when prompted by your browser.');
|
1133
1138
|
// Hide "update" button in server dialog.
|
1134
1139
|
UI.modals.server.element('update').style.display = 'none';
|
1135
|
-
return;
|
1136
1140
|
} else {
|
1137
1141
|
// Inform user that install appears to have failed.
|
1138
1142
|
msg.push(
|
@@ -1142,7 +1146,8 @@ class GUIController extends Controller {
|
|
1142
1146
|
}
|
1143
1147
|
md.element('msg').innerHTML = msg.join('<br>');
|
1144
1148
|
// Reload `index.html`. This will start Linny-R anew.
|
1145
|
-
|
1149
|
+
// NOTE: Wait for 2 seconds so the message can be read.
|
1150
|
+
setTimeout(() => { window.open('./', '_self'); }, 2000);
|
1146
1151
|
}
|
1147
1152
|
})
|
1148
1153
|
.catch((err) => {
|
@@ -1591,7 +1596,7 @@ class GUIController extends Controller {
|
|
1591
1596
|
// Updates the buttons on the main GUI toolbars
|
1592
1597
|
const
|
1593
1598
|
node_btns = 'process product link constraint cluster note ',
|
1594
|
-
edit_btns = 'clone paste delete undo redo ',
|
1599
|
+
edit_btns = 'replace clone paste delete undo redo ',
|
1595
1600
|
model_btns = 'settings save actors dataset equation chart ' +
|
1596
1601
|
'diagram savediagram finder monitor solve';
|
1597
1602
|
if(MODEL === null) {
|
@@ -1617,9 +1622,30 @@ class GUIController extends Controller {
|
|
1617
1622
|
this.enableButtons(node_btns + model_btns);
|
1618
1623
|
this.active_button = this.stayActiveButton;
|
1619
1624
|
this.disableButtons(edit_btns);
|
1620
|
-
if(MODEL.selection.length > 0)
|
1625
|
+
if(MODEL.selection.length > 0) {
|
1626
|
+
this.enableButtons('clone delete');
|
1627
|
+
// Replace applies only to a single product.
|
1628
|
+
if(MODEL.selection.length === 1) {
|
1629
|
+
const p = MODEL.selection[0];
|
1630
|
+
if(p instanceof Product) {
|
1631
|
+
const
|
1632
|
+
b = this.buttons.replace,
|
1633
|
+
t = 'Replace selected product by some other product (Alt-P)';
|
1634
|
+
// Differentiate between product types, as products can be
|
1635
|
+
// replaced only by products of the same type.
|
1636
|
+
if(p.is_data) {
|
1637
|
+
b.title = t.replaceAll('product', 'data product');
|
1638
|
+
b.src = 'images/replace-data-product.png';
|
1639
|
+
} else {
|
1640
|
+
b.title = t;
|
1641
|
+
b.src = 'images/replace-product.png';
|
1642
|
+
}
|
1643
|
+
this.enableButtons('replace');
|
1644
|
+
}
|
1645
|
+
}
|
1646
|
+
}
|
1621
1647
|
if(this.canPaste) this.enableButtons('paste');
|
1622
|
-
// Only allow
|
1648
|
+
// Only allow soling when some target or process constraint is defined.
|
1623
1649
|
if(MODEL.hasTargets) this.enableButtons('solve');
|
1624
1650
|
var u = UNDO_STACK.canUndo;
|
1625
1651
|
if(u) {
|
@@ -2315,13 +2341,16 @@ class GUIController extends Controller {
|
|
2315
2341
|
} else if(alt && code === 'KeyR') {
|
2316
2342
|
// Alt-R means: run to diagnose infeasible/unbounded problem.
|
2317
2343
|
VM.solveModel(true);
|
2318
|
-
} else if(alt && ['KeyC', 'KeyM'].indexOf(code) >= 0) {
|
2319
|
-
// Special shortcut keys for "clone selection"
|
2344
|
+
} else if(alt && ['KeyC', 'KeyM', 'KeyP'].indexOf(code) >= 0) {
|
2345
|
+
// Special shortcut keys for "clone selection", "model settings"
|
2346
|
+
// and "replace product".
|
2320
2347
|
const be = new Event('click');
|
2321
2348
|
if(code === 'KeyC') {
|
2322
2349
|
this.buttons.clone.dispatchEvent(be);
|
2323
|
-
} else {
|
2350
|
+
} else if(code === 'KeyM') {
|
2324
2351
|
this.buttons.settings.dispatchEvent(be);
|
2352
|
+
} else if(code === 'KeyP') {
|
2353
|
+
this.buttons.replace.dispatchEvent(be);
|
2325
2354
|
}
|
2326
2355
|
} else if(!e.shiftKey && !alt &&
|
2327
2356
|
(!topmod || ['KeyA', 'KeyC', 'KeyV'].indexOf(code) < 0)) {
|
@@ -2333,7 +2362,9 @@ class GUIController extends Controller {
|
|
2333
2362
|
CONSTRAINT_EDITOR.deleteBoundLine();
|
2334
2363
|
} else if(!this.hidden('variable-modal')) {
|
2335
2364
|
CHART_MANAGER.deleteVariable();
|
2336
|
-
} else {
|
2365
|
+
} else if(!topmod) {
|
2366
|
+
// Do not delete entity from model diagram when some modal
|
2367
|
+
// is showing.
|
2337
2368
|
this.buttons['delete'].dispatchEvent(new Event('click'));
|
2338
2369
|
}
|
2339
2370
|
} else if (code === 'Period' && (e.ctrlKey || e.metaKey)) {
|
@@ -4276,6 +4307,15 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
4276
4307
|
soc = 0;
|
4277
4308
|
this.warn('Cost can only be attributed to level-based links');
|
4278
4309
|
}
|
4310
|
+
// For multipliers requiring a binary variable, and also for those
|
4311
|
+
// based on the node's upper bound, warn the modeler when the UB for
|
4312
|
+
// this node is infinite or unspecified.
|
4313
|
+
if(VM.LM_NEEDING_ON_OFF.indexOf(m) >= 0 || m === VM.LM_AVAILABLE_CAPACITY) {
|
4314
|
+
if(!l.from_node.upper_bound.text) {
|
4315
|
+
UI.warn('Infinite upper bound of <strong>' + l.from_node.displayName +
|
4316
|
+
`</strong> will cause issues for ${VM.LM_SYMBOLS[m]} link`);
|
4317
|
+
}
|
4318
|
+
}
|
4279
4319
|
// NOTE: share of cost is input as a percentage, but stored as a floating
|
4280
4320
|
// point value between 0 and 1
|
4281
4321
|
l.share_of_cost = soc / 100;
|
@@ -4307,9 +4347,17 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
4307
4347
|
'constraint-to-name').innerHTML = c.to_node.displayName;
|
4308
4348
|
CONSTRAINT_EDITOR.showDialog();
|
4309
4349
|
}
|
4350
|
+
|
4351
|
+
replaceSelectedProduct() {
|
4352
|
+
// Check whether selection contains one product, and if so, prompt
|
4353
|
+
// for replacement.
|
4354
|
+
if(MODEL.selection.length !== 1) return;
|
4355
|
+
const p = MODEL.selection[0];
|
4356
|
+
if(p instanceof Product) this.showReplaceProductDialog(p);
|
4357
|
+
}
|
4310
4358
|
|
4311
4359
|
showReplaceProductDialog(p) {
|
4312
|
-
//
|
4360
|
+
// Prompt for a product (different from `p`) by which `p` should be
|
4313
4361
|
// replaced for the selected product position
|
4314
4362
|
const pp = MODEL.focal_cluster.indexOfProduct(p);
|
4315
4363
|
if(pp >= 0) {
|
@@ -4076,16 +4076,24 @@ class LinnyRModel {
|
|
4076
4076
|
replaceProduct(p, r, global) {
|
4077
4077
|
const
|
4078
4078
|
ppi = this.focal_cluster.indexOfProduct(p),
|
4079
|
-
// NOTE:
|
4079
|
+
// NOTE: Record whether `r` is shown in focal cluster.
|
4080
4080
|
rshown = this.focal_cluster.indexOfProduct(r) >= 0;
|
4081
4081
|
// NOTE: since `ppi` should always be >= 0
|
4082
4082
|
if(ppi >= 0) {
|
4083
|
-
// Build list of information needed for "undo"
|
4084
|
-
const undo_info = {
|
4085
|
-
|
4086
|
-
|
4083
|
+
// Build list of information needed for "undo".
|
4084
|
+
const undo_info = {
|
4085
|
+
p: p.displayName,
|
4086
|
+
r: r.displayName,
|
4087
|
+
g: global,
|
4088
|
+
lf: [],
|
4089
|
+
lt: [],
|
4090
|
+
cf: [],
|
4091
|
+
ct: [],
|
4092
|
+
cl: []
|
4093
|
+
};
|
4094
|
+
// Keep track of redirected links.
|
4087
4095
|
const rl = [];
|
4088
|
-
// First replace product in (local) links
|
4096
|
+
// First replace product in (local) links.
|
4089
4097
|
for(let i = p.inputs.length - 1; i >= 0; i--) {
|
4090
4098
|
const l = p.inputs[i];
|
4091
4099
|
if(global || l.hasArrow) {
|
@@ -4093,7 +4101,7 @@ class LinnyRModel {
|
|
4093
4101
|
ml.copyPropertiesFrom(l);
|
4094
4102
|
this.deleteLink(l);
|
4095
4103
|
rl.push(ml);
|
4096
|
-
// NOTE: push identifier of *modified* link
|
4104
|
+
// NOTE: push identifier of *modified* link.
|
4097
4105
|
undo_info.lt.push(ml.identifier);
|
4098
4106
|
}
|
4099
4107
|
}
|
@@ -4107,7 +4115,7 @@ class LinnyRModel {
|
|
4107
4115
|
}
|
4108
4116
|
}
|
4109
4117
|
// Then also replace product in (local) constraints
|
4110
|
-
// (also keeping track of affected constraints)
|
4118
|
+
// (also keeping track of affected constraints).
|
4111
4119
|
const rc = [];
|
4112
4120
|
for(let k in this.constraints) {
|
4113
4121
|
if(this.constraints.hasOwnProperty(k)) {
|
@@ -4125,26 +4133,26 @@ class LinnyRModel {
|
|
4125
4133
|
}
|
4126
4134
|
}
|
4127
4135
|
}
|
4128
|
-
// Replace `p` by `r` as the positioned product
|
4136
|
+
// Replace `p` by `r` as the positioned product.
|
4129
4137
|
const pp = this.focal_cluster.product_positions[ppi];
|
4130
4138
|
undo_info.x = pp.x;
|
4131
4139
|
undo_info.y = pp.y;
|
4132
4140
|
pp.product = r;
|
4133
|
-
// Change coordinates only if `r` is also shown in the focal cluster
|
4141
|
+
// Change coordinates only if `r` is also shown in the focal cluster.
|
4134
4142
|
if(rshown) {
|
4135
4143
|
pp.x = r.x;
|
4136
4144
|
pp.y = r.y;
|
4137
4145
|
}
|
4138
|
-
// Likewise replace product of other placeholders of `p` by `r
|
4146
|
+
// Likewise replace product of other placeholders of `p` by `r`.
|
4139
4147
|
for(let k in this.clusters) if(this.clusters.hasOwnProperty(k)) {
|
4140
4148
|
const
|
4141
4149
|
c = this.clusters[k],
|
4142
4150
|
ppi = c.indexOfProduct(p);
|
4143
|
-
// NOTE:
|
4151
|
+
// NOTE: When local, replace only if sub-cluster is in view...
|
4144
4152
|
if(ppi >= 0 && (global || this.focal_cluster.containsCluster(c))) {
|
4145
4153
|
const pp = c.product_positions[ppi];
|
4146
|
-
//
|
4147
|
-
// links to `p` were NOT redirected
|
4154
|
+
// ... and then it MAY be that within this sub-cluster, the local
|
4155
|
+
// links to `p` were NOT redirected.
|
4148
4156
|
const ll = [];
|
4149
4157
|
for(let i = 0; i < p.inputs.length; i++) {
|
4150
4158
|
const l = p.inputs[i];
|
@@ -4169,7 +4177,7 @@ class LinnyRModel {
|
|
4169
4177
|
}
|
4170
4178
|
// Now prepare for undo, so that deleteNode can add its XML
|
4171
4179
|
UNDO_STACK.push('replace', undo_info);
|
4172
|
-
// Delete original product `p` if it has no more product positions
|
4180
|
+
// Delete original product `p` if it has no more product positions.
|
4173
4181
|
if(!this.top_cluster.containsProduct(p)) this.deleteNode(p);
|
4174
4182
|
}
|
4175
4183
|
// Prepare for redraw
|
@@ -2157,6 +2157,10 @@ class VirtualMachine {
|
|
2157
2157
|
// diagnostic purposes -- see below.
|
2158
2158
|
this.PLUS_INFINITY = 1e+25;
|
2159
2159
|
this.MINUS_INFINITY = -1e+25;
|
2160
|
+
// Expression results having an infinite term may be less than infinity,
|
2161
|
+
// but still exceptionally high, and this should be shown.
|
2162
|
+
this.NEAR_PLUS_INFINITY = this.PLUS_INFINITY / 200;
|
2163
|
+
this.NEAR_MINUS_INFINITY = this.MINUS_INFINITY / 200;
|
2160
2164
|
// As of version 1.8.0, Linny-R imposes no +INF bounds on processes
|
2161
2165
|
// unless diagnosing an unbounded problem. For such diagnosis, the
|
2162
2166
|
// (relatively) low value 9.999999999e+9 is used.
|
@@ -2218,11 +2222,12 @@ class VirtualMachine {
|
|
2218
2222
|
this.LM_FIRST_COMMIT = 9; // Symbol: hollow asterisk
|
2219
2223
|
this.LM_SHUTDOWN = 10; // Symbol: thick chevron down
|
2220
2224
|
this.LM_PEAK_INC = 11; // Symbol: plus inside triangle ("peak-plus")
|
2225
|
+
this.LM_AVAILABLE_CAPACITY = 12; // Symbol: up-arrow with baseline
|
2221
2226
|
// List of link multipliers that require binary ON/OFF variables
|
2222
2227
|
this.LM_NEEDING_ON_OFF = [5, 6, 7, 8, 9, 10];
|
2223
2228
|
this.LM_SYMBOLS = ['', '\u21C9', '\u0394', '\u03A3', '\u03BC', '\u25B2',
|
2224
|
-
'+', '0', '\u2934', '\u2732', '\u25BC', '\u2A39'];
|
2225
|
-
this.LM_LETTERS = ' TDSMU+
|
2229
|
+
'+', '0', '\u2934', '\u2732', '\u25BC', '\u2A39', '\u21A5'];
|
2230
|
+
this.LM_LETTERS = ' TDSMU+0RFDPA';
|
2226
2231
|
|
2227
2232
|
// VM max. expression stack size.
|
2228
2233
|
this.MAX_STACK = 200;
|
@@ -2560,14 +2565,14 @@ class VirtualMachine {
|
|
2560
2565
|
if(n <= this.CYCLIC) return [true, '#CYCLE!'];
|
2561
2566
|
// Any other number less than or equal to 10^30 is considered as
|
2562
2567
|
// minus infinity.
|
2563
|
-
if(n <= this.
|
2568
|
+
if(n <= this.NEAR_MINUS_INFINITY) return [true, '-\u221E'];
|
2564
2569
|
// Other special values are very big POSITIVE numbers, so start
|
2565
2570
|
// comparing `n` with the highest value.
|
2566
2571
|
if(n >= this.COMPUTING) return [true, '\u25A6']; // Checkered square
|
2567
2572
|
// NOTE: The prettier circled bold X 2BBF does not display on macOS !!
|
2568
2573
|
if(n >= this.NOT_COMPUTED) return [true, '\u2297']; // Circled X
|
2569
2574
|
if(n >= this.UNDEFINED) return [true, '\u2047']; // Double question mark ??
|
2570
|
-
if(n >= this.
|
2575
|
+
if(n >= this.NEAR_PLUS_INFINITY) return [true, '\u221E'];
|
2571
2576
|
if(n === this.NO_COST) return [true, '\u00A2']; // c-slash (cent symbol)
|
2572
2577
|
return [false, n];
|
2573
2578
|
}
|
@@ -3693,6 +3698,15 @@ class VirtualMachine {
|
|
3693
3698
|
VM.PRODUCE, VM.SPIN_RES, p.on_off_var_index,
|
3694
3699
|
l.flow_delay, vi, l.from_node.upper_bound, tnpx,
|
3695
3700
|
l.relative_rate]]);
|
3701
|
+
} else if(l.multiplier === VM.LM_REMAINING_CAPACITY) {
|
3702
|
+
// "remaining capacity" equals UB - level. This is a
|
3703
|
+
// simpler version of "spinning reserve". We signal this
|
3704
|
+
// by passing -1 as the index of the secondary variable,
|
3705
|
+
// and the level variable index as the primary variable.
|
3706
|
+
this.code.push([VMI_update_cash_coefficient, [
|
3707
|
+
VM.PRODUCE, VM.SPIN_RES, vi, // <-- now as primary
|
3708
|
+
l.flow_delay, -1, // <-- signal that it is "REM_CAP"
|
3709
|
+
l.from_node.upper_bound, tnpx, l.relative_rate]]);
|
3696
3710
|
} else if(l.multiplier === VM.LM_PEAK_INC) {
|
3697
3711
|
// NOTE: "peak increase" may be > 0 only in the first
|
3698
3712
|
// time step of the block being optimized, and in the
|
@@ -4035,6 +4049,12 @@ class VirtualMachine {
|
|
4035
4049
|
// NOTE: no delay on this type of link
|
4036
4050
|
this.code.push([VMI_add_peak_increase_at_t_0,
|
4037
4051
|
[vi, l.relative_rate]]);
|
4052
|
+
} else if(l.multiplier === VM.LM_AVAILABLE_CAPACITY) {
|
4053
|
+
// The "available capacity" equals UB - level, so subtract
|
4054
|
+
// UB * rate from RHS, while considering the delay.
|
4055
|
+
// NOTE: New instruction style that passes pointers to
|
4056
|
+
// model entities instead of their properties.
|
4057
|
+
this.code.push([VMI_add_available_capacity, l]);
|
4038
4058
|
} else if(l.relative_rate.isStatic) {
|
4039
4059
|
// Static rates permit simpler VM instructions
|
4040
4060
|
c = l.relative_rate.result(0);
|
@@ -4311,7 +4331,7 @@ class VirtualMachine {
|
|
4311
4331
|
hub = ub;
|
4312
4332
|
if(ub > VM.MEGA_UPPER_BOUND) {
|
4313
4333
|
hub = p.highestUpperBound([]);
|
4314
|
-
// If UB still very high, warn modeler on infoline and in monitor
|
4334
|
+
// If UB still very high, warn modeler on infoline and in monitor.
|
4315
4335
|
if(hub > VM.MEGA_UPPER_BOUND) {
|
4316
4336
|
const msg = 'High upper bound (' + this.sig4Dig(hub) +
|
4317
4337
|
') for <strong>' + p.displayName + '</strong>' +
|
@@ -5005,6 +5025,8 @@ class VirtualMachine {
|
|
5005
5025
|
} else if(l.multiplier === VM.LM_SHUTDOWN) {
|
5006
5026
|
// Similar to STARTUP, but now look in the shut-down list.
|
5007
5027
|
pl = (p.shut_downs.indexOf(bt) < 0 ? 0 : 1);
|
5028
|
+
} else if(l.multiplier === VM.LM_AVAILABLE_CAPACITY) {
|
5029
|
+
pl = p.upper_bound.result(bt) - pl;
|
5008
5030
|
} else if(l.multiplier === VM.LM_INCREASE) {
|
5009
5031
|
const ppl = p.actualLevel(bt - 1);
|
5010
5032
|
pl = this.severestIssue([pl, ppl], pl - ppl);
|
@@ -6423,6 +6445,8 @@ Solver status = ${json.status}`);
|
|
6423
6445
|
this.MINUS_INFINITY = this.SOLVER_MINUS_INFINITY;
|
6424
6446
|
console.log('DIAGNOSIS OFF');
|
6425
6447
|
}
|
6448
|
+
this.NEAR_PLUS_INFINITY = this.PLUS_INFINITY / 200;
|
6449
|
+
this.NEAR_MINUS_INFINITY = this.MINUS_INFINITY / 200;
|
6426
6450
|
// The "propt to diagnose" flag is set when some block posed an
|
6427
6451
|
// infeasible or unbounded problem.
|
6428
6452
|
this.prompt_to_diagnose = false;
|
@@ -8538,9 +8562,12 @@ function VMI_update_cash_coefficient(args) {
|
|
8538
8562
|
// The ON/OFF variable index is passed as third argument, hence `plvi`
|
8539
8563
|
// (process level variable index) as first extra parameter, plus three
|
8540
8564
|
// expressions (UB, price, rate).
|
8565
|
+
// NOTE: This type is also used to compute "available capacity".
|
8541
8566
|
const
|
8542
|
-
|
8543
|
-
//
|
8567
|
+
// args[4] = -1 signals that the remaining capacity should be
|
8568
|
+
// computed, which means that ON/OFF should be disregarded.
|
8569
|
+
plvi = (args[4] < 0 ? vi : args[4]),
|
8570
|
+
// Column of second variable will be relative to same offset.
|
8544
8571
|
plk = k + plvi - vi,
|
8545
8572
|
ub = args[5].result(VM.t),
|
8546
8573
|
price_rate = args[6].result(VM.t) * args[7].result(VM.t);
|
@@ -9198,6 +9225,44 @@ function VMI_add_peak_increase_at_t_0(args) {
|
|
9198
9225
|
// series of coefficient-setting instructions
|
9199
9226
|
}
|
9200
9227
|
|
9228
|
+
function VMI_add_available_capacity(link) {
|
9229
|
+
// Adds the "available capacity" of the FROM node (process) to the
|
9230
|
+
// level of the TO node (data product) while considering the delay.
|
9231
|
+
// NOTE: New instruction style that passes pointers to model entities
|
9232
|
+
// instead of their properties.
|
9233
|
+
const
|
9234
|
+
d = link.actualDelay(VM.t),
|
9235
|
+
fnvi = link.from_node.level_var_index,
|
9236
|
+
// Column number in the tableau.
|
9237
|
+
fnk = VM.offset + fnvi - d * VM.cols,
|
9238
|
+
// Use flow rate and upper bound for t minus delay.
|
9239
|
+
t = VM.t - d,
|
9240
|
+
r = link.relative_rate.result(t),
|
9241
|
+
u = link.from_node.upper_bound.result(t);
|
9242
|
+
if(DEBUGGING) {
|
9243
|
+
console.log('VMI_add_available_capacity (t = ' + VM.t + ')',
|
9244
|
+
link.displayName, 'UB', u, 'rate', r);
|
9245
|
+
}
|
9246
|
+
// Available capacity equals UB - level, so subtract UB * rate
|
9247
|
+
// from RHS...
|
9248
|
+
VM.rhs -= u * r;
|
9249
|
+
// ... and subtract rate from FROM node coefficient.
|
9250
|
+
if(fnk <= 0) {
|
9251
|
+
// NOTE: If `fnk` falls PRIOR to the start of the block being solved,
|
9252
|
+
// this means that the value of the decision variable X for which the
|
9253
|
+
// coefficient C is to be set by this instruction has been calculated
|
9254
|
+
// while solving a previous block. Since the value of X is known,
|
9255
|
+
// adding X*rate to C is implemented as subtracting X*rate from the
|
9256
|
+
// right hand side of the constraint.
|
9257
|
+
VM.rhs += knownValue(fnvi, t) * r;
|
9258
|
+
} else if(fnk in VM.coefficients) {
|
9259
|
+
VM.coefficients[fnk] -= r;
|
9260
|
+
} else {
|
9261
|
+
VM.coefficients[fnk] = -r;
|
9262
|
+
}
|
9263
|
+
}
|
9264
|
+
|
9265
|
+
|
9201
9266
|
// NOTE: the global constants below are not defined in linny-r-globals.js
|
9202
9267
|
// because some comprise the identifiers of functions for VM instructions
|
9203
9268
|
|