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.
Files changed (31) hide show
  1. package/README.md +3 -40
  2. package/package.json +1 -1
  3. package/server.js +19 -157
  4. package/static/index.html +74 -21
  5. package/static/linny-r.css +22 -16
  6. package/static/scripts/iro.min.js +7 -7
  7. package/static/scripts/linny-r-ctrl.js +51 -72
  8. package/static/scripts/linny-r-gui-actor-manager.js +23 -33
  9. package/static/scripts/linny-r-gui-chart-manager.js +50 -45
  10. package/static/scripts/linny-r-gui-constraint-editor.js +6 -10
  11. package/static/scripts/linny-r-gui-controller.js +254 -230
  12. package/static/scripts/linny-r-gui-dataset-manager.js +143 -32
  13. package/static/scripts/linny-r-gui-documentation-manager.js +11 -17
  14. package/static/scripts/linny-r-gui-equation-manager.js +22 -22
  15. package/static/scripts/linny-r-gui-experiment-manager.js +102 -129
  16. package/static/scripts/linny-r-gui-file-manager.js +53 -46
  17. package/static/scripts/linny-r-gui-finder.js +105 -51
  18. package/static/scripts/linny-r-gui-model-autosaver.js +2 -4
  19. package/static/scripts/linny-r-gui-monitor.js +35 -41
  20. package/static/scripts/linny-r-gui-paper.js +42 -70
  21. package/static/scripts/linny-r-gui-power-grid-manager.js +31 -34
  22. package/static/scripts/linny-r-gui-receiver.js +1 -2
  23. package/static/scripts/linny-r-gui-repository-browser.js +44 -46
  24. package/static/scripts/linny-r-gui-scale-unit-manager.js +32 -32
  25. package/static/scripts/linny-r-gui-sensitivity-analysis.js +61 -67
  26. package/static/scripts/linny-r-gui-undo-redo.js +94 -95
  27. package/static/scripts/linny-r-milp.js +20 -24
  28. package/static/scripts/linny-r-model.js +1832 -2248
  29. package/static/scripts/linny-r-utils.js +35 -27
  30. package/static/scripts/linny-r-vm.js +807 -905
  31. 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(let i = 0; i < vl.length; i++) {
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 = vl[i].substring(1, vl[i].length - 1).split(UI.OA_SEPARATOR);
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; if so, process both
258
- e = e.split(UI.LINK_ARROW);
259
- const enl = [];
260
- let n = 0;
261
- for(let j = 0; j < e.length; j++) {
262
- const id = UI.nameToID(e[j]);
263
- if(MODEL.black_box_entities.hasOwnProperty(id)) {
264
- enl.push(MODEL.black_box_entities[id]);
265
- n++;
266
- } else {
267
- enl.push(e[j]);
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
- const s = [];
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(let i = 0; i < matches.length; i++) {
584
+ for(const m of matches) {
578
585
  const
579
- m = matches[i],
580
- e = m.split('|');
581
- // Let `ao` be attribute + offset (if any) without right bracket.
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(let i = 0; i < MODEL.experiments.length; i++) {
965
- let xri = MODEL.experiments[i].resultIndex(name);
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 = MODEL.experiments[i].resultIndex(name);
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: experiment may still be FALSE, as this will be interpreted
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(let i = 0; i < ewa.length; i++) {
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(let i = 0; i < prefs.length; i++) {
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(let i = 0; i < ee.length; i++) {
1279
+ for(const e of MODEL.entitiesEndingOn(tail, attr)) {
1277
1280
  const
1278
- en = ee[i].displayName,
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: Postpone check whether method will make the expression
1300
- // dynamic to after the expression has been parsed and the exact
1301
- // set of eligible entities and the set of attributes is known.
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 (!=, <>, <=, >=) and if so, append
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: distinguish between run length N and block length n
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
- this.error = `Invalid symbol "${v}"`;
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(let i = 0; i < this.entity_letters.length; i++) {
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(let j = 0; j < ac.length; j++) {
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(let i = 0; i < csl; i++) {
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(let i = 0; i < r.block_messages.length; i++) {
2945
- const
2946
- bm = r.block_messages[i],
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
- let i, j, k, l, vi, p, c, lbx, ubx;
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(i = 0; i < actor_keys.length; i++) {
3376
- const a = MODEL.actors[actor_keys[i]];
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(i = 0; i < process_keys.length; i++) {
3383
- k = process_keys[i];
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(i = 0; i < product_keys.length; i++) {
3391
- k = product_keys[i];
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(i = 0; i < constraint_keys.length; i++) {
3416
- k = constraint_keys[i];
3417
- if(!MODEL.ignored_entities[k]) {
3418
- c = MODEL.constraints[k];
3419
- for(l = 0; l < c.bound_lines.length; l++) {
3420
- const bl = c.bound_lines[l];
3421
- bl.sos_var_indices = [];
3422
- if(bl.constrainsY) {
3423
- // Define SOS2 variables w[i] (plus associated binaries if
3424
- // solver does not support special ordered sets).
3425
- // NOTE: `addVariable` will add as many as there are points!
3426
- bl.first_sos_var_index = this.addVariable('W1', bl);
3427
- if(this.diagnose && !c.no_slack) {
3428
- // Define the slack variable(s) for bound line constraints.
3429
- // NOTE: Category [2] means: highest slack penalty.
3430
- if(bl.type !== VM.GE) {
3431
- bl.LE_slack_var_index = this.addVariable('CLE', bl);
3432
- this.slack_variables[2].push(bl.LE_slack_var_index);
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(i = 0; i < process_keys.length; i++) {
3449
- k = process_keys[i];
3450
- p = MODEL.processes[k];
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(i = 0; i < product_keys.length; i++) {
3465
- k = product_keys[i];
3466
- p = MODEL.products[k];
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(i = 0; i < actor_keys.length; i++) {
3489
- const a = MODEL.actors[actor_keys[i]];
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(i = 0; i < process_keys.length; i++) {
3506
- k = process_keys[i];
3507
- if(!MODEL.ignored_entities[k]) {
3508
- p = MODEL.processes[k];
3509
- lbx = p.lower_bound;
3510
- // NOTE: If UB = LB, set UB to LB only if LB is defined,
3511
- // because LB expressions default to -INF while UB expressions
3512
- // default to +INF.
3513
- ubx = (!p.grid && p.equal_bounds && lbx.defined ? lbx : p.upper_bound);
3514
- if(lbx.isStatic) lbx = lbx.result(0);
3515
- if(ubx.isStatic) {
3516
- ubx = ubx.result(0);
3517
- if(p.grid) lbx = -ubx;
3518
- } else if (p.grid) {
3519
- // When UB is dynamic, pass NULL as LB; the VM instruction will
3520
- // interpret this as "LB = -UB".
3521
- lbx = null;
3522
- }
3523
- // NOTE: When semic_var_index is set, the lower bound must be
3524
- // zero, as the semi-continuous lower bound is implemented with
3525
- // a binary variable.
3526
- if(p.semic_var_index >= 0) lbx = 0;
3527
- // NOTE: Pass TRUE as fourth parameter to indicate that +INF
3528
- // and -INF can be coded as the infinity values used by the
3529
- // solver, rather than the Linny-R values used to detect
3530
- // unbounded problems.
3531
- this.code.push([VMI_set_bounds, [p.level_var_index, lbx, ubx, true]]);
3532
- // Add level variable index to "fixed" list for specified rounds.
3533
- const rf = p.actor.round_flags;
3534
- if(rf != 0) {
3535
- // Note: 32-bit integer `b` is used for bit-wise AND
3536
- let b = 1;
3537
- for(j = 0; j < MODEL.rounds; j++) {
3538
- if((rf & b) != 0) {
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(i = 0; i < product_keys.length; i++) {
3550
- k = product_keys[i];
3551
- if(!MODEL.ignored_entities[k]) {
3552
- p = MODEL.products[k];
3553
- // Get index of variable that is constrained by LB and UB.
3554
- vi = p.level_var_index;
3555
- if(p.no_slack || !this.diagnose) {
3556
- // If no slack, the bound constraints can be set on the
3557
- // variables themselves.
3558
- lbx = p.lower_bound;
3559
- // NOTE: If UB = LB, set UB to LB only if LB is defined,
3560
- // because LB expressions default to -INF while UB expressions
3561
- // default to + INF.
3562
- ubx = (p.equal_bounds && lbx.defined ? lbx : p.upper_bound);
3563
- if(lbx.isStatic) lbx = lbx.result(0);
3564
- if(ubx.isStatic) ubx = ubx.result(0);
3565
- this.code.push([VMI_set_bounds, [vi, lbx, ubx]]);
3566
- } else {
3567
- // Otherwise, set bounds of stock variable to -INF and +INF,
3568
- // as product constraints will be added later on.
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(let ai = 0; ai < actor_keys.length; ai++) {
3614
- const a = MODEL.actors[actor_keys[ai]];
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(i = 0; i < process_keys.length; i++) {
3618
- k = process_keys[i];
3619
- if(!MODEL.ignored_entities[k]) {
3620
- const p = MODEL.processes[k];
3621
- // Only consider processes owned by this actor.
3622
- if(p.actor === a) {
3623
- if(p.grid) {
3624
- // Grid processes are a special case, as they can have a
3625
- // negative level and potentially multiple slopes. Hence a
3626
- // special VM instruction.
3627
- this.code.push([VMI_update_grid_process_cash_coefficients, p]);
3628
- } else {
3629
- // Iterate over links IN, but only consider consumed products
3630
- // having a market price.
3631
- for(j = 0; j < p.inputs.length; j++) {
3632
- l = p.inputs[j];
3633
- if(!MODEL.ignored_entities[l.identifier] &&
3634
- l.from_node.price.defined) {
3635
- if(l.from_node.price.isStatic && l.relative_rate.isStatic) {
3636
- k = l.from_node.price.result(0) * l.relative_rate.result(0);
3637
- // NOTE: VMI_update_cash_coefficient has at least 4 arguments:
3638
- // flow (CONSUME or PRODUCE), type (specifies the number and
3639
- // type of arguments), the level_var_index of the process,
3640
- // and the delay.
3641
- // NOTE: Input links cannot have delay, so then delay = 0.
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
- // NOTE: "throughput", "spinning reserve" and "peak increase"
3684
- // are special cases that send a different parameter list.
3685
- if(l.multiplier === VM.LM_THROUGHPUT) {
3686
- // When throughput is read from process Y, calculation
3687
- // is simple: no delays, so the flow over link `l`
3688
- // equals the (sum of all Ri) times the level of Y
3689
- // times the rate of `l`.
3690
- for(k = 0; k < l.from_node.inputs.length; j++) {
3691
- ll = l.from_node.inputs[k];
3692
- // NOTE: No attempt for efficiency -- assume that
3693
- // price and both rates are dynamic.
3694
- this.code.push([VMI_update_cash_coefficient, [
3695
- VM.PRODUCE, VM.THREE_X, vi, l.flow_delay, tnpx,
3696
- l.relative_rate, ll.relative_rate]]);
3697
- }
3698
- } else if(l.multiplier === VM.LM_SPINNING_RESERVE) {
3699
- // "spinning reserve" equals UB - level if level > 0,
3700
- // and otherwise 0. The cash flow then equals
3701
- // ON/OFF * UB * price * rate MINUS level * price * rate,
3702
- // hence a special instruction type.
3703
- // NOTE: Only the ON/OFF variable determines whether
3704
- // there will be any cash flow, hence it is passed as
3705
- // the primary variable, and the process level as the
3706
- // secondary variable.
3707
- this.code.push([VMI_update_cash_coefficient, [
3708
- VM.PRODUCE, VM.SPIN_RES, p.on_off_var_index,
3709
- l.flow_delay, vi, l.from_node.upper_bound, tnpx,
3710
- l.relative_rate]]);
3711
- } else if(l.multiplier === VM.LM_REMAINING_CAPACITY) {
3712
- // "remaining capacity" equals UB - level. This is a
3713
- // simpler version of "spinning reserve". We signal this
3714
- // by passing -1 as the index of the secondary variable,
3715
- // and the level variable index as the primary variable.
3716
- this.code.push([VMI_update_cash_coefficient, [
3717
- VM.PRODUCE, VM.SPIN_RES, vi, // <-- now as primary
3718
- l.flow_delay, -1, // <-- signal that it is "REM_CAP"
3719
- l.from_node.upper_bound, tnpx, l.relative_rate]]);
3720
- } else if(l.multiplier === VM.LM_PEAK_INC) {
3721
- // NOTE: "peak increase" may be > 0 only in the first
3722
- // time step of the block being optimized, and in the
3723
- // first step of the look-ahead period (if peak rises
3724
- // in that period), and will be 0 in all other time steps.
3725
- // The VM instruction handles this.
3726
- // NOTE: Delay is always 0 for this link flow.
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.PEAK_INC, p.peak_inc_var_index, 0,
3729
- tnpx, l.relative_rate]]);
3730
- } else if(tnpx.isStatic && l.relative_rate.isStatic) {
3731
- // If link rate and product price are static, only add
3732
- // the variable if rate*price is non-zero (and then pass
3733
- // the constant rate*price to the VM instruction.
3734
- k = tnpx.result(0) * l.relative_rate.result(0);
3735
- if(Math.abs(k) > VM.NEAR_ZERO) {
3736
- // Production rate & price are static: pass one constant.
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
- [VM.PRODUCE, VM.ONE_C, vi, l.flow_delay, k]]);
3739
- // When multiplier is Delta, subtract level in previous t
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
- } else {
3749
- // Production rate or price are dynamic: pass two expressions.
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.PRODUCE, VM.TWO_X, vi, l.flow_delay,
3752
- tnpx, l.relative_rate]]);
3753
- // When multiplier is Delta, consume level in previous t.
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 not ignored
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(i = 0; i < process_keys.length; i++) {
3790
- k = process_keys[i];
3791
- if(!MODEL.ignored_entities[k]) {
3792
- p = MODEL.processes[k];
3793
- const a = p.actor;
3794
- if(a.weight.defined) {
3795
- if(a.weight.isStatic) {
3796
- this.code.push([VMI_subtract_const_from_coefficient,
3797
- [p.level_var_index, a.weight.result(0)]]);
3798
- } else {
3799
- this.code.push([VMI_subtract_var_from_coefficient,
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(let ai = 0; ai < actor_keys.length; ai++) {
3809
- const a = MODEL.actors[actor_keys[ai]];
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(i = 0; i < process_keys.length; i++) {
3835
- k = process_keys[i];
3836
- if(!MODEL.ignored_entities[k]) {
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
- let pen, hb;
3856
- for(i = 0; i < product_keys.length; i++) {
3857
- k = product_keys[i];
3858
- if(!MODEL.ignored_entities[k]) {
3859
- p = MODEL.products[k];
3860
- if(p.level_var_index >= 0 && !p.no_slack && this.diagnose) {
3861
- hb = p.hasBounds;
3862
- pen = (p.is_data ? 2 :
3863
- // NOTE: Lowest penalty also for IMPLIED sources and sinks.
3864
- (p.equal_bounds || (!hb && (p.isSourceNode || p.isSinkNode)) ? 0 :
3865
- (hb ? 1 : 2)));
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(i = 0; i < process_keys.length; i++) {
3875
- k = process_keys[i];
3876
- if(!MODEL.ignored_entities[k]) {
3877
- p = MODEL.processes[k];
3878
- const svi = p.semic_var_index;
3879
- if(svi >= 0) {
3880
- const
3881
- vi = p.level_var_index,
3882
- lbx = p.lower_bound,
3883
- ubx = (p.equal_bounds && lbx.defined ? lbx : p.upper_bound);
3884
- // LB*binary - level <= 0
3885
- this.code.push(
3886
- [VMI_clear_coefficients, null],
3887
- [VMI_add_const_to_coefficient, [vi, -1]]
3888
- );
3889
- if(lbx.isStatic) {
3890
- this.code.push([VMI_add_const_to_coefficient,
3891
- [svi, lbx.result(0)]]);
3892
- } else {
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(i = 0; i < process_keys.length; i++) {
3916
- k = process_keys[i];
3917
- if(!MODEL.ignored_entities[k]) {
3918
- p = MODEL.processes[k];
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(let pi = 0; pi < product_keys.length; pi++) {
3931
- k = product_keys[pi];
3932
- if(!MODEL.ignored_entities[k]) {
3933
- p = MODEL.products[k];
3934
- // NOTE: Actor cash flow data products are a special case.
3935
- if(p.name.startsWith('$')) {
3936
- // Get the associated actor entity.
3937
- const parts = p.name.substring(1).split(' ');
3938
- parts.shift();
3939
- const
3940
- aid = UI.nameToID(parts.join(' ')),
3941
- a = MODEL.actorByID(aid);
3942
- if(a) {
3943
- this.code.push([VMI_clear_coefficients, null]);
3944
- // Use actor's cash variable indices w/o weight.
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
- [p.level_var_index, -1, 0]]);
3964
- // NOTE: Pass special constraint type parameter to indicate
3965
- // that this constraint must be scaled by the cash scalar.
3966
- this.code.push([VMI_add_constraint, VM.ACTOR_CASH]);
3967
- } else {
3968
- console.log('ANOMALY: no actor for cash flow product', p.displayName);
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
- // NOTE: constants are not affected by their outgoing data (!) links
3971
- } else if(!p.isConstant) {
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
- // FIRST: add a constraint that "computes" the product stock level
3974
- // set coefficients vector to 0 (NOTE: this also sets RHS to 0)
3975
- this.code.push([VMI_clear_coefficients, null]);
3976
-
3977
- // Add inflow into product P from input nodes
3978
- for(i = 0; i < p.inputs.length; i++) {
3979
- l = p.inputs[i];
3980
- if(!MODEL.ignored_entities[l.identifier]) {
3981
- const fn = l.from_node;
3982
- // If data flow, use the appropriate variable
3983
- if(l.multiplier === VM.LM_POSITIVE) {
3984
- vi = fn.on_off_var_index;
3985
- } else if (l.multiplier === VM.LM_ZERO) {
3986
- vi = fn.is_zero_var_index;
3987
- } else if(l.multiplier === VM.LM_STARTUP) {
3988
- vi = fn.start_up_var_index;
3989
- } else if(l.multiplier === VM.LM_FIRST_COMMIT) {
3990
- vi = fn.first_commit_var_index;
3991
- } else if(l.multiplier === VM.LM_SHUTDOWN) {
3992
- vi = fn.shut_down_var_index;
3993
- } else if(l.multiplier === VM.LM_PEAK_INC) {
3994
- vi = fn.peak_inc_var_index;
3995
- } else {
3996
- vi = fn.level_var_index;
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
- // First check whether the link is a power flow.
3999
- if(l.multiplier === VM.LM_LEVEL && !p.is_data && fn.grid) {
4000
- // If so, pass the grid process to a special VM instruction
4001
- // that will add coefficients that account for losses.
4002
- // NOTES:
4003
- // (1) The second parameter (+1) indicates that the
4004
- // coefficients of the UP flows should be positive
4005
- // and those of the DOWN flows should be negative
4006
- // (because it is a P -> Q link).
4007
- // (2) The rate and delay properties of the link are ignored.
4008
- this.code.push(
4009
- [VMI_add_power_flow_to_coefficients, [fn, 1]]);
4010
- // Then check for throughput links, as these are elaborate.
4011
- } else if(l.multiplier === VM.LM_THROUGHPUT) {
4012
- // Link `l` is Y-->Z and "reads" the total inflow into Y
4013
- // over links Xi-->Y having rate Ri and when Y is a
4014
- // product potentially also delay Di.
4015
- let ll, lfn, lvi;
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 now an expression
4105
- c = l.relative_rate;
4106
- if(l.multiplier === VM.LM_SUM) {
4107
- this.code.push([VMI_add_var_to_weighted_sum_coefficients,
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
- } // END IF not ignored
4132
- } // END FOR all inputs
4133
-
4134
- // Subtract outflow from product P to consuming processes (outputs)
4135
- for(i = 0; i < p.outputs.length; i++) {
4136
- l = p.outputs[i];
4137
- if(!MODEL.ignored_entities[l.identifier]) {
4138
- const tn = l.to_node;
4139
- // NOTE: Only consider outputs to processes; data flows do
4140
- // not subtract from their tail nodes.
4141
- if(tn instanceof Process) {
4142
- if(tn.grid) {
4143
- // If the link is a power flow, pass the grid process to
4144
- // a special VM instruction that will add coefficients that
4145
- // account for losses.
4146
- // NOTES:
4147
- // (1) The second parameter (-1) indicates that the
4148
- // coefficients of the UP flows should be negative
4149
- // and those of the DOWN flows should be positive
4150
- // (because it is a Q -> P link).
4151
- // (2) The rate and delay properties of the link are ignored.
4152
- this.code.push(
4153
- [VMI_add_power_flow_to_coefficients, [tn, -1]]);
4154
- } else {
4155
- const rr = l.relative_rate;
4156
- if(rr.isStatic) {
4157
- this.code.push([VMI_subtract_const_from_coefficient,
4158
- [tn.level_var_index, rr.result(0), l.flow_delay]]);
4159
- } else {
4160
- this.code.push([VMI_subtract_var_from_coefficient,
4161
- [tn.level_var_index, rr, l.flow_delay]]);
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
- // NOTES:
4169
- // (1) for products with storage, set the coefficient for this product's
4170
- // stock IN THE PREVIOUS TIME STEP to 1
4171
- // (2) the VM instruction will subtract the stock level at the end of the
4172
- // previous block from the RHS if t=block_start, or the initial level if t=1
4173
- if(p.is_buffer) {
4174
- this.code.push([VMI_add_const_to_coefficient,
4175
- [p.level_var_index, 1, 1]]); // delay of 1
4176
- }
4177
-
4178
- // Set the coefficient for this product's stock NOW to -1 so that
4179
- // the EQ constraint (having RHS = 0) will effectuate that the
4180
- // stock variable takes on the correct value
4181
- // NOTE: do this only when `p` is NOT data, or `p` has links
4182
- // IN or OUT (meaning: 1 or more coefficients)
4183
- if(!p.is_data || p.inputs.length + p.outputs.length > 0) {
4184
- this.code.push([VMI_add_const_to_coefficient,
4185
- [p.level_var_index, -1]]);
4186
- this.code.push([VMI_add_constraint, VM.EQ]);
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
- // Set the bound constraints on the product stock variable
4191
- this.setBoundConstraints(p);
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(i = 0; i < process_keys.length; i++) {
4288
- k = process_keys[i];
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(i = 0; i < product_keys.length; i++) {
4292
- k = product_keys[i];
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(let i = 0; i < pp_nodes.length; i++) {
4297
- p = pp_nodes[i];
4226
+ for(const p of pp_nodes) {
4298
4227
  if(p.on_off_var_index >= 0) {
4299
- // NOTE: when UB is dynamic, its value may become <= 0, and in such
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: the "add constraints flag" must be reset to TRUE
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: this is independent of the binary variables!
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(i = 0; i < constraint_keys.length; i++) {
4561
- k = constraint_keys[i];
4562
- if(!MODEL.ignored_entities[k]) {
4563
- c = MODEL.constraints[k];
4564
- // Get the two associated nodes.
4565
- const
4566
- x = c.from_node,
4567
- y = c.to_node;
4568
- for(j = 0; j < c.bound_lines.length; j++) {
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
- } // end FOR all constraints
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 i in MODEL.links) if(MODEL.links.hasOwnProperty(i) &&
4597
- !MODEL.ignored_entities[i]) {
4598
- for(let j = this.block_start; j < this.block_start + this.chunk_length; j++) {
4599
- const r = MODEL.links[i].relative_rate.result(j);
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 i in MODEL.constraints) if(MODEL.constraints.hasOwnProperty(i) &&
4610
- !MODEL.ignored_entities[i]) {
4611
- const c = MODEL.constraints[i];
4612
- for(let j = this.block_start; j < this.block_start + this.chunk_length; j++) {
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(j),
4615
- fnub = c.from_node.upper_bound.result(j),
4616
- tnlb = c.to_node.lower_bound.result(j),
4617
- tnub = c.to_node.upper_bound.result(j),
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(let i = 0; i < this.cash_constraints.length; i++) {
4667
- const cc = this.matrix[this.cash_constraints[i]];
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(let i = 0; i < this.actor_cash_constraints.length; i++) {
4685
- const cc = this.matrix[this.actor_cash_constraints[i]];
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 o in MODEL.actors) if(MODEL.actors.hasOwnProperty(o)) {
4763
- const a = MODEL.actors[o];
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 o in MODEL.processes) if(MODEL.processes.hasOwnProperty(o) &&
4795
- !MODEL.ignored_entities[o]) {
4717
+ for(let k in MODEL.processes) if(MODEL.processes.hasOwnProperty(k) &&
4718
+ !MODEL.ignored_entities[k]) {
4796
4719
  const
4797
- p = MODEL.processes[o],
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 o in MODEL.products) if(MODEL.products.hasOwnProperty(o) &&
4835
- !MODEL.ignored_entities[o]) {
4757
+ for(let k in MODEL.products) if(MODEL.products.hasOwnProperty(k) &&
4758
+ !MODEL.ignored_entities[k]) {
4836
4759
  const
4837
- p = MODEL.products[o],
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
- // Index `svt` iterates over types of slack variable (0 - 2).
4889
- for(let svt = 0; svt <= 2; svt++) {
4890
- const
4891
- svl = this.slack_variables[svt],
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: for constraints, add 'UB' or 'LB' to its vector for
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
- const ppc = v[1].productPositionClusters;
4912
- for(let ci = 0; ci < ppc.length; ci++) {
4913
- ppc[ci].usesSlack(b, v[1], v[0]);
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 o in MODEL.processes) if(MODEL.processes.hasOwnProperty(o) &&
4928
- !MODEL.ignored_entities[o]) {
4846
+ for(let k in MODEL.processes) if(MODEL.processes.hasOwnProperty(k) &&
4847
+ !MODEL.ignored_entities[k]) {
4929
4848
  const
4930
- p = MODEL.processes[o],
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(let i = 0; i < list.length; i++) {
4956
- if(list[i] <= VM.MINUS_INFINITY) {
4957
- issue = Math.min(list[i], issue);
4958
- } else if(list[i] >= VM.PLUS_INFINITY) {
4959
- issue = Math.max(list[i], issue);
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 b, bt, p, pl, ld, ci;
4983
- for(let g in MODEL.power_grids) if(MODEL.power_grids.hasOwnProperty(g)) {
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 l in MODEL.links) if(MODEL.links.hasOwnProperty(l) &&
4987
- !MODEL.ignored_entities[l]) {
4988
- l = MODEL.links[l];
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
- ld = l.actualDelay(b);
4997
- bt = b - ld;
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(let j = 0; j < p.inputs.length; j++) {
4998
+ for(const ll of p.inputs) {
5080
4999
  const
5081
- ipl = p.inputs[j].from_node.actualLevel(bt),
5082
- rr = p.inputs[j].relative_rate.result(bt);
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 g in MODEL.power_grids) if(MODEL.power_grids.hasOwnProperty(g)) {
5120
- const pg = MODEL.power_grids[g];
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 o in MODEL.clusters) if(MODEL.clusters.hasOwnProperty(o) &&
5136
- !MODEL.ignored_entities[o]) {
5137
- const c = MODEL.clusters[o];
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 o in MODEL.processes) if(MODEL.processes.hasOwnProperty(o) &&
5144
- !MODEL.ignored_entities[o]) {
5145
- const p = MODEL.processes[o];
5146
- let ci = 0, co = 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(let j = 0; j < p.inputs.length; j++) {
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(let j = 0; j < p.outputs.length; j++) {
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(let j = 0; j < pwnd.length; j++) {
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(let k = 0; k < p.inputs.length; k++) {
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(let i = 0; i < stocks.length; i++) {
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(let k = 0; k < p.outputs.length; k++) {
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(let k = 0; k < p.inputs.length; k++) {
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 o in MODEL.datasets) if(MODEL.datasets.hasOwnProperty(o)) {
5328
- const ds = MODEL.datasets[o];
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(let i = 0; i < MODEL.charts.length; i++) {
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 o in MODEL.clusters) if(MODEL.clusters.hasOwnProperty(o)) {
5357
- const c = MODEL.clusters[o];
5358
- for(let i = 0; i < c.notes.length; i++) {
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
- let l = [];
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
- // Index i iterates over types of slack variable: 0 = market demand (EQ),
5592
- // 1 = LE and GE bound constraints, 2 = highest (data, composite constraints)
5593
- for(let i = 0; i <= 2; i++) {
5594
- const svl = this.slack_variables[i];
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[svl[j] + k*this.cols] = hsp;
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(let i = 0; i < this.sos_var_indices.length; i++) {
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 p in row) if (row.hasOwnProperty(p)) {
5828
+ for(let i in row) if (isNumber(i)) {
5930
5829
  const
5931
- c = this.sig4Dig(row[p]),
5932
- vi = p % this.cols,
5933
- t = Math.floor(p / this.cols);
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(let i = 0; i < keys.length; i++) {
6229
+ for(const k of keys) {
6331
6230
  const
6332
- vi = parseInt(keys[i]),
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(let i = 0; i < fvk.length; i++) {
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(let i = 0; i < n; i++) {
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(let i = 0; i < MODEL.loading_datasets.length; i++) {
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
- lead = v + '=',
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:', obj.displayName, method.selector,
7044
- tot[1] + (tot[2] ? ':' + tot[2] : ''), 'value =', VM.sig4Dig(v));
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(let i = 0; !obj && i < el.length; i++) {
7084
- if(el[i].name === nn) obj = el[i];
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: variables may be vectors or expressions
7435
- for(let i = 0; i < list.length; i++) {
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 ? u :
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(let i = 0; i <= p.inputs.length; i++) {
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(let i = 0; i <= p.outputs.length; i++) {
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(let i = 0; i < cb.length; i++) {
9146
- const c = cb[i];
9050
+ for(const c of cb) {
9147
9051
  let not_broken = true;
9148
9052
  VMI_clear_coefficients();
9149
- for(let j = 0; j < c.length; j++) {
9053
+ for(const e of c) {
9150
9054
  const
9151
- p = c[j].process,
9055
+ p = e.process,
9152
9056
  x = p.length_in_km * p.grid.reactancePerKm,
9153
- o = c[j].orientation,
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(let i = 0; i < n; i++) {
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(let i = 0; i < w.length; i++) {
9268
- VM.lower_bounds[w[i]] = 0;
9269
- VM.upper_bounds[w[i]] = 1;
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(let i = 0; i < n; i++) {
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, VMI_mod,
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, 5.5, 6, 6, 7, 7, 7, 8, 8, 10,
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(let i = 0; i < av.length; i++) {
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
  }