linny-r 2.0.8 → 2.0.10

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 (36) hide show
  1. package/README.md +3 -40
  2. package/package.json +6 -2
  3. package/server.js +19 -157
  4. package/static/images/solve-not-same-changed.png +0 -0
  5. package/static/images/solve-not-same-not-changed.png +0 -0
  6. package/static/images/solve-same-changed.png +0 -0
  7. package/static/images/solve-same-not-changed.png +0 -0
  8. package/static/index.html +137 -20
  9. package/static/linny-r.css +260 -23
  10. package/static/scripts/iro.min.js +7 -7
  11. package/static/scripts/linny-r-ctrl.js +126 -85
  12. package/static/scripts/linny-r-gui-actor-manager.js +23 -33
  13. package/static/scripts/linny-r-gui-chart-manager.js +56 -53
  14. package/static/scripts/linny-r-gui-constraint-editor.js +10 -14
  15. package/static/scripts/linny-r-gui-controller.js +644 -260
  16. package/static/scripts/linny-r-gui-dataset-manager.js +64 -66
  17. package/static/scripts/linny-r-gui-documentation-manager.js +11 -17
  18. package/static/scripts/linny-r-gui-equation-manager.js +22 -22
  19. package/static/scripts/linny-r-gui-experiment-manager.js +124 -141
  20. package/static/scripts/linny-r-gui-expression-editor.js +26 -12
  21. package/static/scripts/linny-r-gui-file-manager.js +42 -48
  22. package/static/scripts/linny-r-gui-finder.js +294 -55
  23. package/static/scripts/linny-r-gui-model-autosaver.js +2 -4
  24. package/static/scripts/linny-r-gui-monitor.js +35 -41
  25. package/static/scripts/linny-r-gui-paper.js +42 -70
  26. package/static/scripts/linny-r-gui-power-grid-manager.js +31 -34
  27. package/static/scripts/linny-r-gui-receiver.js +1 -2
  28. package/static/scripts/linny-r-gui-repository-browser.js +44 -46
  29. package/static/scripts/linny-r-gui-scale-unit-manager.js +32 -32
  30. package/static/scripts/linny-r-gui-sensitivity-analysis.js +61 -67
  31. package/static/scripts/linny-r-gui-undo-redo.js +94 -95
  32. package/static/scripts/linny-r-milp.js +20 -24
  33. package/static/scripts/linny-r-model.js +1932 -2274
  34. package/static/scripts/linny-r-utils.js +27 -27
  35. package/static/scripts/linny-r-vm.js +900 -998
  36. 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 {
@@ -580,12 +581,11 @@ class Expression {
580
581
  if(matches) {
581
582
  // Match is case-insensitive, so check each for matching case of
582
583
  // attribute.
583
- for(let i = 0; i < matches.length; i++) {
584
+ for(const m of matches) {
584
585
  const
585
- m = matches[i],
586
- e = m.split('|');
587
- // Let `ao` be attribute + offset (if any) without right bracket.
588
- 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),
589
589
  // Then also trim offset and spaces.
590
590
  a = ao.split('@')[0].trim();
591
591
  // Check whether `a` (without bracket and without spaces) indeed
@@ -671,30 +671,8 @@ class ExpressionParser {
671
671
  //
672
672
  if(owner) {
673
673
  this.context_number = owner.numberContext;
674
- // NOTE: The owner prefix includes the trailing colon+space.
675
- if(owner instanceof Link || owner instanceof Constraint) {
676
- // For links and constraints, it depends:
677
- const
678
- fn = owner.from_node.displayName,
679
- tn = owner.to_node.displayName;
680
- if(fn.indexOf(UI.PREFIXER) >= 0) {
681
- if(tn.indexOf(UI.PREFIXER) >= 0) {
682
- // If both nodes are prefixed, use the longest prefix that these
683
- // nodes have in common.
684
- this.owner_prefix = UI.sharedPrefix(fn, tn) + UI.PREFIXER;
685
- } else {
686
- // Use the FROM node prefix.
687
- this.owner_prefix = UI.completePrefix(fn);
688
- }
689
- } else if(tn.indexOf(UI.PREFIXER) >= 0) {
690
- // Use the TO node prefix.
691
- this.owner_prefix = UI.completePrefix(tn);
692
- }
693
- } else if(owner === MODEL.equations_dataset) {
694
- this.owner_prefix = UI.completePrefix(attribute);
695
- } else {
696
- this.owner_prefix = UI.completePrefix(owner.displayName);
697
- }
674
+ this.owner_prefix = UI.entityPrefix(
675
+ owner === MODEL.equations_dataset ? attribute : owner.displayName);
698
676
  if(owner instanceof Dataset) {
699
677
  this.dataset = owner;
700
678
  // The attribute (if specified) is a dataset modifier selector.
@@ -848,30 +826,33 @@ class ExpressionParser {
848
826
  }
849
827
  }
850
828
  }
851
- // Run specifier (optional) must be leading and braced
852
- // Specifier format: {method$title|run} where method and title are
853
- // optional -- NOTE: # in title or run is NOT seen as a wildcard
829
+ // Experiment result specifier (optional) must be leading and braced.
830
+ // Specifier format: {method$title|run} where method$ and title| are
831
+ // optional. The run specifier may be a # followed by a run number, or
832
+ // a comma- or space-separated list of selectors.
833
+ // NOTE: # in title or run is NOT seen as a wildcard.
854
834
  if(name.startsWith('{')) {
855
835
  s = name.split('}');
856
836
  if(s.length > 1) {
857
- // Brace pair => interpret it as experiment result reference
837
+ // Brace pair => interpret it as experiment result reference.
858
838
  const x = {
859
839
  x: false, // experiment
860
840
  r: false, // run number
861
841
  v: false, // variable; if parametrized {n: name seg's, p: indices}
862
842
  s: '', // statistic
863
- m: '', // method
843
+ m: '', // method
864
844
  p: false, // periodic
865
845
  nr: false // run number range
866
846
  };
867
- // NOTE: name should then be in the experiment's variable list
847
+ // NOTE: Name should then be in the experiment's variable list.
848
+ // This will be checked later, after validating the run specifier.
868
849
  name = s[1].trim();
869
850
  s = s[0].substring(1);
870
- // Check for scaling method
871
- // NOTE: simply ignore $ unless it indicates a valid method
851
+ // Check for a time scaling method (used only for dataset run results).
852
+ // NOTE: Simply ignore $ unless it indicates a valid method.
872
853
  const msep = s.indexOf('$');
873
854
  if(msep <= 5) {
874
- // Be tolerant as to case
855
+ // Be tolerant as to case.
875
856
  let method = s.substring(0, msep).toUpperCase();
876
857
  if(method.endsWith('P')) {
877
858
  x.p = true;
@@ -879,70 +860,89 @@ class ExpressionParser {
879
860
  }
880
861
  if(['ABS', 'MEAN', 'SUM', 'MAX', ''].indexOf(method) >= 0) {
881
862
  x.m = method;
882
- s = s.substring(msep + 1);
863
+ s = s.substring(msep + 1).trim();
883
864
  }
884
865
  }
885
- s = s.split('#');
886
- let rn = (s.length > 1 ? s[1].trim() : false);
887
- // Experiment specifier may contain modifier selectors.
888
- s = s[0].trim().split(UI.OA_SEPARATOR);
889
- if(s.length > 1) {
890
- // If so, the selector list may indicate the run number.
891
- // NOTE: permit selectors to be separated by various characters.
892
- x.r = s.slice(1).join('|').split(/[\|\,\.\:\;\/\s]+/g);
893
- }
894
- if(rn) {
895
- // NOTE: Special notation for run numbers to permit modelers
896
- // to chart results as if run numbers are on the time axis
897
- // (with a given step size). The chart will be made as usual,
898
- // i.e., plot a point for each time step t, but the value v[t]
899
- // will then stay the same for the time interval that corresponds
900
- // to simulation period length / number of runs.
901
- // NOTE: This will fail to produce a meaningful chart when the
902
- // simulation period is small compared to the number of runs.
903
- if(rn.startsWith('n')) {
904
- // #n may be followed by a range, or this range defaults to
905
- // 0 - last run number. Of this range, the i-th number will
906
- // be used, where i is computes as:
907
- // floor(current time step * number of runs / period length)
908
- const range = rn.substring(1);
909
- // Call rangeToList only to validate the range syntax.
910
- if(rangeToList(range)) {
911
- x.nr = range;
912
- this.is_static = false;
913
- this.log('dynamic because experiment run number range');
914
- } else {
915
- msg = `Invalid experiment run number range "${range}"`;
916
- }
866
+ // Now `s` may still have format title|run specifier.
867
+ let x_title = '',
868
+ run_spec = '';
869
+ s = s.split('|');
870
+ if(s.length > 2) {
871
+ msg = `Experiment result specifier may contain only one "|"`;
872
+ } else {
873
+ if(s.length == 2) {
874
+ run_spec = s.pop().trim();
875
+ x_title = s[0].trim();
876
+ } else {
877
+ // No vertical bar => no title, only the run specifier.
878
+ run_spec = s[0].trim();
879
+ }
880
+ // Run specifier can start with a # sign...
881
+ if(!run_spec.startsWith('#')) {
882
+ // ... and if not, it is assumed to be a list of modifier selectors
883
+ // that will identify (during problem set-up) a specific run.
884
+ // NOTE: Permit selectors to be separated by any combination
885
+ // of commas, semicolons and spaces.
886
+ x.r = run_spec.split(/[\,\;\/\s]+/g);
887
+ // NOTE: The VMI instruction accepts `x.r` to be a list of selectors
888
+ // or an integer number.
917
889
  } else {
918
- // Explicit run number is specified.
919
- const n = parseInt(rn);
920
- if(isNaN(n)) {
921
- msg = `Invalid experiment run number "${rn}"`;
890
+ // If the specifier does start with a #, trim it...
891
+ run_spec = run_spec.substring(1);
892
+ // ... and then
893
+ // NOTE: Special notation for run numbers to permit modelers
894
+ // to chart results as if run numbers are on the time axis
895
+ // (with a given step size). The chart will be made as usual,
896
+ // i.e., plot a point for each time step t, but the value v[t]
897
+ // will then stay the same for the time interval that corresponds
898
+ // to simulation period length / number of runs.
899
+ // NOTE: This will fail to produce a meaningful chart when the
900
+ // simulation period is small compared to the number of runs.
901
+ if(run_spec.startsWith('n')) {
902
+ // #n may be followed by a range, or this range defaults to
903
+ // 0 - last run number. Of this range, the i-th number will
904
+ // be used, where i is computes as:
905
+ // floor(current time step * number of runs / period length)
906
+ const range = run_spec.substring(1);
907
+ // Call rangeToList only to validate the range syntax.
908
+ if(rangeToList(range)) {
909
+ x.nr = range;
910
+ this.is_static = false;
911
+ this.log('dynamic because experiment run number range');
912
+ } else {
913
+ msg = `Invalid experiment run number range "${range}"`;
914
+ }
922
915
  } else {
923
- // Explicit run number overrules selector list.
924
- x.r = n;
916
+ // Explicit run number is specified.
917
+ const n = parseInt(run_spec);
918
+ if(isNaN(n)) {
919
+ msg = `Invalid experiment run number "${run_spec}"`;
920
+ } else {
921
+ // NOTE: Negative run numbers are acceptable.
922
+ x.r = n;
923
+ }
925
924
  }
926
925
  }
927
926
  }
928
- // NOTE: s[0] still holds the experiment title
929
- s = s[0].trim();
930
- if(s) {
931
- // NOTE: title cannot be parametrized with a # wildcard
932
- const n = MODEL.indexOfExperiment(s);
927
+ // NOTE: Experiment title cannot be parametrized with a # wildcard.
928
+ if(x_title) {
929
+ const n = MODEL.indexOfExperiment(x_title);
933
930
  if(n < 0) {
934
- msg = `Unknown experiment "${s}"`;
931
+ msg = `Unknown experiment "${x_title}"`;
935
932
  } else {
936
933
  x.x = MODEL.experiments[n];
937
934
  }
938
935
  }
936
+ // END of code for parsing an experiment result specifier.
937
+ // Now proceed with parsing the variable name.
938
+
939
939
  // Variable name may start with a (case insensitive) statistic
940
- // specifier such as SUM or MEAN
940
+ // specifier such as SUM or MEAN.
941
941
  s = name.split('$');
942
942
  if(s.length > 1) {
943
943
  const stat = s[0].trim().toUpperCase();
944
- // NOTE: simply ignore $ (i.e., consider it as part of the
945
- // variable name) unless it is preceded by a valid statistic
944
+ // NOTE: Simply ignore $ (i.e., consider it as part of the
945
+ // variable name) unless it is preceded by a valid statistic.
946
946
  if(VM.outcome_statistics.indexOf(stat) >= 0) {
947
947
  x.s = stat;
948
948
  name = s[1].trim();
@@ -952,12 +952,12 @@ class ExpressionParser {
952
952
  // prefix should be added.
953
953
  name = UI.colonPrefixedName(name, this.owner_prefix);
954
954
  if(x.x) {
955
- // Look up name in experiment outcomes list
955
+ // Look up name in experiment outcomes list.
956
956
  x.v = x.x.resultIndex(name);
957
957
  if(x.v < 0 && name.indexOf('#') >= 0 &&
958
958
  typeof this.context_number === 'number') {
959
959
  // Variable name may be parametrized with #, but not in
960
- // expressions for wildcard selectors
960
+ // expressions for wildcard selectors.
961
961
  name = name.replace('#', this.context_number);
962
962
  x.v = x.x.resultIndex(name);
963
963
  }
@@ -966,24 +966,24 @@ class ExpressionParser {
966
966
  x.x.displayName, '"'].join('');
967
967
  }
968
968
  } else {
969
- // Check outcome list of ALL experiments
970
- for(let i = 0; i < MODEL.experiments.length; i++) {
971
- 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);
972
972
  if(xri < 0 && name.indexOf('#') >= 0 &&
973
973
  typeof this.context_number === 'number') {
974
974
  // Variable name may be parametrized with #, but not in
975
975
  // expressions for wildcard selectors.
976
976
  name = name.replace('#', this.context_number);
977
- xri = MODEL.experiments[i].resultIndex(name);
977
+ xri = mx.resultIndex(name);
978
978
  }
979
979
  if(xri >= 0) {
980
- // If some match is found, the name specifies a variable
980
+ // If some match is found, the name specifies a variable.
981
981
  x.v = xri;
982
982
  break;
983
983
  }
984
984
  }
985
985
  }
986
- // NOTE: experiment may still be FALSE, as this will be interpreted
986
+ // NOTE: Experiment may still be FALSE, as this will be interpreted
987
987
  // as "use current experiment", but run number should be specified.
988
988
  if(!msg) {
989
989
  if(x.r === false && x.t === false) {
@@ -1106,8 +1106,7 @@ class ExpressionParser {
1106
1106
  // narrowing the selection at run time, based on the expression's
1107
1107
  // wildcard number.
1108
1108
  const wdict = {};
1109
- for(let i = 0; i < ewa.length; i++) {
1110
- const e = ewa[i];
1109
+ for(const e of ewa) {
1111
1110
  if(patternMatch(e.displayName, pat)) {
1112
1111
  const mnr = matchingWildcardNumber(e.displayName, pat);
1113
1112
  // NOTE: Attribute may be a single value, a vector, or an expression.
@@ -1251,8 +1250,7 @@ class ExpressionParser {
1251
1250
  mep = method.expression.eligible_prefixes,
1252
1251
  prefs = Object.keys(mep);
1253
1252
  // NOTE: Prefix keys will always be in lower case.
1254
- for(let i = 0; i < prefs.length; i++) {
1255
- const pref = prefs[i];
1253
+ for(const pref of prefs) {
1256
1254
  if(this.eligible_prefixes === null || this.eligible_prefixes[pref]) {
1257
1255
  ep[pref] = true;
1258
1256
  }
@@ -1277,11 +1275,10 @@ class ExpressionParser {
1277
1275
  // This should not be empty when a method reference is parsed.
1278
1276
  const
1279
1277
  tail = UI.PREFIXER + name.substring(1).trim(),
1280
- ee = MODEL.entitiesEndingOn(tail, attr),
1281
1278
  ep = {};
1282
- for(let i = 0; i < ee.length; i++) {
1279
+ for(const e of MODEL.entitiesEndingOn(tail, attr)) {
1283
1280
  const
1284
- en = ee[i].displayName,
1281
+ en = e.displayName,
1285
1282
  pref = en.substring(0, en.length - tail.length).toLowerCase();
1286
1283
  if(this.eligible_prefixes === null || this.eligible_prefixes[pref]) {
1287
1284
  ep[pref] = true;
@@ -1302,10 +1299,10 @@ class ExpressionParser {
1302
1299
  // NOTE: Some attributes make the method expression level-dependent.
1303
1300
  this.is_level_based = this.is_level_based ||
1304
1301
  VM.level_based_attr.indexOf(attr) >= 0;
1305
- // NOTE: Postpone check whether method will make the expression
1306
- // dynamic to after the expression has been parsed and the exact
1307
- // set of eligible entities and the set of attributes is known.
1308
-
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');
1309
1306
  // Colon-prefixed variables in method expressions are similar to
1310
1307
  // wildcard variables, so the same VM instruction is coded for,
1311
1308
  // except that the entity that is the object of the method will
@@ -1720,11 +1717,11 @@ class ExpressionParser {
1720
1717
  } else {
1721
1718
  v = this.expr.substring(this.pit + 1, i);
1722
1719
  this.pit = i + 1;
1723
- // NOTE: Enclosing quotes are also part of this symbol
1720
+ // NOTE: Enclosing quotes are also part of this symbol.
1724
1721
  this.los = v.length + 2;
1725
1722
  v = UI.cleanName(v);
1726
1723
  if(MODEL.scale_units.hasOwnProperty(v)) {
1727
- // Symbol is a scale unit => use its multiplier as numerical value
1724
+ // Symbol is a scale unit => use its multiplier as numerical value.
1728
1725
  this.sym = MODEL.scale_units[v].multiplier;
1729
1726
  } else {
1730
1727
  this.error = `Unknown scale unit "${v}"`;
@@ -1736,8 +1733,8 @@ class ExpressionParser {
1736
1733
  this.pit++;
1737
1734
  } else if(OPERATOR_CHARS.indexOf(c) >= 0) {
1738
1735
  this.pit++;
1739
- // Check for compound operators (!=, <>, <=, >=) and if so, append
1740
- // the second character
1736
+ // Check for compound operators (!=, <>, <=, >=, //) and if so, append
1737
+ // the second character.
1741
1738
  if(this.pit <= this.eot &&
1742
1739
  COMPOUND_OPERATORS.indexOf(c + this.expr.charAt(this.pit)) >= 0) {
1743
1740
  c += this.expr.charAt(this.pit);
@@ -1745,18 +1742,18 @@ class ExpressionParser {
1745
1742
  }
1746
1743
  this.los = c.length;
1747
1744
  // Instead of the operator symbol, the corresponding VM instruction
1748
- // should be pushed onto the symbol stack
1745
+ // should be pushed onto the symbol stack.
1749
1746
  this.sym = OPERATOR_CODES[OPERATORS.indexOf(c)];
1750
1747
  } else {
1751
1748
  // Take any text up to the next operator, parenthesis,
1752
- // opening bracket, quote or space
1749
+ // opening bracket, quote or space.
1753
1750
  this.los = 0;
1754
1751
  let pl = this.pit + this.los,
1755
1752
  cpl = this.expr.charAt(pl),
1756
1753
  pcpl = '',
1757
1754
  digs = false;
1758
1755
  // NOTE: + and - operators are special case, since they may also
1759
- // 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.
1760
1757
  while(pl <= this.eot && (SEPARATOR_CHARS.indexOf(cpl) < 0 ||
1761
1758
  ('+-'.indexOf(cpl) >= 0 && digs && pcpl.toLowerCase() === 'e'))) {
1762
1759
  digs = digs || '0123456789'.indexOf(cpl) >= 0;
@@ -1770,15 +1767,15 @@ class ExpressionParser {
1770
1767
  this.expr.charAt(this.pit + this.los) === ' ') {
1771
1768
  this.los++;
1772
1769
  }
1773
- // ... but trim spaces from the symbol
1770
+ // ... but trim spaces from the symbol.
1774
1771
  v = this.expr.substring(this.pit, this.pit + this.los).trim();
1775
- // Ignore case
1772
+ // Ignore case.
1776
1773
  l = v.toLowerCase();
1777
1774
  if(l === '#') {
1778
1775
  // # symbolizes the numeric part of a dataset selector, so check
1779
1776
  // whether the expression being parsed is a dataset modifier with
1780
1777
  // a selector that has a numeric wildcard OR whether # can be inferred
1781
- // from the owner
1778
+ // from the owner.
1782
1779
  if(this.selector.indexOf('*') >= 0 ||
1783
1780
  this.selector.indexOf('?') >= 0 ||
1784
1781
  this.owner.numberContext) {
@@ -1797,18 +1794,20 @@ class ExpressionParser {
1797
1794
  if(isNaN(f) || !isFinite(f)) {
1798
1795
  this.error = `Invalid number "${v}"`;
1799
1796
  } else {
1800
- // If a valid number, keep it within the +/- infinity range
1797
+ // If a valid number, keep it within the +/- infinity range.
1801
1798
  this.sym = Math.max(VM.MINUS_INFINITY, Math.min(VM.PLUS_INFINITY, f));
1802
1799
  }
1803
- } else if(MODEL.scale_units.hasOwnProperty(v)) {
1804
- // Symbol is a scale unit => use its multiplier as numerical value
1805
- this.sym = MODEL.scale_units[v].multiplier;
1806
1800
  } else {
1807
- // Symbol does not start with a digit
1808
- // 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.
1809
1803
  i = ACTUAL_SYMBOLS.indexOf(l === 'n' ? v : l);
1810
1804
  if(i < 0) {
1811
- 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
+ }
1812
1811
  } else {
1813
1812
  this.sym = SYMBOL_CODES[i];
1814
1813
  // NOTE: Using time symbols or `random` makes the expression dynamic!
@@ -1820,7 +1819,7 @@ class ExpressionParser {
1820
1819
  // A minus is monadic if at the start of the expression, or NOT preceded
1821
1820
  // by a "constant symbol", a number, or a closing parenthesis `)`.
1822
1821
  // Constant symbols are time 't', block start 'b', block length 'n',
1823
- // look-ahead 'l', 'random', 'true', 'false', 'pi', and 'infinity'
1822
+ // look-ahead 'l', 'random', 'true', 'false', 'pi', and 'infinity'.
1824
1823
  if(DYADIC_CODES.indexOf(this.sym) === DYADIC_OPERATORS.indexOf('-') &&
1825
1824
  (this.prev_sym === null ||
1826
1825
  !(Array.isArray(this.prev_sym) ||
@@ -2051,21 +2050,6 @@ class ExpressionParser {
2051
2050
  this.error = 'Invalid parameter list';
2052
2051
  }
2053
2052
  }
2054
- // When compiling a method, check for all eligible prefixes whether
2055
- // they might make the expression dynamic.
2056
- if(this.is_static && this.eligible_prefixes) {
2057
- const epl = Object.keys(this.eligible_prefixes);
2058
- for(let i = 0; i < epl.length; i++) {
2059
- const ep = epl[i];
2060
- for(let j = 0; j < ep.length; j++) {
2061
- if(ep[j] instanceof Dataset && ep[j].mayBeDynamic) {
2062
- this.is_static = false;
2063
- this.log('dynamic because some modifiers of eligible datasets are dynamic');
2064
- break;
2065
- }
2066
- }
2067
- }
2068
- }
2069
2053
  if(this.TRACE || DEBUGGING) console.log('PARSED', this.ownerName, ':',
2070
2054
  this.expr, this.code);
2071
2055
  }
@@ -2371,14 +2355,10 @@ class VirtualMachine {
2371
2355
  Q: this.product_attr
2372
2356
  };
2373
2357
  this.entity_attribute_names = {};
2374
- for(let i = 0; i < this.entity_letters.length; i++) {
2375
- const
2376
- el = this.entity_letters.charAt(i),
2377
- ac = this.attribute_codes[el];
2358
+ for(const el of this.entity_letters) {
2359
+ const ac = this.attribute_codes[el];
2378
2360
  this.entity_attribute_names[el] = [];
2379
- for(let j = 0; j < ac.length; j++) {
2380
- this.entity_attribute_names[el].push(ac[j]);
2381
- }
2361
+ for(const a of ac) this.entity_attribute_names[el].push(a);
2382
2362
  }
2383
2363
  // Level-based attributes are computed only AFTER optimization.
2384
2364
  this.level_based_attr = ['L', 'CP', 'HCP', 'CF', 'CI', 'CO', 'F', 'A'];
@@ -2886,8 +2866,7 @@ class VirtualMachine {
2886
2866
  const
2887
2867
  vlist = [],
2888
2868
  xlist = [];
2889
- for(let i = 0; i < csl; i++) {
2890
- const x = this.call_stack[i];
2869
+ for(const x of this.call_stack) {
2891
2870
  vlist.push(x.object.displayName + '|' + x.attribute);
2892
2871
  // Trim spaces around all object-attribute separators in the
2893
2872
  // expression as entered by the modeler.
@@ -2900,8 +2879,8 @@ class VirtualMachine {
2900
2879
  // Then iterate upwards over the call stack.
2901
2880
  for(let i = 0; i < vlist.length - 1; i++) {
2902
2881
  // Log the expression, followed by the next computed variable.
2903
- console.log(pad + xlist[i] + '\u279C' + vlist[i+1]);
2904
- // Increase indentation
2882
+ console.log(pad + xlist[i] + '\u279C' + vlist[i + 1]);
2883
+ // Increase indentation.
2905
2884
  pad += ' ';
2906
2885
  }
2907
2886
  // Log the last expression.
@@ -2947,11 +2926,9 @@ class VirtualMachine {
2947
2926
  this.nr_of_blocks = 0;
2948
2927
  this.block_count = 0;
2949
2928
  MONITOR.clearProgressBar();
2950
- for(let i = 0; i < r.block_messages.length; i++) {
2951
- const
2952
- bm = r.block_messages[i],
2953
- err = (bm.messages.indexOf('Solver status = 0') < 0 ||
2954
- 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);
2955
2932
  this.solver_times.push(bm.solver_time);
2956
2933
  this.messages.push(bm.messages);
2957
2934
  this.variables.push(this.no_variables);
@@ -3325,7 +3302,7 @@ class VirtualMachine {
3325
3302
  // NOTE: The setupProblem() function implements the essential idea of
3326
3303
  // Linny-R! It sets up the VM variable list, and then generates VM code
3327
3304
  // that that, when executed, creates the MILP tableau for a chunk.
3328
- let i, j, k, l, vi, p, c, lbx, ubx;
3305
+
3329
3306
  // Reset variable arrays and code array.
3330
3307
  this.variables.length = 0;
3331
3308
  this.chunk_variables.length = 0;
@@ -3338,9 +3315,7 @@ class VirtualMachine {
3338
3315
  this.slack_variables = [[], [], []];
3339
3316
  this.code.length = 0;
3340
3317
  // Initialize fixed variable array: 1 list per round.
3341
- for(i = 0; i < MODEL.rounds; i++) {
3342
- this.fixed_var_indices.push([]);
3343
- }
3318
+ for(let i = 0; i < MODEL.rounds; i++) this.fixed_var_indices.push([]);
3344
3319
 
3345
3320
  // Log if run is performed in "diagnosis" mode.
3346
3321
  if(this.diagnose) {
@@ -3378,24 +3353,22 @@ class VirtualMachine {
3378
3353
 
3379
3354
  // Each actor has a variable to compute its cash in and its cash out.
3380
3355
  const actor_keys = Object.keys(MODEL.actors).sort();
3381
- for(i = 0; i < actor_keys.length; i++) {
3382
- const a = MODEL.actors[actor_keys[i]];
3356
+ for(const k of actor_keys) {
3357
+ const a = MODEL.actors[k];
3383
3358
  a.cash_in_var_index = this.addVariable('CI', a);
3384
3359
  a.cash_out_var_index = this.addVariable('CO', a);
3385
3360
  }
3386
- // Define variable indices for all processes
3361
+ // Define variable indices for all processes.
3387
3362
  const process_keys = Object.keys(MODEL.processes).sort();
3388
- for(i = 0; i < process_keys.length; i++) {
3389
- k = process_keys[i];
3390
- p = MODEL.processes[k];
3363
+ for(const k of process_keys) {
3364
+ const p = MODEL.processes[k];
3391
3365
  this.resetVariableIndices(p);
3392
3366
  if(!MODEL.ignored_entities[k]) this.addNodeVariables(p);
3393
3367
  }
3394
- // Do likewise for all products
3368
+ // Do likewise for all products.
3395
3369
  const product_keys = Object.keys(MODEL.products).sort();
3396
- for(i = 0; i < product_keys.length; i++) {
3397
- k = product_keys[i];
3398
- p = MODEL.products[k];
3370
+ for(const k of product_keys) {
3371
+ const p = MODEL.products[k];
3399
3372
  this.resetVariableIndices(p);
3400
3373
  if(!MODEL.ignored_entities[k]) this.addNodeVariables(p);
3401
3374
  }
@@ -3418,29 +3391,25 @@ class VirtualMachine {
3418
3391
  // NOTE: Slack variables are omitted when the "no slack" property
3419
3392
  // of the constraint is set.
3420
3393
  const constraint_keys = Object.keys(MODEL.constraints).sort();
3421
- for(i = 0; i < constraint_keys.length; i++) {
3422
- k = constraint_keys[i];
3423
- if(!MODEL.ignored_entities[k]) {
3424
- c = MODEL.constraints[k];
3425
- for(l = 0; l < c.bound_lines.length; l++) {
3426
- const bl = c.bound_lines[l];
3427
- bl.sos_var_indices = [];
3428
- if(bl.constrainsY) {
3429
- // Define SOS2 variables w[i] (plus associated binaries if
3430
- // solver does not support special ordered sets).
3431
- // NOTE: `addVariable` will add as many as there are points!
3432
- bl.first_sos_var_index = this.addVariable('W1', bl);
3433
- if(this.diagnose && !c.no_slack) {
3434
- // Define the slack variable(s) for bound line constraints.
3435
- // NOTE: Category [2] means: highest slack penalty.
3436
- if(bl.type !== VM.GE) {
3437
- bl.LE_slack_var_index = this.addVariable('CLE', bl);
3438
- this.slack_variables[2].push(bl.LE_slack_var_index);
3439
- }
3440
- if(bl.type !== VM.LE) {
3441
- bl.GE_slack_var_index = this.addVariable('CGE', bl);
3442
- this.slack_variables[2].push(bl.GE_slack_var_index);
3443
- }
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);
3444
3413
  }
3445
3414
  }
3446
3415
  }
@@ -3451,10 +3420,9 @@ class VirtualMachine {
3451
3420
  // been defined; next step is to add "chunk variables".
3452
3421
  let cvi = 0;
3453
3422
  // Add *two* chunk variables for processes having a peak increase link.
3454
- for(i = 0; i < process_keys.length; i++) {
3455
- k = process_keys[i];
3456
- p = MODEL.processes[k];
3457
- 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) {
3458
3426
  // First variable: "peak increase" for block.
3459
3427
  p.peak_inc_var_index = cvi;
3460
3428
  this.chunk_variables.push(['b-peak', p]);
@@ -3467,10 +3435,9 @@ class VirtualMachine {
3467
3435
  }
3468
3436
  }
3469
3437
  // Do likewise for such products.
3470
- for(i = 0; i < product_keys.length; i++) {
3471
- k = product_keys[i];
3472
- p = MODEL.products[k];
3473
- 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) {
3474
3441
  p.peak_inc_var_index = cvi;
3475
3442
  this.chunk_variables.push(['b-peak', p]);
3476
3443
  cvi++;
@@ -3491,8 +3458,8 @@ class VirtualMachine {
3491
3458
  // However, Linny-R does not prohibit negative bounds on processes, nor
3492
3459
  // negative rates on links. To be consistently permissive, cash IN and
3493
3460
  // cash OUT of all actors are both allowed to become negative.
3494
- for(i = 0; i < actor_keys.length; i++) {
3495
- const a = MODEL.actors[actor_keys[i]];
3461
+ for(const k of actor_keys) {
3462
+ const a = MODEL.actors[k];
3496
3463
  // NOTE: Add fourth parameter TRUE to signal that the SOLVER's
3497
3464
  // infinity constants should be used, as this is likely to be more
3498
3465
  // efficient, while cash flows are inferred properties and will not
@@ -3508,73 +3475,67 @@ class VirtualMachine {
3508
3475
  // NEXT: Define the bounds for all production level variables.
3509
3476
  // NOTE: The VM instructions check dynamically whether the variable
3510
3477
  // index is listed as "fixed" for the round that is being solved.
3511
- for(i = 0; i < process_keys.length; i++) {
3512
- k = process_keys[i];
3513
- if(!MODEL.ignored_entities[k]) {
3514
- p = MODEL.processes[k];
3515
- lbx = p.lower_bound;
3516
- // NOTE: If UB = LB, set UB to LB only if LB is defined,
3517
- // because LB expressions default to -INF while UB expressions
3518
- // default to +INF.
3519
- ubx = (!p.grid && p.equal_bounds && lbx.defined ? lbx : p.upper_bound);
3520
- if(lbx.isStatic) lbx = lbx.result(0);
3521
- if(ubx.isStatic) {
3522
- ubx = ubx.result(0);
3523
- if(p.grid) lbx = -ubx;
3524
- } else if (p.grid) {
3525
- // When UB is dynamic, pass NULL as LB; the VM instruction will
3526
- // interpret this as "LB = -UB".
3527
- lbx = null;
3528
- }
3529
- // NOTE: When semic_var_index is set, the lower bound must be
3530
- // zero, as the semi-continuous lower bound is implemented with
3531
- // a binary variable.
3532
- if(p.semic_var_index >= 0) lbx = 0;
3533
- // NOTE: Pass TRUE as fourth parameter to indicate that +INF
3534
- // and -INF can be coded as the infinity values used by the
3535
- // solver, rather than the Linny-R values used to detect
3536
- // unbounded problems.
3537
- this.code.push([VMI_set_bounds, [p.level_var_index, lbx, ubx, true]]);
3538
- // Add level variable index to "fixed" list for specified rounds.
3539
- const rf = p.actor.round_flags;
3540
- if(rf != 0) {
3541
- // Note: 32-bit integer `b` is used for bit-wise AND
3542
- let b = 1;
3543
- for(j = 0; j < MODEL.rounds; j++) {
3544
- if((rf & b) != 0) {
3545
- this.fixed_var_indices[j][p.level_var_index] = true;
3546
- // @@ TO DO: fix associated binary variables if applicable!
3547
- }
3548
- 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!
3549
3512
  }
3513
+ b *= 2;
3550
3514
  }
3551
3515
  }
3552
3516
  }
3553
3517
 
3554
3518
  // NEXT: Define the bounds for all stock level variables.
3555
- for(i = 0; i < product_keys.length; i++) {
3556
- k = product_keys[i];
3557
- if(!MODEL.ignored_entities[k]) {
3558
- p = MODEL.products[k];
3559
- // Get index of variable that is constrained by LB and UB.
3560
- vi = p.level_var_index;
3561
- if(p.no_slack || !this.diagnose) {
3562
- // If no slack, the bound constraints can be set on the
3563
- // variables themselves.
3564
- lbx = p.lower_bound;
3565
- // NOTE: If UB = LB, set UB to LB only if LB is defined,
3566
- // because LB expressions default to -INF while UB expressions
3567
- // default to + INF.
3568
- ubx = (p.equal_bounds && lbx.defined ? lbx : p.upper_bound);
3569
- if(lbx.isStatic) lbx = lbx.result(0);
3570
- if(ubx.isStatic) ubx = ubx.result(0);
3571
- this.code.push([VMI_set_bounds, [vi, lbx, ubx]]);
3572
- } else {
3573
- // Otherwise, set bounds of stock variable to -INF and +INF,
3574
- // as product constraints will be added later on.
3575
- this.code.push([VMI_set_bounds,
3576
- [vi, VM.MINUS_INFINITY, VM.PLUS_INFINITY]]);
3577
- }
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]]);
3578
3539
  }
3579
3540
  }
3580
3541
 
@@ -3616,158 +3577,150 @@ class VirtualMachine {
3616
3577
  this.no_cash_flows = true;
3617
3578
 
3618
3579
  // Iterate over all actors to add the cash flow computation constraints.
3619
- for(let ai = 0; ai < actor_keys.length; ai++) {
3620
- const a = MODEL.actors[actor_keys[ai]];
3580
+ for(const k of actor_keys) {
3581
+ const a = MODEL.actors[k];
3621
3582
  // NOTE: No need for VMI_clear_coefficients because the cash flow
3622
3583
  // coefficients operate on two special "registers" of the VM.
3623
- for(i = 0; i < process_keys.length; i++) {
3624
- k = process_keys[i];
3625
- if(!MODEL.ignored_entities[k]) {
3626
- const p = MODEL.processes[k];
3627
- // Only consider processes owned by this actor.
3628
- if(p.actor === a) {
3629
- if(p.grid) {
3630
- // Grid processes are a special case, as they can have a
3631
- // negative level and potentially multiple slopes. Hence a
3632
- // special VM instruction.
3633
- this.code.push([VMI_update_grid_process_cash_coefficients, p]);
3634
- } else {
3635
- // Iterate over links IN, but only consider consumed products
3636
- // having a market price.
3637
- for(j = 0; j < p.inputs.length; j++) {
3638
- l = p.inputs[j];
3639
- if(!MODEL.ignored_entities[l.identifier] &&
3640
- l.from_node.price.defined) {
3641
- if(l.from_node.price.isStatic && l.relative_rate.isStatic) {
3642
- k = l.from_node.price.result(0) * l.relative_rate.result(0);
3643
- // NOTE: VMI_update_cash_coefficient has at least 4 arguments:
3644
- // flow (CONSUME or PRODUCE), type (specifies the number and
3645
- // type of arguments), the level_var_index of the process,
3646
- // and the delay.
3647
- // NOTE: Input links cannot have delay, so then delay = 0.
3648
- if(Math.abs(k) > VM.NEAR_ZERO) {
3649
- // Consumption rate & price are static: pass one constant.
3650
- this.code.push([VMI_update_cash_coefficient,
3651
- [VM.CONSUME, VM.ONE_C, p.level_var_index, 0, k]]);
3652
- }
3653
- } else {
3654
- // No further optimization: assume two dynamic expressions.
3655
- this.code.push([VMI_update_cash_coefficient,
3656
- [VM.CONSUME, VM.TWO_X, p.level_var_index, 0,
3657
- l.from_node.price, l.relative_rate]]);
3658
- }
3659
- }
3660
- } // END of FOR ALL input links
3661
- }
3662
- // Now iterate over links OUT, but only consider produced
3663
- // products having a (non-zero) market price.
3664
- // NOTE: Grid processes can have output links to *data* products,
3665
- // so do NOT skip this iteration...
3666
- for(j = 0; j < p.outputs.length; j++) {
3667
- l = p.outputs[j];
3668
- const
3669
- tnpx = l.to_node.price,
3670
- // ... but DO skip links from grid processes to regular products.
3671
- skip = p.grid && !l.to_node.is_data;
3672
- if(!(skip || MODEL.ignored_entities[l.identifier]) && tnpx.defined &&
3673
- !(tnpx.isStatic && Math.abs(tnpx.result(0)) < VM.NEAR_ZERO)) {
3674
- // By default, use the process level as multiplier.
3675
- vi = p.level_var_index;
3676
- // For "binary data links", use the correct binary variable
3677
- // instead of the level.
3678
- if(l.multiplier === VM.LM_STARTUP) {
3679
- vi = p.start_up_var_index;
3680
- } else if(l.multiplier === VM.LM_FIRST_COMMIT) {
3681
- vi = p.first_commit_var_index;
3682
- } else if(l.multiplier === VM.LM_SHUTDOWN) {
3683
- vi = p.shut_down_var_index;
3684
- } else if(l.multiplier === VM.LM_POSITIVE) {
3685
- vi = p.on_off_var_index;
3686
- } else if(l.multiplier === VM.LM_ZERO) {
3687
- 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]]);
3688
3609
  }
3689
- // NOTE: "throughput", "spinning reserve" and "peak increase"
3690
- // are special cases that send a different parameter list.
3691
- if(l.multiplier === VM.LM_THROUGHPUT) {
3692
- // When throughput is read from process Y, calculation
3693
- // is simple: no delays, so the flow over link `l`
3694
- // equals the (sum of all Ri) times the level of Y
3695
- // times the rate of `l`.
3696
- for(k = 0; k < l.from_node.inputs.length; j++) {
3697
- ll = l.from_node.inputs[k];
3698
- // NOTE: No attempt for efficiency -- assume that
3699
- // price and both rates are dynamic.
3700
- this.code.push([VMI_update_cash_coefficient, [
3701
- VM.PRODUCE, VM.THREE_X, vi, l.flow_delay, tnpx,
3702
- l.relative_rate, ll.relative_rate]]);
3703
- }
3704
- } else if(l.multiplier === VM.LM_SPINNING_RESERVE) {
3705
- // "spinning reserve" equals UB - level if level > 0,
3706
- // and otherwise 0. The cash flow then equals
3707
- // ON/OFF * UB * price * rate MINUS level * price * rate,
3708
- // hence a special instruction type.
3709
- // NOTE: Only the ON/OFF variable determines whether
3710
- // there will be any cash flow, hence it is passed as
3711
- // the primary variable, and the process level as the
3712
- // secondary variable.
3713
- this.code.push([VMI_update_cash_coefficient, [
3714
- VM.PRODUCE, VM.SPIN_RES, p.on_off_var_index,
3715
- l.flow_delay, vi, l.from_node.upper_bound, tnpx,
3716
- l.relative_rate]]);
3717
- } else if(l.multiplier === VM.LM_REMAINING_CAPACITY) {
3718
- // "remaining capacity" equals UB - level. This is a
3719
- // simpler version of "spinning reserve". We signal this
3720
- // by passing -1 as the index of the secondary variable,
3721
- // and the level variable index as the primary variable.
3722
- this.code.push([VMI_update_cash_coefficient, [
3723
- VM.PRODUCE, VM.SPIN_RES, vi, // <-- now as primary
3724
- l.flow_delay, -1, // <-- signal that it is "REM_CAP"
3725
- l.from_node.upper_bound, tnpx, l.relative_rate]]);
3726
- } else if(l.multiplier === VM.LM_PEAK_INC) {
3727
- // NOTE: "peak increase" may be > 0 only in the first
3728
- // time step of the block being optimized, and in the
3729
- // first step of the look-ahead period (if peak rises
3730
- // in that period), and will be 0 in all other time steps.
3731
- // The VM instruction handles this.
3732
- // 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.
3733
3654
  this.code.push([VMI_update_cash_coefficient, [
3734
- VM.PRODUCE, VM.PEAK_INC, p.peak_inc_var_index, 0,
3735
- tnpx, l.relative_rate]]);
3736
- } else if(tnpx.isStatic && l.relative_rate.isStatic) {
3737
- // If link rate and product price are static, only add
3738
- // the variable if rate*price is non-zero (and then pass
3739
- // the constant rate*price to the VM instruction.
3740
- k = tnpx.result(0) * l.relative_rate.result(0);
3741
- if(Math.abs(k) > VM.NEAR_ZERO) {
3742
- // 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) {
3743
3703
  this.code.push([VMI_update_cash_coefficient,
3744
- [VM.PRODUCE, VM.ONE_C, vi, l.flow_delay, k]]);
3745
- // When multiplier is Delta, subtract level in previous t
3746
- // (so add 1 to flow delay, and consume, rather than
3747
- // produce).
3748
- if(l.multiplier === VM.LM_INCREASE) {
3749
- this.code.push([VMI_update_cash_coefficient,
3750
- // NOTE: 6th argument = 1 indicates "delay + 1".
3751
- [VM.CONSUME, VM.ONE_C, vi, l.flow_delay, k, 1]]);
3752
- }
3704
+ // NOTE: 6th argument = 1 indicates "delay + 1".
3705
+ [VM.CONSUME, VM.ONE_C, vi, l.flow_delay, c, 1]]);
3753
3706
  }
3754
- } else {
3755
- // 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) {
3756
3715
  this.code.push([VMI_update_cash_coefficient, [
3757
- VM.PRODUCE, VM.TWO_X, vi, l.flow_delay,
3758
- tnpx, l.relative_rate]]);
3759
- // When multiplier is Delta, consume level in previous t.
3760
- if(l.multiplier === VM.LM_INCREASE) {
3761
- this.code.push([VMI_update_cash_coefficient, [
3762
- VM.CONSUME, VM.TWO_X, vi, l.flow_delay,
3763
- // NOTE: Now 7th argument indicates "delay + 1".
3764
- tnpx, l.relative_rate, 1]]);
3765
- }
3716
+ VM.CONSUME, VM.TWO_X, vi, l.flow_delay,
3717
+ // NOTE: Now 7th argument indicates "delay + 1".
3718
+ tnpx, l.relative_rate, 1]]);
3766
3719
  }
3767
3720
  }
3768
3721
  }
3769
3722
  } // END of FOR ALL output links
3770
- } // END of IF process not ignored
3723
+ } // END of IF process "owned" by actor a
3771
3724
  } // END of FOR ALL processes
3772
3725
 
3773
3726
  // Check whether any VMI_update_cash_coefficient instructions have
@@ -3792,27 +3745,25 @@ class VirtualMachine {
3792
3745
  // considered, no cash flows have been detected, the solver should aim
3793
3746
  // for minimal effort, i.e., lowest weighted sum of process levels.
3794
3747
  if(this.no_cash_flows) {
3795
- for(i = 0; i < process_keys.length; i++) {
3796
- k = process_keys[i];
3797
- if(!MODEL.ignored_entities[k]) {
3798
- p = MODEL.processes[k];
3799
- const a = p.actor;
3800
- if(a.weight.defined) {
3801
- if(a.weight.isStatic) {
3802
- this.code.push([VMI_subtract_const_from_coefficient,
3803
- [p.level_var_index, a.weight.result(0)]]);
3804
- } else {
3805
- this.code.push([VMI_subtract_var_from_coefficient,
3806
- [p.level_var_index, a.weight]]);
3807
- }
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]]);
3808
3759
  }
3809
3760
  }
3810
3761
  }
3811
3762
  } else {
3812
3763
  // If cash flows HAVE been detected, use actor weights as coefficients:
3813
3764
  // positive for their cash IN, and negative for their cash OUT
3814
- for(let ai = 0; ai < actor_keys.length; ai++) {
3815
- const a = MODEL.actors[actor_keys[ai]];
3765
+ for(const k of actor_keys) {
3766
+ const a = MODEL.actors[k];
3816
3767
  // Ignore actors with undefined weights (should not occur since
3817
3768
  // default weight = 1)
3818
3769
  if(a.weight.defined) {
@@ -3837,12 +3788,9 @@ class VirtualMachine {
3837
3788
  // been added (by looking at the last VM instruction added to the code)
3838
3789
  if(this.code[this.code.length - 1][0] === VMI_clear_coefficients) {
3839
3790
  // If not, set the coefficients for ALL processes to -1
3840
- for(i = 0; i < process_keys.length; i++) {
3841
- k = process_keys[i];
3842
- if(!MODEL.ignored_entities[k]) {
3843
- this.code.push([VMI_add_const_to_coefficient,
3844
- [MODEL.processes[k].level_var_index, -1]]);
3845
- }
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]]);
3846
3794
  }
3847
3795
  }
3848
3796
 
@@ -3858,345 +3806,323 @@ class VirtualMachine {
3858
3806
  // (2) Slack variables have different penalties: type 0 = market demands,
3859
3807
  // i.e., EQ constraints on stocks, 1 = GE and LE constraints on product
3860
3808
  // levels, 2 = strongest constraints: on data, or set by boundlines.
3861
- let pen, hb;
3862
- for(i = 0; i < product_keys.length; i++) {
3863
- k = product_keys[i];
3864
- if(!MODEL.ignored_entities[k]) {
3865
- p = MODEL.products[k];
3866
- if(p.level_var_index >= 0 && !p.no_slack && this.diagnose) {
3867
- hb = p.hasBounds;
3868
- pen = (p.is_data ? 2 :
3869
- // NOTE: Lowest penalty also for IMPLIED sources and sinks.
3870
- (p.equal_bounds || (!hb && (p.isSourceNode || p.isSinkNode)) ? 0 :
3871
- (hb ? 1 : 2)));
3872
- this.slack_variables[pen].push(
3873
- p.stock_LE_slack_var_index, p.stock_GE_slack_var_index);
3874
- }
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);
3875
3820
  }
3876
3821
  }
3877
3822
 
3878
3823
  // NEXT: Add semi-continuous constraints only if not supported by solver.
3879
3824
  if(!this.noSemiContinuous) {
3880
- for(i = 0; i < process_keys.length; i++) {
3881
- k = process_keys[i];
3882
- if(!MODEL.ignored_entities[k]) {
3883
- p = MODEL.processes[k];
3884
- const svi = p.semic_var_index;
3885
- if(svi >= 0) {
3886
- const
3887
- vi = p.level_var_index,
3888
- lbx = p.lower_bound,
3889
- ubx = (p.equal_bounds && lbx.defined ? lbx : p.upper_bound);
3890
- // LB*binary - level <= 0
3891
- this.code.push(
3892
- [VMI_clear_coefficients, null],
3893
- [VMI_add_const_to_coefficient, [vi, -1]]
3894
- );
3895
- if(lbx.isStatic) {
3896
- this.code.push([VMI_add_const_to_coefficient,
3897
- [svi, lbx.result(0)]]);
3898
- } else {
3899
- this.code.push([VMI_add_var_to_coefficient, [svi, lbx]]);
3900
- }
3901
- this.code.push([VMI_add_constraint, VM.LE]);
3902
- // level - UB*binary <= 0
3903
- this.code.push(
3904
- [VMI_clear_coefficients, null],
3905
- [VMI_add_const_to_coefficient, [vi, 1]]
3906
- );
3907
- if(ubx.isStatic) {
3908
- this.code.push([VMI_subtract_const_from_coefficient,
3909
- [svi, ubx.result(0)]]);
3910
- } else {
3911
- this.code.push([VMI_subtract_var_from_coefficient, [svi, ubx]]);
3912
- }
3913
- 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]]);
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]]);
3914
3856
  }
3857
+ this.code.push([VMI_add_constraint, VM.LE]);
3915
3858
  }
3916
3859
  }
3917
3860
  }
3918
3861
 
3919
3862
  // NEXT: Add constraints for processes representing grid elements.
3920
3863
  if(MODEL.with_power_flow) {
3921
- for(i = 0; i < process_keys.length; i++) {
3922
- k = process_keys[i];
3923
- if(!MODEL.ignored_entities[k]) {
3924
- p = MODEL.processes[k];
3925
- if(p.grid) {
3926
- this.code.push([VMI_add_grid_process_constraints, p]);
3927
- }
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]);
3928
3868
  }
3929
3869
  }
3930
- this.code.push(
3870
+ if(!MODEL.ignore_KVL) this.code.push(
3931
3871
  [VMI_add_kirchhoff_constraints, POWER_GRID_MANAGER.cycle_basis]);
3932
3872
  }
3933
3873
 
3934
3874
  // NEXT: Add product constraints to calculate (and constrain) their stock.
3935
3875
 
3936
- for(let pi = 0; pi < product_keys.length; pi++) {
3937
- k = product_keys[pi];
3938
- if(!MODEL.ignored_entities[k]) {
3939
- p = MODEL.products[k];
3940
- // NOTE: Actor cash flow data products are a special case.
3941
- if(p.name.startsWith('$')) {
3942
- // Get the associated actor entity.
3943
- const parts = p.name.substring(1).split(' ');
3944
- parts.shift();
3945
- const
3946
- aid = UI.nameToID(parts.join(' ')),
3947
- a = MODEL.actorByID(aid);
3948
- if(a) {
3949
- this.code.push([VMI_clear_coefficients, null]);
3950
- // Use actor's cash variable indices w/o weight.
3951
- if(p.name.startsWith('$IN ')) {
3952
- // Add coefficient +1 for cash IN index.
3953
- this.code.push([VMI_add_const_to_coefficient,
3954
- [a.cash_in_var_index, 1, 0]]);
3955
- } else if(p.name.startsWith('$OUT ')) {
3956
- // Add coefficient +1 for cash OUT index.
3957
- this.code.push([VMI_add_const_to_coefficient,
3958
- [a.cash_out_var_index, 1, 0]]);
3959
- } else if(p.name.startsWith('$FLOW ')) {
3960
- // Add coefficient +1 for cash IN index.
3961
- this.code.push([VMI_add_const_to_coefficient,
3962
- [a.cash_in_var_index, 1, 0]]);
3963
- // Add coefficient -1 for cash OUT index.
3964
- this.code.push([VMI_add_const_to_coefficient,
3965
- [a.cash_out_var_index, -1, 0]]);
3966
- }
3967
- // 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.
3968
3891
  this.code.push([VMI_add_const_to_coefficient,
3969
- [p.level_var_index, -1, 0]]);
3970
- // NOTE: Pass special constraint type parameter to indicate
3971
- // that this constraint must be scaled by the cash scalar.
3972
- this.code.push([VMI_add_constraint, VM.ACTOR_CASH]);
3973
- } else {
3974
- 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]]);
3975
3904
  }
3976
- // NOTE: constants are not affected by their outgoing data (!) links
3977
- } 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]);
3978
3920
 
3979
- // FIRST: add a constraint that "computes" the product stock level
3980
- // set coefficients vector to 0 (NOTE: this also sets RHS to 0)
3981
- this.code.push([VMI_clear_coefficients, null]);
3982
-
3983
- // Add inflow into product P from input nodes
3984
- for(i = 0; i < p.inputs.length; i++) {
3985
- l = p.inputs[i];
3986
- if(!MODEL.ignored_entities[l.identifier]) {
3987
- const fn = l.from_node;
3988
- // If data flow, use the appropriate variable
3989
- if(l.multiplier === VM.LM_POSITIVE) {
3990
- vi = fn.on_off_var_index;
3991
- } else if (l.multiplier === VM.LM_ZERO) {
3992
- vi = fn.is_zero_var_index;
3993
- } else if(l.multiplier === VM.LM_STARTUP) {
3994
- vi = fn.start_up_var_index;
3995
- } else if(l.multiplier === VM.LM_FIRST_COMMIT) {
3996
- vi = fn.first_commit_var_index;
3997
- } else if(l.multiplier === VM.LM_SHUTDOWN) {
3998
- vi = fn.shut_down_var_index;
3999
- } else if(l.multiplier === VM.LM_PEAK_INC) {
4000
- vi = fn.peak_inc_var_index;
4001
- } else {
4002
- 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]]);
4003
3965
  }
4004
- // First check whether the link is a power flow.
4005
- if(l.multiplier === VM.LM_LEVEL && !p.is_data && fn.grid) {
4006
- // If so, pass the grid process to a special VM instruction
4007
- // that will add coefficients that account for losses.
4008
- // NOTES:
4009
- // (1) The second parameter (+1) indicates that the
4010
- // coefficients of the UP flows should be positive
4011
- // and those of the DOWN flows should be negative
4012
- // (because it is a P -> Q link).
4013
- // (2) The rate and delay properties of the link are ignored.
4014
- this.code.push(
4015
- [VMI_add_power_flow_to_coefficients, [fn, 1]]);
4016
- // Then check for throughput links, as these are elaborate.
4017
- } else if(l.multiplier === VM.LM_THROUGHPUT) {
4018
- // Link `l` is Y-->Z and "reads" the total inflow into Y
4019
- // over links Xi-->Y having rate Ri and when Y is a
4020
- // product potentially also delay Di.
4021
- let ll, lfn, lvi;
4022
- if(fn instanceof Process) {
4023
- // When throughput is read from process Y, the flow
4024
- // over link `l` equals the (sum of all Ri) times the
4025
- // level of Y times the rate of `l`
4026
- for(j = 0; j < fn.inputs.length; j++) {
4027
- ll = fn.inputs[j];
4028
- this.code.push([VMI_add_throughput_to_coefficient,
4029
- [vi, l.relative_rate, l.flow_delay,
4030
- // Input links of processes have no delay
4031
- ll.relative_rate, 0]]);
4032
- }
4033
- } else {
4034
- // When read from product Y, throughput to be added to
4035
- // Z equals sum of inflows of FROM node Y:
4036
- // Xi --(r2,d2)--> Y --(r1,d1)--> Z
4037
- // so instead of the level of Y (having index vi), use
4038
- // the level of Xi (for each input i of Y)
4039
- for(j = 0; j < fn.inputs.length; j++) {
4040
- ll = fn.inputs[j];
4041
- lfn = ll.from_node;
4042
- // here, too, use the *correct* variable index for Xi!
4043
- if(ll.multiplier === VM.LM_POSITIVE || ll.multiplier === VM.LM_ZERO) {
4044
- lvi = lfn.on_off_var_index;
4045
- } else if(ll.multiplier === VM.LM_STARTUP) {
4046
- lvi = lfn.start_up_var_index;
4047
- } else if(ll.multiplier === VM.LM_FIRST_COMMIT) {
4048
- lvi = lfn.first_commit_var_index;
4049
- } else if(ll.multiplier === VM.LM_SHUTDOWN) {
4050
- lvi = lfn.shut_down_var_index;
4051
- } else {
4052
- lvi = lfn.level_var_index;
4053
- }
4054
- // NOTE: we trade-off efficiency gain during execution
4055
- // against simplicity now by not checking whether rates
4056
- // are static; the VM instruction will be a bit slower
4057
- // as it calls the result(t) method for both rates
4058
- this.code.push([VMI_add_throughput_to_coefficient,
4059
- [lvi, l.relative_rate, l.flow_delay,
4060
- ll.relative_rate, ll.flow_delay]]);
4061
- }
4062
- }
4063
- } else if(l.multiplier === VM.LM_PEAK_INC) {
4064
- // SPECIAL instruction that adds flow only for first t of block
4065
- // NOTE: no delay on this type of link
4066
- this.code.push([VMI_add_peak_increase_at_t_0,
4067
- [vi, l.relative_rate]]);
4068
- } else if(l.multiplier === VM.LM_AVAILABLE_CAPACITY) {
4069
- // The "available capacity" equals UB - level, so subtract
4070
- // UB * rate from RHS, while considering the delay.
4071
- // NOTE: New instruction style that passes pointers to
4072
- // model entities instead of their properties.
4073
- this.code.push([VMI_add_available_capacity, l]);
4074
- } else if(l.relative_rate.isStatic) {
4075
- // Static rates permit simpler VM instructions
4076
- c = l.relative_rate.result(0);
4077
- if(l.multiplier === VM.LM_SUM) {
4078
- this.code.push([VMI_add_const_to_sum_coefficients,
4079
- [vi, c, l.flow_delay]]);
4080
- } else if(l.multiplier === VM.LM_MEAN) {
4081
- this.code.push([VMI_add_const_to_sum_coefficients,
4082
- // NOTE: 4th parameter = 1 indicates "divide c by delay + 1"
4083
- [vi, c, l.flow_delay, 1]]);
4084
- } else if(l.multiplier === VM.LM_SPINNING_RESERVE) {
4085
- // "spinning reserve" equals UB - level if level > 0, or 0
4086
- // so add ON/OFF * UB * rate ...
4087
- const fnub = l.from_node.upper_bound;
4088
- if(fnub.isStatic) {
4089
- this.code.push([VMI_add_const_to_coefficient,
4090
- [fn.on_off_var_index, fnub.result(0) * c, l.flow_delay]]);
4091
- } else {
4092
- // NOTE: constant `c` is passed as 5th parameter
4093
- // (var multiplier) since 4th parameter = 1 indicates "delay + 1"
4094
- this.code.push([VMI_add_var_to_coefficient,
4095
- [fn.on_off_var_index, fnub, l.flow_delay, 0, c]]);
4096
- }
4097
- // ... and subtract level * rate
4098
- this.code.push([VMI_subtract_const_from_coefficient,
4099
- [vi, c, l.flow_delay]]);
4100
- } else {
4101
- this.code.push([VMI_add_const_to_coefficient,
4102
- [vi, c, l.flow_delay]]);
4103
- if(l.multiplier === VM.LM_INCREASE) {
4104
- this.code.push([VMI_subtract_const_from_coefficient,
4105
- // NOTE: 4th argument indicates "delay + 1"
4106
- [vi, c, l.flow_delay, 1]]);
4107
- }
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;
4108
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]]);
4109
4022
  } else {
4110
- // NOTE: `c` is now an expression
4111
- c = l.relative_rate;
4112
- if(l.multiplier === VM.LM_SUM) {
4113
- this.code.push([VMI_add_var_to_weighted_sum_coefficients,
4114
- [vi, c, l.flow_delay]]);
4115
- } else if(l.multiplier === VM.LM_MEAN) {
4116
- this.code.push([VMI_add_var_to_weighted_sum_coefficients,
4117
- [vi, c, l.flow_delay, 1]]);
4118
- } else if(l.multiplier === VM.LM_SPINNING_RESERVE) {
4119
- // "spinning reserve" equals UB - level if level > 0, or 0
4120
- // so add ON/OFF * UB * rate ...
4121
- this.code.push([VMI_add_var_product_to_coefficient,
4122
- [fn.on_off_var_index, l.from_node.upper_bound,
4123
- c, l.flow_delay]]);
4124
- // ... and subtract level * rate
4125
- this.code.push([VMI_subtract_var_from_coefficient,
4126
- [vi, c, l.flow_delay]]);
4127
- } else {
4128
- this.code.push([VMI_add_var_to_coefficient,
4129
- [vi, c, l.flow_delay]]);
4130
- if(l.multiplier === VM.LM_INCREASE) {
4131
- this.code.push([VMI_subtract_var_from_coefficient,
4132
- // NOTE: 4th argument indicates "delay + 1"
4133
- [vi, c, l.flow_delay, 1]]);
4134
- }
4135
- }
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]]);
4136
4027
  }
4137
- } // END IF not ignored
4138
- } // END FOR all inputs
4139
-
4140
- // Subtract outflow from product P to consuming processes (outputs)
4141
- for(i = 0; i < p.outputs.length; i++) {
4142
- l = p.outputs[i];
4143
- if(!MODEL.ignored_entities[l.identifier]) {
4144
- const tn = l.to_node;
4145
- // NOTE: Only consider outputs to processes; data flows do
4146
- // not subtract from their tail nodes.
4147
- if(tn instanceof Process) {
4148
- if(tn.grid) {
4149
- // If the link is a power flow, pass the grid process to
4150
- // a special VM instruction that will add coefficients that
4151
- // account for losses.
4152
- // NOTES:
4153
- // (1) The second parameter (-1) indicates that the
4154
- // coefficients of the UP flows should be negative
4155
- // and those of the DOWN flows should be positive
4156
- // (because it is a Q -> P link).
4157
- // (2) The rate and delay properties of the link are ignored.
4158
- this.code.push(
4159
- [VMI_add_power_flow_to_coefficients, [tn, -1]]);
4160
- } else {
4161
- const rr = l.relative_rate;
4162
- if(rr.isStatic) {
4163
- this.code.push([VMI_subtract_const_from_coefficient,
4164
- [tn.level_var_index, rr.result(0), l.flow_delay]]);
4165
- } else {
4166
- this.code.push([VMI_subtract_var_from_coefficient,
4167
- [tn.level_var_index, rr, l.flow_delay]]);
4168
- }
4169
- }
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]]);
4170
4065
  }
4171
4066
  }
4172
4067
  }
4173
-
4174
- // NOTES:
4175
- // (1) for products with storage, set the coefficient for this product's
4176
- // stock IN THE PREVIOUS TIME STEP to 1
4177
- // (2) the VM instruction will subtract the stock level at the end of the
4178
- // previous block from the RHS if t=block_start, or the initial level if t=1
4179
- if(p.is_buffer) {
4180
- this.code.push([VMI_add_const_to_coefficient,
4181
- [p.level_var_index, 1, 1]]); // delay of 1
4182
- }
4183
-
4184
- // Set the coefficient for this product's stock NOW to -1 so that
4185
- // the EQ constraint (having RHS = 0) will effectuate that the
4186
- // stock variable takes on the correct value
4187
- // NOTE: do this only when `p` is NOT data, or `p` has links
4188
- // IN or OUT (meaning: 1 or more coefficients)
4189
- if(!p.is_data || p.inputs.length + p.outputs.length > 0) {
4190
- this.code.push([VMI_add_const_to_coefficient,
4191
- [p.level_var_index, -1]]);
4192
- 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
+ }
4193
4098
  }
4194
4099
  }
4195
-
4196
- // Set the bound constraints on the product stock variable
4197
- this.setBoundConstraints(p);
4198
- }
4199
- }
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
4200
4126
 
4201
4127
  // NEXT: add constraints that will set values of binary variables
4202
4128
  // NOTE: This is not trivial!
@@ -4290,24 +4216,21 @@ class VirtualMachine {
4290
4216
  */
4291
4217
  // NOTE: As of 20 June 2021, binary attributes of products are also computed.
4292
4218
  const pp_nodes = [];
4293
- for(i = 0; i < process_keys.length; i++) {
4294
- k = process_keys[i];
4295
- 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]);
4296
4221
  }
4297
- for(i = 0; i < product_keys.length; i++) {
4298
- k = product_keys[i];
4299
- 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]);
4300
4224
  }
4301
4225
 
4302
- for(let i = 0; i < pp_nodes.length; i++) {
4303
- p = pp_nodes[i];
4226
+ for(const p of pp_nodes) {
4304
4227
  if(p.on_off_var_index >= 0) {
4305
- // 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
4306
4229
  // cases, the default constraints for computing OO, IZ and SU will fail.
4307
4230
  // To deal with this, the default equations will NOT be set when UB <= 0,
4308
4231
  // while the "exceptional" equations (q.v.) will NOT be set when UB > 0.
4309
4232
  // This can be realized using a special VM instruction:
4310
- ubx = (p.equal_bounds && p.lower_bound.defined && !p.grid ?
4233
+ const ubx = (p.equal_bounds && p.lower_bound.defined && !p.grid ?
4311
4234
  p.lower_bound : p.upper_bound);
4312
4235
  this.code.push([VMI_set_add_constraints_flag, [ubx, '>', 0]]);
4313
4236
  // This instruction ensures that when UB <= 0, the constraints for
@@ -4510,7 +4433,7 @@ class VirtualMachine {
4510
4433
  [VMI_add_constraint, VM.EQ]
4511
4434
  );
4512
4435
  }
4513
- // Add constraints for start-up and first commit only if needed
4436
+ // Add constraints for start-up and first commit only if needed.
4514
4437
  if(p.start_up_var_index >= 0) {
4515
4438
  this.code.push(
4516
4439
  // SU[t] = 0
@@ -4527,7 +4450,7 @@ class VirtualMachine {
4527
4450
  );
4528
4451
  }
4529
4452
  }
4530
- // Add constraint for shut-down only if needed
4453
+ // Add constraint for shut-down only if needed.
4531
4454
  if(p.shut_down_var_index >= 0) {
4532
4455
  this.code.push(
4533
4456
  // SD[t] - OO[t-1] = 0
@@ -4539,21 +4462,22 @@ class VirtualMachine {
4539
4462
  );
4540
4463
  }
4541
4464
 
4542
- // NOTE: the "add constraints flag" must be reset to TRUE
4465
+ // NOTE: The "add constraints flag" must be reset to TRUE.
4543
4466
  this.code.push([VMI_set_add_constraints_flag, true]);
4544
- }
4467
+ } // END IF product has on/off binary variable
4468
+
4545
4469
  // Check whether constraints (n) through (p) need to be added
4546
- // to compute the peak level for a block of time steps
4547
- // 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!
4548
4472
  if(p.peak_inc_var_index >= 0) {
4549
4473
  this.code.push(
4550
4474
  // One special instruction implements this operation, as part
4551
- // of it must be performed only at block time = 0
4475
+ // of it must be performed only at block time = 0.
4552
4476
  [VMI_add_peak_increase_constraints,
4553
4477
  [p.level_var_index, p.peak_inc_var_index]]
4554
4478
  );
4555
4479
  }
4556
- }
4480
+ } // END of FOR all processes and products
4557
4481
 
4558
4482
  // NEXT: Add composite constraints.
4559
4483
  // NOTE: As of version 1.0.10, constraints are implemented using special
@@ -4563,22 +4487,17 @@ class VirtualMachine {
4563
4487
  // - variable indices for the constraining node X, the constrained node Y
4564
4488
  // - expressions for the LB and UB of X and Y
4565
4489
  // - the bound line object, as this provides all further information
4566
- for(i = 0; i < constraint_keys.length; i++) {
4567
- k = constraint_keys[i];
4568
- if(!MODEL.ignored_entities[k]) {
4569
- c = MODEL.constraints[k];
4570
- // Get the two associated nodes.
4571
- const
4572
- x = c.from_node,
4573
- y = c.to_node;
4574
- for(j = 0; j < c.bound_lines.length; j++) {
4575
- this.code.push([VMI_add_bound_line_constraint,
4576
- [x.level_var_index, x.lower_bound, x.upper_bound,
4577
- y.level_var_index, y.lower_bound, y.upper_bound,
4578
- c.bound_lines[j]]]);
4579
- }
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]]);
4580
4499
  }
4581
- } // end FOR all constraints
4500
+ }
4582
4501
 
4583
4502
  MODEL.set_up = true;
4584
4503
  this.logMessage(1,
@@ -4599,10 +4518,10 @@ class VirtualMachine {
4599
4518
  // Slack penalty must exceed the maximum joint utility of all processes
4600
4519
  // Use 1 even if highest link rate < 1
4601
4520
  let high_rate = 1;
4602
- for(let i in MODEL.links) if(MODEL.links.hasOwnProperty(i) &&
4603
- !MODEL.ignored_entities[i]) {
4604
- for(let j = this.block_start; j < this.block_start + this.chunk_length; j++) {
4605
- 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);
4606
4525
  // NOTE: ignore errors and "undefined" (chunk Length may exceed actual block length)
4607
4526
  if(r <= VM.PLUS_INFINITY) {
4608
4527
  high_rate = Math.max(high_rate, Math.abs(r));
@@ -4612,15 +4531,15 @@ class VirtualMachine {
4612
4531
  // Similar to links, composite constraints X-->Y can act as multipliers:
4613
4532
  // since CC map the range (UB - LB) of node X to range (UB - LB) of node Y,
4614
4533
  // the multiplier is rangeY / rangeX:
4615
- for(let i in MODEL.constraints) if(MODEL.constraints.hasOwnProperty(i) &&
4616
- !MODEL.ignored_entities[i]) {
4617
- const c = MODEL.constraints[i];
4618
- 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++) {
4619
4538
  const
4620
- fnlb = c.from_node.lower_bound.result(j),
4621
- fnub = c.from_node.upper_bound.result(j),
4622
- tnlb = c.to_node.lower_bound.result(j),
4623
- 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),
4624
4543
  fnrange = (fnub > fnlb + VM.NEAR_ZERO ? fnub - fnlb : fnub),
4625
4544
  tnrange = (tnub > tnlb + VM.NEAR_ZERO ? tnub - tnlb : tnub),
4626
4545
  // Divisor near 0 => multiplier
@@ -4641,14 +4560,12 @@ class VirtualMachine {
4641
4560
  }
4642
4561
  const m = Math.max(
4643
4562
  Math.abs(this.low_coefficient), Math.abs(this.high_coefficient));
4644
- // Scaling is useful if m is larger than 2
4563
+ // Scaling is useful if m is larger than 2.
4645
4564
  if(m > 2 && m < VM.PLUS_INFINITY) {
4646
- // Use reciprocal because multiplication is faster than division
4565
+ // Use reciprocal because multiplication is faster than division.
4647
4566
  const scalar = 2 / m;
4648
4567
  this.scaling_factor = 0.5 * m;
4649
- for(let i in this.objective) {
4650
- if(Number(i)) this.objective[i] *= scalar;
4651
- }
4568
+ for(let i in this.objective) if(Number(i)) this.objective[i] *= scalar;
4652
4569
  this.low_coefficient *= scalar;
4653
4570
  this.high_coefficient *= scalar;
4654
4571
  } else {
@@ -4669,8 +4586,8 @@ class VirtualMachine {
4669
4586
  // Use reciprocal as multiplier to scale the constraint coefficients.
4670
4587
  const m = 1 / this.cash_scalar;
4671
4588
  let cv;
4672
- for(let i = 0; i < this.cash_constraints.length; i++) {
4673
- const cc = this.matrix[this.cash_constraints[i]];
4589
+ for(const k of this.cash_constraints) {
4590
+ const cc = this.matrix[k];
4674
4591
  for(let ci in cc) if(cc.hasOwnProperty(ci)) {
4675
4592
  if(ci < this.chunk_offset) {
4676
4593
  // NOTE: Subtract 1 as variables array is zero-based.
@@ -4687,8 +4604,8 @@ class VirtualMachine {
4687
4604
  // cash flow, the coefficients of the constraint that equates the
4688
4605
  // product level to the cash flow must be *multiplied* by the cash
4689
4606
  // scalar so that they equal the cash flow in the model's monetary unit.
4690
- for(let i = 0; i < this.actor_cash_constraints.length; i++) {
4691
- const cc = this.matrix[this.actor_cash_constraints[i]];
4607
+ for(const k of this.actor_cash_constraints) {
4608
+ const cc = this.matrix[k];
4692
4609
  for(let ci in cc) if(cc.hasOwnProperty(ci)) {
4693
4610
  if(ci < this.chunk_offset) {
4694
4611
  // NOTE: Subtract 1 as variables array is zero-based.
@@ -4765,8 +4682,8 @@ class VirtualMachine {
4765
4682
  // but since Linny-R permits negative lower bounds on processes, and also
4766
4683
  // negative link rates, cash flows may become negative. If that occurs,
4767
4684
  // the modeler should be warned.
4768
- for(let o in MODEL.actors) if(MODEL.actors.hasOwnProperty(o)) {
4769
- const a = MODEL.actors[o];
4685
+ for(let k in MODEL.actors) if(MODEL.actors.hasOwnProperty(k)) {
4686
+ const a = MODEL.actors[k];
4770
4687
  // NOTE: `b` is the index to be used for the vectors.
4771
4688
  let b = bb;
4772
4689
  // Iterate over all time steps in this block.
@@ -4797,10 +4714,10 @@ class VirtualMachine {
4797
4714
  }
4798
4715
  }
4799
4716
  // Set production levels and start-up moments for all processes.
4800
- for(let o in MODEL.processes) if(MODEL.processes.hasOwnProperty(o) &&
4801
- !MODEL.ignored_entities[o]) {
4717
+ for(let k in MODEL.processes) if(MODEL.processes.hasOwnProperty(k) &&
4718
+ !MODEL.ignored_entities[k]) {
4802
4719
  const
4803
- p = MODEL.processes[o],
4720
+ p = MODEL.processes[k],
4804
4721
  has_OO = (p.on_off_var_index >= 0),
4805
4722
  has_SU = (p.start_up_var_index >= 0),
4806
4723
  has_SD = (p.shut_down_var_index >= 0);
@@ -4837,10 +4754,10 @@ class VirtualMachine {
4837
4754
  }
4838
4755
  }
4839
4756
  // Set stock levels for all products.
4840
- for(let o in MODEL.products) if(MODEL.products.hasOwnProperty(o) &&
4841
- !MODEL.ignored_entities[o]) {
4757
+ for(let k in MODEL.products) if(MODEL.products.hasOwnProperty(k) &&
4758
+ !MODEL.ignored_entities[k]) {
4842
4759
  const
4843
- p = MODEL.products[o],
4760
+ p = MODEL.products[k],
4844
4761
  has_OO = (p.on_off_var_index >= 0),
4845
4762
  has_SU = (p.start_up_var_index >= 0),
4846
4763
  has_SD = (p.shut_down_var_index >= 0);
@@ -4891,32 +4808,28 @@ class VirtualMachine {
4891
4808
  // Iterate over all time steps in this block.
4892
4809
  let j = -1;
4893
4810
  for(let i = 0; i < abl; i++) {
4894
- // Index `svt` iterates over types of slack variable (0 - 2).
4895
- for(let svt = 0; svt <= 2; svt++) {
4896
- const
4897
- svl = this.slack_variables[svt],
4898
- l = svl.length;
4899
- 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) {
4900
4815
  const
4901
- vi = svl[k],
4902
4816
  slack = parseFloat(x[vi + j]),
4903
4817
  absl = Math.abs(slack);
4904
4818
  if(absl > VM.NEAR_ZERO) {
4905
4819
  const v = this.variables[vi - 1];
4906
- // NOTE: for constraints, add 'UB' or 'LB' to its vector for
4820
+ // NOTE: For constraints, add 'UB' or 'LB' to its vector for
4907
4821
  // the time step where slack was used.
4908
- if(v[1] instanceof BoundLine) {
4909
- v[1].constraint.slack_info[b] = v[0];
4910
- }
4822
+ if(v[1] instanceof BoundLine) v[1].constraint.slack_info[b] = v[0];
4911
4823
  if(b <= this.nr_of_time_steps && absl > VM.ON_OFF_THRESHOLD) {
4912
4824
  this.logMessage(block, `${this.WARNING}(t=${b}${round}) ` +
4913
4825
  `${v[1].displayName} ${v[0]} slack = ` +
4914
4826
  // NOTE: TRUE denotes "show tiny values with precision".
4915
4827
  this.sig4Dig(slack, true));
4916
4828
  if(v[1] instanceof Product) {
4917
- const ppc = v[1].productPositionClusters;
4918
- for(let ci = 0; ci < ppc.length; ci++) {
4919
- 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]);
4920
4833
  }
4921
4834
  }
4922
4835
  } else if(MODEL.show_notices) {
@@ -4930,10 +4843,10 @@ class VirtualMachine {
4930
4843
  if(this.diagnose) {
4931
4844
  // Iterate over all processes, and set the "slack use" flag
4932
4845
  // for their cluster so that these clusters will be highlighted.
4933
- for(let o in MODEL.processes) if(MODEL.processes.hasOwnProperty(o) &&
4934
- !MODEL.ignored_entities[o]) {
4846
+ for(let k in MODEL.processes) if(MODEL.processes.hasOwnProperty(k) &&
4847
+ !MODEL.ignored_entities[k]) {
4935
4848
  const
4936
- p = MODEL.processes[o],
4849
+ p = MODEL.processes[k],
4937
4850
  l = p.level[b];
4938
4851
  if(l >= VM.PLUS_INFINITY) {
4939
4852
  this.logMessage(block,
@@ -4958,11 +4871,11 @@ class VirtualMachine {
4958
4871
  // Returns severest exception code or +/- INFINITY in `list`, or the
4959
4872
  // result of the computation that involves the elements of `list`.
4960
4873
  let issue = 0;
4961
- for(let i = 0; i < list.length; i++) {
4962
- if(list[i] <= VM.MINUS_INFINITY) {
4963
- issue = Math.min(list[i], issue);
4964
- } else if(list[i] >= VM.PLUS_INFINITY) {
4965
- 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);
4966
4879
  }
4967
4880
  }
4968
4881
  if(issue) return issue;
@@ -4985,22 +4898,22 @@ class VirtualMachine {
4985
4898
  // Start with an empty list of variables to "fixate" in the next block.
4986
4899
  this.variables_to_fixate = {};
4987
4900
  // FIRST: Calculate the actual flows on links.
4988
- let b, bt, p, pl, ld, ci;
4989
- for(let g in MODEL.power_grids) if(MODEL.power_grids.hasOwnProperty(g)) {
4990
- 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;
4991
4903
  }
4992
- for(let l in MODEL.links) if(MODEL.links.hasOwnProperty(l) &&
4993
- !MODEL.ignored_entities[l]) {
4994
- 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];
4995
4907
  // NOTE: Flow is determined by the process node, or in case
4996
4908
  // of a P -> P data link by the FROM product node.
4997
- p = (l.to_node instanceof Process ? l.to_node : l.from_node);
4998
- b = bb;
4909
+ const p = (l.to_node instanceof Process ? l.to_node : l.from_node);
4999
4910
  // Iterate over all time steps in this chunk.
5000
4911
  for(let i = 0; i < cbl; i++) {
5001
4912
  // NOTE: Flows may have a delay (but will be 0 for grid processes).
5002
- ld = l.actualDelay(b);
5003
- bt = b - ld;
4913
+ const
4914
+ b = bb + i,
4915
+ ld = l.actualDelay(b),
4916
+ bt = b - ld;
5004
4917
  latest_time_step = Math.max(latest_time_step, bt);
5005
4918
  // If delay < 0 AND this results in a block time beyond the
5006
4919
  // block length, this means that the level of the FROM node
@@ -5019,11 +4932,11 @@ class VirtualMachine {
5019
4932
  l.from_node.nonZeroLevel(bt));
5020
4933
  }
5021
4934
  // NOTE: Block index may fall beyond actual chunk length.
5022
- ci = i - ld;
4935
+ const ci = i - ld;
5023
4936
  // NOTE: Use non-zero level here to ignore non-zero values that
5024
4937
  // are very small relative to the bounds on the process
5025
4938
  // (typically values below the non-zero tolerance of the solver).
5026
- pl = p.nonZeroLevel(bt);
4939
+ let pl = p.nonZeroLevel(bt);
5027
4940
  if(l.multiplier === VM.LM_SPINNING_RESERVE) {
5028
4941
  pl = (pl > VM.NEAR_ZERO ? p.upper_bound.result(bt) - pl : 0);
5029
4942
  } else if(l.multiplier === VM.LM_POSITIVE) {
@@ -5082,10 +4995,10 @@ class VirtualMachine {
5082
4995
  // NOTE: calculate throughput on basis of levels and rates,
5083
4996
  // as not all actual flows may have been computed yet
5084
4997
  pl = 0;
5085
- for(let j = 0; j < p.inputs.length; j++) {
4998
+ for(const ll of p.inputs) {
5086
4999
  const
5087
- ipl = p.inputs[j].from_node.actualLevel(bt),
5088
- rr = p.inputs[j].relative_rate.result(bt);
5000
+ ipl = ll.from_node.actualLevel(bt),
5001
+ rr = ll.relative_rate.result(bt);
5089
5002
  pl = this.severestIssue([pl, ipl, rr], pl + ipl * rr);
5090
5003
  }
5091
5004
  } else if(l.multiplier === VM.LM_PEAK_INC) {
@@ -5106,7 +5019,7 @@ class VirtualMachine {
5106
5019
  // For grid processes, rates depend on losses, which depend on
5107
5020
  // the process level, and whether the link is P -> Q or Q -> P.
5108
5021
  rr = 1;
5109
- if(p.grid.loss_approximation > 0 &&
5022
+ if(p.grid.loss_approximation > 0 && !MODEL.ignore_power_losses &&
5110
5023
  ((pl > 0 && p === l.from_node) ||
5111
5024
  (pl < 0 && p === l.to_node))) {
5112
5025
  const alr = p.actualLossRate(bt);
@@ -5116,14 +5029,13 @@ class VirtualMachine {
5116
5029
  }
5117
5030
  const af = this.severestIssue([pl, rr], rr * pl);
5118
5031
  l.actual_flow[b] = (Math.abs(af) > VM.NEAR_ZERO ? af : 0);
5119
- b++;
5120
5032
  }
5121
5033
  }
5122
5034
  // Report power losses per grid, if applicable.
5123
- if(MODEL.with_power_flow) {
5035
+ if(MODEL.with_power_flow && !MODEL.ignore_power_losses) {
5124
5036
  const ll = [];
5125
- for(let g in MODEL.power_grids) if(MODEL.power_grids.hasOwnProperty(g)) {
5126
- 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];
5127
5039
  if(pg.loss_approximation > 0) {
5128
5040
  ll.push(`${pg.name}: ${VM.sig4Dig(pg.total_losses / cbl)} ${pg.power_unit}`);
5129
5041
  }
@@ -5135,26 +5047,26 @@ class VirtualMachine {
5135
5047
  }
5136
5048
 
5137
5049
  // THEN: Calculate cash flows one step at a time because of delays.
5138
- b = bb;
5139
5050
  for(let i = 0; i < cbl; i++) {
5051
+ const b = bb + i;
5140
5052
  // Initialize cumulative cash flows for clusters.
5141
- for(let o in MODEL.clusters) if(MODEL.clusters.hasOwnProperty(o) &&
5142
- !MODEL.ignored_entities[o]) {
5143
- 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];
5144
5056
  c.cash_in[b] = 0;
5145
5057
  c.cash_out[b] = 0;
5146
5058
  c.cash_flow[b] = 0;
5147
5059
  }
5148
5060
  // NOTE: Cash flows ONLY result from processes.
5149
- for(let o in MODEL.processes) if(MODEL.processes.hasOwnProperty(o) &&
5150
- !MODEL.ignored_entities[o]) {
5151
- const p = MODEL.processes[o];
5152
- 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;
5153
5066
  // INPUT links from priced products generate cash OUT...
5154
- for(let j = 0; j < p.inputs.length; j++) {
5067
+ for(const l of p.inputs) {
5155
5068
  // NOTE: Input links do NOT have a delay.
5156
5069
  const
5157
- l = p.inputs[j],
5158
5070
  af = l.actual_flow[b],
5159
5071
  fnp = l.from_node.price;
5160
5072
  if(af > VM.NEAR_ZERO && fnp.defined) {
@@ -5168,10 +5080,9 @@ class VirtualMachine {
5168
5080
  }
5169
5081
  }
5170
5082
  // OUTPUT links to priced products generate cash IN ...
5171
- for(let j = 0; j < p.outputs.length; j++) {
5083
+ for(const l of p.outputs) {
5172
5084
  // NOTE: actualFlows already consider delay!
5173
5085
  const
5174
- l = p.outputs[j],
5175
5086
  af = l.actualFlow(b),
5176
5087
  tnp = l.to_node.price;
5177
5088
  if(af > VM.NEAR_ZERO && tnp.defined) {
@@ -5198,7 +5109,6 @@ class VirtualMachine {
5198
5109
  c = c.cluster;
5199
5110
  }
5200
5111
  }
5201
- b++;
5202
5112
  }
5203
5113
 
5204
5114
  // THEN: If cost prices should be inferred, calculate them one step
@@ -5208,16 +5118,14 @@ class VirtualMachine {
5208
5118
  // Keep track of products for which CP will not be computed correctly
5209
5119
  // due to negative delays.
5210
5120
  MODEL.products_with_negative_delays = {};
5211
- b = bb;
5212
5121
  for(let i = 0; i < cbl; i++) {
5122
+ const b = bb + i;
5213
5123
  if(b <= this.nr_of_time_steps && !MODEL.calculateCostPrices(b)) {
5214
5124
  // NOTE: Issues with cost price calculation beyond simulation
5215
5125
  // period need not be reported unless model is set to ignore this.
5216
5126
  if(!MODEL.ignore_negative_flows) this.logMessage(block,
5217
5127
  `${this.WARNING}(t=${b}) Invalid cost prices due to negative flow(s)`);
5218
5128
  }
5219
- // Move on to the next time step of the block.
5220
- b++;
5221
5129
  }
5222
5130
  // NOTE: Links with negative delays will not have correct cost
5223
5131
  // prices as these occur in the future. Having calculated (insofar
@@ -5235,15 +5143,13 @@ class VirtualMachine {
5235
5143
  // @@TO DO: Sort products in order of precedence to avoid that
5236
5144
  // when Q1 --> Q2 the CP of Q2 is computed first, and remains
5237
5145
  // "undefined" while the CP of Q1 can be known.
5238
- for(let j = 0; j < pwnd.length; j++) {
5239
- const p = pwnd[j];
5146
+ for(const p of pwnd) {
5240
5147
  if(p.is_buffer) addDistinct(p, stocks);
5241
5148
  // Compute total cost price as sum of inflow * unit cost price.
5242
5149
  let tcp = 0,
5243
5150
  taf = 0;
5244
- for(let k = 0; k < p.inputs.length; k++) {
5151
+ for(const l of p.inputs) {
5245
5152
  const
5246
- l = p.inputs[k],
5247
5153
  d = l.actualDelay(t),
5248
5154
  td = t - d;
5249
5155
  // Only compute if t lies within the optimization period.
@@ -5286,25 +5192,21 @@ class VirtualMachine {
5286
5192
  }
5287
5193
  // NOTE: Stocks require special treatment due to working backwards
5288
5194
  // in time.
5289
- for(let i = 0; i < stocks.length; i++) {
5290
- const p = stocks[i];
5195
+ for(const p of stocks) {
5291
5196
  // Get previous stock level and stock price (prior to block start).
5292
5197
  let sl = p.actualLevel(bb - 1),
5293
5198
  psp = p.stock_price[bb - 1] || 0;
5294
5199
  for(let t = bb; t < bb + cbl; t++) {
5295
5200
  // Subtract outflows from stock level.
5296
- for(let k = 0; k < p.outputs.length; k++) {
5297
- const
5298
- l = p.outputs[k],
5299
- af = l.actualFlow(t);
5201
+ for(const l of p.outputs) {
5202
+ const af = l.actualFlow(t);
5300
5203
  if(af > VM.NEAR_ZERO) sl -= af;
5301
5204
  }
5302
5205
  // Start with total CP = remaining old stock times old price.
5303
5206
  let tcp = sl * psp;
5304
5207
  // Add inflows to both level and total CP.
5305
- for(let k = 0; k < p.inputs.length; k++) {
5208
+ for(const l of p.inputs) {
5306
5209
  const
5307
- l = p.inputs[k],
5308
5210
  af = l.actualFlow(t),
5309
5211
  d = l.actualDelay(t);
5310
5212
  if(af > VM.NEAR_ZERO) {
@@ -5330,8 +5232,8 @@ class VirtualMachine {
5330
5232
  }
5331
5233
 
5332
5234
  // THEN: Reset all datasets that are outcomes or serve as "formulas".
5333
- for(let o in MODEL.datasets) if(MODEL.datasets.hasOwnProperty(o)) {
5334
- const ds = MODEL.datasets[o];
5235
+ for(let k in MODEL.datasets) if(MODEL.datasets.hasOwnProperty(k)) {
5236
+ const ds = MODEL.datasets[k];
5335
5237
  // NOTE: Assume that datasets having modifiers but no data serve as
5336
5238
  // "formulas", i.e., expressions to be calculated AFTER a model run.
5337
5239
  // This will automatically include the equations dataset.
@@ -5343,9 +5245,7 @@ class VirtualMachine {
5343
5245
  }
5344
5246
 
5345
5247
  // THEN: Reset the vectors of all chart variables.
5346
- for(let i = 0; i < MODEL.charts.length; i++) {
5347
- MODEL.charts[i].resetVectors();
5348
- }
5248
+ for(const c of MODEL.charts) c.resetVectors();
5349
5249
 
5350
5250
  // Update the chart dialog if it is visible.
5351
5251
  // NOTE: Do NOT do this while an experiment is running, as this may
@@ -5359,11 +5259,9 @@ class VirtualMachine {
5359
5259
  `Calculating dependent variables took ${this.elapsedTime} seconds.\n`);
5360
5260
 
5361
5261
  // FINALLY: Reset the vectors of all note colors.
5362
- for(let o in MODEL.clusters) if(MODEL.clusters.hasOwnProperty(o)) {
5363
- const c = MODEL.clusters[o];
5364
- for(let i = 0; i < c.notes.length; i++) {
5365
- c.notes[i].color.reset();
5366
- }
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();
5367
5265
  }
5368
5266
  }
5369
5267
 
@@ -5393,9 +5291,7 @@ class VirtualMachine {
5393
5291
  if(n) return '[' + n + ']';
5394
5292
  return a.constructor.name;
5395
5293
  }
5396
- let l = [];
5397
- for(let i = 0; i < a.length; i++) l.push(arg(a[i]));
5398
- return '(' + l.join(', ') + ')';
5294
+ return '(' + a.map((x) => arg(x)).join(', ') + ')';
5399
5295
  };
5400
5296
  for(let i = 0; i < this.code.length; i++) {
5401
5297
  const vmi = this.code[i];
@@ -5594,15 +5490,13 @@ class VirtualMachine {
5594
5490
  // NOTE: penalties must become negative coefficients (solver MAXimizes!)
5595
5491
  let p = -1,
5596
5492
  hsp = 0;
5597
- // Index i iterates over types of slack variable: 0 = market demand (EQ),
5598
- // 1 = LE and GE bound constraints, 2 = highest (data, composite constraints)
5599
- for(let i = 0; i <= 2; i++) {
5600
- const svl = this.slack_variables[i];
5601
- let l = svl.length;
5602
- 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) {
5603
5497
  for(let k = 0; k < abl; k++) {
5604
5498
  hsp = this.slack_penalty * p;
5605
- this.objective[svl[j] + k*this.cols] = hsp;
5499
+ this.objective[sv + k*this.cols] = hsp;
5606
5500
  }
5607
5501
  }
5608
5502
  // For the next type of slack, double the penalty
@@ -5914,8 +5808,7 @@ class VirtualMachine {
5914
5808
  if(this.sos_var_indices.length > 0) {
5915
5809
  this.lines += 'sos\n';
5916
5810
  for(let j = 0; j < abl; j++) {
5917
- for(let i = 0; i < this.sos_var_indices.length; i++) {
5918
- const svi = this.sos_var_indices[i];
5811
+ for(const svi of this.sos_var_indices) {
5919
5812
  v_set.length = 0;
5920
5813
  let vi = svi[0] + j * this.cols;
5921
5814
  for(let j = 1; j <= svi[1]; j++) {
@@ -5932,11 +5825,11 @@ class VirtualMachine {
5932
5825
 
5933
5826
  rowToEquation(row, ct, rhs) {
5934
5827
  const eq = [];
5935
- for(let p in row) if (row.hasOwnProperty(p)) {
5828
+ for(let i in row) if (isNumber(i)) {
5936
5829
  const
5937
- c = this.sig4Dig(row[p]),
5938
- vi = p % this.cols,
5939
- t = Math.floor(p / this.cols);
5830
+ c = this.sig4Dig(row[i]),
5831
+ vi = i % this.cols,
5832
+ t = Math.floor(i / this.cols);
5940
5833
  eq.push(c + ' ' + this.variables[vi][1].displayName + ' ' +
5941
5834
  this.variables[vi][0] + ' [' + t + ']');
5942
5835
  }
@@ -6333,18 +6226,17 @@ Solver status = ${json.status}`);
6333
6226
  if(keys.length) {
6334
6227
  const msg = ['NOTE: Due to negative link delays, levels for ' +
6335
6228
  pluralS(keys.length, 'variable') + ' are pre-set:'];
6336
- for(let i = 0; i < keys.length; i++) {
6229
+ for(const k of keys) {
6337
6230
  const
6338
- vi = parseInt(keys[i]),
6231
+ vi = parseInt(k),
6339
6232
  // NOTE: Subtract 1 because variable list is zero-based.
6340
6233
  vbl = this.variables[vi - 1],
6341
6234
  fv = this.variables_to_fixate[vi],
6342
6235
  fvk = Object.keys(fv),
6343
6236
  fvl = [];
6344
6237
  // Add constraints that fixate the levels directly to the tableau.
6345
- for(let i = 0; i < fvk.length; i++) {
6238
+ for(const bt of fvk) {
6346
6239
  const
6347
- bt = fvk[i],
6348
6240
  pl = fv[bt],
6349
6241
  k = (bt - 1) * VM.cols + vi,
6350
6242
  row = {};
@@ -6364,10 +6256,8 @@ Solver status = ${json.status}`);
6364
6256
  if(n) {
6365
6257
  let vlist = '',
6366
6258
  first = 1e20;
6367
- for(let i = 0; i < n; i++) {
6368
- const
6369
- k = keys[i],
6370
- bit = this.bound_issues[k];
6259
+ for(const k of keys) {
6260
+ const bit = this.bound_issues[k];
6371
6261
  vlist += `\n - ${k} (t=${listToRange(bit)})`;
6372
6262
  first = Math.min(first, bit[0]);
6373
6263
  }
@@ -6503,9 +6393,7 @@ Solver status = ${json.status}`);
6503
6393
  } else {
6504
6394
  // Wait no longer, but warn user that data may be incomplete.
6505
6395
  const dsl = [];
6506
- for(let i = 0; i < MODEL.loading_datasets.length; i++) {
6507
- dsl.push(MODEL.loading_datasets[i].displayName);
6508
- }
6396
+ for(const ds of MODEL.loading_datasets) dsl.push(ds.displayName);
6509
6397
  UI.warn('Loading of ' + pluralS(dsl.length, 'dataset') + ' (' +
6510
6398
  dsl.join(', ') + ') takes too long');
6511
6399
  }
@@ -6709,11 +6597,8 @@ function valueOfIndexVariable(v) {
6709
6597
  // Return the value of the iterator index variable for the current
6710
6598
  // experiment.
6711
6599
  if(MODEL.running_experiment) {
6712
- const
6713
- lead = v + '=',
6714
- combi = MODEL.running_experiment.activeCombination;
6715
- for(let i = 0; i < combi.length; i++) {
6716
- const sel = combi[i] ;
6600
+ const lead = v + '=';
6601
+ for(const sel of MODEL.running_experiment.activeCombination) {
6717
6602
  if(sel.startsWith(lead)) return parseInt(sel.substring(2));
6718
6603
  }
6719
6604
  }
@@ -7041,15 +6926,16 @@ function VMI_push_method(x, args) {
7041
6926
  const
7042
6927
  t = tot[0],
7043
6928
  v = mex.result(t);
7044
- // Clear the method object & prefix -- just to be neat.
7045
- mex.method_object = null;
7046
- mex.method_object_prefix = '';
7047
6929
  // Trace only now that time step t has been computed.
7048
6930
  if(DEBUGGING) {
7049
- console.log('push method:', obj.displayName, method.selector,
7050
- 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));
7051
6934
  }
7052
6935
  x.push(v);
6936
+ // Clear the method object & prefix -- just to be neat.
6937
+ mex.method_object = null;
6938
+ mex.method_object_prefix = '';
7053
6939
  }
7054
6940
 
7055
6941
  function VMI_push_wildcard_entity(x, args) {
@@ -7086,8 +6972,9 @@ function VMI_push_wildcard_entity(x, args) {
7086
6972
  MODEL.running_experiment.activeCombination, x.attribute);
7087
6973
  }
7088
6974
  nn = nn.replace('#', x.wildcard_vector_index);
7089
- for(let i = 0; !obj && i < el.length; i++) {
7090
- if(el[i].name === nn) obj = el[i];
6975
+ for(const e of el) {
6976
+ if(e.name === nn) obj = e;
6977
+ break;
7091
6978
  }
7092
6979
  // If no match, then this indicates a bad reference.
7093
6980
  if(!obj) {
@@ -7258,7 +7145,7 @@ function VMI_push_dataset_modifier(x, args) {
7258
7145
 
7259
7146
 
7260
7147
  function VMI_push_run_result(x, args) {
7261
- // NOTE: the first argument specifies the experiment run results:
7148
+ // NOTE: The first argument specifies the experiment run results:
7262
7149
  // x: experiment object (FALSE indicates: use current experiment)
7263
7150
  // r: integer number, or selector list
7264
7151
  // v: variable index (integer number), or identifier (string)
@@ -7269,12 +7156,13 @@ function VMI_push_run_result(x, args) {
7269
7156
  // t: if integer t > 0, use floor(current time step / t) as run number
7270
7157
  const
7271
7158
  rrspec = args[0],
7272
- // NOTE: when expression `x` for which this instruction is executed is
7273
- // a dataset modifier, use the time scale of the dataset, not of the
7274
- // model, because the dataset vector is scaled to the model time scale
7159
+ // NOTE: When *expression* `x` for which this instruction is executed
7160
+ // is a dataset modifier, use the time scale of the dataset, not of the
7161
+ // model, because the dataset vector is scaled to the model time scale.
7275
7162
  model_dt = MODEL.timeStepDuration;
7276
- // NOTE: run result now defaults to UNDEFINED, because the VM handles errors
7277
- // better now (no call stack dump on "undefined" etc., but only on errors)
7163
+ // NOTE: Run result can now default to UNDEFINED, because the VM now handles
7164
+ // exceptional values better: no call stack dump on "undefined" etc., but
7165
+ // only on real errors.
7278
7166
  let v = rrspec.dv || VM.UNDEFINED;
7279
7167
  if(rrspec && rrspec.hasOwnProperty('x')) {
7280
7168
  let xp = rrspec.x,
@@ -7283,12 +7171,14 @@ function VMI_push_run_result(x, args) {
7283
7171
  if(xp === false) xp = MODEL.running_experiment;
7284
7172
  if(xp instanceof Experiment) {
7285
7173
  if(Array.isArray(rn)) {
7174
+ // Let the running experiment infer run number from selector list `rn`
7175
+ // and its own "active combination" of selectors.
7286
7176
  rn = xp.matchingCombinationIndex(rn);
7287
7177
  } else if(rn < 0) {
7288
- // Relative run number: use current run # + r (first run has number 0)
7178
+ // Relative run number: use current run # + r (first run has number 0).
7289
7179
  rn += xp.active_combination_index;
7290
7180
  } else if(rrspec.nr !== false) {
7291
- // Run number inferred from local time step of expression
7181
+ // Run number inferred from local time step of expression.
7292
7182
  const
7293
7183
  rl = VM.nr_of_time_steps,
7294
7184
  range = rangeToList(rrspec.nr, xp.runs.length - 1);
@@ -7299,9 +7189,9 @@ function VMI_push_run_result(x, args) {
7299
7189
  rn = (ri < l ? range[ri] : range[l - 1]);
7300
7190
  }
7301
7191
  }
7302
- // If variable is passed as identifier, get its index for the experiment
7192
+ // If variable is passed as identifier, get its index for the experiment.
7303
7193
  if(typeof rri === 'string') rri = xp.resultIndex(rri);
7304
- // Then proceed only if run number and result index both make sense
7194
+ // Then proceed only if run number and result index both make sense.
7305
7195
  const run_count = (xp.completed ? xp.runs.length :
7306
7196
  xp.active_combination_index);
7307
7197
  if(rn !== false && rn >= 0 && rn < run_count) {
@@ -7346,11 +7236,14 @@ function VMI_push_run_result(x, args) {
7346
7236
  } else {
7347
7237
  // No statistic => return the vector for local time step
7348
7238
  // using here, too, the delta-time-modifier to adjust the offsets
7349
- // for different time steps per experiment
7239
+ // for different time steps per experiment.
7350
7240
  const tot = twoOffsetTimeStep(x.step[x.step.length - 1],
7351
7241
  args[1], args[2], args[3], args[4], dtm, x);
7352
7242
  // Scale the (midpoint) time step (at current model run time scale)
7353
- // to the experiment run time scale and get the run result value
7243
+ // to the experiment run time scale and get the run result value.
7244
+ // NOTE: the .m property specifies the time scaling method, and
7245
+ // the .p property whether the run result vector should be used as
7246
+ // a periodic time series.
7354
7247
  v = rr.valueAtModelTime(tot[0], model_dt, rrspec.m, rrspec.p);
7355
7248
  if(DEBUGGING) {
7356
7249
  const trc = ['push run result: ', xp.title,
@@ -7436,10 +7329,9 @@ function VMI_push_statistic(x, args) {
7436
7329
  let obj,
7437
7330
  vlist = [];
7438
7331
  for(let t = t1; t <= t2; t++) {
7439
- // Get the list of values
7440
- // NOTE: variables may be vectors or expressions
7441
- for(let i = 0; i < list.length; i++) {
7442
- obj = list[i];
7332
+ // Get the list of values.
7333
+ // NOTE: Variables may be vectors or expressions.
7334
+ for(const obj of list) {
7443
7335
  if(Array.isArray(obj)) {
7444
7336
  // Object is a vector
7445
7337
  if(t < obj.length) {
@@ -7448,11 +7340,11 @@ function VMI_push_statistic(x, args) {
7448
7340
  v = VM.UNDEFINED;
7449
7341
  }
7450
7342
  } else {
7451
- // Object is an expression
7343
+ // Object is an expression.
7452
7344
  v = obj.result(t);
7453
7345
  }
7454
7346
  // Push value unless it is zero and NZ is TRUE, or if it is undefined
7455
- // (this will occur when a variable has been deleted)
7347
+ // (this will occur when a variable has been deleted).
7456
7348
  if(v <= VM.PLUS_INFINITY && (!nz || Math.abs(v) > VM.NEAR_ZERO)) {
7457
7349
  vlist.push(v);
7458
7350
  }
@@ -7460,18 +7352,18 @@ function VMI_push_statistic(x, args) {
7460
7352
  }
7461
7353
  const
7462
7354
  n = vlist.length,
7463
- // NOTE: count is the number of values used in the statistic
7355
+ // NOTE: count is the number of values used in the statistic.
7464
7356
  count = (nz ? n : list.length * (t2 - t1 + 1));
7465
7357
  if(stat === 'N') {
7466
7358
  x.push(count);
7467
7359
  return;
7468
7360
  }
7469
- // If no non-zero values remain, all statistics are zero (as ALL values were zero)
7361
+ // If no non-zero values remain, all statistics are zero (as ALL values were zero).
7470
7362
  if(n === 0) {
7471
7363
  x.push(0);
7472
7364
  return;
7473
7365
  }
7474
- // Check which statistic, starting with the most likely to be used
7366
+ // Check which statistic, starting with the most likely to be used.
7475
7367
  if(stat === 'MIN') {
7476
7368
  x.push(Math.min(...vlist));
7477
7369
  return;
@@ -7480,7 +7372,7 @@ function VMI_push_statistic(x, args) {
7480
7372
  x.push(Math.max(...vlist));
7481
7373
  return;
7482
7374
  }
7483
- // For all remaining statistics, the sum must be calculated
7375
+ // For all remaining statistics, the sum must be calculated.
7484
7376
  let sum = 0;
7485
7377
  for(let i = 0; i < n; i++) {
7486
7378
  sum += vlist[i];
@@ -7764,6 +7656,21 @@ function VMI_div(x) {
7764
7656
  }
7765
7657
  }
7766
7658
 
7659
+ function VMI_div_zero(x) {
7660
+ // Implements the "robust" division operator A // B.
7661
+ // Pop the top number B from the stack. If B = 0, retain the new
7662
+ // top number A; otherwise replace the top by A/B.
7663
+ const d = x.pop();
7664
+ if(d !== false) {
7665
+ if(DEBUGGING) console.log('DIV-ZERO (' + d.join(', ') + ')');
7666
+ if(Math.abs(d[1]) <= VM.NEAR_ZERO) {
7667
+ x.retop(d[0]);
7668
+ } else {
7669
+ x.retop(d[0] / d[1]);
7670
+ }
7671
+ }
7672
+ }
7673
+
7767
7674
  function VMI_mod(x) {
7768
7675
  // Perform a "floating point MOD operation" as explained below.
7769
7676
  // Pop the top number on the stack. If zero, push error code #DIV/0!.
@@ -8389,8 +8296,8 @@ function VMI_set_bounds(args) {
8389
8296
  // For grid elements, bounds must be set on UP and DOWN variables.
8390
8297
  if(vbl.grid) {
8391
8298
  // When considering losses, partition range 0...UB in sections.
8392
- const step = (vbl.grid.loss_approximation < 2 ? u :
8393
- u / vbl.grid.loss_approximation);
8299
+ const step = (MODEL.ignore_power_losses || vbl.grid.loss_approximation < 2 ?
8300
+ u : u / vbl.grid.loss_approximation);
8394
8301
  VM.upper_bounds[VM.offset + vbl.up_1_var_index] = step;
8395
8302
  VM.upper_bounds[VM.offset + vbl.down_1_var_index] = step;
8396
8303
  if(vbl.grid.loss_approximation > 1) {
@@ -8779,16 +8686,14 @@ function VMI_update_grid_process_cash_coefficients(p) {
8779
8686
  // VMI_update_cash_coefficient).
8780
8687
  let fn = null,
8781
8688
  tn = null;
8782
- for(let i = 0; i <= p.inputs.length; i++) {
8783
- const l = p.inputs[i];
8689
+ for(const l of p.inputs) {
8784
8690
  if(l.multiplier === VM.LM_LEVEL &&
8785
8691
  !MODEL.ignored_entities[l.identifier]) {
8786
8692
  fn = l.from_node;
8787
8693
  break;
8788
8694
  }
8789
8695
  }
8790
- for(let i = 0; i <= p.outputs.length; i++) {
8791
- const l = p.outputs[i];
8696
+ for(const l of p.outputs) {
8792
8697
  if(l.multiplier === VM.LM_LEVEL &&
8793
8698
  !MODEL.ignored_entities[l.identifier]) {
8794
8699
  tn = l.to_node;
@@ -9096,7 +9001,7 @@ function VMI_add_grid_process_constraints(p) {
9096
9001
  // Now the variable index lists all contain 1, 2 or 3 indices,
9097
9002
  // depending on the loss approximation level.
9098
9003
  let ub = p.upper_bound.result(VM.t);
9099
- if(ub >= VM.PLUS_INFINITY) {
9004
+ if(ub >= VM.PLUS_INFINITY || MODEL.ignore_grid_capacity) {
9100
9005
  // When UB = +INF, this is interpreted as "unlimited", which is
9101
9006
  // implemented as 99999 grid power units.
9102
9007
  ub = VM.UNLIMITED_POWER_FLOW;
@@ -9148,15 +9053,14 @@ function VMI_add_kirchhoff_constraints(cb) {
9148
9053
  // Add Kirchhoff's voltage law constraint for each cycle in `cb`.
9149
9054
  // NOTE: Do not add a constraint for cyles that have been "broken"
9150
9055
  // because one or more of its processes have UB = 0.
9151
- for(let i = 0; i < cb.length; i++) {
9152
- const c = cb[i];
9056
+ for(const c of cb) {
9153
9057
  let not_broken = true;
9154
9058
  VMI_clear_coefficients();
9155
- for(let j = 0; j < c.length; j++) {
9059
+ for(const e of c) {
9156
9060
  const
9157
- p = c[j].process,
9061
+ p = e.process,
9158
9062
  x = p.length_in_km * p.grid.reactancePerKm,
9159
- o = c[j].orientation,
9063
+ o = e.orientation,
9160
9064
  ub = p.upper_bound.result(VM.t);
9161
9065
  if(ub <= VM.NEAR_ZERO) {
9162
9066
  not_broken = false;
@@ -9242,9 +9146,7 @@ function VMI_add_bound_line_constraint(args) {
9242
9146
  }
9243
9147
  // Add constraint (1):
9244
9148
  VMI_clear_coefficients();
9245
- for(let i = 0; i < n; i++) {
9246
- VM.coefficients[w[i]] = 1;
9247
- }
9149
+ for(const wi of w) VM.coefficients[wi] = 1;
9248
9150
  VM.rhs = 1;
9249
9151
  VMI_add_constraint(VM.EQ);
9250
9152
  // Add constraint (2):
@@ -9270,9 +9172,9 @@ function VMI_add_bound_line_constraint(args) {
9270
9172
  VMI_add_constraint(bl.type);
9271
9173
  // NOTE: SOS variables w[i] have bounds [0, 1], but these have not been
9272
9174
  // set yet.
9273
- for(let i = 0; i < w.length; i++) {
9274
- VM.lower_bounds[w[i]] = 0;
9275
- VM.upper_bounds[w[i]] = 1;
9175
+ for(const wi of w) {
9176
+ VM.lower_bounds[wi] = 0;
9177
+ VM.upper_bounds[wi] = 1;
9276
9178
  }
9277
9179
  // NOTE: Some solvers do not support SOS. To ensure that only 2
9278
9180
  // adjacent w[i]-variables can be non-zero (they range from 0 to 1),
@@ -9308,9 +9210,7 @@ function VMI_add_bound_line_constraint(args) {
9308
9210
  VMI_add_constraint(VM.LE); // w[N] - b[N] <= 0
9309
9211
  // Add last constraint: sum of binaries must be <= 2.
9310
9212
  VMI_clear_coefficients();
9311
- for(let i = 0; i < n; i++) {
9312
- VM.coefficients[w[i] + n] = 1;
9313
- }
9213
+ for(const wi of w) VM.coefficients[wi + n] = 1;
9314
9214
  VM.rhs = 2;
9315
9215
  VMI_add_constraint(VM.LE);
9316
9216
  }
@@ -9420,7 +9320,7 @@ const
9420
9320
  OPERATOR_CHARS = ';?:+-*/%=!<>^|@',
9421
9321
  // Opening bracket, space and single quote indicate a separation
9422
9322
  SEPARATOR_CHARS = PARENTHESES + OPERATOR_CHARS + "[ '",
9423
- COMPOUND_OPERATORS = ['!=', '<>', '>=', '<='],
9323
+ COMPOUND_OPERATORS = ['!=', '<>', '>=', '<=', '//'],
9424
9324
  CONSTANT_SYMBOLS = [
9425
9325
  't', 'rt', 'bt', 'ct', 'b', 'N', 'n', 'l', 'r', 'lr', 'nr', 'x', 'nx',
9426
9326
  'random', 'dt', 'true', 'false', 'pi', 'infinity', '#',
@@ -9450,13 +9350,13 @@ const
9450
9350
  DYADIC_OPERATORS = [
9451
9351
  ';', '?', ':', 'or', 'and',
9452
9352
  '=', '<>', '!=', '>', '<', '>=', '<=',
9453
- '@', '+', '-', '*', '/',
9353
+ '@', '+', '-', '*', '/', '//',
9454
9354
  '%', '^', 'log', '|'],
9455
9355
  DYADIC_CODES = [
9456
9356
  VMI_concat, VMI_if_then, VMI_if_else, VMI_or, VMI_and,
9457
9357
  VMI_eq, VMI_ne, VMI_ne, VMI_gt, VMI_lt, VMI_ge, VMI_le,
9458
- VMI_at, VMI_add, VMI_sub, VMI_mul, VMI_div, VMI_mod,
9459
- VMI_power, VMI_log, VMI_replace_undefined],
9358
+ VMI_at, VMI_add, VMI_sub, VMI_mul, VMI_div, VMI_div_zero,
9359
+ VMI_mod, VMI_power, VMI_log, VMI_replace_undefined],
9460
9360
 
9461
9361
  // Compiler checks for random codes as they make an expression dynamic
9462
9362
  RANDOM_CODES = [VMI_binomial, VMI_exponential, VMI_normal, VMI_poisson,
@@ -9471,7 +9371,10 @@ const
9471
9371
 
9472
9372
  OPERATORS = DYADIC_OPERATORS.concat(MONADIC_OPERATORS),
9473
9373
  OPERATOR_CODES = DYADIC_CODES.concat(MONADIC_CODES),
9474
- PRIORITIES = [1, 2, 2, 3, 4, 5, 5, 5, 5, 5, 5, 5, 5.5, 6, 6, 7, 7, 7, 8, 8, 10,
9374
+ PRIORITIES = [1, 2, 2, 3, 4, 5, 5, 5, 5, 5, 5, 5,
9375
+ // NOTE: The new @ operator has higher priority than comparisons,
9376
+ // and lower than arithmetic operators.
9377
+ 5.5, 6, 6, 7, 7, 7, 7, 8, 8, 10,
9475
9378
  9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
9476
9379
  ACTUAL_SYMBOLS = CONSTANT_SYMBOLS.concat(OPERATORS),
9477
9380
  SYMBOL_CODES = CONSTANT_CODES.concat(OPERATOR_CODES);
@@ -9688,7 +9591,7 @@ function VMI_highest_cumulative_consecutive_deviation(x) {
9688
9591
 
9689
9592
  // NOTE: HCCD is not time-dependent => result is stored in cache
9690
9593
  // As expressions may contain several HCCD operators, create a unique key
9691
- // based on its parameters
9594
+ // based on its parameters.
9692
9595
  const cache_key = ['hccd', e.identifier, a, block_size, first, last].join('_');
9693
9596
  if(x.cache[cache_key]) {
9694
9597
  x.retop(x.cache[cache_key]);
@@ -9697,14 +9600,14 @@ function VMI_highest_cumulative_consecutive_deviation(x) {
9697
9600
 
9698
9601
  if(DEBUGGING) console.log(`*${vmi} for ${name}`);
9699
9602
 
9700
- // Compute the aggregated vector and sum
9603
+ // Compute the aggregated vector and sum.
9701
9604
  let sum = 0,
9702
9605
  b = 0,
9703
9606
  n = 0,
9704
9607
  av = [];
9705
9608
  for(let i = first; i <= last; i++) {
9706
9609
  const v = vector[i];
9707
- // Handle exceptional values in vector
9610
+ // Handle exceptional values in vector.
9708
9611
  if(v <= VM.BEYOND_MINUS_INFINITY || v >= VM.BEYOND_PLUS_INFINITY) {
9709
9612
  x.retop(v);
9710
9613
  return;
@@ -9717,34 +9620,33 @@ function VMI_highest_cumulative_consecutive_deviation(x) {
9717
9620
  b = 0;
9718
9621
  }
9719
9622
  }
9720
- // Always push the remaining block sum, even if it is 0
9623
+ // Always push the remaining block sum, even if it is 0.
9721
9624
  av.push(b);
9722
9625
  // Compute the mean (per block)
9723
9626
  const mean = sum / av.length;
9724
9627
  let hccd = 0,
9725
9628
  positive = av[0] > mean;
9726
9629
  sum = 0;
9727
- // Iterate over the aggregated vector
9728
- for(let i = 0; i < av.length; i++) {
9729
- const v = av[i];
9630
+ // Iterate over the aggregated vector.
9631
+ for(const v of av) {
9730
9632
  if((positive && v < mean) || (!positive && v > mean)) {
9731
9633
  hccd = Math.max(hccd, Math.abs(sum));
9732
9634
  sum = v;
9733
9635
  positive = !positive;
9734
9636
  } else {
9735
- // No sign change => add deviation
9637
+ // No sign change => add deviation.
9736
9638
  sum += v;
9737
9639
  }
9738
9640
  }
9739
9641
  hccd = Math.max(hccd, Math.abs(sum));
9740
- // Store the result in the expression's cache
9642
+ // Store the result in the expression's cache.
9741
9643
  x.cache[cache_key] = hccd;
9742
- // Push the result onto the stack
9644
+ // Push the result onto the stack.
9743
9645
  x.retop(hccd);
9744
9646
  return;
9745
9647
  }
9746
9648
  }
9747
- // Fall-trough indicates error
9649
+ // Fall-trough indicates error.
9748
9650
  if(DEBUGGING) console.log(vmi + ': invalid parameter(s)\n', d);
9749
9651
  x.retop(VM.PARAMS);
9750
9652
  }