linny-r 2.0.7 → 2.0.9
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 +3 -40
- package/package.json +1 -1
- package/server.js +19 -157
- package/static/index.html +74 -21
- package/static/linny-r.css +22 -16
- package/static/scripts/iro.min.js +7 -7
- package/static/scripts/linny-r-ctrl.js +51 -72
- package/static/scripts/linny-r-gui-actor-manager.js +23 -33
- package/static/scripts/linny-r-gui-chart-manager.js +50 -45
- package/static/scripts/linny-r-gui-constraint-editor.js +6 -10
- package/static/scripts/linny-r-gui-controller.js +254 -230
- package/static/scripts/linny-r-gui-dataset-manager.js +143 -32
- package/static/scripts/linny-r-gui-documentation-manager.js +11 -17
- package/static/scripts/linny-r-gui-equation-manager.js +22 -22
- package/static/scripts/linny-r-gui-experiment-manager.js +102 -129
- package/static/scripts/linny-r-gui-file-manager.js +53 -46
- package/static/scripts/linny-r-gui-finder.js +105 -51
- package/static/scripts/linny-r-gui-model-autosaver.js +2 -4
- package/static/scripts/linny-r-gui-monitor.js +35 -41
- package/static/scripts/linny-r-gui-paper.js +42 -70
- package/static/scripts/linny-r-gui-power-grid-manager.js +31 -34
- package/static/scripts/linny-r-gui-receiver.js +1 -2
- package/static/scripts/linny-r-gui-repository-browser.js +44 -46
- package/static/scripts/linny-r-gui-scale-unit-manager.js +32 -32
- package/static/scripts/linny-r-gui-sensitivity-analysis.js +61 -67
- package/static/scripts/linny-r-gui-undo-redo.js +94 -95
- package/static/scripts/linny-r-milp.js +20 -24
- package/static/scripts/linny-r-model.js +1832 -2248
- package/static/scripts/linny-r-utils.js +35 -27
- package/static/scripts/linny-r-vm.js +807 -905
- package/static/show-png.html +0 -113
@@ -236,12 +236,12 @@ class Expression {
|
|
236
236
|
// Returns XML-encoded expression after replacing "black-boxed" entities.
|
237
237
|
let text = this.text;
|
238
238
|
if(MODEL.black_box) {
|
239
|
-
// Get all entity names that occur in this expression
|
239
|
+
// Get all entity names that occur in this expression.
|
240
240
|
const vl = text.match(/\[[^\[]+\]/g);
|
241
|
-
if(vl) for(
|
242
|
-
// Trim enclosing brackets and remove the "tail" (attribute or offset)
|
241
|
+
if(vl) for(const v of vl) {
|
242
|
+
// Trim enclosing brackets and remove the "tail" (attribute or offset).
|
243
243
|
let tail = '',
|
244
|
-
e =
|
244
|
+
e = v.substring(1, v.length - 1).split(UI.OA_SEPARATOR);
|
245
245
|
if(e.length > 1) {
|
246
246
|
tail = UI.OA_SEPARATOR + e.pop();
|
247
247
|
e = e.join(UI.OA_SEPARATOR);
|
@@ -254,21 +254,29 @@ class Expression {
|
|
254
254
|
e = e[0];
|
255
255
|
}
|
256
256
|
}
|
257
|
-
// Link names comprise two entities
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
257
|
+
// Link names and constraint names comprise two entities.
|
258
|
+
// If so, process both entity names.
|
259
|
+
let arrow = UI.LINK_ARROW,
|
260
|
+
parts = e.split(arrow);
|
261
|
+
if(parts.length === 1) {
|
262
|
+
arrow = UI.CONSTRAINT_ARROW;
|
263
|
+
parts = e.split(arrow);
|
264
|
+
}
|
265
|
+
if(parts.length > 1) {
|
266
|
+
let n = 0;
|
267
|
+
const enl = [];
|
268
|
+
for(const en of parts) {
|
269
|
+
const id = UI.nameToID(en);
|
270
|
+
if(MODEL.black_box_entities.hasOwnProperty(id)) {
|
271
|
+
enl.push(MODEL.black_box_entities[id]);
|
272
|
+
n++;
|
273
|
+
} else {
|
274
|
+
enl.push(en);
|
275
|
+
}
|
276
|
+
}
|
277
|
+
if(n > 0) {
|
278
|
+
text = text.replace(v, '[' + enl.join(arrow) + tail + ']');
|
268
279
|
}
|
269
|
-
}
|
270
|
-
if(n > 0) {
|
271
|
-
text = text.replace(vl[i], '[' + enl.join(UI.LINK_ARROW) + tail + ']');
|
272
280
|
}
|
273
281
|
}
|
274
282
|
}
|
@@ -301,11 +309,7 @@ class Expression {
|
|
301
309
|
if(DEBUGGING) {
|
302
310
|
// Show the "time step stack" for --START and --STOP
|
303
311
|
if(action.startsWith('--') || action.startsWith('"')) {
|
304
|
-
|
305
|
-
for(let i = 0; i < this.step.length; i++) {
|
306
|
-
s.push(this.step[i]);
|
307
|
-
}
|
308
|
-
action = `[${s.join(', ')}] ${action}`;
|
312
|
+
action = `[${step.join(', ')}] ${action}`;
|
309
313
|
}
|
310
314
|
console.log(action);
|
311
315
|
}
|
@@ -317,7 +321,6 @@ class Expression {
|
|
317
321
|
if((typeof number !== 'number' ||
|
318
322
|
(this.isStatic && !this.isWildcardExpression)) &&
|
319
323
|
!this.isMethod) return this.vector;
|
320
|
-
//console.log('HERE choosing wcnr', number, this);
|
321
324
|
// Method expressions are not "numbered" but differentiate by the
|
322
325
|
// entity to which they are applied. Their "vector number" is then
|
323
326
|
// inferred by looking up this entity in a method object list.
|
@@ -332,9 +335,7 @@ class Expression {
|
|
332
335
|
}
|
333
336
|
// Use the vector for the wildcard number (create it if necessary).
|
334
337
|
if(!this.wildcard_vectors.hasOwnProperty(number)) {
|
335
|
-
//console.log('HERE adding wc vector', number, this);
|
336
338
|
this.wildcard_vectors[number] = [];
|
337
|
-
//console.log('HERE adding wc vector', number, this.wildcard_vectors);
|
338
339
|
if(this.isStatic) {
|
339
340
|
this.wildcard_vectors[number][0] = VM.NOT_COMPUTED;
|
340
341
|
} else {
|
@@ -444,6 +445,12 @@ class Expression {
|
|
444
445
|
// expression).
|
445
446
|
if(t < 0 || this.isStatic) t = 0;
|
446
447
|
if(t >= v.length) return VM.UNDEFINED;
|
448
|
+
// Check for recursive calls.
|
449
|
+
if(v[t] === VM.COMPUTING) {
|
450
|
+
console.log('Already computing expression for', this.variableName);
|
451
|
+
console.log(this.text);
|
452
|
+
return VM.CYCLIC;
|
453
|
+
}
|
447
454
|
// NOTES:
|
448
455
|
// (1) When VM is setting up a tableau, values computed for the
|
449
456
|
// look-ahead period must be recomputed.
|
@@ -574,12 +581,11 @@ class Expression {
|
|
574
581
|
if(matches) {
|
575
582
|
// Match is case-insensitive, so check each for matching case of
|
576
583
|
// attribute.
|
577
|
-
for(
|
584
|
+
for(const m of matches) {
|
578
585
|
const
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
let ao = e.pop().slice(0, -1),
|
586
|
+
e = m.split('|'),
|
587
|
+
// Let `ao` be attribute + offset (if any) without right bracket.
|
588
|
+
ao = e.pop().slice(0, -1),
|
583
589
|
// Then also trim offset and spaces.
|
584
590
|
a = ao.split('@')[0].trim();
|
585
591
|
// Check whether `a` (without bracket and without spaces) indeed
|
@@ -946,12 +952,12 @@ class ExpressionParser {
|
|
946
952
|
// prefix should be added.
|
947
953
|
name = UI.colonPrefixedName(name, this.owner_prefix);
|
948
954
|
if(x.x) {
|
949
|
-
// Look up name in experiment outcomes list
|
955
|
+
// Look up name in experiment outcomes list.
|
950
956
|
x.v = x.x.resultIndex(name);
|
951
957
|
if(x.v < 0 && name.indexOf('#') >= 0 &&
|
952
958
|
typeof this.context_number === 'number') {
|
953
959
|
// Variable name may be parametrized with #, but not in
|
954
|
-
// expressions for wildcard selectors
|
960
|
+
// expressions for wildcard selectors.
|
955
961
|
name = name.replace('#', this.context_number);
|
956
962
|
x.v = x.x.resultIndex(name);
|
957
963
|
}
|
@@ -960,24 +966,24 @@ class ExpressionParser {
|
|
960
966
|
x.x.displayName, '"'].join('');
|
961
967
|
}
|
962
968
|
} else {
|
963
|
-
// Check outcome list of ALL experiments
|
964
|
-
for(
|
965
|
-
let xri =
|
969
|
+
// Check outcome list of ALL experiments.
|
970
|
+
for(const mx of MODEL.experiments) {
|
971
|
+
let xri = mx.resultIndex(name);
|
966
972
|
if(xri < 0 && name.indexOf('#') >= 0 &&
|
967
973
|
typeof this.context_number === 'number') {
|
968
974
|
// Variable name may be parametrized with #, but not in
|
969
975
|
// expressions for wildcard selectors.
|
970
976
|
name = name.replace('#', this.context_number);
|
971
|
-
xri =
|
977
|
+
xri = mx.resultIndex(name);
|
972
978
|
}
|
973
979
|
if(xri >= 0) {
|
974
|
-
// If some match is found, the name specifies a variable
|
980
|
+
// If some match is found, the name specifies a variable.
|
975
981
|
x.v = xri;
|
976
982
|
break;
|
977
983
|
}
|
978
984
|
}
|
979
985
|
}
|
980
|
-
// NOTE:
|
986
|
+
// NOTE: Experiment may still be FALSE, as this will be interpreted
|
981
987
|
// as "use current experiment", but run number should be specified.
|
982
988
|
if(!msg) {
|
983
989
|
if(x.r === false && x.t === false) {
|
@@ -1100,8 +1106,7 @@ class ExpressionParser {
|
|
1100
1106
|
// narrowing the selection at run time, based on the expression's
|
1101
1107
|
// wildcard number.
|
1102
1108
|
const wdict = {};
|
1103
|
-
for(
|
1104
|
-
const e = ewa[i];
|
1109
|
+
for(const e of ewa) {
|
1105
1110
|
if(patternMatch(e.displayName, pat)) {
|
1106
1111
|
const mnr = matchingWildcardNumber(e.displayName, pat);
|
1107
1112
|
// NOTE: Attribute may be a single value, a vector, or an expression.
|
@@ -1245,8 +1250,7 @@ class ExpressionParser {
|
|
1245
1250
|
mep = method.expression.eligible_prefixes,
|
1246
1251
|
prefs = Object.keys(mep);
|
1247
1252
|
// NOTE: Prefix keys will always be in lower case.
|
1248
|
-
for(
|
1249
|
-
const pref = prefs[i];
|
1253
|
+
for(const pref of prefs) {
|
1250
1254
|
if(this.eligible_prefixes === null || this.eligible_prefixes[pref]) {
|
1251
1255
|
ep[pref] = true;
|
1252
1256
|
}
|
@@ -1271,11 +1275,10 @@ class ExpressionParser {
|
|
1271
1275
|
// This should not be empty when a method reference is parsed.
|
1272
1276
|
const
|
1273
1277
|
tail = UI.PREFIXER + name.substring(1).trim(),
|
1274
|
-
ee = MODEL.entitiesEndingOn(tail, attr),
|
1275
1278
|
ep = {};
|
1276
|
-
for(
|
1279
|
+
for(const e of MODEL.entitiesEndingOn(tail, attr)) {
|
1277
1280
|
const
|
1278
|
-
en =
|
1281
|
+
en = e.displayName,
|
1279
1282
|
pref = en.substring(0, en.length - tail.length).toLowerCase();
|
1280
1283
|
if(this.eligible_prefixes === null || this.eligible_prefixes[pref]) {
|
1281
1284
|
ep[pref] = true;
|
@@ -1296,10 +1299,10 @@ class ExpressionParser {
|
|
1296
1299
|
// NOTE: Some attributes make the method expression level-dependent.
|
1297
1300
|
this.is_level_based = this.is_level_based ||
|
1298
1301
|
VM.level_based_attr.indexOf(attr) >= 0;
|
1299
|
-
// NOTE:
|
1300
|
-
// dynamic
|
1301
|
-
|
1302
|
-
|
1302
|
+
// NOTE: Simply assume that callin a method makes the expression
|
1303
|
+
// dynamic.
|
1304
|
+
this.is_static = false;
|
1305
|
+
this.log('assumed to be dynamic because method is used');
|
1303
1306
|
// Colon-prefixed variables in method expressions are similar to
|
1304
1307
|
// wildcard variables, so the same VM instruction is coded for,
|
1305
1308
|
// except that the entity that is the object of the method will
|
@@ -1714,11 +1717,11 @@ class ExpressionParser {
|
|
1714
1717
|
} else {
|
1715
1718
|
v = this.expr.substring(this.pit + 1, i);
|
1716
1719
|
this.pit = i + 1;
|
1717
|
-
// NOTE: Enclosing quotes are also part of this symbol
|
1720
|
+
// NOTE: Enclosing quotes are also part of this symbol.
|
1718
1721
|
this.los = v.length + 2;
|
1719
1722
|
v = UI.cleanName(v);
|
1720
1723
|
if(MODEL.scale_units.hasOwnProperty(v)) {
|
1721
|
-
// Symbol is a scale unit => use its multiplier as numerical value
|
1724
|
+
// Symbol is a scale unit => use its multiplier as numerical value.
|
1722
1725
|
this.sym = MODEL.scale_units[v].multiplier;
|
1723
1726
|
} else {
|
1724
1727
|
this.error = `Unknown scale unit "${v}"`;
|
@@ -1730,8 +1733,8 @@ class ExpressionParser {
|
|
1730
1733
|
this.pit++;
|
1731
1734
|
} else if(OPERATOR_CHARS.indexOf(c) >= 0) {
|
1732
1735
|
this.pit++;
|
1733
|
-
// Check for compound operators (!=, <>, <=,
|
1734
|
-
// the second character
|
1736
|
+
// Check for compound operators (!=, <>, <=, >=, //) and if so, append
|
1737
|
+
// the second character.
|
1735
1738
|
if(this.pit <= this.eot &&
|
1736
1739
|
COMPOUND_OPERATORS.indexOf(c + this.expr.charAt(this.pit)) >= 0) {
|
1737
1740
|
c += this.expr.charAt(this.pit);
|
@@ -1739,18 +1742,18 @@ class ExpressionParser {
|
|
1739
1742
|
}
|
1740
1743
|
this.los = c.length;
|
1741
1744
|
// Instead of the operator symbol, the corresponding VM instruction
|
1742
|
-
// should be pushed onto the symbol stack
|
1745
|
+
// should be pushed onto the symbol stack.
|
1743
1746
|
this.sym = OPERATOR_CODES[OPERATORS.indexOf(c)];
|
1744
1747
|
} else {
|
1745
1748
|
// Take any text up to the next operator, parenthesis,
|
1746
|
-
// opening bracket, quote or space
|
1749
|
+
// opening bracket, quote or space.
|
1747
1750
|
this.los = 0;
|
1748
1751
|
let pl = this.pit + this.los,
|
1749
1752
|
cpl = this.expr.charAt(pl),
|
1750
1753
|
pcpl = '',
|
1751
1754
|
digs = false;
|
1752
1755
|
// NOTE: + and - operators are special case, since they may also
|
1753
|
-
// be part of a floating point number, hence the more elaborate check
|
1756
|
+
// be part of a floating point number, hence the more elaborate check.
|
1754
1757
|
while(pl <= this.eot && (SEPARATOR_CHARS.indexOf(cpl) < 0 ||
|
1755
1758
|
('+-'.indexOf(cpl) >= 0 && digs && pcpl.toLowerCase() === 'e'))) {
|
1756
1759
|
digs = digs || '0123456789'.indexOf(cpl) >= 0;
|
@@ -1764,15 +1767,15 @@ class ExpressionParser {
|
|
1764
1767
|
this.expr.charAt(this.pit + this.los) === ' ') {
|
1765
1768
|
this.los++;
|
1766
1769
|
}
|
1767
|
-
// ... but trim spaces from the symbol
|
1770
|
+
// ... but trim spaces from the symbol.
|
1768
1771
|
v = this.expr.substring(this.pit, this.pit + this.los).trim();
|
1769
|
-
// Ignore case
|
1772
|
+
// Ignore case.
|
1770
1773
|
l = v.toLowerCase();
|
1771
1774
|
if(l === '#') {
|
1772
1775
|
// # symbolizes the numeric part of a dataset selector, so check
|
1773
1776
|
// whether the expression being parsed is a dataset modifier with
|
1774
1777
|
// a selector that has a numeric wildcard OR whether # can be inferred
|
1775
|
-
// from the owner
|
1778
|
+
// from the owner.
|
1776
1779
|
if(this.selector.indexOf('*') >= 0 ||
|
1777
1780
|
this.selector.indexOf('?') >= 0 ||
|
1778
1781
|
this.owner.numberContext) {
|
@@ -1791,18 +1794,20 @@ class ExpressionParser {
|
|
1791
1794
|
if(isNaN(f) || !isFinite(f)) {
|
1792
1795
|
this.error = `Invalid number "${v}"`;
|
1793
1796
|
} else {
|
1794
|
-
// If a valid number, keep it within the +/- infinity range
|
1797
|
+
// If a valid number, keep it within the +/- infinity range.
|
1795
1798
|
this.sym = Math.max(VM.MINUS_INFINITY, Math.min(VM.PLUS_INFINITY, f));
|
1796
1799
|
}
|
1797
|
-
} else if(MODEL.scale_units.hasOwnProperty(v)) {
|
1798
|
-
// Symbol is a scale unit => use its multiplier as numerical value
|
1799
|
-
this.sym = MODEL.scale_units[v].multiplier;
|
1800
1800
|
} else {
|
1801
|
-
// Symbol does not start with a digit
|
1802
|
-
// NOTE:
|
1801
|
+
// Symbol does not start with a digit.
|
1802
|
+
// NOTE: Distinguish between run length N and block length n.
|
1803
1803
|
i = ACTUAL_SYMBOLS.indexOf(l === 'n' ? v : l);
|
1804
1804
|
if(i < 0) {
|
1805
|
-
|
1805
|
+
if(MODEL.scale_units.hasOwnProperty(v)) {
|
1806
|
+
// Symbol is a scale unit => use its multiplier as numerical value.
|
1807
|
+
this.sym = MODEL.scale_units[v].multiplier;
|
1808
|
+
} else {
|
1809
|
+
this.error = `Invalid symbol "${v}"`;
|
1810
|
+
}
|
1806
1811
|
} else {
|
1807
1812
|
this.sym = SYMBOL_CODES[i];
|
1808
1813
|
// NOTE: Using time symbols or `random` makes the expression dynamic!
|
@@ -1814,7 +1819,7 @@ class ExpressionParser {
|
|
1814
1819
|
// A minus is monadic if at the start of the expression, or NOT preceded
|
1815
1820
|
// by a "constant symbol", a number, or a closing parenthesis `)`.
|
1816
1821
|
// Constant symbols are time 't', block start 'b', block length 'n',
|
1817
|
-
// look-ahead 'l', 'random', 'true', 'false', 'pi', and 'infinity'
|
1822
|
+
// look-ahead 'l', 'random', 'true', 'false', 'pi', and 'infinity'.
|
1818
1823
|
if(DYADIC_CODES.indexOf(this.sym) === DYADIC_OPERATORS.indexOf('-') &&
|
1819
1824
|
(this.prev_sym === null ||
|
1820
1825
|
!(Array.isArray(this.prev_sym) ||
|
@@ -2045,21 +2050,6 @@ class ExpressionParser {
|
|
2045
2050
|
this.error = 'Invalid parameter list';
|
2046
2051
|
}
|
2047
2052
|
}
|
2048
|
-
// When compiling a method, check for all eligible prefixes whether
|
2049
|
-
// they might make the expression dynamic.
|
2050
|
-
if(this.is_static && this.eligible_prefixes) {
|
2051
|
-
const epl = Object.keys(this.eligible_prefixes);
|
2052
|
-
for(let i = 0; i < epl.length; i++) {
|
2053
|
-
const ep = epl[i];
|
2054
|
-
for(let j = 0; j < ep.length; j++) {
|
2055
|
-
if(ep[j] instanceof Dataset && ep[j].mayBeDynamic) {
|
2056
|
-
this.is_static = false;
|
2057
|
-
this.log('dynamic because some modifiers of eligible datasets are dynamic');
|
2058
|
-
break;
|
2059
|
-
}
|
2060
|
-
}
|
2061
|
-
}
|
2062
|
-
}
|
2063
2053
|
if(this.TRACE || DEBUGGING) console.log('PARSED', this.ownerName, ':',
|
2064
2054
|
this.expr, this.code);
|
2065
2055
|
}
|
@@ -2365,14 +2355,10 @@ class VirtualMachine {
|
|
2365
2355
|
Q: this.product_attr
|
2366
2356
|
};
|
2367
2357
|
this.entity_attribute_names = {};
|
2368
|
-
for(
|
2369
|
-
const
|
2370
|
-
el = this.entity_letters.charAt(i),
|
2371
|
-
ac = this.attribute_codes[el];
|
2358
|
+
for(const el of this.entity_letters) {
|
2359
|
+
const ac = this.attribute_codes[el];
|
2372
2360
|
this.entity_attribute_names[el] = [];
|
2373
|
-
for(
|
2374
|
-
this.entity_attribute_names[el].push(ac[j]);
|
2375
|
-
}
|
2361
|
+
for(const a of ac) this.entity_attribute_names[el].push(a);
|
2376
2362
|
}
|
2377
2363
|
// Level-based attributes are computed only AFTER optimization.
|
2378
2364
|
this.level_based_attr = ['L', 'CP', 'HCP', 'CF', 'CI', 'CO', 'F', 'A'];
|
@@ -2880,8 +2866,7 @@ class VirtualMachine {
|
|
2880
2866
|
const
|
2881
2867
|
vlist = [],
|
2882
2868
|
xlist = [];
|
2883
|
-
for(
|
2884
|
-
const x = this.call_stack[i];
|
2869
|
+
for(const x of this.call_stack) {
|
2885
2870
|
vlist.push(x.object.displayName + '|' + x.attribute);
|
2886
2871
|
// Trim spaces around all object-attribute separators in the
|
2887
2872
|
// expression as entered by the modeler.
|
@@ -2894,8 +2879,8 @@ class VirtualMachine {
|
|
2894
2879
|
// Then iterate upwards over the call stack.
|
2895
2880
|
for(let i = 0; i < vlist.length - 1; i++) {
|
2896
2881
|
// Log the expression, followed by the next computed variable.
|
2897
|
-
console.log(pad + xlist[i] + '\u279C' + vlist[i+1]);
|
2898
|
-
// Increase indentation
|
2882
|
+
console.log(pad + xlist[i] + '\u279C' + vlist[i + 1]);
|
2883
|
+
// Increase indentation.
|
2899
2884
|
pad += ' ';
|
2900
2885
|
}
|
2901
2886
|
// Log the last expression.
|
@@ -2941,11 +2926,9 @@ class VirtualMachine {
|
|
2941
2926
|
this.nr_of_blocks = 0;
|
2942
2927
|
this.block_count = 0;
|
2943
2928
|
MONITOR.clearProgressBar();
|
2944
|
-
for(
|
2945
|
-
const
|
2946
|
-
bm
|
2947
|
-
err = (bm.messages.indexOf('Solver status = 0') < 0 ||
|
2948
|
-
bm.messages.indexOf(this.WARNING) >= 0);
|
2929
|
+
for(const bm of r.block_messages) {
|
2930
|
+
const err = (bm.messages.indexOf('Solver status = 0') < 0 ||
|
2931
|
+
bm.messages.indexOf(this.WARNING) >= 0);
|
2949
2932
|
this.solver_times.push(bm.solver_time);
|
2950
2933
|
this.messages.push(bm.messages);
|
2951
2934
|
this.variables.push(this.no_variables);
|
@@ -3319,7 +3302,7 @@ class VirtualMachine {
|
|
3319
3302
|
// NOTE: The setupProblem() function implements the essential idea of
|
3320
3303
|
// Linny-R! It sets up the VM variable list, and then generates VM code
|
3321
3304
|
// that that, when executed, creates the MILP tableau for a chunk.
|
3322
|
-
|
3305
|
+
|
3323
3306
|
// Reset variable arrays and code array.
|
3324
3307
|
this.variables.length = 0;
|
3325
3308
|
this.chunk_variables.length = 0;
|
@@ -3332,9 +3315,7 @@ class VirtualMachine {
|
|
3332
3315
|
this.slack_variables = [[], [], []];
|
3333
3316
|
this.code.length = 0;
|
3334
3317
|
// Initialize fixed variable array: 1 list per round.
|
3335
|
-
for(i = 0; i < MODEL.rounds; i++)
|
3336
|
-
this.fixed_var_indices.push([]);
|
3337
|
-
}
|
3318
|
+
for(let i = 0; i < MODEL.rounds; i++) this.fixed_var_indices.push([]);
|
3338
3319
|
|
3339
3320
|
// Log if run is performed in "diagnosis" mode.
|
3340
3321
|
if(this.diagnose) {
|
@@ -3372,24 +3353,22 @@ class VirtualMachine {
|
|
3372
3353
|
|
3373
3354
|
// Each actor has a variable to compute its cash in and its cash out.
|
3374
3355
|
const actor_keys = Object.keys(MODEL.actors).sort();
|
3375
|
-
for(
|
3376
|
-
const a = MODEL.actors[
|
3356
|
+
for(const k of actor_keys) {
|
3357
|
+
const a = MODEL.actors[k];
|
3377
3358
|
a.cash_in_var_index = this.addVariable('CI', a);
|
3378
3359
|
a.cash_out_var_index = this.addVariable('CO', a);
|
3379
3360
|
}
|
3380
|
-
// Define variable indices for all processes
|
3361
|
+
// Define variable indices for all processes.
|
3381
3362
|
const process_keys = Object.keys(MODEL.processes).sort();
|
3382
|
-
for(
|
3383
|
-
|
3384
|
-
p = MODEL.processes[k];
|
3363
|
+
for(const k of process_keys) {
|
3364
|
+
const p = MODEL.processes[k];
|
3385
3365
|
this.resetVariableIndices(p);
|
3386
3366
|
if(!MODEL.ignored_entities[k]) this.addNodeVariables(p);
|
3387
3367
|
}
|
3388
|
-
// Do likewise for all products
|
3368
|
+
// Do likewise for all products.
|
3389
3369
|
const product_keys = Object.keys(MODEL.products).sort();
|
3390
|
-
for(
|
3391
|
-
|
3392
|
-
p = MODEL.products[k];
|
3370
|
+
for(const k of product_keys) {
|
3371
|
+
const p = MODEL.products[k];
|
3393
3372
|
this.resetVariableIndices(p);
|
3394
3373
|
if(!MODEL.ignored_entities[k]) this.addNodeVariables(p);
|
3395
3374
|
}
|
@@ -3412,29 +3391,25 @@ class VirtualMachine {
|
|
3412
3391
|
// NOTE: Slack variables are omitted when the "no slack" property
|
3413
3392
|
// of the constraint is set.
|
3414
3393
|
const constraint_keys = Object.keys(MODEL.constraints).sort();
|
3415
|
-
for(
|
3416
|
-
|
3417
|
-
|
3418
|
-
|
3419
|
-
|
3420
|
-
|
3421
|
-
|
3422
|
-
|
3423
|
-
|
3424
|
-
|
3425
|
-
//
|
3426
|
-
|
3427
|
-
if(
|
3428
|
-
|
3429
|
-
|
3430
|
-
|
3431
|
-
|
3432
|
-
|
3433
|
-
|
3434
|
-
if(bl.type !== VM.LE) {
|
3435
|
-
bl.GE_slack_var_index = this.addVariable('CGE', bl);
|
3436
|
-
this.slack_variables[2].push(bl.GE_slack_var_index);
|
3437
|
-
}
|
3394
|
+
for(const k of constraint_keys) if(!MODEL.ignored_entities[k]) {
|
3395
|
+
const c = MODEL.constraints[k];
|
3396
|
+
for(const bl of c.bound_lines) {
|
3397
|
+
bl.sos_var_indices = [];
|
3398
|
+
if(bl.constrainsY) {
|
3399
|
+
// Define SOS2 variables w[i] (plus associated binaries if
|
3400
|
+
// solver does not support special ordered sets).
|
3401
|
+
// NOTE: `addVariable` will add as many as there are points!
|
3402
|
+
bl.first_sos_var_index = this.addVariable('W1', bl);
|
3403
|
+
if(this.diagnose && !c.no_slack) {
|
3404
|
+
// Define the slack variable(s) for bound line constraints.
|
3405
|
+
// NOTE: Category [2] means: highest slack penalty.
|
3406
|
+
if(bl.type !== VM.GE) {
|
3407
|
+
bl.LE_slack_var_index = this.addVariable('CLE', bl);
|
3408
|
+
this.slack_variables[2].push(bl.LE_slack_var_index);
|
3409
|
+
}
|
3410
|
+
if(bl.type !== VM.LE) {
|
3411
|
+
bl.GE_slack_var_index = this.addVariable('CGE', bl);
|
3412
|
+
this.slack_variables[2].push(bl.GE_slack_var_index);
|
3438
3413
|
}
|
3439
3414
|
}
|
3440
3415
|
}
|
@@ -3445,10 +3420,9 @@ class VirtualMachine {
|
|
3445
3420
|
// been defined; next step is to add "chunk variables".
|
3446
3421
|
let cvi = 0;
|
3447
3422
|
// Add *two* chunk variables for processes having a peak increase link.
|
3448
|
-
for(
|
3449
|
-
|
3450
|
-
p
|
3451
|
-
if(!MODEL.ignored_entities[k] && p.needsMaximumData) {
|
3423
|
+
for(const k of process_keys) if(!MODEL.ignored_entities[k]) {
|
3424
|
+
const p = MODEL.processes[k];
|
3425
|
+
if(p.needsMaximumData) {
|
3452
3426
|
// First variable: "peak increase" for block.
|
3453
3427
|
p.peak_inc_var_index = cvi;
|
3454
3428
|
this.chunk_variables.push(['b-peak', p]);
|
@@ -3461,10 +3435,9 @@ class VirtualMachine {
|
|
3461
3435
|
}
|
3462
3436
|
}
|
3463
3437
|
// Do likewise for such products.
|
3464
|
-
for(
|
3465
|
-
|
3466
|
-
p
|
3467
|
-
if(!MODEL.ignored_entities[k] && p.needsMaximumData) {
|
3438
|
+
for(const k of product_keys) if(!MODEL.ignored_entities[k]) {
|
3439
|
+
const p = MODEL.products[k];
|
3440
|
+
if(p.needsMaximumData) {
|
3468
3441
|
p.peak_inc_var_index = cvi;
|
3469
3442
|
this.chunk_variables.push(['b-peak', p]);
|
3470
3443
|
cvi++;
|
@@ -3485,8 +3458,8 @@ class VirtualMachine {
|
|
3485
3458
|
// However, Linny-R does not prohibit negative bounds on processes, nor
|
3486
3459
|
// negative rates on links. To be consistently permissive, cash IN and
|
3487
3460
|
// cash OUT of all actors are both allowed to become negative.
|
3488
|
-
for(
|
3489
|
-
const a = MODEL.actors[
|
3461
|
+
for(const k of actor_keys) {
|
3462
|
+
const a = MODEL.actors[k];
|
3490
3463
|
// NOTE: Add fourth parameter TRUE to signal that the SOLVER's
|
3491
3464
|
// infinity constants should be used, as this is likely to be more
|
3492
3465
|
// efficient, while cash flows are inferred properties and will not
|
@@ -3502,73 +3475,67 @@ class VirtualMachine {
|
|
3502
3475
|
// NEXT: Define the bounds for all production level variables.
|
3503
3476
|
// NOTE: The VM instructions check dynamically whether the variable
|
3504
3477
|
// index is listed as "fixed" for the round that is being solved.
|
3505
|
-
for(
|
3506
|
-
|
3507
|
-
|
3508
|
-
|
3509
|
-
|
3510
|
-
|
3511
|
-
|
3512
|
-
|
3513
|
-
|
3514
|
-
|
3515
|
-
if(
|
3516
|
-
|
3517
|
-
|
3518
|
-
|
3519
|
-
|
3520
|
-
|
3521
|
-
|
3522
|
-
|
3523
|
-
|
3524
|
-
|
3525
|
-
|
3526
|
-
|
3527
|
-
|
3528
|
-
|
3529
|
-
|
3530
|
-
|
3531
|
-
|
3532
|
-
|
3533
|
-
|
3534
|
-
|
3535
|
-
|
3536
|
-
|
3537
|
-
|
3538
|
-
|
3539
|
-
this.fixed_var_indices[j][p.level_var_index] = true;
|
3540
|
-
// @@ TO DO: fix associated binary variables if applicable!
|
3541
|
-
}
|
3542
|
-
b *= 2;
|
3478
|
+
for(const k of process_keys) if(!MODEL.ignored_entities[k]) {
|
3479
|
+
const p = MODEL.processes[k];
|
3480
|
+
let lbx = p.lower_bound;
|
3481
|
+
// NOTE: If UB = LB, set UB to LB only if LB is defined,
|
3482
|
+
// because LB expressions default to -INF while UB expressions
|
3483
|
+
// default to +INF.
|
3484
|
+
let ubx = (!p.grid && p.equal_bounds && lbx.defined ? lbx : p.upper_bound);
|
3485
|
+
if(lbx.isStatic) lbx = lbx.result(0);
|
3486
|
+
if(ubx.isStatic) {
|
3487
|
+
ubx = ubx.result(0);
|
3488
|
+
if(p.grid) lbx = -ubx;
|
3489
|
+
} else if (p.grid) {
|
3490
|
+
// When UB is dynamic, pass NULL as LB; the VM instruction will
|
3491
|
+
// interpret this as "LB = -UB".
|
3492
|
+
lbx = null;
|
3493
|
+
}
|
3494
|
+
// NOTE: When semic_var_index is set, the lower bound must be
|
3495
|
+
// zero, as the semi-continuous lower bound is implemented with
|
3496
|
+
// a binary variable.
|
3497
|
+
if(p.semic_var_index >= 0) lbx = 0;
|
3498
|
+
// NOTE: Pass TRUE as fourth parameter to indicate that +INF
|
3499
|
+
// and -INF can be coded as the infinity values used by the
|
3500
|
+
// solver, rather than the Linny-R values used to detect
|
3501
|
+
// unbounded problems.
|
3502
|
+
this.code.push([VMI_set_bounds, [p.level_var_index, lbx, ubx, true]]);
|
3503
|
+
// Add level variable index to "fixed" list for specified rounds.
|
3504
|
+
const rf = p.actor.round_flags;
|
3505
|
+
if(rf != 0) {
|
3506
|
+
// Note: 32-bit integer `b` is used for bit-wise AND
|
3507
|
+
let b = 1;
|
3508
|
+
for(j = 0; j < MODEL.rounds; j++) {
|
3509
|
+
if((rf & b) != 0) {
|
3510
|
+
this.fixed_var_indices[j][p.level_var_index] = true;
|
3511
|
+
// @@ TO DO: fix associated binary variables if applicable!
|
3543
3512
|
}
|
3513
|
+
b *= 2;
|
3544
3514
|
}
|
3545
3515
|
}
|
3546
3516
|
}
|
3547
3517
|
|
3548
3518
|
// NEXT: Define the bounds for all stock level variables.
|
3549
|
-
for(
|
3550
|
-
|
3551
|
-
|
3552
|
-
|
3553
|
-
|
3554
|
-
|
3555
|
-
|
3556
|
-
|
3557
|
-
|
3558
|
-
|
3559
|
-
|
3560
|
-
|
3561
|
-
|
3562
|
-
|
3563
|
-
|
3564
|
-
|
3565
|
-
|
3566
|
-
|
3567
|
-
|
3568
|
-
|
3569
|
-
this.code.push([VMI_set_bounds,
|
3570
|
-
[vi, VM.MINUS_INFINITY, VM.PLUS_INFINITY]]);
|
3571
|
-
}
|
3519
|
+
for(const k of product_keys) if(!MODEL.ignored_entities[k]) {
|
3520
|
+
const p = MODEL.products[k];
|
3521
|
+
// Get index of variable that is constrained by LB and UB.
|
3522
|
+
const vi = p.level_var_index;
|
3523
|
+
if(p.no_slack || !this.diagnose) {
|
3524
|
+
// If no slack, the bound constraints can be set on the
|
3525
|
+
// variables themselves.
|
3526
|
+
let lbx = p.lower_bound;
|
3527
|
+
// NOTE: If UB = LB, set UB to LB only if LB is defined,
|
3528
|
+
// because LB expressions default to -INF while UB expressions
|
3529
|
+
// default to + INF.
|
3530
|
+
let ubx = (p.equal_bounds && lbx.defined ? lbx : p.upper_bound);
|
3531
|
+
if(lbx.isStatic) lbx = lbx.result(0);
|
3532
|
+
if(ubx.isStatic) ubx = ubx.result(0);
|
3533
|
+
this.code.push([VMI_set_bounds, [vi, lbx, ubx]]);
|
3534
|
+
} else {
|
3535
|
+
// Otherwise, set bounds of stock variable to -INF and +INF,
|
3536
|
+
// as product constraints will be added later on.
|
3537
|
+
this.code.push([VMI_set_bounds,
|
3538
|
+
[vi, VM.MINUS_INFINITY, VM.PLUS_INFINITY]]);
|
3572
3539
|
}
|
3573
3540
|
}
|
3574
3541
|
|
@@ -3610,158 +3577,150 @@ class VirtualMachine {
|
|
3610
3577
|
this.no_cash_flows = true;
|
3611
3578
|
|
3612
3579
|
// Iterate over all actors to add the cash flow computation constraints.
|
3613
|
-
for(
|
3614
|
-
const a = MODEL.actors[
|
3580
|
+
for(const k of actor_keys) {
|
3581
|
+
const a = MODEL.actors[k];
|
3615
3582
|
// NOTE: No need for VMI_clear_coefficients because the cash flow
|
3616
3583
|
// coefficients operate on two special "registers" of the VM.
|
3617
|
-
for(
|
3618
|
-
|
3619
|
-
|
3620
|
-
|
3621
|
-
|
3622
|
-
|
3623
|
-
|
3624
|
-
|
3625
|
-
|
3626
|
-
|
3627
|
-
|
3628
|
-
|
3629
|
-
|
3630
|
-
|
3631
|
-
|
3632
|
-
|
3633
|
-
|
3634
|
-
|
3635
|
-
|
3636
|
-
|
3637
|
-
|
3638
|
-
|
3639
|
-
|
3640
|
-
|
3641
|
-
|
3642
|
-
if(Math.abs(k) > VM.NEAR_ZERO) {
|
3643
|
-
// Consumption rate & price are static: pass one constant.
|
3644
|
-
this.code.push([VMI_update_cash_coefficient,
|
3645
|
-
[VM.CONSUME, VM.ONE_C, p.level_var_index, 0, k]]);
|
3646
|
-
}
|
3647
|
-
} else {
|
3648
|
-
// No further optimization: assume two dynamic expressions.
|
3649
|
-
this.code.push([VMI_update_cash_coefficient,
|
3650
|
-
[VM.CONSUME, VM.TWO_X, p.level_var_index, 0,
|
3651
|
-
l.from_node.price, l.relative_rate]]);
|
3652
|
-
}
|
3653
|
-
}
|
3654
|
-
} // END of FOR ALL input links
|
3655
|
-
}
|
3656
|
-
// Now iterate over links OUT, but only consider produced
|
3657
|
-
// products having a (non-zero) market price.
|
3658
|
-
// NOTE: Grid processes can have output links to *data* products,
|
3659
|
-
// so do NOT skip this iteration...
|
3660
|
-
for(j = 0; j < p.outputs.length; j++) {
|
3661
|
-
l = p.outputs[j];
|
3662
|
-
const
|
3663
|
-
tnpx = l.to_node.price,
|
3664
|
-
// ... but DO skip links from grid processes to regular products.
|
3665
|
-
skip = p.grid && !l.to_node.is_data;
|
3666
|
-
if(!(skip || MODEL.ignored_entities[l.identifier]) && tnpx.defined &&
|
3667
|
-
!(tnpx.isStatic && Math.abs(tnpx.result(0)) < VM.NEAR_ZERO)) {
|
3668
|
-
// By default, use the process level as multiplier.
|
3669
|
-
vi = p.level_var_index;
|
3670
|
-
// For "binary data links", use the correct binary variable
|
3671
|
-
// instead of the level.
|
3672
|
-
if(l.multiplier === VM.LM_STARTUP) {
|
3673
|
-
vi = p.start_up_var_index;
|
3674
|
-
} else if(l.multiplier === VM.LM_FIRST_COMMIT) {
|
3675
|
-
vi = p.first_commit_var_index;
|
3676
|
-
} else if(l.multiplier === VM.LM_SHUTDOWN) {
|
3677
|
-
vi = p.shut_down_var_index;
|
3678
|
-
} else if(l.multiplier === VM.LM_POSITIVE) {
|
3679
|
-
vi = p.on_off_var_index;
|
3680
|
-
} else if(l.multiplier === VM.LM_ZERO) {
|
3681
|
-
vi = p.is_zero_var_index;
|
3584
|
+
for(const k of process_keys) if(!MODEL.ignored_entities[k]) {
|
3585
|
+
const p = MODEL.processes[k];
|
3586
|
+
// Only consider processes owned by this actor.
|
3587
|
+
if(p.actor === a) {
|
3588
|
+
if(p.grid) {
|
3589
|
+
// Grid processes are a special case, as they can have a
|
3590
|
+
// negative level and potentially multiple slopes. Hence a
|
3591
|
+
// special VM instruction.
|
3592
|
+
this.code.push([VMI_update_grid_process_cash_coefficients, p]);
|
3593
|
+
} else {
|
3594
|
+
// Iterate over links IN, but only consider consumed products
|
3595
|
+
// having a market price.
|
3596
|
+
for(const l of p.inputs) if(!MODEL.ignored_entities[l.identifier] &&
|
3597
|
+
l.from_node.price.defined) {
|
3598
|
+
if(l.from_node.price.isStatic && l.relative_rate.isStatic) {
|
3599
|
+
const c = l.from_node.price.result(0) * l.relative_rate.result(0);
|
3600
|
+
// NOTE: VMI_update_cash_coefficient has at least 4 arguments:
|
3601
|
+
// flow (CONSUME or PRODUCE), type (specifies the number and
|
3602
|
+
// type of arguments), the level_var_index of the process,
|
3603
|
+
// and the delay.
|
3604
|
+
// NOTE: Input links cannot have delay, so then delay = 0.
|
3605
|
+
if(Math.abs(c) > VM.NEAR_ZERO) {
|
3606
|
+
// Consumption rate & price are static: pass one constant.
|
3607
|
+
this.code.push([VMI_update_cash_coefficient,
|
3608
|
+
[VM.CONSUME, VM.ONE_C, p.level_var_index, 0, c]]);
|
3682
3609
|
}
|
3683
|
-
|
3684
|
-
//
|
3685
|
-
|
3686
|
-
|
3687
|
-
|
3688
|
-
|
3689
|
-
|
3690
|
-
|
3691
|
-
|
3692
|
-
|
3693
|
-
|
3694
|
-
|
3695
|
-
|
3696
|
-
|
3697
|
-
|
3698
|
-
|
3699
|
-
|
3700
|
-
|
3701
|
-
|
3702
|
-
|
3703
|
-
|
3704
|
-
|
3705
|
-
|
3706
|
-
|
3707
|
-
|
3708
|
-
|
3709
|
-
|
3710
|
-
|
3711
|
-
|
3712
|
-
|
3713
|
-
|
3714
|
-
|
3715
|
-
|
3716
|
-
|
3717
|
-
|
3718
|
-
|
3719
|
-
|
3720
|
-
|
3721
|
-
|
3722
|
-
|
3723
|
-
|
3724
|
-
|
3725
|
-
//
|
3726
|
-
//
|
3610
|
+
} else {
|
3611
|
+
// No further optimization: assume two dynamic expressions.
|
3612
|
+
this.code.push([VMI_update_cash_coefficient,
|
3613
|
+
[VM.CONSUME, VM.TWO_X, p.level_var_index, 0,
|
3614
|
+
l.from_node.price, l.relative_rate]]);
|
3615
|
+
}
|
3616
|
+
} // END of FOR ALL input links
|
3617
|
+
}
|
3618
|
+
// Now iterate over links OUT, but only consider produced
|
3619
|
+
// products having a (non-zero) market price.
|
3620
|
+
// NOTE: Grid processes can have output links to *data* products,
|
3621
|
+
// so do NOT skip this iteration...
|
3622
|
+
for(const l of p.outputs) {
|
3623
|
+
const
|
3624
|
+
tnpx = l.to_node.price,
|
3625
|
+
// ... but DO skip links from grid processes to regular products.
|
3626
|
+
skip = p.grid && !l.to_node.is_data;
|
3627
|
+
if(!(skip || MODEL.ignored_entities[l.identifier]) && tnpx.defined &&
|
3628
|
+
!(tnpx.isStatic && Math.abs(tnpx.result(0)) < VM.NEAR_ZERO)) {
|
3629
|
+
// By default, use the process level as multiplier.
|
3630
|
+
let vi = p.level_var_index;
|
3631
|
+
// For "binary data links", use the correct binary variable
|
3632
|
+
// instead of the level.
|
3633
|
+
if(l.multiplier === VM.LM_STARTUP) {
|
3634
|
+
vi = p.start_up_var_index;
|
3635
|
+
} else if(l.multiplier === VM.LM_FIRST_COMMIT) {
|
3636
|
+
vi = p.first_commit_var_index;
|
3637
|
+
} else if(l.multiplier === VM.LM_SHUTDOWN) {
|
3638
|
+
vi = p.shut_down_var_index;
|
3639
|
+
} else if(l.multiplier === VM.LM_POSITIVE) {
|
3640
|
+
vi = p.on_off_var_index;
|
3641
|
+
} else if(l.multiplier === VM.LM_ZERO) {
|
3642
|
+
vi = p.is_zero_var_index;
|
3643
|
+
}
|
3644
|
+
// NOTE: "throughput", "spinning reserve" and "peak increase"
|
3645
|
+
// are special cases that send a different parameter list.
|
3646
|
+
if(l.multiplier === VM.LM_THROUGHPUT) {
|
3647
|
+
// When throughput is read from process Y, calculation
|
3648
|
+
// is simple: no delays, so the flow over link `l`
|
3649
|
+
// equals the (sum of all Ri) times the level of Y
|
3650
|
+
// times the rate of `l`.
|
3651
|
+
for(const ll of l.from_node.inputs) {
|
3652
|
+
// NOTE: No attempt for efficiency -- assume that
|
3653
|
+
// price and both rates are dynamic.
|
3727
3654
|
this.code.push([VMI_update_cash_coefficient, [
|
3728
|
-
VM.PRODUCE, VM.
|
3729
|
-
|
3730
|
-
}
|
3731
|
-
|
3732
|
-
|
3733
|
-
|
3734
|
-
|
3735
|
-
|
3736
|
-
|
3655
|
+
VM.PRODUCE, VM.THREE_X, vi, l.flow_delay, tnpx,
|
3656
|
+
l.relative_rate, ll.relative_rate]]);
|
3657
|
+
}
|
3658
|
+
} else if(l.multiplier === VM.LM_SPINNING_RESERVE) {
|
3659
|
+
// "spinning reserve" equals UB - level if level > 0,
|
3660
|
+
// and otherwise 0. The cash flow then equals
|
3661
|
+
// ON/OFF * UB * price * rate MINUS level * price * rate,
|
3662
|
+
// hence a special instruction type.
|
3663
|
+
// NOTE: Only the ON/OFF variable determines whether
|
3664
|
+
// there will be any cash flow, hence it is passed as
|
3665
|
+
// the primary variable, and the process level as the
|
3666
|
+
// secondary variable.
|
3667
|
+
this.code.push([VMI_update_cash_coefficient, [
|
3668
|
+
VM.PRODUCE, VM.SPIN_RES, p.on_off_var_index,
|
3669
|
+
l.flow_delay, vi, l.from_node.upper_bound, tnpx,
|
3670
|
+
l.relative_rate]]);
|
3671
|
+
} else if(l.multiplier === VM.LM_REMAINING_CAPACITY) {
|
3672
|
+
// "remaining capacity" equals UB - level. This is a
|
3673
|
+
// simpler version of "spinning reserve". We signal this
|
3674
|
+
// by passing -1 as the index of the secondary variable,
|
3675
|
+
// and the level variable index as the primary variable.
|
3676
|
+
this.code.push([VMI_update_cash_coefficient, [
|
3677
|
+
VM.PRODUCE, VM.SPIN_RES, vi, // <-- now as primary
|
3678
|
+
l.flow_delay, -1, // <-- signal that it is "REM_CAP"
|
3679
|
+
l.from_node.upper_bound, tnpx, l.relative_rate]]);
|
3680
|
+
} else if(l.multiplier === VM.LM_PEAK_INC) {
|
3681
|
+
// NOTE: "peak increase" may be > 0 only in the first
|
3682
|
+
// time step of the block being optimized, and in the
|
3683
|
+
// first step of the look-ahead period (if peak rises
|
3684
|
+
// in that period), and will be 0 in all other time steps.
|
3685
|
+
// The VM instruction handles this.
|
3686
|
+
// NOTE: Delay is always 0 for this link flow.
|
3687
|
+
this.code.push([VMI_update_cash_coefficient, [
|
3688
|
+
VM.PRODUCE, VM.PEAK_INC, p.peak_inc_var_index, 0,
|
3689
|
+
tnpx, l.relative_rate]]);
|
3690
|
+
} else if(tnpx.isStatic && l.relative_rate.isStatic) {
|
3691
|
+
// If link rate and product price are static, only add
|
3692
|
+
// the variable if rate*price is non-zero (and then pass
|
3693
|
+
// the constant rate*price to the VM instruction.
|
3694
|
+
const c = tnpx.result(0) * l.relative_rate.result(0);
|
3695
|
+
if(Math.abs(c) > VM.NEAR_ZERO) {
|
3696
|
+
// Production rate & price are static: pass one constant.
|
3697
|
+
this.code.push([VMI_update_cash_coefficient,
|
3698
|
+
[VM.PRODUCE, VM.ONE_C, vi, l.flow_delay, c]]);
|
3699
|
+
// When multiplier is Delta, subtract level in previous t
|
3700
|
+
// (so add 1 to flow delay, and consume, rather than
|
3701
|
+
// produce).
|
3702
|
+
if(l.multiplier === VM.LM_INCREASE) {
|
3737
3703
|
this.code.push([VMI_update_cash_coefficient,
|
3738
|
-
|
3739
|
-
|
3740
|
-
// (so add 1 to flow delay, and consume, rather than
|
3741
|
-
// produce).
|
3742
|
-
if(l.multiplier === VM.LM_INCREASE) {
|
3743
|
-
this.code.push([VMI_update_cash_coefficient,
|
3744
|
-
// NOTE: 6th argument = 1 indicates "delay + 1".
|
3745
|
-
[VM.CONSUME, VM.ONE_C, vi, l.flow_delay, k, 1]]);
|
3746
|
-
}
|
3704
|
+
// NOTE: 6th argument = 1 indicates "delay + 1".
|
3705
|
+
[VM.CONSUME, VM.ONE_C, vi, l.flow_delay, c, 1]]);
|
3747
3706
|
}
|
3748
|
-
}
|
3749
|
-
|
3707
|
+
}
|
3708
|
+
} else {
|
3709
|
+
// Production rate or price are dynamic: pass two expressions.
|
3710
|
+
this.code.push([VMI_update_cash_coefficient, [
|
3711
|
+
VM.PRODUCE, VM.TWO_X, vi, l.flow_delay,
|
3712
|
+
tnpx, l.relative_rate]]);
|
3713
|
+
// When multiplier is Delta, consume level in previous t.
|
3714
|
+
if(l.multiplier === VM.LM_INCREASE) {
|
3750
3715
|
this.code.push([VMI_update_cash_coefficient, [
|
3751
|
-
VM.
|
3752
|
-
|
3753
|
-
|
3754
|
-
if(l.multiplier === VM.LM_INCREASE) {
|
3755
|
-
this.code.push([VMI_update_cash_coefficient, [
|
3756
|
-
VM.CONSUME, VM.TWO_X, vi, l.flow_delay,
|
3757
|
-
// NOTE: Now 7th argument indicates "delay + 1".
|
3758
|
-
tnpx, l.relative_rate, 1]]);
|
3759
|
-
}
|
3716
|
+
VM.CONSUME, VM.TWO_X, vi, l.flow_delay,
|
3717
|
+
// NOTE: Now 7th argument indicates "delay + 1".
|
3718
|
+
tnpx, l.relative_rate, 1]]);
|
3760
3719
|
}
|
3761
3720
|
}
|
3762
3721
|
}
|
3763
3722
|
} // END of FOR ALL output links
|
3764
|
-
} // END of IF process
|
3723
|
+
} // END of IF process "owned" by actor a
|
3765
3724
|
} // END of FOR ALL processes
|
3766
3725
|
|
3767
3726
|
// Check whether any VMI_update_cash_coefficient instructions have
|
@@ -3786,27 +3745,25 @@ class VirtualMachine {
|
|
3786
3745
|
// considered, no cash flows have been detected, the solver should aim
|
3787
3746
|
// for minimal effort, i.e., lowest weighted sum of process levels.
|
3788
3747
|
if(this.no_cash_flows) {
|
3789
|
-
for(
|
3790
|
-
|
3791
|
-
|
3792
|
-
|
3793
|
-
|
3794
|
-
if(a.weight.
|
3795
|
-
|
3796
|
-
|
3797
|
-
|
3798
|
-
|
3799
|
-
|
3800
|
-
[p.level_var_index, a.weight]]);
|
3801
|
-
}
|
3748
|
+
for(const k of process_keys) if(!MODEL.ignored_entities[k]) {
|
3749
|
+
const
|
3750
|
+
p = MODEL.processes[k],
|
3751
|
+
a = p.actor;
|
3752
|
+
if(a.weight.defined) {
|
3753
|
+
if(a.weight.isStatic) {
|
3754
|
+
this.code.push([VMI_subtract_const_from_coefficient,
|
3755
|
+
[p.level_var_index, a.weight.result(0)]]);
|
3756
|
+
} else {
|
3757
|
+
this.code.push([VMI_subtract_var_from_coefficient,
|
3758
|
+
[p.level_var_index, a.weight]]);
|
3802
3759
|
}
|
3803
3760
|
}
|
3804
3761
|
}
|
3805
3762
|
} else {
|
3806
3763
|
// If cash flows HAVE been detected, use actor weights as coefficients:
|
3807
3764
|
// positive for their cash IN, and negative for their cash OUT
|
3808
|
-
for(
|
3809
|
-
const a = MODEL.actors[
|
3765
|
+
for(const k of actor_keys) {
|
3766
|
+
const a = MODEL.actors[k];
|
3810
3767
|
// Ignore actors with undefined weights (should not occur since
|
3811
3768
|
// default weight = 1)
|
3812
3769
|
if(a.weight.defined) {
|
@@ -3831,12 +3788,9 @@ class VirtualMachine {
|
|
3831
3788
|
// been added (by looking at the last VM instruction added to the code)
|
3832
3789
|
if(this.code[this.code.length - 1][0] === VMI_clear_coefficients) {
|
3833
3790
|
// If not, set the coefficients for ALL processes to -1
|
3834
|
-
for(
|
3835
|
-
|
3836
|
-
|
3837
|
-
this.code.push([VMI_add_const_to_coefficient,
|
3838
|
-
[MODEL.processes[k].level_var_index, -1]]);
|
3839
|
-
}
|
3791
|
+
for(const k of process_keys) if(!MODEL.ignored_entities[k]) {
|
3792
|
+
this.code.push([VMI_add_const_to_coefficient,
|
3793
|
+
[MODEL.processes[k].level_var_index, -1]]);
|
3840
3794
|
}
|
3841
3795
|
}
|
3842
3796
|
|
@@ -3852,345 +3806,323 @@ class VirtualMachine {
|
|
3852
3806
|
// (2) Slack variables have different penalties: type 0 = market demands,
|
3853
3807
|
// i.e., EQ constraints on stocks, 1 = GE and LE constraints on product
|
3854
3808
|
// levels, 2 = strongest constraints: on data, or set by boundlines.
|
3855
|
-
|
3856
|
-
|
3857
|
-
|
3858
|
-
|
3859
|
-
|
3860
|
-
|
3861
|
-
|
3862
|
-
|
3863
|
-
|
3864
|
-
|
3865
|
-
|
3866
|
-
this.slack_variables[pen].push(
|
3867
|
-
p.stock_LE_slack_var_index, p.stock_GE_slack_var_index);
|
3868
|
-
}
|
3809
|
+
for(const k of product_keys) if(!MODEL.ignored_entities[k]) {
|
3810
|
+
const p = MODEL.products[k];
|
3811
|
+
if(p.level_var_index >= 0 && !p.no_slack && this.diagnose) {
|
3812
|
+
const
|
3813
|
+
hb = p.hasBounds,
|
3814
|
+
pen = (p.is_data ? 2 :
|
3815
|
+
// NOTE: Lowest penalty also for IMPLIED sources and sinks.
|
3816
|
+
(p.equal_bounds || (!hb && (p.isSourceNode || p.isSinkNode)) ? 0 :
|
3817
|
+
(hb ? 1 : 2)));
|
3818
|
+
this.slack_variables[pen].push(
|
3819
|
+
p.stock_LE_slack_var_index, p.stock_GE_slack_var_index);
|
3869
3820
|
}
|
3870
3821
|
}
|
3871
3822
|
|
3872
3823
|
// NEXT: Add semi-continuous constraints only if not supported by solver.
|
3873
3824
|
if(!this.noSemiContinuous) {
|
3874
|
-
for(
|
3875
|
-
|
3876
|
-
|
3877
|
-
|
3878
|
-
|
3879
|
-
|
3880
|
-
|
3881
|
-
|
3882
|
-
|
3883
|
-
|
3884
|
-
|
3885
|
-
|
3886
|
-
|
3887
|
-
|
3888
|
-
|
3889
|
-
|
3890
|
-
|
3891
|
-
|
3892
|
-
|
3893
|
-
this.code.push([VMI_add_var_to_coefficient, [svi, lbx]]);
|
3894
|
-
}
|
3895
|
-
this.code.push([VMI_add_constraint, VM.LE]);
|
3896
|
-
// level - UB*binary <= 0
|
3897
|
-
this.code.push(
|
3898
|
-
[VMI_clear_coefficients, null],
|
3899
|
-
[VMI_add_const_to_coefficient, [vi, 1]]
|
3900
|
-
);
|
3901
|
-
if(ubx.isStatic) {
|
3902
|
-
this.code.push([VMI_subtract_const_from_coefficient,
|
3903
|
-
[svi, ubx.result(0)]]);
|
3904
|
-
} else {
|
3905
|
-
this.code.push([VMI_subtract_var_from_coefficient, [svi, ubx]]);
|
3906
|
-
}
|
3907
|
-
this.code.push([VMI_add_constraint, VM.LE]);
|
3825
|
+
for(const k of process_keys) if(!MODEL.ignored_entities[k]) {
|
3826
|
+
const
|
3827
|
+
p = MODEL.processes[k],
|
3828
|
+
svi = p.semic_var_index;
|
3829
|
+
if(svi >= 0) {
|
3830
|
+
const
|
3831
|
+
vi = p.level_var_index,
|
3832
|
+
lbx = p.lower_bound,
|
3833
|
+
ubx = (p.equal_bounds && lbx.defined ? lbx : p.upper_bound);
|
3834
|
+
// LB*binary - level <= 0
|
3835
|
+
this.code.push(
|
3836
|
+
[VMI_clear_coefficients, null],
|
3837
|
+
[VMI_add_const_to_coefficient, [vi, -1]]
|
3838
|
+
);
|
3839
|
+
if(lbx.isStatic) {
|
3840
|
+
this.code.push([VMI_add_const_to_coefficient,
|
3841
|
+
[svi, lbx.result(0)]]);
|
3842
|
+
} else {
|
3843
|
+
this.code.push([VMI_add_var_to_coefficient, [svi, lbx]]);
|
3908
3844
|
}
|
3845
|
+
this.code.push([VMI_add_constraint, VM.LE]);
|
3846
|
+
// level - UB*binary <= 0
|
3847
|
+
this.code.push(
|
3848
|
+
[VMI_clear_coefficients, null],
|
3849
|
+
[VMI_add_const_to_coefficient, [vi, 1]]
|
3850
|
+
);
|
3851
|
+
if(ubx.isStatic) {
|
3852
|
+
this.code.push([VMI_subtract_const_from_coefficient,
|
3853
|
+
[svi, ubx.result(0)]]);
|
3854
|
+
} else {
|
3855
|
+
this.code.push([VMI_subtract_var_from_coefficient, [svi, ubx]]);
|
3856
|
+
}
|
3857
|
+
this.code.push([VMI_add_constraint, VM.LE]);
|
3909
3858
|
}
|
3910
3859
|
}
|
3911
3860
|
}
|
3912
3861
|
|
3913
3862
|
// NEXT: Add constraints for processes representing grid elements.
|
3914
3863
|
if(MODEL.with_power_flow) {
|
3915
|
-
for(
|
3916
|
-
|
3917
|
-
if(
|
3918
|
-
|
3919
|
-
if(p.grid) {
|
3920
|
-
this.code.push([VMI_add_grid_process_constraints, p]);
|
3921
|
-
}
|
3864
|
+
for(const k of process_keys) if(!MODEL.ignored_entities[k]) {
|
3865
|
+
const p = MODEL.processes[k];
|
3866
|
+
if(p.grid) {
|
3867
|
+
this.code.push([VMI_add_grid_process_constraints, p]);
|
3922
3868
|
}
|
3923
3869
|
}
|
3924
|
-
this.code.push(
|
3870
|
+
if(!MODEL.ignore_KVL) this.code.push(
|
3925
3871
|
[VMI_add_kirchhoff_constraints, POWER_GRID_MANAGER.cycle_basis]);
|
3926
3872
|
}
|
3927
3873
|
|
3928
3874
|
// NEXT: Add product constraints to calculate (and constrain) their stock.
|
3929
3875
|
|
3930
|
-
for(
|
3931
|
-
|
3932
|
-
|
3933
|
-
|
3934
|
-
//
|
3935
|
-
|
3936
|
-
|
3937
|
-
|
3938
|
-
|
3939
|
-
|
3940
|
-
|
3941
|
-
|
3942
|
-
|
3943
|
-
|
3944
|
-
//
|
3945
|
-
if(p.name.startsWith('$IN ')) {
|
3946
|
-
// Add coefficient +1 for cash IN index.
|
3947
|
-
this.code.push([VMI_add_const_to_coefficient,
|
3948
|
-
[a.cash_in_var_index, 1, 0]]);
|
3949
|
-
} else if(p.name.startsWith('$OUT ')) {
|
3950
|
-
// Add coefficient +1 for cash OUT index.
|
3951
|
-
this.code.push([VMI_add_const_to_coefficient,
|
3952
|
-
[a.cash_out_var_index, 1, 0]]);
|
3953
|
-
} else if(p.name.startsWith('$FLOW ')) {
|
3954
|
-
// Add coefficient +1 for cash IN index.
|
3955
|
-
this.code.push([VMI_add_const_to_coefficient,
|
3956
|
-
[a.cash_in_var_index, 1, 0]]);
|
3957
|
-
// Add coefficient -1 for cash OUT index.
|
3958
|
-
this.code.push([VMI_add_const_to_coefficient,
|
3959
|
-
[a.cash_out_var_index, -1, 0]]);
|
3960
|
-
}
|
3961
|
-
// Add coefficient -1 for level index variable of `p`.
|
3876
|
+
for(const k of product_keys) if(!MODEL.ignored_entities[k]) {
|
3877
|
+
const p = MODEL.products[k];
|
3878
|
+
// NOTE: Actor cash flow data products are a special case.
|
3879
|
+
if(p.name.startsWith('$')) {
|
3880
|
+
// Get the associated actor entity.
|
3881
|
+
const parts = p.name.substring(1).split(' ');
|
3882
|
+
parts.shift();
|
3883
|
+
const
|
3884
|
+
aid = UI.nameToID(parts.join(' ')),
|
3885
|
+
a = MODEL.actorByID(aid);
|
3886
|
+
if(a) {
|
3887
|
+
this.code.push([VMI_clear_coefficients, null]);
|
3888
|
+
// Use actor's cash variable indices w/o weight.
|
3889
|
+
if(p.name.startsWith('$IN ')) {
|
3890
|
+
// Add coefficient +1 for cash IN index.
|
3962
3891
|
this.code.push([VMI_add_const_to_coefficient,
|
3963
|
-
[
|
3964
|
-
|
3965
|
-
//
|
3966
|
-
this.code.push([
|
3967
|
-
|
3968
|
-
|
3892
|
+
[a.cash_in_var_index, 1, 0]]);
|
3893
|
+
} else if(p.name.startsWith('$OUT ')) {
|
3894
|
+
// Add coefficient +1 for cash OUT index.
|
3895
|
+
this.code.push([VMI_add_const_to_coefficient,
|
3896
|
+
[a.cash_out_var_index, 1, 0]]);
|
3897
|
+
} else if(p.name.startsWith('$FLOW ')) {
|
3898
|
+
// Add coefficient +1 for cash IN index.
|
3899
|
+
this.code.push([VMI_add_const_to_coefficient,
|
3900
|
+
[a.cash_in_var_index, 1, 0]]);
|
3901
|
+
// Add coefficient -1 for cash OUT index.
|
3902
|
+
this.code.push([VMI_add_const_to_coefficient,
|
3903
|
+
[a.cash_out_var_index, -1, 0]]);
|
3969
3904
|
}
|
3970
|
-
|
3971
|
-
|
3905
|
+
// Add coefficient -1 for level index variable of `p`.
|
3906
|
+
this.code.push([VMI_add_const_to_coefficient,
|
3907
|
+
[p.level_var_index, -1, 0]]);
|
3908
|
+
// NOTE: Pass special constraint type parameter to indicate
|
3909
|
+
// that this constraint must be scaled by the cash scalar.
|
3910
|
+
this.code.push([VMI_add_constraint, VM.ACTOR_CASH]);
|
3911
|
+
} else {
|
3912
|
+
console.log('ANOMALY: no actor for cash flow product', p.displayName);
|
3913
|
+
}
|
3914
|
+
// NOTE: constants are not affected by their outgoing data (!) links
|
3915
|
+
} else if(!p.isConstant) {
|
3916
|
+
|
3917
|
+
// FIRST: add a constraint that "computes" the product stock level
|
3918
|
+
// set coefficients vector to 0 (NOTE: this also sets RHS to 0)
|
3919
|
+
this.code.push([VMI_clear_coefficients, null]);
|
3972
3920
|
|
3973
|
-
|
3974
|
-
|
3975
|
-
|
3976
|
-
|
3977
|
-
//
|
3978
|
-
|
3979
|
-
|
3980
|
-
|
3981
|
-
|
3982
|
-
|
3983
|
-
|
3984
|
-
|
3985
|
-
|
3986
|
-
|
3987
|
-
|
3988
|
-
|
3989
|
-
|
3990
|
-
|
3991
|
-
|
3992
|
-
|
3993
|
-
|
3994
|
-
|
3995
|
-
|
3996
|
-
|
3921
|
+
// Add inflow into product P from input nodes
|
3922
|
+
for(const l of p.inputs) if(!MODEL.ignored_entities[l.identifier]) {
|
3923
|
+
const fn = l.from_node;
|
3924
|
+
let vi = fn.level_var_index;
|
3925
|
+
// If data flow, use the appropriate variable
|
3926
|
+
if(l.multiplier === VM.LM_POSITIVE) {
|
3927
|
+
vi = fn.on_off_var_index;
|
3928
|
+
} else if (l.multiplier === VM.LM_ZERO) {
|
3929
|
+
vi = fn.is_zero_var_index;
|
3930
|
+
} else if(l.multiplier === VM.LM_STARTUP) {
|
3931
|
+
vi = fn.start_up_var_index;
|
3932
|
+
} else if(l.multiplier === VM.LM_FIRST_COMMIT) {
|
3933
|
+
vi = fn.first_commit_var_index;
|
3934
|
+
} else if(l.multiplier === VM.LM_SHUTDOWN) {
|
3935
|
+
vi = fn.shut_down_var_index;
|
3936
|
+
} else if(l.multiplier === VM.LM_PEAK_INC) {
|
3937
|
+
vi = fn.peak_inc_var_index;
|
3938
|
+
}
|
3939
|
+
// First check whether the link is a power flow.
|
3940
|
+
if(l.multiplier === VM.LM_LEVEL && !p.is_data && fn.grid) {
|
3941
|
+
// If so, pass the grid process to a special VM instruction
|
3942
|
+
// that will add coefficients that account for losses.
|
3943
|
+
// NOTES:
|
3944
|
+
// (1) The second parameter (+1) indicates that the
|
3945
|
+
// coefficients of the UP flows should be positive
|
3946
|
+
// and those of the DOWN flows should be negative
|
3947
|
+
// (because it is a P -> Q link).
|
3948
|
+
// (2) The rate and delay properties of the link are ignored.
|
3949
|
+
this.code.push(
|
3950
|
+
[VMI_add_power_flow_to_coefficients, [fn, 1]]);
|
3951
|
+
// Then check for throughput links, as these are elaborate.
|
3952
|
+
} else if(l.multiplier === VM.LM_THROUGHPUT) {
|
3953
|
+
// Link `l` is Y-->Z and "reads" the total inflow into Y
|
3954
|
+
// over links Xi-->Y having rate Ri and when Y is a
|
3955
|
+
// product potentially also delay Di.
|
3956
|
+
if(fn instanceof Process) {
|
3957
|
+
// When throughput is read from process Y, the flow
|
3958
|
+
// over link `l` equals the (sum of all Ri) times the
|
3959
|
+
// level of Y times the rate of `l`
|
3960
|
+
for(const ll of fn.inputs) {
|
3961
|
+
this.code.push([VMI_add_throughput_to_coefficient,
|
3962
|
+
[vi, l.relative_rate, l.flow_delay,
|
3963
|
+
// Input links of processes have no delay
|
3964
|
+
ll.relative_rate, 0]]);
|
3997
3965
|
}
|
3998
|
-
|
3999
|
-
|
4000
|
-
|
4001
|
-
|
4002
|
-
|
4003
|
-
|
4004
|
-
|
4005
|
-
|
4006
|
-
|
4007
|
-
//
|
4008
|
-
|
4009
|
-
|
4010
|
-
|
4011
|
-
|
4012
|
-
|
4013
|
-
|
4014
|
-
|
4015
|
-
|
4016
|
-
if(fn instanceof Process) {
|
4017
|
-
// When throughput is read from process Y, the flow
|
4018
|
-
// over link `l` equals the (sum of all Ri) times the
|
4019
|
-
// level of Y times the rate of `l`
|
4020
|
-
for(j = 0; j < fn.inputs.length; j++) {
|
4021
|
-
ll = fn.inputs[j];
|
4022
|
-
this.code.push([VMI_add_throughput_to_coefficient,
|
4023
|
-
[vi, l.relative_rate, l.flow_delay,
|
4024
|
-
// Input links of processes have no delay
|
4025
|
-
ll.relative_rate, 0]]);
|
4026
|
-
}
|
4027
|
-
} else {
|
4028
|
-
// When read from product Y, throughput to be added to
|
4029
|
-
// Z equals sum of inflows of FROM node Y:
|
4030
|
-
// Xi --(r2,d2)--> Y --(r1,d1)--> Z
|
4031
|
-
// so instead of the level of Y (having index vi), use
|
4032
|
-
// the level of Xi (for each input i of Y)
|
4033
|
-
for(j = 0; j < fn.inputs.length; j++) {
|
4034
|
-
ll = fn.inputs[j];
|
4035
|
-
lfn = ll.from_node;
|
4036
|
-
// here, too, use the *correct* variable index for Xi!
|
4037
|
-
if(ll.multiplier === VM.LM_POSITIVE || ll.multiplier === VM.LM_ZERO) {
|
4038
|
-
lvi = lfn.on_off_var_index;
|
4039
|
-
} else if(ll.multiplier === VM.LM_STARTUP) {
|
4040
|
-
lvi = lfn.start_up_var_index;
|
4041
|
-
} else if(ll.multiplier === VM.LM_FIRST_COMMIT) {
|
4042
|
-
lvi = lfn.first_commit_var_index;
|
4043
|
-
} else if(ll.multiplier === VM.LM_SHUTDOWN) {
|
4044
|
-
lvi = lfn.shut_down_var_index;
|
4045
|
-
} else {
|
4046
|
-
lvi = lfn.level_var_index;
|
4047
|
-
}
|
4048
|
-
// NOTE: we trade-off efficiency gain during execution
|
4049
|
-
// against simplicity now by not checking whether rates
|
4050
|
-
// are static; the VM instruction will be a bit slower
|
4051
|
-
// as it calls the result(t) method for both rates
|
4052
|
-
this.code.push([VMI_add_throughput_to_coefficient,
|
4053
|
-
[lvi, l.relative_rate, l.flow_delay,
|
4054
|
-
ll.relative_rate, ll.flow_delay]]);
|
4055
|
-
}
|
4056
|
-
}
|
4057
|
-
} else if(l.multiplier === VM.LM_PEAK_INC) {
|
4058
|
-
// SPECIAL instruction that adds flow only for first t of block
|
4059
|
-
// NOTE: no delay on this type of link
|
4060
|
-
this.code.push([VMI_add_peak_increase_at_t_0,
|
4061
|
-
[vi, l.relative_rate]]);
|
4062
|
-
} else if(l.multiplier === VM.LM_AVAILABLE_CAPACITY) {
|
4063
|
-
// The "available capacity" equals UB - level, so subtract
|
4064
|
-
// UB * rate from RHS, while considering the delay.
|
4065
|
-
// NOTE: New instruction style that passes pointers to
|
4066
|
-
// model entities instead of their properties.
|
4067
|
-
this.code.push([VMI_add_available_capacity, l]);
|
4068
|
-
} else if(l.relative_rate.isStatic) {
|
4069
|
-
// Static rates permit simpler VM instructions
|
4070
|
-
c = l.relative_rate.result(0);
|
4071
|
-
if(l.multiplier === VM.LM_SUM) {
|
4072
|
-
this.code.push([VMI_add_const_to_sum_coefficients,
|
4073
|
-
[vi, c, l.flow_delay]]);
|
4074
|
-
} else if(l.multiplier === VM.LM_MEAN) {
|
4075
|
-
this.code.push([VMI_add_const_to_sum_coefficients,
|
4076
|
-
// NOTE: 4th parameter = 1 indicates "divide c by delay + 1"
|
4077
|
-
[vi, c, l.flow_delay, 1]]);
|
4078
|
-
} else if(l.multiplier === VM.LM_SPINNING_RESERVE) {
|
4079
|
-
// "spinning reserve" equals UB - level if level > 0, or 0
|
4080
|
-
// so add ON/OFF * UB * rate ...
|
4081
|
-
const fnub = l.from_node.upper_bound;
|
4082
|
-
if(fnub.isStatic) {
|
4083
|
-
this.code.push([VMI_add_const_to_coefficient,
|
4084
|
-
[fn.on_off_var_index, fnub.result(0) * c, l.flow_delay]]);
|
4085
|
-
} else {
|
4086
|
-
// NOTE: constant `c` is passed as 5th parameter
|
4087
|
-
// (var multiplier) since 4th parameter = 1 indicates "delay + 1"
|
4088
|
-
this.code.push([VMI_add_var_to_coefficient,
|
4089
|
-
[fn.on_off_var_index, fnub, l.flow_delay, 0, c]]);
|
4090
|
-
}
|
4091
|
-
// ... and subtract level * rate
|
4092
|
-
this.code.push([VMI_subtract_const_from_coefficient,
|
4093
|
-
[vi, c, l.flow_delay]]);
|
4094
|
-
} else {
|
4095
|
-
this.code.push([VMI_add_const_to_coefficient,
|
4096
|
-
[vi, c, l.flow_delay]]);
|
4097
|
-
if(l.multiplier === VM.LM_INCREASE) {
|
4098
|
-
this.code.push([VMI_subtract_const_from_coefficient,
|
4099
|
-
// NOTE: 4th argument indicates "delay + 1"
|
4100
|
-
[vi, c, l.flow_delay, 1]]);
|
4101
|
-
}
|
3966
|
+
} else {
|
3967
|
+
// When read from product Y, throughput to be added to
|
3968
|
+
// Z equals sum of inflows of FROM node Y:
|
3969
|
+
// Xi --(r2,d2)--> Y --(r1,d1)--> Z
|
3970
|
+
// so instead of the level of Y (having index vi), use
|
3971
|
+
// the level of Xi (for each input i of Y)
|
3972
|
+
for(const ll of fn.inputs) {
|
3973
|
+
const lfn = ll.from_node;
|
3974
|
+
let lvi = fn.level_var_index;
|
3975
|
+
// Here, too, use the *correct* variable index for Xi!
|
3976
|
+
if(ll.multiplier === VM.LM_POSITIVE || ll.multiplier === VM.LM_ZERO) {
|
3977
|
+
lvi = lfn.on_off_var_index;
|
3978
|
+
} else if(ll.multiplier === VM.LM_STARTUP) {
|
3979
|
+
lvi = lfn.start_up_var_index;
|
3980
|
+
} else if(ll.multiplier === VM.LM_FIRST_COMMIT) {
|
3981
|
+
lvi = lfn.first_commit_var_index;
|
3982
|
+
} else if(ll.multiplier === VM.LM_SHUTDOWN) {
|
3983
|
+
lvi = lfn.shut_down_var_index;
|
4102
3984
|
}
|
3985
|
+
// NOTE: we trade-off efficiency gain during execution
|
3986
|
+
// against simplicity now by not checking whether rates
|
3987
|
+
// are static; the VM instruction will be a bit slower
|
3988
|
+
// as it calls the result(t) method for both rates
|
3989
|
+
this.code.push([VMI_add_throughput_to_coefficient,
|
3990
|
+
[lvi, l.relative_rate, l.flow_delay,
|
3991
|
+
ll.relative_rate, ll.flow_delay]]);
|
3992
|
+
}
|
3993
|
+
}
|
3994
|
+
} else if(l.multiplier === VM.LM_PEAK_INC) {
|
3995
|
+
// SPECIAL instruction that adds flow only for first t of block
|
3996
|
+
// NOTE: no delay on this type of link
|
3997
|
+
this.code.push([VMI_add_peak_increase_at_t_0,
|
3998
|
+
[vi, l.relative_rate]]);
|
3999
|
+
} else if(l.multiplier === VM.LM_AVAILABLE_CAPACITY) {
|
4000
|
+
// The "available capacity" equals UB - level, so subtract
|
4001
|
+
// UB * rate from RHS, while considering the delay.
|
4002
|
+
// NOTE: New instruction style that passes pointers to
|
4003
|
+
// model entities instead of their properties.
|
4004
|
+
this.code.push([VMI_add_available_capacity, l]);
|
4005
|
+
} else if(l.relative_rate.isStatic) {
|
4006
|
+
// Static rates permit simpler VM instructions
|
4007
|
+
const c = l.relative_rate.result(0);
|
4008
|
+
if(l.multiplier === VM.LM_SUM) {
|
4009
|
+
this.code.push([VMI_add_const_to_sum_coefficients,
|
4010
|
+
[vi, c, l.flow_delay]]);
|
4011
|
+
} else if(l.multiplier === VM.LM_MEAN) {
|
4012
|
+
this.code.push([VMI_add_const_to_sum_coefficients,
|
4013
|
+
// NOTE: 4th parameter = 1 indicates "divide c by delay + 1"
|
4014
|
+
[vi, c, l.flow_delay, 1]]);
|
4015
|
+
} else if(l.multiplier === VM.LM_SPINNING_RESERVE) {
|
4016
|
+
// "spinning reserve" equals UB - level if level > 0, or 0
|
4017
|
+
// so add ON/OFF * UB * rate ...
|
4018
|
+
const fnub = l.from_node.upper_bound;
|
4019
|
+
if(fnub.isStatic) {
|
4020
|
+
this.code.push([VMI_add_const_to_coefficient,
|
4021
|
+
[fn.on_off_var_index, fnub.result(0) * c, l.flow_delay]]);
|
4103
4022
|
} else {
|
4104
|
-
// NOTE: `c` is
|
4105
|
-
|
4106
|
-
|
4107
|
-
|
4108
|
-
[vi, c, l.flow_delay]]);
|
4109
|
-
} else if(l.multiplier === VM.LM_MEAN) {
|
4110
|
-
this.code.push([VMI_add_var_to_weighted_sum_coefficients,
|
4111
|
-
[vi, c, l.flow_delay, 1]]);
|
4112
|
-
} else if(l.multiplier === VM.LM_SPINNING_RESERVE) {
|
4113
|
-
// "spinning reserve" equals UB - level if level > 0, or 0
|
4114
|
-
// so add ON/OFF * UB * rate ...
|
4115
|
-
this.code.push([VMI_add_var_product_to_coefficient,
|
4116
|
-
[fn.on_off_var_index, l.from_node.upper_bound,
|
4117
|
-
c, l.flow_delay]]);
|
4118
|
-
// ... and subtract level * rate
|
4119
|
-
this.code.push([VMI_subtract_var_from_coefficient,
|
4120
|
-
[vi, c, l.flow_delay]]);
|
4121
|
-
} else {
|
4122
|
-
this.code.push([VMI_add_var_to_coefficient,
|
4123
|
-
[vi, c, l.flow_delay]]);
|
4124
|
-
if(l.multiplier === VM.LM_INCREASE) {
|
4125
|
-
this.code.push([VMI_subtract_var_from_coefficient,
|
4126
|
-
// NOTE: 4th argument indicates "delay + 1"
|
4127
|
-
[vi, c, l.flow_delay, 1]]);
|
4128
|
-
}
|
4129
|
-
}
|
4023
|
+
// NOTE: constant `c` is passed as 5th parameter
|
4024
|
+
// (var multiplier) since 4th parameter = 1 indicates "delay + 1"
|
4025
|
+
this.code.push([VMI_add_var_to_coefficient,
|
4026
|
+
[fn.on_off_var_index, fnub, l.flow_delay, 0, c]]);
|
4130
4027
|
}
|
4131
|
-
|
4132
|
-
|
4133
|
-
|
4134
|
-
|
4135
|
-
|
4136
|
-
|
4137
|
-
|
4138
|
-
|
4139
|
-
|
4140
|
-
|
4141
|
-
|
4142
|
-
|
4143
|
-
|
4144
|
-
|
4145
|
-
|
4146
|
-
|
4147
|
-
|
4148
|
-
|
4149
|
-
|
4150
|
-
|
4151
|
-
|
4152
|
-
|
4153
|
-
|
4154
|
-
|
4155
|
-
|
4156
|
-
|
4157
|
-
|
4158
|
-
|
4159
|
-
|
4160
|
-
|
4161
|
-
|
4162
|
-
|
4163
|
-
|
4028
|
+
// ... and subtract level * rate
|
4029
|
+
this.code.push([VMI_subtract_const_from_coefficient,
|
4030
|
+
[vi, c, l.flow_delay]]);
|
4031
|
+
} else {
|
4032
|
+
this.code.push([VMI_add_const_to_coefficient,
|
4033
|
+
[vi, c, l.flow_delay]]);
|
4034
|
+
if(l.multiplier === VM.LM_INCREASE) {
|
4035
|
+
this.code.push([VMI_subtract_const_from_coefficient,
|
4036
|
+
// NOTE: 4th argument indicates "delay + 1"
|
4037
|
+
[vi, c, l.flow_delay, 1]]);
|
4038
|
+
}
|
4039
|
+
}
|
4040
|
+
} else {
|
4041
|
+
// NOTE: Rate is now an expression.
|
4042
|
+
const rx = l.relative_rate;
|
4043
|
+
if(l.multiplier === VM.LM_SUM) {
|
4044
|
+
this.code.push([VMI_add_var_to_weighted_sum_coefficients,
|
4045
|
+
[vi, rx, l.flow_delay]]);
|
4046
|
+
} else if(l.multiplier === VM.LM_MEAN) {
|
4047
|
+
this.code.push([VMI_add_var_to_weighted_sum_coefficients,
|
4048
|
+
[vi, rx, l.flow_delay, 1]]);
|
4049
|
+
} else if(l.multiplier === VM.LM_SPINNING_RESERVE) {
|
4050
|
+
// "spinning reserve" equals UB - level if level > 0, or 0
|
4051
|
+
// so add ON/OFF * UB * rate ...
|
4052
|
+
this.code.push([VMI_add_var_product_to_coefficient,
|
4053
|
+
[fn.on_off_var_index, l.from_node.upper_bound,
|
4054
|
+
rx, l.flow_delay]]);
|
4055
|
+
// ... and subtract level * rate
|
4056
|
+
this.code.push([VMI_subtract_var_from_coefficient,
|
4057
|
+
[vi, rx, l.flow_delay]]);
|
4058
|
+
} else {
|
4059
|
+
this.code.push([VMI_add_var_to_coefficient,
|
4060
|
+
[vi, rx, l.flow_delay]]);
|
4061
|
+
if(l.multiplier === VM.LM_INCREASE) {
|
4062
|
+
this.code.push([VMI_subtract_var_from_coefficient,
|
4063
|
+
// NOTE: 4th argument indicates "delay + 1"
|
4064
|
+
[vi, rx, l.flow_delay, 1]]);
|
4164
4065
|
}
|
4165
4066
|
}
|
4166
4067
|
}
|
4167
|
-
|
4168
|
-
|
4169
|
-
|
4170
|
-
|
4171
|
-
|
4172
|
-
//
|
4173
|
-
|
4174
|
-
|
4175
|
-
|
4176
|
-
|
4177
|
-
|
4178
|
-
|
4179
|
-
|
4180
|
-
|
4181
|
-
|
4182
|
-
|
4183
|
-
|
4184
|
-
|
4185
|
-
|
4186
|
-
|
4068
|
+
} // END FOR all inputs
|
4069
|
+
|
4070
|
+
// Subtract outflow from product P to consuming processes (outputs)
|
4071
|
+
for(const l of p.outputs) if(!MODEL.ignored_entities[l.identifier]) {
|
4072
|
+
const tn = l.to_node;
|
4073
|
+
// NOTE: Only consider outputs to processes; data flows do
|
4074
|
+
// not subtract from their tail nodes.
|
4075
|
+
if(tn instanceof Process) {
|
4076
|
+
if(tn.grid) {
|
4077
|
+
// If the link is a power flow, pass the grid process to
|
4078
|
+
// a special VM instruction that will add coefficients that
|
4079
|
+
// account for losses.
|
4080
|
+
// NOTES:
|
4081
|
+
// (1) The second parameter (-1) indicates that the
|
4082
|
+
// coefficients of the UP flows should be negative
|
4083
|
+
// and those of the DOWN flows should be positive
|
4084
|
+
// (because it is a Q -> P link).
|
4085
|
+
// (2) The rate and delay properties of the link are ignored.
|
4086
|
+
this.code.push(
|
4087
|
+
[VMI_add_power_flow_to_coefficients, [tn, -1]]);
|
4088
|
+
} else {
|
4089
|
+
const rr = l.relative_rate;
|
4090
|
+
if(rr.isStatic) {
|
4091
|
+
this.code.push([VMI_subtract_const_from_coefficient,
|
4092
|
+
[tn.level_var_index, rr.result(0), l.flow_delay]]);
|
4093
|
+
} else {
|
4094
|
+
this.code.push([VMI_subtract_var_from_coefficient,
|
4095
|
+
[tn.level_var_index, rr, l.flow_delay]]);
|
4096
|
+
}
|
4097
|
+
}
|
4187
4098
|
}
|
4188
4099
|
}
|
4189
|
-
|
4190
|
-
//
|
4191
|
-
|
4192
|
-
|
4193
|
-
|
4100
|
+
|
4101
|
+
// NOTES:
|
4102
|
+
// (1) for products with storage, set the coefficient for this product's
|
4103
|
+
// stock IN THE PREVIOUS TIME STEP to 1
|
4104
|
+
// (2) the VM instruction will subtract the stock level at the end of the
|
4105
|
+
// previous block from the RHS if t=block_start, or the initial level if t=1
|
4106
|
+
if(p.is_buffer) {
|
4107
|
+
this.code.push([VMI_add_const_to_coefficient,
|
4108
|
+
[p.level_var_index, 1, 1]]); // delay of 1
|
4109
|
+
}
|
4110
|
+
|
4111
|
+
// Set the coefficient for this product's stock NOW to -1 so that
|
4112
|
+
// the EQ constraint (having RHS = 0) will effectuate that the
|
4113
|
+
// stock variable takes on the correct value
|
4114
|
+
// NOTE: do this only when `p` is NOT data, or `p` has links
|
4115
|
+
// IN or OUT (meaning: 1 or more coefficients)
|
4116
|
+
if(!p.is_data || p.inputs.length + p.outputs.length > 0) {
|
4117
|
+
this.code.push([VMI_add_const_to_coefficient,
|
4118
|
+
[p.level_var_index, -1]]);
|
4119
|
+
this.code.push([VMI_add_constraint, VM.EQ]);
|
4120
|
+
}
|
4121
|
+
} // END of IF p not a constant
|
4122
|
+
|
4123
|
+
// Set the bound constraints on the product stock variable
|
4124
|
+
this.setBoundConstraints(p);
|
4125
|
+
} // End of FOR all products
|
4194
4126
|
|
4195
4127
|
// NEXT: add constraints that will set values of binary variables
|
4196
4128
|
// NOTE: This is not trivial!
|
@@ -4284,24 +4216,21 @@ class VirtualMachine {
|
|
4284
4216
|
*/
|
4285
4217
|
// NOTE: As of 20 June 2021, binary attributes of products are also computed.
|
4286
4218
|
const pp_nodes = [];
|
4287
|
-
for(
|
4288
|
-
k
|
4289
|
-
if(!MODEL.ignored_entities[k]) pp_nodes.push(MODEL.processes[k]);
|
4219
|
+
for(const k of process_keys) if(!MODEL.ignored_entities[k]) {
|
4220
|
+
pp_nodes.push(MODEL.processes[k]);
|
4290
4221
|
}
|
4291
|
-
for(
|
4292
|
-
k
|
4293
|
-
if(!MODEL.ignored_entities[k]) pp_nodes.push(MODEL.products[k]);
|
4222
|
+
for(const k of product_keys) if(!MODEL.ignored_entities[k]) {
|
4223
|
+
pp_nodes.push(MODEL.products[k]);
|
4294
4224
|
}
|
4295
4225
|
|
4296
|
-
for(
|
4297
|
-
p = pp_nodes[i];
|
4226
|
+
for(const p of pp_nodes) {
|
4298
4227
|
if(p.on_off_var_index >= 0) {
|
4299
|
-
// NOTE:
|
4228
|
+
// NOTE: When UB is dynamic, its value may become <= 0, and in such
|
4300
4229
|
// cases, the default constraints for computing OO, IZ and SU will fail.
|
4301
4230
|
// To deal with this, the default equations will NOT be set when UB <= 0,
|
4302
4231
|
// while the "exceptional" equations (q.v.) will NOT be set when UB > 0.
|
4303
4232
|
// This can be realized using a special VM instruction:
|
4304
|
-
ubx = (p.equal_bounds && p.lower_bound.defined && !p.grid ?
|
4233
|
+
const ubx = (p.equal_bounds && p.lower_bound.defined && !p.grid ?
|
4305
4234
|
p.lower_bound : p.upper_bound);
|
4306
4235
|
this.code.push([VMI_set_add_constraints_flag, [ubx, '>', 0]]);
|
4307
4236
|
// This instruction ensures that when UB <= 0, the constraints for
|
@@ -4504,7 +4433,7 @@ class VirtualMachine {
|
|
4504
4433
|
[VMI_add_constraint, VM.EQ]
|
4505
4434
|
);
|
4506
4435
|
}
|
4507
|
-
// Add constraints for start-up and first commit only if needed
|
4436
|
+
// Add constraints for start-up and first commit only if needed.
|
4508
4437
|
if(p.start_up_var_index >= 0) {
|
4509
4438
|
this.code.push(
|
4510
4439
|
// SU[t] = 0
|
@@ -4521,7 +4450,7 @@ class VirtualMachine {
|
|
4521
4450
|
);
|
4522
4451
|
}
|
4523
4452
|
}
|
4524
|
-
// Add constraint for shut-down only if needed
|
4453
|
+
// Add constraint for shut-down only if needed.
|
4525
4454
|
if(p.shut_down_var_index >= 0) {
|
4526
4455
|
this.code.push(
|
4527
4456
|
// SD[t] - OO[t-1] = 0
|
@@ -4533,21 +4462,22 @@ class VirtualMachine {
|
|
4533
4462
|
);
|
4534
4463
|
}
|
4535
4464
|
|
4536
|
-
// NOTE:
|
4465
|
+
// NOTE: The "add constraints flag" must be reset to TRUE.
|
4537
4466
|
this.code.push([VMI_set_add_constraints_flag, true]);
|
4538
|
-
}
|
4467
|
+
} // END IF product has on/off binary variable
|
4468
|
+
|
4539
4469
|
// Check whether constraints (n) through (p) need to be added
|
4540
|
-
// to compute the peak level for a block of time steps
|
4541
|
-
// NOTE:
|
4470
|
+
// to compute the peak level for a block of time steps.
|
4471
|
+
// NOTE: This is independent of the binary variables!
|
4542
4472
|
if(p.peak_inc_var_index >= 0) {
|
4543
4473
|
this.code.push(
|
4544
4474
|
// One special instruction implements this operation, as part
|
4545
|
-
// of it must be performed only at block time = 0
|
4475
|
+
// of it must be performed only at block time = 0.
|
4546
4476
|
[VMI_add_peak_increase_constraints,
|
4547
4477
|
[p.level_var_index, p.peak_inc_var_index]]
|
4548
4478
|
);
|
4549
4479
|
}
|
4550
|
-
}
|
4480
|
+
} // END of FOR all processes and products
|
4551
4481
|
|
4552
4482
|
// NEXT: Add composite constraints.
|
4553
4483
|
// NOTE: As of version 1.0.10, constraints are implemented using special
|
@@ -4557,22 +4487,17 @@ class VirtualMachine {
|
|
4557
4487
|
// - variable indices for the constraining node X, the constrained node Y
|
4558
4488
|
// - expressions for the LB and UB of X and Y
|
4559
4489
|
// - the bound line object, as this provides all further information
|
4560
|
-
for(
|
4561
|
-
|
4562
|
-
|
4563
|
-
|
4564
|
-
|
4565
|
-
|
4566
|
-
|
4567
|
-
|
4568
|
-
|
4569
|
-
this.code.push([VMI_add_bound_line_constraint,
|
4570
|
-
[x.level_var_index, x.lower_bound, x.upper_bound,
|
4571
|
-
y.level_var_index, y.lower_bound, y.upper_bound,
|
4572
|
-
c.bound_lines[j]]]);
|
4573
|
-
}
|
4490
|
+
for(const k of constraint_keys) if(!MODEL.ignored_entities[k]) {
|
4491
|
+
const
|
4492
|
+
c = MODEL.constraints[k],
|
4493
|
+
x = c.from_node,
|
4494
|
+
y = c.to_node;
|
4495
|
+
for(const bl of c.bound_lines) {
|
4496
|
+
this.code.push([VMI_add_bound_line_constraint,
|
4497
|
+
[x.level_var_index, x.lower_bound, x.upper_bound,
|
4498
|
+
y.level_var_index, y.lower_bound, y.upper_bound, bl]]);
|
4574
4499
|
}
|
4575
|
-
}
|
4500
|
+
}
|
4576
4501
|
|
4577
4502
|
MODEL.set_up = true;
|
4578
4503
|
this.logMessage(1,
|
@@ -4593,10 +4518,10 @@ class VirtualMachine {
|
|
4593
4518
|
// Slack penalty must exceed the maximum joint utility of all processes
|
4594
4519
|
// Use 1 even if highest link rate < 1
|
4595
4520
|
let high_rate = 1;
|
4596
|
-
for(let
|
4597
|
-
!MODEL.ignored_entities[
|
4598
|
-
for(let
|
4599
|
-
const r = MODEL.links[
|
4521
|
+
for(let k in MODEL.links) if(MODEL.links.hasOwnProperty(k) &&
|
4522
|
+
!MODEL.ignored_entities[k]) {
|
4523
|
+
for(let t = this.block_start; t < this.block_start + this.chunk_length; t++) {
|
4524
|
+
const r = MODEL.links[k].relative_rate.result(t);
|
4600
4525
|
// NOTE: ignore errors and "undefined" (chunk Length may exceed actual block length)
|
4601
4526
|
if(r <= VM.PLUS_INFINITY) {
|
4602
4527
|
high_rate = Math.max(high_rate, Math.abs(r));
|
@@ -4606,15 +4531,15 @@ class VirtualMachine {
|
|
4606
4531
|
// Similar to links, composite constraints X-->Y can act as multipliers:
|
4607
4532
|
// since CC map the range (UB - LB) of node X to range (UB - LB) of node Y,
|
4608
4533
|
// the multiplier is rangeY / rangeX:
|
4609
|
-
for(let
|
4610
|
-
!MODEL.ignored_entities[
|
4611
|
-
const c = MODEL.constraints[
|
4612
|
-
for(let
|
4534
|
+
for(let k in MODEL.constraints) if(MODEL.constraints.hasOwnProperty(k) &&
|
4535
|
+
!MODEL.ignored_entities[k]) {
|
4536
|
+
const c = MODEL.constraints[k];
|
4537
|
+
for(let t = this.block_start; t < this.block_start + this.chunk_length; t++) {
|
4613
4538
|
const
|
4614
|
-
fnlb = c.from_node.lower_bound.result(
|
4615
|
-
fnub = c.from_node.upper_bound.result(
|
4616
|
-
tnlb = c.to_node.lower_bound.result(
|
4617
|
-
tnub = c.to_node.upper_bound.result(
|
4539
|
+
fnlb = c.from_node.lower_bound.result(t),
|
4540
|
+
fnub = c.from_node.upper_bound.result(t),
|
4541
|
+
tnlb = c.to_node.lower_bound.result(t),
|
4542
|
+
tnub = c.to_node.upper_bound.result(t),
|
4618
4543
|
fnrange = (fnub > fnlb + VM.NEAR_ZERO ? fnub - fnlb : fnub),
|
4619
4544
|
tnrange = (tnub > tnlb + VM.NEAR_ZERO ? tnub - tnlb : tnub),
|
4620
4545
|
// Divisor near 0 => multiplier
|
@@ -4635,14 +4560,12 @@ class VirtualMachine {
|
|
4635
4560
|
}
|
4636
4561
|
const m = Math.max(
|
4637
4562
|
Math.abs(this.low_coefficient), Math.abs(this.high_coefficient));
|
4638
|
-
// Scaling is useful if m is larger than 2
|
4563
|
+
// Scaling is useful if m is larger than 2.
|
4639
4564
|
if(m > 2 && m < VM.PLUS_INFINITY) {
|
4640
|
-
// Use reciprocal because multiplication is faster than division
|
4565
|
+
// Use reciprocal because multiplication is faster than division.
|
4641
4566
|
const scalar = 2 / m;
|
4642
4567
|
this.scaling_factor = 0.5 * m;
|
4643
|
-
for(let i in this.objective)
|
4644
|
-
if(Number(i)) this.objective[i] *= scalar;
|
4645
|
-
}
|
4568
|
+
for(let i in this.objective) if(Number(i)) this.objective[i] *= scalar;
|
4646
4569
|
this.low_coefficient *= scalar;
|
4647
4570
|
this.high_coefficient *= scalar;
|
4648
4571
|
} else {
|
@@ -4663,8 +4586,8 @@ class VirtualMachine {
|
|
4663
4586
|
// Use reciprocal as multiplier to scale the constraint coefficients.
|
4664
4587
|
const m = 1 / this.cash_scalar;
|
4665
4588
|
let cv;
|
4666
|
-
for(
|
4667
|
-
const cc = this.matrix[
|
4589
|
+
for(const k of this.cash_constraints) {
|
4590
|
+
const cc = this.matrix[k];
|
4668
4591
|
for(let ci in cc) if(cc.hasOwnProperty(ci)) {
|
4669
4592
|
if(ci < this.chunk_offset) {
|
4670
4593
|
// NOTE: Subtract 1 as variables array is zero-based.
|
@@ -4681,8 +4604,8 @@ class VirtualMachine {
|
|
4681
4604
|
// cash flow, the coefficients of the constraint that equates the
|
4682
4605
|
// product level to the cash flow must be *multiplied* by the cash
|
4683
4606
|
// scalar so that they equal the cash flow in the model's monetary unit.
|
4684
|
-
for(
|
4685
|
-
const cc = this.matrix[
|
4607
|
+
for(const k of this.actor_cash_constraints) {
|
4608
|
+
const cc = this.matrix[k];
|
4686
4609
|
for(let ci in cc) if(cc.hasOwnProperty(ci)) {
|
4687
4610
|
if(ci < this.chunk_offset) {
|
4688
4611
|
// NOTE: Subtract 1 as variables array is zero-based.
|
@@ -4759,8 +4682,8 @@ class VirtualMachine {
|
|
4759
4682
|
// but since Linny-R permits negative lower bounds on processes, and also
|
4760
4683
|
// negative link rates, cash flows may become negative. If that occurs,
|
4761
4684
|
// the modeler should be warned.
|
4762
|
-
for(let
|
4763
|
-
const a = MODEL.actors[
|
4685
|
+
for(let k in MODEL.actors) if(MODEL.actors.hasOwnProperty(k)) {
|
4686
|
+
const a = MODEL.actors[k];
|
4764
4687
|
// NOTE: `b` is the index to be used for the vectors.
|
4765
4688
|
let b = bb;
|
4766
4689
|
// Iterate over all time steps in this block.
|
@@ -4791,10 +4714,10 @@ class VirtualMachine {
|
|
4791
4714
|
}
|
4792
4715
|
}
|
4793
4716
|
// Set production levels and start-up moments for all processes.
|
4794
|
-
for(let
|
4795
|
-
!MODEL.ignored_entities[
|
4717
|
+
for(let k in MODEL.processes) if(MODEL.processes.hasOwnProperty(k) &&
|
4718
|
+
!MODEL.ignored_entities[k]) {
|
4796
4719
|
const
|
4797
|
-
p = MODEL.processes[
|
4720
|
+
p = MODEL.processes[k],
|
4798
4721
|
has_OO = (p.on_off_var_index >= 0),
|
4799
4722
|
has_SU = (p.start_up_var_index >= 0),
|
4800
4723
|
has_SD = (p.shut_down_var_index >= 0);
|
@@ -4831,10 +4754,10 @@ class VirtualMachine {
|
|
4831
4754
|
}
|
4832
4755
|
}
|
4833
4756
|
// Set stock levels for all products.
|
4834
|
-
for(let
|
4835
|
-
!MODEL.ignored_entities[
|
4757
|
+
for(let k in MODEL.products) if(MODEL.products.hasOwnProperty(k) &&
|
4758
|
+
!MODEL.ignored_entities[k]) {
|
4836
4759
|
const
|
4837
|
-
p = MODEL.products[
|
4760
|
+
p = MODEL.products[k],
|
4838
4761
|
has_OO = (p.on_off_var_index >= 0),
|
4839
4762
|
has_SU = (p.start_up_var_index >= 0),
|
4840
4763
|
has_SD = (p.shut_down_var_index >= 0);
|
@@ -4885,32 +4808,28 @@ class VirtualMachine {
|
|
4885
4808
|
// Iterate over all time steps in this block.
|
4886
4809
|
let j = -1;
|
4887
4810
|
for(let i = 0; i < abl; i++) {
|
4888
|
-
//
|
4889
|
-
for(
|
4890
|
-
|
4891
|
-
|
4892
|
-
l = svl.length;
|
4893
|
-
for(let k = 0; k < l; k++) {
|
4811
|
+
// Iterates over 3 types of slack variable.
|
4812
|
+
for(const svi_list of this.slack_variables) {
|
4813
|
+
// Each list contains indices of slack variables
|
4814
|
+
for(const vi of svi_list) {
|
4894
4815
|
const
|
4895
|
-
vi = svl[k],
|
4896
4816
|
slack = parseFloat(x[vi + j]),
|
4897
4817
|
absl = Math.abs(slack);
|
4898
4818
|
if(absl > VM.NEAR_ZERO) {
|
4899
4819
|
const v = this.variables[vi - 1];
|
4900
|
-
// NOTE:
|
4820
|
+
// NOTE: For constraints, add 'UB' or 'LB' to its vector for
|
4901
4821
|
// the time step where slack was used.
|
4902
|
-
if(v[1] instanceof BoundLine)
|
4903
|
-
v[1].constraint.slack_info[b] = v[0];
|
4904
|
-
}
|
4822
|
+
if(v[1] instanceof BoundLine) v[1].constraint.slack_info[b] = v[0];
|
4905
4823
|
if(b <= this.nr_of_time_steps && absl > VM.ON_OFF_THRESHOLD) {
|
4906
4824
|
this.logMessage(block, `${this.WARNING}(t=${b}${round}) ` +
|
4907
4825
|
`${v[1].displayName} ${v[0]} slack = ` +
|
4908
4826
|
// NOTE: TRUE denotes "show tiny values with precision".
|
4909
4827
|
this.sig4Dig(slack, true));
|
4910
4828
|
if(v[1] instanceof Product) {
|
4911
|
-
|
4912
|
-
|
4913
|
-
|
4829
|
+
// Ensure that clusters containing this product "know" that
|
4830
|
+
// slack is used so that they will be drawn in color.
|
4831
|
+
for(const ppc of v[1].productPositionClusters) {
|
4832
|
+
ppc.usesSlack(b, v[1], v[0]);
|
4914
4833
|
}
|
4915
4834
|
}
|
4916
4835
|
} else if(MODEL.show_notices) {
|
@@ -4924,10 +4843,10 @@ class VirtualMachine {
|
|
4924
4843
|
if(this.diagnose) {
|
4925
4844
|
// Iterate over all processes, and set the "slack use" flag
|
4926
4845
|
// for their cluster so that these clusters will be highlighted.
|
4927
|
-
for(let
|
4928
|
-
!MODEL.ignored_entities[
|
4846
|
+
for(let k in MODEL.processes) if(MODEL.processes.hasOwnProperty(k) &&
|
4847
|
+
!MODEL.ignored_entities[k]) {
|
4929
4848
|
const
|
4930
|
-
p = MODEL.processes[
|
4849
|
+
p = MODEL.processes[k],
|
4931
4850
|
l = p.level[b];
|
4932
4851
|
if(l >= VM.PLUS_INFINITY) {
|
4933
4852
|
this.logMessage(block,
|
@@ -4952,11 +4871,11 @@ class VirtualMachine {
|
|
4952
4871
|
// Returns severest exception code or +/- INFINITY in `list`, or the
|
4953
4872
|
// result of the computation that involves the elements of `list`.
|
4954
4873
|
let issue = 0;
|
4955
|
-
for(
|
4956
|
-
if(
|
4957
|
-
issue = Math.min(
|
4958
|
-
} else if(
|
4959
|
-
issue = Math.max(
|
4874
|
+
for(const ec of list) {
|
4875
|
+
if(ec <= VM.MINUS_INFINITY) {
|
4876
|
+
issue = Math.min(ec, issue);
|
4877
|
+
} else if(ec >= VM.PLUS_INFINITY) {
|
4878
|
+
issue = Math.max(ec, issue);
|
4960
4879
|
}
|
4961
4880
|
}
|
4962
4881
|
if(issue) return issue;
|
@@ -4979,22 +4898,22 @@ class VirtualMachine {
|
|
4979
4898
|
// Start with an empty list of variables to "fixate" in the next block.
|
4980
4899
|
this.variables_to_fixate = {};
|
4981
4900
|
// FIRST: Calculate the actual flows on links.
|
4982
|
-
let
|
4983
|
-
|
4984
|
-
MODEL.power_grids[g].total_losses = 0;
|
4901
|
+
for(let k in MODEL.power_grids) if(MODEL.power_grids.hasOwnProperty(k)) {
|
4902
|
+
MODEL.power_grids[k].total_losses = 0;
|
4985
4903
|
}
|
4986
|
-
for(let
|
4987
|
-
!MODEL.ignored_entities[
|
4988
|
-
l = MODEL.links[
|
4904
|
+
for(let k in MODEL.links) if(MODEL.links.hasOwnProperty(k) &&
|
4905
|
+
!MODEL.ignored_entities[k]) {
|
4906
|
+
const l = MODEL.links[k];
|
4989
4907
|
// NOTE: Flow is determined by the process node, or in case
|
4990
4908
|
// of a P -> P data link by the FROM product node.
|
4991
|
-
p = (l.to_node instanceof Process ? l.to_node : l.from_node);
|
4992
|
-
b = bb;
|
4909
|
+
const p = (l.to_node instanceof Process ? l.to_node : l.from_node);
|
4993
4910
|
// Iterate over all time steps in this chunk.
|
4994
4911
|
for(let i = 0; i < cbl; i++) {
|
4995
4912
|
// NOTE: Flows may have a delay (but will be 0 for grid processes).
|
4996
|
-
|
4997
|
-
|
4913
|
+
const
|
4914
|
+
b = bb + i,
|
4915
|
+
ld = l.actualDelay(b),
|
4916
|
+
bt = b - ld;
|
4998
4917
|
latest_time_step = Math.max(latest_time_step, bt);
|
4999
4918
|
// If delay < 0 AND this results in a block time beyond the
|
5000
4919
|
// block length, this means that the level of the FROM node
|
@@ -5013,11 +4932,11 @@ class VirtualMachine {
|
|
5013
4932
|
l.from_node.nonZeroLevel(bt));
|
5014
4933
|
}
|
5015
4934
|
// NOTE: Block index may fall beyond actual chunk length.
|
5016
|
-
ci = i - ld;
|
4935
|
+
const ci = i - ld;
|
5017
4936
|
// NOTE: Use non-zero level here to ignore non-zero values that
|
5018
4937
|
// are very small relative to the bounds on the process
|
5019
4938
|
// (typically values below the non-zero tolerance of the solver).
|
5020
|
-
pl = p.nonZeroLevel(bt);
|
4939
|
+
let pl = p.nonZeroLevel(bt);
|
5021
4940
|
if(l.multiplier === VM.LM_SPINNING_RESERVE) {
|
5022
4941
|
pl = (pl > VM.NEAR_ZERO ? p.upper_bound.result(bt) - pl : 0);
|
5023
4942
|
} else if(l.multiplier === VM.LM_POSITIVE) {
|
@@ -5076,10 +4995,10 @@ class VirtualMachine {
|
|
5076
4995
|
// NOTE: calculate throughput on basis of levels and rates,
|
5077
4996
|
// as not all actual flows may have been computed yet
|
5078
4997
|
pl = 0;
|
5079
|
-
for(
|
4998
|
+
for(const ll of p.inputs) {
|
5080
4999
|
const
|
5081
|
-
ipl =
|
5082
|
-
rr =
|
5000
|
+
ipl = ll.from_node.actualLevel(bt),
|
5001
|
+
rr = ll.relative_rate.result(bt);
|
5083
5002
|
pl = this.severestIssue([pl, ipl, rr], pl + ipl * rr);
|
5084
5003
|
}
|
5085
5004
|
} else if(l.multiplier === VM.LM_PEAK_INC) {
|
@@ -5100,7 +5019,7 @@ class VirtualMachine {
|
|
5100
5019
|
// For grid processes, rates depend on losses, which depend on
|
5101
5020
|
// the process level, and whether the link is P -> Q or Q -> P.
|
5102
5021
|
rr = 1;
|
5103
|
-
if(p.grid.loss_approximation > 0 &&
|
5022
|
+
if(p.grid.loss_approximation > 0 && !MODEL.ignore_power_losses &&
|
5104
5023
|
((pl > 0 && p === l.from_node) ||
|
5105
5024
|
(pl < 0 && p === l.to_node))) {
|
5106
5025
|
const alr = p.actualLossRate(bt);
|
@@ -5110,14 +5029,13 @@ class VirtualMachine {
|
|
5110
5029
|
}
|
5111
5030
|
const af = this.severestIssue([pl, rr], rr * pl);
|
5112
5031
|
l.actual_flow[b] = (Math.abs(af) > VM.NEAR_ZERO ? af : 0);
|
5113
|
-
b++;
|
5114
5032
|
}
|
5115
5033
|
}
|
5116
5034
|
// Report power losses per grid, if applicable.
|
5117
|
-
if(MODEL.with_power_flow) {
|
5035
|
+
if(MODEL.with_power_flow && !MODEL.ignore_power_losses) {
|
5118
5036
|
const ll = [];
|
5119
|
-
for(let
|
5120
|
-
const pg = MODEL.power_grids[
|
5037
|
+
for(let k in MODEL.power_grids) if(MODEL.power_grids.hasOwnProperty(k)) {
|
5038
|
+
const pg = MODEL.power_grids[k];
|
5121
5039
|
if(pg.loss_approximation > 0) {
|
5122
5040
|
ll.push(`${pg.name}: ${VM.sig4Dig(pg.total_losses / cbl)} ${pg.power_unit}`);
|
5123
5041
|
}
|
@@ -5129,26 +5047,26 @@ class VirtualMachine {
|
|
5129
5047
|
}
|
5130
5048
|
|
5131
5049
|
// THEN: Calculate cash flows one step at a time because of delays.
|
5132
|
-
b = bb;
|
5133
5050
|
for(let i = 0; i < cbl; i++) {
|
5051
|
+
const b = bb + i;
|
5134
5052
|
// Initialize cumulative cash flows for clusters.
|
5135
|
-
for(let
|
5136
|
-
!MODEL.ignored_entities[
|
5137
|
-
const c = MODEL.clusters[
|
5053
|
+
for(let k in MODEL.clusters) if(MODEL.clusters.hasOwnProperty(k) &&
|
5054
|
+
!MODEL.ignored_entities[k]) {
|
5055
|
+
const c = MODEL.clusters[k];
|
5138
5056
|
c.cash_in[b] = 0;
|
5139
5057
|
c.cash_out[b] = 0;
|
5140
5058
|
c.cash_flow[b] = 0;
|
5141
5059
|
}
|
5142
5060
|
// NOTE: Cash flows ONLY result from processes.
|
5143
|
-
for(let
|
5144
|
-
!MODEL.ignored_entities[
|
5145
|
-
const p = MODEL.processes[
|
5146
|
-
let ci = 0,
|
5061
|
+
for(let k in MODEL.processes) if(MODEL.processes.hasOwnProperty(k) &&
|
5062
|
+
!MODEL.ignored_entities[k]) {
|
5063
|
+
const p = MODEL.processes[k];
|
5064
|
+
let ci = 0,
|
5065
|
+
co = 0;
|
5147
5066
|
// INPUT links from priced products generate cash OUT...
|
5148
|
-
for(
|
5067
|
+
for(const l of p.inputs) {
|
5149
5068
|
// NOTE: Input links do NOT have a delay.
|
5150
5069
|
const
|
5151
|
-
l = p.inputs[j],
|
5152
5070
|
af = l.actual_flow[b],
|
5153
5071
|
fnp = l.from_node.price;
|
5154
5072
|
if(af > VM.NEAR_ZERO && fnp.defined) {
|
@@ -5162,10 +5080,9 @@ class VirtualMachine {
|
|
5162
5080
|
}
|
5163
5081
|
}
|
5164
5082
|
// OUTPUT links to priced products generate cash IN ...
|
5165
|
-
for(
|
5083
|
+
for(const l of p.outputs) {
|
5166
5084
|
// NOTE: actualFlows already consider delay!
|
5167
5085
|
const
|
5168
|
-
l = p.outputs[j],
|
5169
5086
|
af = l.actualFlow(b),
|
5170
5087
|
tnp = l.to_node.price;
|
5171
5088
|
if(af > VM.NEAR_ZERO && tnp.defined) {
|
@@ -5192,7 +5109,6 @@ class VirtualMachine {
|
|
5192
5109
|
c = c.cluster;
|
5193
5110
|
}
|
5194
5111
|
}
|
5195
|
-
b++;
|
5196
5112
|
}
|
5197
5113
|
|
5198
5114
|
// THEN: If cost prices should be inferred, calculate them one step
|
@@ -5202,16 +5118,14 @@ class VirtualMachine {
|
|
5202
5118
|
// Keep track of products for which CP will not be computed correctly
|
5203
5119
|
// due to negative delays.
|
5204
5120
|
MODEL.products_with_negative_delays = {};
|
5205
|
-
b = bb;
|
5206
5121
|
for(let i = 0; i < cbl; i++) {
|
5122
|
+
const b = bb + i;
|
5207
5123
|
if(b <= this.nr_of_time_steps && !MODEL.calculateCostPrices(b)) {
|
5208
5124
|
// NOTE: Issues with cost price calculation beyond simulation
|
5209
5125
|
// period need not be reported unless model is set to ignore this.
|
5210
5126
|
if(!MODEL.ignore_negative_flows) this.logMessage(block,
|
5211
5127
|
`${this.WARNING}(t=${b}) Invalid cost prices due to negative flow(s)`);
|
5212
5128
|
}
|
5213
|
-
// Move on to the next time step of the block.
|
5214
|
-
b++;
|
5215
5129
|
}
|
5216
5130
|
// NOTE: Links with negative delays will not have correct cost
|
5217
5131
|
// prices as these occur in the future. Having calculated (insofar
|
@@ -5229,15 +5143,13 @@ class VirtualMachine {
|
|
5229
5143
|
// @@TO DO: Sort products in order of precedence to avoid that
|
5230
5144
|
// when Q1 --> Q2 the CP of Q2 is computed first, and remains
|
5231
5145
|
// "undefined" while the CP of Q1 can be known.
|
5232
|
-
for(
|
5233
|
-
const p = pwnd[j];
|
5146
|
+
for(const p of pwnd) {
|
5234
5147
|
if(p.is_buffer) addDistinct(p, stocks);
|
5235
5148
|
// Compute total cost price as sum of inflow * unit cost price.
|
5236
5149
|
let tcp = 0,
|
5237
5150
|
taf = 0;
|
5238
|
-
for(
|
5151
|
+
for(const l of p.inputs) {
|
5239
5152
|
const
|
5240
|
-
l = p.inputs[k],
|
5241
5153
|
d = l.actualDelay(t),
|
5242
5154
|
td = t - d;
|
5243
5155
|
// Only compute if t lies within the optimization period.
|
@@ -5280,25 +5192,21 @@ class VirtualMachine {
|
|
5280
5192
|
}
|
5281
5193
|
// NOTE: Stocks require special treatment due to working backwards
|
5282
5194
|
// in time.
|
5283
|
-
for(
|
5284
|
-
const p = stocks[i];
|
5195
|
+
for(const p of stocks) {
|
5285
5196
|
// Get previous stock level and stock price (prior to block start).
|
5286
5197
|
let sl = p.actualLevel(bb - 1),
|
5287
5198
|
psp = p.stock_price[bb - 1] || 0;
|
5288
5199
|
for(let t = bb; t < bb + cbl; t++) {
|
5289
5200
|
// Subtract outflows from stock level.
|
5290
|
-
for(
|
5291
|
-
const
|
5292
|
-
l = p.outputs[k],
|
5293
|
-
af = l.actualFlow(t);
|
5201
|
+
for(const l of p.outputs) {
|
5202
|
+
const af = l.actualFlow(t);
|
5294
5203
|
if(af > VM.NEAR_ZERO) sl -= af;
|
5295
5204
|
}
|
5296
5205
|
// Start with total CP = remaining old stock times old price.
|
5297
5206
|
let tcp = sl * psp;
|
5298
5207
|
// Add inflows to both level and total CP.
|
5299
|
-
for(
|
5208
|
+
for(const l of p.inputs) {
|
5300
5209
|
const
|
5301
|
-
l = p.inputs[k],
|
5302
5210
|
af = l.actualFlow(t),
|
5303
5211
|
d = l.actualDelay(t);
|
5304
5212
|
if(af > VM.NEAR_ZERO) {
|
@@ -5324,8 +5232,8 @@ class VirtualMachine {
|
|
5324
5232
|
}
|
5325
5233
|
|
5326
5234
|
// THEN: Reset all datasets that are outcomes or serve as "formulas".
|
5327
|
-
for(let
|
5328
|
-
const ds = MODEL.datasets[
|
5235
|
+
for(let k in MODEL.datasets) if(MODEL.datasets.hasOwnProperty(k)) {
|
5236
|
+
const ds = MODEL.datasets[k];
|
5329
5237
|
// NOTE: Assume that datasets having modifiers but no data serve as
|
5330
5238
|
// "formulas", i.e., expressions to be calculated AFTER a model run.
|
5331
5239
|
// This will automatically include the equations dataset.
|
@@ -5337,9 +5245,7 @@ class VirtualMachine {
|
|
5337
5245
|
}
|
5338
5246
|
|
5339
5247
|
// THEN: Reset the vectors of all chart variables.
|
5340
|
-
for(
|
5341
|
-
MODEL.charts[i].resetVectors();
|
5342
|
-
}
|
5248
|
+
for(const c of MODEL.charts) c.resetVectors();
|
5343
5249
|
|
5344
5250
|
// Update the chart dialog if it is visible.
|
5345
5251
|
// NOTE: Do NOT do this while an experiment is running, as this may
|
@@ -5353,11 +5259,9 @@ class VirtualMachine {
|
|
5353
5259
|
`Calculating dependent variables took ${this.elapsedTime} seconds.\n`);
|
5354
5260
|
|
5355
5261
|
// FINALLY: Reset the vectors of all note colors.
|
5356
|
-
for(let
|
5357
|
-
const c = MODEL.clusters[
|
5358
|
-
for(
|
5359
|
-
c.notes[i].color.reset();
|
5360
|
-
}
|
5262
|
+
for(let k in MODEL.clusters) if(MODEL.clusters.hasOwnProperty(k)) {
|
5263
|
+
const c = MODEL.clusters[k];
|
5264
|
+
for(const n of c.notes) n.color.reset();
|
5361
5265
|
}
|
5362
5266
|
}
|
5363
5267
|
|
@@ -5387,9 +5291,7 @@ class VirtualMachine {
|
|
5387
5291
|
if(n) return '[' + n + ']';
|
5388
5292
|
return a.constructor.name;
|
5389
5293
|
}
|
5390
|
-
|
5391
|
-
for(let i = 0; i < a.length; i++) l.push(arg(a[i]));
|
5392
|
-
return '(' + l.join(', ') + ')';
|
5294
|
+
return '(' + a.map((x) => arg(x)).join(', ') + ')';
|
5393
5295
|
};
|
5394
5296
|
for(let i = 0; i < this.code.length; i++) {
|
5395
5297
|
const vmi = this.code[i];
|
@@ -5588,15 +5490,13 @@ class VirtualMachine {
|
|
5588
5490
|
// NOTE: penalties must become negative coefficients (solver MAXimizes!)
|
5589
5491
|
let p = -1,
|
5590
5492
|
hsp = 0;
|
5591
|
-
//
|
5592
|
-
//
|
5593
|
-
for(
|
5594
|
-
const svl
|
5595
|
-
let l = svl.length;
|
5596
|
-
for(let j = 0; j < l; j++) {
|
5493
|
+
// Three types of slack variable: market demand (EQ),
|
5494
|
+
// LE and GE bound constraints and highest (data, composite constraints)
|
5495
|
+
for(const svl of this.slack_variables) {
|
5496
|
+
for(const sv of svl) {
|
5597
5497
|
for(let k = 0; k < abl; k++) {
|
5598
5498
|
hsp = this.slack_penalty * p;
|
5599
|
-
this.objective[
|
5499
|
+
this.objective[sv + k*this.cols] = hsp;
|
5600
5500
|
}
|
5601
5501
|
}
|
5602
5502
|
// For the next type of slack, double the penalty
|
@@ -5908,8 +5808,7 @@ class VirtualMachine {
|
|
5908
5808
|
if(this.sos_var_indices.length > 0) {
|
5909
5809
|
this.lines += 'sos\n';
|
5910
5810
|
for(let j = 0; j < abl; j++) {
|
5911
|
-
for(
|
5912
|
-
const svi = this.sos_var_indices[i];
|
5811
|
+
for(const svi of this.sos_var_indices) {
|
5913
5812
|
v_set.length = 0;
|
5914
5813
|
let vi = svi[0] + j * this.cols;
|
5915
5814
|
for(let j = 1; j <= svi[1]; j++) {
|
@@ -5926,11 +5825,11 @@ class VirtualMachine {
|
|
5926
5825
|
|
5927
5826
|
rowToEquation(row, ct, rhs) {
|
5928
5827
|
const eq = [];
|
5929
|
-
for(let
|
5828
|
+
for(let i in row) if (isNumber(i)) {
|
5930
5829
|
const
|
5931
|
-
c = this.sig4Dig(row[
|
5932
|
-
vi =
|
5933
|
-
t = Math.floor(
|
5830
|
+
c = this.sig4Dig(row[i]),
|
5831
|
+
vi = i % this.cols,
|
5832
|
+
t = Math.floor(i / this.cols);
|
5934
5833
|
eq.push(c + ' ' + this.variables[vi][1].displayName + ' ' +
|
5935
5834
|
this.variables[vi][0] + ' [' + t + ']');
|
5936
5835
|
}
|
@@ -6327,18 +6226,17 @@ Solver status = ${json.status}`);
|
|
6327
6226
|
if(keys.length) {
|
6328
6227
|
const msg = ['NOTE: Due to negative link delays, levels for ' +
|
6329
6228
|
pluralS(keys.length, 'variable') + ' are pre-set:'];
|
6330
|
-
for(
|
6229
|
+
for(const k of keys) {
|
6331
6230
|
const
|
6332
|
-
vi = parseInt(
|
6231
|
+
vi = parseInt(k),
|
6333
6232
|
// NOTE: Subtract 1 because variable list is zero-based.
|
6334
6233
|
vbl = this.variables[vi - 1],
|
6335
6234
|
fv = this.variables_to_fixate[vi],
|
6336
6235
|
fvk = Object.keys(fv),
|
6337
6236
|
fvl = [];
|
6338
6237
|
// Add constraints that fixate the levels directly to the tableau.
|
6339
|
-
for(
|
6238
|
+
for(const bt of fvk) {
|
6340
6239
|
const
|
6341
|
-
bt = fvk[i],
|
6342
6240
|
pl = fv[bt],
|
6343
6241
|
k = (bt - 1) * VM.cols + vi,
|
6344
6242
|
row = {};
|
@@ -6358,10 +6256,8 @@ Solver status = ${json.status}`);
|
|
6358
6256
|
if(n) {
|
6359
6257
|
let vlist = '',
|
6360
6258
|
first = 1e20;
|
6361
|
-
for(
|
6362
|
-
const
|
6363
|
-
k = keys[i],
|
6364
|
-
bit = this.bound_issues[k];
|
6259
|
+
for(const k of keys) {
|
6260
|
+
const bit = this.bound_issues[k];
|
6365
6261
|
vlist += `\n - ${k} (t=${listToRange(bit)})`;
|
6366
6262
|
first = Math.min(first, bit[0]);
|
6367
6263
|
}
|
@@ -6497,9 +6393,7 @@ Solver status = ${json.status}`);
|
|
6497
6393
|
} else {
|
6498
6394
|
// Wait no longer, but warn user that data may be incomplete.
|
6499
6395
|
const dsl = [];
|
6500
|
-
for(
|
6501
|
-
dsl.push(MODEL.loading_datasets[i].displayName);
|
6502
|
-
}
|
6396
|
+
for(const ds of MODEL.loading_datasets) dsl.push(ds.displayName);
|
6503
6397
|
UI.warn('Loading of ' + pluralS(dsl.length, 'dataset') + ' (' +
|
6504
6398
|
dsl.join(', ') + ') takes too long');
|
6505
6399
|
}
|
@@ -6703,11 +6597,8 @@ function valueOfIndexVariable(v) {
|
|
6703
6597
|
// Return the value of the iterator index variable for the current
|
6704
6598
|
// experiment.
|
6705
6599
|
if(MODEL.running_experiment) {
|
6706
|
-
const
|
6707
|
-
|
6708
|
-
combi = MODEL.running_experiment.activeCombination;
|
6709
|
-
for(let i = 0; i < combi.length; i++) {
|
6710
|
-
const sel = combi[i] ;
|
6600
|
+
const lead = v + '=';
|
6601
|
+
for(const sel of MODEL.running_experiment.activeCombination) {
|
6711
6602
|
if(sel.startsWith(lead)) return parseInt(sel.substring(2));
|
6712
6603
|
}
|
6713
6604
|
}
|
@@ -7035,15 +6926,16 @@ function VMI_push_method(x, args) {
|
|
7035
6926
|
const
|
7036
6927
|
t = tot[0],
|
7037
6928
|
v = mex.result(t);
|
7038
|
-
// Clear the method object & prefix -- just to be neat.
|
7039
|
-
mex.method_object = null;
|
7040
|
-
mex.method_object_prefix = '';
|
7041
6929
|
// Trace only now that time step t has been computed.
|
7042
6930
|
if(DEBUGGING) {
|
7043
|
-
console.log('push method:',
|
7044
|
-
|
6931
|
+
console.log('push method:',
|
6932
|
+
(mex.method_object ? mex.method_object.displayName : 'PREFIX=' + mex.method_object_prefix),
|
6933
|
+
method.selector, tot[1] + (tot[2] ? ':' + tot[2] : ''), 'value =', VM.sig4Dig(v));
|
7045
6934
|
}
|
7046
6935
|
x.push(v);
|
6936
|
+
// Clear the method object & prefix -- just to be neat.
|
6937
|
+
mex.method_object = null;
|
6938
|
+
mex.method_object_prefix = '';
|
7047
6939
|
}
|
7048
6940
|
|
7049
6941
|
function VMI_push_wildcard_entity(x, args) {
|
@@ -7080,8 +6972,9 @@ function VMI_push_wildcard_entity(x, args) {
|
|
7080
6972
|
MODEL.running_experiment.activeCombination, x.attribute);
|
7081
6973
|
}
|
7082
6974
|
nn = nn.replace('#', x.wildcard_vector_index);
|
7083
|
-
for(
|
7084
|
-
if(
|
6975
|
+
for(const e of el) {
|
6976
|
+
if(e.name === nn) obj = e;
|
6977
|
+
break;
|
7085
6978
|
}
|
7086
6979
|
// If no match, then this indicates a bad reference.
|
7087
6980
|
if(!obj) {
|
@@ -7430,10 +7323,9 @@ function VMI_push_statistic(x, args) {
|
|
7430
7323
|
let obj,
|
7431
7324
|
vlist = [];
|
7432
7325
|
for(let t = t1; t <= t2; t++) {
|
7433
|
-
// Get the list of values
|
7434
|
-
// NOTE:
|
7435
|
-
for(
|
7436
|
-
obj = list[i];
|
7326
|
+
// Get the list of values.
|
7327
|
+
// NOTE: Variables may be vectors or expressions.
|
7328
|
+
for(const obj of list) {
|
7437
7329
|
if(Array.isArray(obj)) {
|
7438
7330
|
// Object is a vector
|
7439
7331
|
if(t < obj.length) {
|
@@ -7442,11 +7334,11 @@ function VMI_push_statistic(x, args) {
|
|
7442
7334
|
v = VM.UNDEFINED;
|
7443
7335
|
}
|
7444
7336
|
} else {
|
7445
|
-
// Object is an expression
|
7337
|
+
// Object is an expression.
|
7446
7338
|
v = obj.result(t);
|
7447
7339
|
}
|
7448
7340
|
// Push value unless it is zero and NZ is TRUE, or if it is undefined
|
7449
|
-
// (this will occur when a variable has been deleted)
|
7341
|
+
// (this will occur when a variable has been deleted).
|
7450
7342
|
if(v <= VM.PLUS_INFINITY && (!nz || Math.abs(v) > VM.NEAR_ZERO)) {
|
7451
7343
|
vlist.push(v);
|
7452
7344
|
}
|
@@ -7454,18 +7346,18 @@ function VMI_push_statistic(x, args) {
|
|
7454
7346
|
}
|
7455
7347
|
const
|
7456
7348
|
n = vlist.length,
|
7457
|
-
// NOTE: count is the number of values used in the statistic
|
7349
|
+
// NOTE: count is the number of values used in the statistic.
|
7458
7350
|
count = (nz ? n : list.length * (t2 - t1 + 1));
|
7459
7351
|
if(stat === 'N') {
|
7460
7352
|
x.push(count);
|
7461
7353
|
return;
|
7462
7354
|
}
|
7463
|
-
// If no non-zero values remain, all statistics are zero (as ALL values were zero)
|
7355
|
+
// If no non-zero values remain, all statistics are zero (as ALL values were zero).
|
7464
7356
|
if(n === 0) {
|
7465
7357
|
x.push(0);
|
7466
7358
|
return;
|
7467
7359
|
}
|
7468
|
-
// Check which statistic, starting with the most likely to be used
|
7360
|
+
// Check which statistic, starting with the most likely to be used.
|
7469
7361
|
if(stat === 'MIN') {
|
7470
7362
|
x.push(Math.min(...vlist));
|
7471
7363
|
return;
|
@@ -7474,7 +7366,7 @@ function VMI_push_statistic(x, args) {
|
|
7474
7366
|
x.push(Math.max(...vlist));
|
7475
7367
|
return;
|
7476
7368
|
}
|
7477
|
-
// For all remaining statistics, the sum must be calculated
|
7369
|
+
// For all remaining statistics, the sum must be calculated.
|
7478
7370
|
let sum = 0;
|
7479
7371
|
for(let i = 0; i < n; i++) {
|
7480
7372
|
sum += vlist[i];
|
@@ -7758,6 +7650,21 @@ function VMI_div(x) {
|
|
7758
7650
|
}
|
7759
7651
|
}
|
7760
7652
|
|
7653
|
+
function VMI_div_zero(x) {
|
7654
|
+
// Implements the "robust" division operator A // B.
|
7655
|
+
// Pop the top number B from the stack. If B = 0, retain the new
|
7656
|
+
// top number A; otherwise replace the top by A/B.
|
7657
|
+
const d = x.pop();
|
7658
|
+
if(d !== false) {
|
7659
|
+
if(DEBUGGING) console.log('DIV-ZERO (' + d.join(', ') + ')');
|
7660
|
+
if(Math.abs(d[1]) <= VM.NEAR_ZERO) {
|
7661
|
+
x.retop(d[0]);
|
7662
|
+
} else {
|
7663
|
+
x.retop(d[0] / d[1]);
|
7664
|
+
}
|
7665
|
+
}
|
7666
|
+
}
|
7667
|
+
|
7761
7668
|
function VMI_mod(x) {
|
7762
7669
|
// Perform a "floating point MOD operation" as explained below.
|
7763
7670
|
// Pop the top number on the stack. If zero, push error code #DIV/0!.
|
@@ -8383,8 +8290,8 @@ function VMI_set_bounds(args) {
|
|
8383
8290
|
// For grid elements, bounds must be set on UP and DOWN variables.
|
8384
8291
|
if(vbl.grid) {
|
8385
8292
|
// When considering losses, partition range 0...UB in sections.
|
8386
|
-
const step = (vbl.grid.loss_approximation < 2 ?
|
8387
|
-
u / vbl.grid.loss_approximation);
|
8293
|
+
const step = (MODEL.ignore_power_losses || vbl.grid.loss_approximation < 2 ?
|
8294
|
+
u : u / vbl.grid.loss_approximation);
|
8388
8295
|
VM.upper_bounds[VM.offset + vbl.up_1_var_index] = step;
|
8389
8296
|
VM.upper_bounds[VM.offset + vbl.down_1_var_index] = step;
|
8390
8297
|
if(vbl.grid.loss_approximation > 1) {
|
@@ -8773,16 +8680,14 @@ function VMI_update_grid_process_cash_coefficients(p) {
|
|
8773
8680
|
// VMI_update_cash_coefficient).
|
8774
8681
|
let fn = null,
|
8775
8682
|
tn = null;
|
8776
|
-
for(
|
8777
|
-
const l = p.inputs[i];
|
8683
|
+
for(const l of p.inputs) {
|
8778
8684
|
if(l.multiplier === VM.LM_LEVEL &&
|
8779
8685
|
!MODEL.ignored_entities[l.identifier]) {
|
8780
8686
|
fn = l.from_node;
|
8781
8687
|
break;
|
8782
8688
|
}
|
8783
8689
|
}
|
8784
|
-
for(
|
8785
|
-
const l = p.outputs[i];
|
8690
|
+
for(const l of p.outputs) {
|
8786
8691
|
if(l.multiplier === VM.LM_LEVEL &&
|
8787
8692
|
!MODEL.ignored_entities[l.identifier]) {
|
8788
8693
|
tn = l.to_node;
|
@@ -9090,7 +8995,7 @@ function VMI_add_grid_process_constraints(p) {
|
|
9090
8995
|
// Now the variable index lists all contain 1, 2 or 3 indices,
|
9091
8996
|
// depending on the loss approximation level.
|
9092
8997
|
let ub = p.upper_bound.result(VM.t);
|
9093
|
-
if(ub >= VM.PLUS_INFINITY) {
|
8998
|
+
if(ub >= VM.PLUS_INFINITY || MODEL.ignore_grid_capacity) {
|
9094
8999
|
// When UB = +INF, this is interpreted as "unlimited", which is
|
9095
9000
|
// implemented as 99999 grid power units.
|
9096
9001
|
ub = VM.UNLIMITED_POWER_FLOW;
|
@@ -9142,15 +9047,14 @@ function VMI_add_kirchhoff_constraints(cb) {
|
|
9142
9047
|
// Add Kirchhoff's voltage law constraint for each cycle in `cb`.
|
9143
9048
|
// NOTE: Do not add a constraint for cyles that have been "broken"
|
9144
9049
|
// because one or more of its processes have UB = 0.
|
9145
|
-
for(
|
9146
|
-
const c = cb[i];
|
9050
|
+
for(const c of cb) {
|
9147
9051
|
let not_broken = true;
|
9148
9052
|
VMI_clear_coefficients();
|
9149
|
-
for(
|
9053
|
+
for(const e of c) {
|
9150
9054
|
const
|
9151
|
-
p =
|
9055
|
+
p = e.process,
|
9152
9056
|
x = p.length_in_km * p.grid.reactancePerKm,
|
9153
|
-
o =
|
9057
|
+
o = e.orientation,
|
9154
9058
|
ub = p.upper_bound.result(VM.t);
|
9155
9059
|
if(ub <= VM.NEAR_ZERO) {
|
9156
9060
|
not_broken = false;
|
@@ -9236,9 +9140,7 @@ function VMI_add_bound_line_constraint(args) {
|
|
9236
9140
|
}
|
9237
9141
|
// Add constraint (1):
|
9238
9142
|
VMI_clear_coefficients();
|
9239
|
-
for(
|
9240
|
-
VM.coefficients[w[i]] = 1;
|
9241
|
-
}
|
9143
|
+
for(const wi of w) VM.coefficients[wi] = 1;
|
9242
9144
|
VM.rhs = 1;
|
9243
9145
|
VMI_add_constraint(VM.EQ);
|
9244
9146
|
// Add constraint (2):
|
@@ -9264,9 +9166,9 @@ function VMI_add_bound_line_constraint(args) {
|
|
9264
9166
|
VMI_add_constraint(bl.type);
|
9265
9167
|
// NOTE: SOS variables w[i] have bounds [0, 1], but these have not been
|
9266
9168
|
// set yet.
|
9267
|
-
for(
|
9268
|
-
VM.lower_bounds[
|
9269
|
-
VM.upper_bounds[
|
9169
|
+
for(const wi of w) {
|
9170
|
+
VM.lower_bounds[wi] = 0;
|
9171
|
+
VM.upper_bounds[wi] = 1;
|
9270
9172
|
}
|
9271
9173
|
// NOTE: Some solvers do not support SOS. To ensure that only 2
|
9272
9174
|
// adjacent w[i]-variables can be non-zero (they range from 0 to 1),
|
@@ -9302,9 +9204,7 @@ function VMI_add_bound_line_constraint(args) {
|
|
9302
9204
|
VMI_add_constraint(VM.LE); // w[N] - b[N] <= 0
|
9303
9205
|
// Add last constraint: sum of binaries must be <= 2.
|
9304
9206
|
VMI_clear_coefficients();
|
9305
|
-
for(
|
9306
|
-
VM.coefficients[w[i] + n] = 1;
|
9307
|
-
}
|
9207
|
+
for(const wi of w) VM.coefficients[wi + n] = 1;
|
9308
9208
|
VM.rhs = 2;
|
9309
9209
|
VMI_add_constraint(VM.LE);
|
9310
9210
|
}
|
@@ -9414,7 +9314,7 @@ const
|
|
9414
9314
|
OPERATOR_CHARS = ';?:+-*/%=!<>^|@',
|
9415
9315
|
// Opening bracket, space and single quote indicate a separation
|
9416
9316
|
SEPARATOR_CHARS = PARENTHESES + OPERATOR_CHARS + "[ '",
|
9417
|
-
COMPOUND_OPERATORS = ['!=', '<>', '>=', '<='],
|
9317
|
+
COMPOUND_OPERATORS = ['!=', '<>', '>=', '<=', '//'],
|
9418
9318
|
CONSTANT_SYMBOLS = [
|
9419
9319
|
't', 'rt', 'bt', 'ct', 'b', 'N', 'n', 'l', 'r', 'lr', 'nr', 'x', 'nx',
|
9420
9320
|
'random', 'dt', 'true', 'false', 'pi', 'infinity', '#',
|
@@ -9444,13 +9344,13 @@ const
|
|
9444
9344
|
DYADIC_OPERATORS = [
|
9445
9345
|
';', '?', ':', 'or', 'and',
|
9446
9346
|
'=', '<>', '!=', '>', '<', '>=', '<=',
|
9447
|
-
'@', '+', '-', '*', '/',
|
9347
|
+
'@', '+', '-', '*', '/', '//',
|
9448
9348
|
'%', '^', 'log', '|'],
|
9449
9349
|
DYADIC_CODES = [
|
9450
9350
|
VMI_concat, VMI_if_then, VMI_if_else, VMI_or, VMI_and,
|
9451
9351
|
VMI_eq, VMI_ne, VMI_ne, VMI_gt, VMI_lt, VMI_ge, VMI_le,
|
9452
|
-
VMI_at, VMI_add, VMI_sub, VMI_mul, VMI_div,
|
9453
|
-
VMI_power, VMI_log, VMI_replace_undefined],
|
9352
|
+
VMI_at, VMI_add, VMI_sub, VMI_mul, VMI_div, VMI_div_zero,
|
9353
|
+
VMI_mod, VMI_power, VMI_log, VMI_replace_undefined],
|
9454
9354
|
|
9455
9355
|
// Compiler checks for random codes as they make an expression dynamic
|
9456
9356
|
RANDOM_CODES = [VMI_binomial, VMI_exponential, VMI_normal, VMI_poisson,
|
@@ -9465,7 +9365,10 @@ const
|
|
9465
9365
|
|
9466
9366
|
OPERATORS = DYADIC_OPERATORS.concat(MONADIC_OPERATORS),
|
9467
9367
|
OPERATOR_CODES = DYADIC_CODES.concat(MONADIC_CODES),
|
9468
|
-
PRIORITIES = [1, 2, 2, 3, 4, 5, 5, 5, 5, 5, 5, 5,
|
9368
|
+
PRIORITIES = [1, 2, 2, 3, 4, 5, 5, 5, 5, 5, 5, 5,
|
9369
|
+
// NOTE: The new @ operator has higher priority than comparisons,
|
9370
|
+
// and lower than arithmetic operators.
|
9371
|
+
5.5, 6, 6, 7, 7, 7, 7, 8, 8, 10,
|
9469
9372
|
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
|
9470
9373
|
ACTUAL_SYMBOLS = CONSTANT_SYMBOLS.concat(OPERATORS),
|
9471
9374
|
SYMBOL_CODES = CONSTANT_CODES.concat(OPERATOR_CODES);
|
@@ -9682,7 +9585,7 @@ function VMI_highest_cumulative_consecutive_deviation(x) {
|
|
9682
9585
|
|
9683
9586
|
// NOTE: HCCD is not time-dependent => result is stored in cache
|
9684
9587
|
// As expressions may contain several HCCD operators, create a unique key
|
9685
|
-
// based on its parameters
|
9588
|
+
// based on its parameters.
|
9686
9589
|
const cache_key = ['hccd', e.identifier, a, block_size, first, last].join('_');
|
9687
9590
|
if(x.cache[cache_key]) {
|
9688
9591
|
x.retop(x.cache[cache_key]);
|
@@ -9691,14 +9594,14 @@ function VMI_highest_cumulative_consecutive_deviation(x) {
|
|
9691
9594
|
|
9692
9595
|
if(DEBUGGING) console.log(`*${vmi} for ${name}`);
|
9693
9596
|
|
9694
|
-
// Compute the aggregated vector and sum
|
9597
|
+
// Compute the aggregated vector and sum.
|
9695
9598
|
let sum = 0,
|
9696
9599
|
b = 0,
|
9697
9600
|
n = 0,
|
9698
9601
|
av = [];
|
9699
9602
|
for(let i = first; i <= last; i++) {
|
9700
9603
|
const v = vector[i];
|
9701
|
-
// Handle exceptional values in vector
|
9604
|
+
// Handle exceptional values in vector.
|
9702
9605
|
if(v <= VM.BEYOND_MINUS_INFINITY || v >= VM.BEYOND_PLUS_INFINITY) {
|
9703
9606
|
x.retop(v);
|
9704
9607
|
return;
|
@@ -9711,34 +9614,33 @@ function VMI_highest_cumulative_consecutive_deviation(x) {
|
|
9711
9614
|
b = 0;
|
9712
9615
|
}
|
9713
9616
|
}
|
9714
|
-
// Always push the remaining block sum, even if it is 0
|
9617
|
+
// Always push the remaining block sum, even if it is 0.
|
9715
9618
|
av.push(b);
|
9716
9619
|
// Compute the mean (per block)
|
9717
9620
|
const mean = sum / av.length;
|
9718
9621
|
let hccd = 0,
|
9719
9622
|
positive = av[0] > mean;
|
9720
9623
|
sum = 0;
|
9721
|
-
// Iterate over the aggregated vector
|
9722
|
-
for(
|
9723
|
-
const v = av[i];
|
9624
|
+
// Iterate over the aggregated vector.
|
9625
|
+
for(const v of av) {
|
9724
9626
|
if((positive && v < mean) || (!positive && v > mean)) {
|
9725
9627
|
hccd = Math.max(hccd, Math.abs(sum));
|
9726
9628
|
sum = v;
|
9727
9629
|
positive = !positive;
|
9728
9630
|
} else {
|
9729
|
-
// No sign change => add deviation
|
9631
|
+
// No sign change => add deviation.
|
9730
9632
|
sum += v;
|
9731
9633
|
}
|
9732
9634
|
}
|
9733
9635
|
hccd = Math.max(hccd, Math.abs(sum));
|
9734
|
-
// Store the result in the expression's cache
|
9636
|
+
// Store the result in the expression's cache.
|
9735
9637
|
x.cache[cache_key] = hccd;
|
9736
|
-
// Push the result onto the stack
|
9638
|
+
// Push the result onto the stack.
|
9737
9639
|
x.retop(hccd);
|
9738
9640
|
return;
|
9739
9641
|
}
|
9740
9642
|
}
|
9741
|
-
// Fall-trough indicates error
|
9643
|
+
// Fall-trough indicates error.
|
9742
9644
|
if(DEBUGGING) console.log(vmi + ': invalid parameter(s)\n', d);
|
9743
9645
|
x.retop(VM.PARAMS);
|
9744
9646
|
}
|