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.
- package/README.md +3 -40
- package/package.json +6 -2
- package/server.js +19 -157
- package/static/images/solve-not-same-changed.png +0 -0
- package/static/images/solve-not-same-not-changed.png +0 -0
- package/static/images/solve-same-changed.png +0 -0
- package/static/images/solve-same-not-changed.png +0 -0
- package/static/index.html +137 -20
- package/static/linny-r.css +260 -23
- package/static/scripts/iro.min.js +7 -7
- package/static/scripts/linny-r-ctrl.js +126 -85
- package/static/scripts/linny-r-gui-actor-manager.js +23 -33
- package/static/scripts/linny-r-gui-chart-manager.js +56 -53
- package/static/scripts/linny-r-gui-constraint-editor.js +10 -14
- package/static/scripts/linny-r-gui-controller.js +644 -260
- package/static/scripts/linny-r-gui-dataset-manager.js +64 -66
- package/static/scripts/linny-r-gui-documentation-manager.js +11 -17
- package/static/scripts/linny-r-gui-equation-manager.js +22 -22
- package/static/scripts/linny-r-gui-experiment-manager.js +124 -141
- package/static/scripts/linny-r-gui-expression-editor.js +26 -12
- package/static/scripts/linny-r-gui-file-manager.js +42 -48
- package/static/scripts/linny-r-gui-finder.js +294 -55
- package/static/scripts/linny-r-gui-model-autosaver.js +2 -4
- package/static/scripts/linny-r-gui-monitor.js +35 -41
- package/static/scripts/linny-r-gui-paper.js +42 -70
- package/static/scripts/linny-r-gui-power-grid-manager.js +31 -34
- package/static/scripts/linny-r-gui-receiver.js +1 -2
- package/static/scripts/linny-r-gui-repository-browser.js +44 -46
- package/static/scripts/linny-r-gui-scale-unit-manager.js +32 -32
- package/static/scripts/linny-r-gui-sensitivity-analysis.js +61 -67
- package/static/scripts/linny-r-gui-undo-redo.js +94 -95
- package/static/scripts/linny-r-milp.js +20 -24
- package/static/scripts/linny-r-model.js +1932 -2274
- package/static/scripts/linny-r-utils.js +27 -27
- package/static/scripts/linny-r-vm.js +900 -998
- package/static/show-png.html +0 -113
@@ -236,12 +236,12 @@ class Expression {
|
|
236
236
|
// Returns XML-encoded expression after replacing "black-boxed" entities.
|
237
237
|
let text = this.text;
|
238
238
|
if(MODEL.black_box) {
|
239
|
-
// Get all entity names that occur in this expression
|
239
|
+
// Get all entity names that occur in this expression.
|
240
240
|
const vl = text.match(/\[[^\[]+\]/g);
|
241
|
-
if(vl) for(
|
242
|
-
// Trim enclosing brackets and remove the "tail" (attribute or offset)
|
241
|
+
if(vl) for(const v of vl) {
|
242
|
+
// Trim enclosing brackets and remove the "tail" (attribute or offset).
|
243
243
|
let tail = '',
|
244
|
-
e =
|
244
|
+
e = v.substring(1, v.length - 1).split(UI.OA_SEPARATOR);
|
245
245
|
if(e.length > 1) {
|
246
246
|
tail = UI.OA_SEPARATOR + e.pop();
|
247
247
|
e = e.join(UI.OA_SEPARATOR);
|
@@ -254,21 +254,29 @@ class Expression {
|
|
254
254
|
e = e[0];
|
255
255
|
}
|
256
256
|
}
|
257
|
-
// Link names comprise two entities
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
257
|
+
// Link names and constraint names comprise two entities.
|
258
|
+
// If so, process both entity names.
|
259
|
+
let arrow = UI.LINK_ARROW,
|
260
|
+
parts = e.split(arrow);
|
261
|
+
if(parts.length === 1) {
|
262
|
+
arrow = UI.CONSTRAINT_ARROW;
|
263
|
+
parts = e.split(arrow);
|
264
|
+
}
|
265
|
+
if(parts.length > 1) {
|
266
|
+
let n = 0;
|
267
|
+
const enl = [];
|
268
|
+
for(const en of parts) {
|
269
|
+
const id = UI.nameToID(en);
|
270
|
+
if(MODEL.black_box_entities.hasOwnProperty(id)) {
|
271
|
+
enl.push(MODEL.black_box_entities[id]);
|
272
|
+
n++;
|
273
|
+
} else {
|
274
|
+
enl.push(en);
|
275
|
+
}
|
276
|
+
}
|
277
|
+
if(n > 0) {
|
278
|
+
text = text.replace(v, '[' + enl.join(arrow) + tail + ']');
|
268
279
|
}
|
269
|
-
}
|
270
|
-
if(n > 0) {
|
271
|
-
text = text.replace(vl[i], '[' + enl.join(UI.LINK_ARROW) + tail + ']');
|
272
280
|
}
|
273
281
|
}
|
274
282
|
}
|
@@ -301,11 +309,7 @@ class Expression {
|
|
301
309
|
if(DEBUGGING) {
|
302
310
|
// Show the "time step stack" for --START and --STOP
|
303
311
|
if(action.startsWith('--') || action.startsWith('"')) {
|
304
|
-
|
305
|
-
for(let i = 0; i < this.step.length; i++) {
|
306
|
-
s.push(this.step[i]);
|
307
|
-
}
|
308
|
-
action = `[${s.join(', ')}] ${action}`;
|
312
|
+
action = `[${step.join(', ')}] ${action}`;
|
309
313
|
}
|
310
314
|
console.log(action);
|
311
315
|
}
|
@@ -317,7 +321,6 @@ class Expression {
|
|
317
321
|
if((typeof number !== 'number' ||
|
318
322
|
(this.isStatic && !this.isWildcardExpression)) &&
|
319
323
|
!this.isMethod) return this.vector;
|
320
|
-
//console.log('HERE choosing wcnr', number, this);
|
321
324
|
// Method expressions are not "numbered" but differentiate by the
|
322
325
|
// entity to which they are applied. Their "vector number" is then
|
323
326
|
// inferred by looking up this entity in a method object list.
|
@@ -332,9 +335,7 @@ class Expression {
|
|
332
335
|
}
|
333
336
|
// Use the vector for the wildcard number (create it if necessary).
|
334
337
|
if(!this.wildcard_vectors.hasOwnProperty(number)) {
|
335
|
-
//console.log('HERE adding wc vector', number, this);
|
336
338
|
this.wildcard_vectors[number] = [];
|
337
|
-
//console.log('HERE adding wc vector', number, this.wildcard_vectors);
|
338
339
|
if(this.isStatic) {
|
339
340
|
this.wildcard_vectors[number][0] = VM.NOT_COMPUTED;
|
340
341
|
} else {
|
@@ -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(
|
584
|
+
for(const m of matches) {
|
584
585
|
const
|
585
|
-
|
586
|
-
|
587
|
-
|
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
|
-
|
675
|
-
|
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
|
-
//
|
852
|
-
// Specifier format: {method$title|run} where method and title are
|
853
|
-
// optional
|
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:
|
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:
|
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
|
886
|
-
let
|
887
|
-
|
888
|
-
s = s
|
889
|
-
if(s.length >
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
//
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
//
|
905
|
-
|
906
|
-
//
|
907
|
-
//
|
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
|
-
//
|
919
|
-
|
920
|
-
|
921
|
-
|
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
|
924
|
-
|
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:
|
929
|
-
|
930
|
-
|
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 "${
|
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:
|
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(
|
971
|
-
let xri =
|
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 =
|
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:
|
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(
|
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(
|
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(
|
1279
|
+
for(const e of MODEL.entitiesEndingOn(tail, attr)) {
|
1283
1280
|
const
|
1284
|
-
en =
|
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:
|
1306
|
-
// dynamic
|
1307
|
-
|
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 (!=, <>, <=,
|
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:
|
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
|
-
|
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(
|
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(
|
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(
|
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(
|
2951
|
-
const
|
2952
|
-
bm
|
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
|
-
|
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(
|
3382
|
-
const a = MODEL.actors[
|
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(
|
3389
|
-
|
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(
|
3397
|
-
|
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(
|
3422
|
-
|
3423
|
-
|
3424
|
-
|
3425
|
-
|
3426
|
-
|
3427
|
-
|
3428
|
-
|
3429
|
-
|
3430
|
-
|
3431
|
-
//
|
3432
|
-
|
3433
|
-
if(
|
3434
|
-
|
3435
|
-
|
3436
|
-
|
3437
|
-
|
3438
|
-
|
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(
|
3455
|
-
|
3456
|
-
p
|
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(
|
3471
|
-
|
3472
|
-
p
|
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(
|
3495
|
-
const a = MODEL.actors[
|
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(
|
3512
|
-
|
3513
|
-
|
3514
|
-
|
3515
|
-
|
3516
|
-
|
3517
|
-
|
3518
|
-
|
3519
|
-
|
3520
|
-
|
3521
|
-
if(
|
3522
|
-
|
3523
|
-
|
3524
|
-
|
3525
|
-
|
3526
|
-
|
3527
|
-
|
3528
|
-
|
3529
|
-
|
3530
|
-
|
3531
|
-
|
3532
|
-
|
3533
|
-
|
3534
|
-
|
3535
|
-
|
3536
|
-
|
3537
|
-
|
3538
|
-
|
3539
|
-
|
3540
|
-
|
3541
|
-
|
3542
|
-
|
3543
|
-
|
3544
|
-
|
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(
|
3556
|
-
|
3557
|
-
|
3558
|
-
|
3559
|
-
|
3560
|
-
|
3561
|
-
|
3562
|
-
|
3563
|
-
|
3564
|
-
|
3565
|
-
|
3566
|
-
|
3567
|
-
|
3568
|
-
|
3569
|
-
|
3570
|
-
|
3571
|
-
|
3572
|
-
|
3573
|
-
|
3574
|
-
|
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(
|
3620
|
-
const a = MODEL.actors[
|
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(
|
3624
|
-
|
3625
|
-
|
3626
|
-
|
3627
|
-
|
3628
|
-
|
3629
|
-
|
3630
|
-
|
3631
|
-
|
3632
|
-
|
3633
|
-
|
3634
|
-
|
3635
|
-
|
3636
|
-
|
3637
|
-
|
3638
|
-
|
3639
|
-
|
3640
|
-
|
3641
|
-
|
3642
|
-
|
3643
|
-
|
3644
|
-
|
3645
|
-
|
3646
|
-
|
3647
|
-
|
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
|
-
|
3690
|
-
//
|
3691
|
-
|
3692
|
-
|
3693
|
-
|
3694
|
-
|
3695
|
-
|
3696
|
-
|
3697
|
-
|
3698
|
-
|
3699
|
-
|
3700
|
-
|
3701
|
-
|
3702
|
-
|
3703
|
-
|
3704
|
-
|
3705
|
-
|
3706
|
-
|
3707
|
-
|
3708
|
-
|
3709
|
-
|
3710
|
-
|
3711
|
-
|
3712
|
-
|
3713
|
-
|
3714
|
-
|
3715
|
-
|
3716
|
-
|
3717
|
-
|
3718
|
-
|
3719
|
-
|
3720
|
-
|
3721
|
-
|
3722
|
-
|
3723
|
-
|
3724
|
-
|
3725
|
-
|
3726
|
-
|
3727
|
-
|
3728
|
-
|
3729
|
-
|
3730
|
-
|
3731
|
-
//
|
3732
|
-
//
|
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.
|
3735
|
-
|
3736
|
-
}
|
3737
|
-
|
3738
|
-
|
3739
|
-
|
3740
|
-
|
3741
|
-
|
3742
|
-
|
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
|
-
|
3745
|
-
|
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
|
-
}
|
3755
|
-
|
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.
|
3758
|
-
|
3759
|
-
|
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
|
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(
|
3796
|
-
|
3797
|
-
|
3798
|
-
|
3799
|
-
|
3800
|
-
if(a.weight.
|
3801
|
-
|
3802
|
-
|
3803
|
-
|
3804
|
-
|
3805
|
-
|
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(
|
3815
|
-
const a = MODEL.actors[
|
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(
|
3841
|
-
|
3842
|
-
|
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
|
-
|
3862
|
-
|
3863
|
-
|
3864
|
-
|
3865
|
-
|
3866
|
-
|
3867
|
-
|
3868
|
-
|
3869
|
-
|
3870
|
-
|
3871
|
-
|
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(
|
3881
|
-
|
3882
|
-
|
3883
|
-
|
3884
|
-
|
3885
|
-
|
3886
|
-
|
3887
|
-
|
3888
|
-
|
3889
|
-
|
3890
|
-
|
3891
|
-
|
3892
|
-
|
3893
|
-
|
3894
|
-
|
3895
|
-
|
3896
|
-
|
3897
|
-
|
3898
|
-
|
3899
|
-
|
3900
|
-
|
3901
|
-
|
3902
|
-
|
3903
|
-
|
3904
|
-
|
3905
|
-
|
3906
|
-
|
3907
|
-
|
3908
|
-
|
3909
|
-
|
3910
|
-
|
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(
|
3922
|
-
|
3923
|
-
if(
|
3924
|
-
|
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(
|
3937
|
-
|
3938
|
-
|
3939
|
-
|
3940
|
-
//
|
3941
|
-
|
3942
|
-
|
3943
|
-
|
3944
|
-
|
3945
|
-
|
3946
|
-
|
3947
|
-
|
3948
|
-
|
3949
|
-
|
3950
|
-
//
|
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
|
-
[
|
3970
|
-
|
3971
|
-
//
|
3972
|
-
this.code.push([
|
3973
|
-
|
3974
|
-
|
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
|
-
|
3977
|
-
|
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
|
-
|
3980
|
-
|
3981
|
-
|
3982
|
-
|
3983
|
-
//
|
3984
|
-
|
3985
|
-
|
3986
|
-
|
3987
|
-
|
3988
|
-
|
3989
|
-
|
3990
|
-
|
3991
|
-
|
3992
|
-
|
3993
|
-
|
3994
|
-
|
3995
|
-
|
3996
|
-
|
3997
|
-
|
3998
|
-
|
3999
|
-
|
4000
|
-
|
4001
|
-
|
4002
|
-
|
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
|
-
|
4005
|
-
|
4006
|
-
|
4007
|
-
|
4008
|
-
|
4009
|
-
|
4010
|
-
|
4011
|
-
|
4012
|
-
|
4013
|
-
//
|
4014
|
-
|
4015
|
-
|
4016
|
-
|
4017
|
-
|
4018
|
-
|
4019
|
-
|
4020
|
-
|
4021
|
-
|
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
|
4111
|
-
|
4112
|
-
|
4113
|
-
|
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
|
-
|
4138
|
-
|
4139
|
-
|
4140
|
-
|
4141
|
-
|
4142
|
-
|
4143
|
-
|
4144
|
-
|
4145
|
-
|
4146
|
-
|
4147
|
-
|
4148
|
-
|
4149
|
-
|
4150
|
-
|
4151
|
-
|
4152
|
-
|
4153
|
-
|
4154
|
-
|
4155
|
-
|
4156
|
-
|
4157
|
-
|
4158
|
-
|
4159
|
-
|
4160
|
-
|
4161
|
-
|
4162
|
-
|
4163
|
-
|
4164
|
-
|
4165
|
-
|
4166
|
-
|
4167
|
-
|
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
|
-
|
4175
|
-
|
4176
|
-
|
4177
|
-
|
4178
|
-
//
|
4179
|
-
|
4180
|
-
|
4181
|
-
|
4182
|
-
|
4183
|
-
|
4184
|
-
|
4185
|
-
|
4186
|
-
|
4187
|
-
|
4188
|
-
|
4189
|
-
|
4190
|
-
|
4191
|
-
|
4192
|
-
|
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
|
-
//
|
4197
|
-
|
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(
|
4294
|
-
k
|
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(
|
4298
|
-
k
|
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(
|
4303
|
-
p = pp_nodes[i];
|
4226
|
+
for(const p of pp_nodes) {
|
4304
4227
|
if(p.on_off_var_index >= 0) {
|
4305
|
-
// NOTE:
|
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:
|
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:
|
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(
|
4567
|
-
|
4568
|
-
|
4569
|
-
|
4570
|
-
|
4571
|
-
|
4572
|
-
|
4573
|
-
|
4574
|
-
|
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
|
-
}
|
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
|
4603
|
-
!MODEL.ignored_entities[
|
4604
|
-
for(let
|
4605
|
-
const r = MODEL.links[
|
4521
|
+
for(let k in MODEL.links) if(MODEL.links.hasOwnProperty(k) &&
|
4522
|
+
!MODEL.ignored_entities[k]) {
|
4523
|
+
for(let t = this.block_start; t < this.block_start + this.chunk_length; t++) {
|
4524
|
+
const r = MODEL.links[k].relative_rate.result(t);
|
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
|
4616
|
-
!MODEL.ignored_entities[
|
4617
|
-
const c = MODEL.constraints[
|
4618
|
-
for(let
|
4534
|
+
for(let k in MODEL.constraints) if(MODEL.constraints.hasOwnProperty(k) &&
|
4535
|
+
!MODEL.ignored_entities[k]) {
|
4536
|
+
const c = MODEL.constraints[k];
|
4537
|
+
for(let t = this.block_start; t < this.block_start + this.chunk_length; t++) {
|
4619
4538
|
const
|
4620
|
-
fnlb = c.from_node.lower_bound.result(
|
4621
|
-
fnub = c.from_node.upper_bound.result(
|
4622
|
-
tnlb = c.to_node.lower_bound.result(
|
4623
|
-
tnub = c.to_node.upper_bound.result(
|
4539
|
+
fnlb = c.from_node.lower_bound.result(t),
|
4540
|
+
fnub = c.from_node.upper_bound.result(t),
|
4541
|
+
tnlb = c.to_node.lower_bound.result(t),
|
4542
|
+
tnub = c.to_node.upper_bound.result(t),
|
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(
|
4673
|
-
const cc = this.matrix[
|
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(
|
4691
|
-
const cc = this.matrix[
|
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
|
4769
|
-
const a = MODEL.actors[
|
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
|
4801
|
-
!MODEL.ignored_entities[
|
4717
|
+
for(let k in MODEL.processes) if(MODEL.processes.hasOwnProperty(k) &&
|
4718
|
+
!MODEL.ignored_entities[k]) {
|
4802
4719
|
const
|
4803
|
-
p = MODEL.processes[
|
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
|
4841
|
-
!MODEL.ignored_entities[
|
4757
|
+
for(let k in MODEL.products) if(MODEL.products.hasOwnProperty(k) &&
|
4758
|
+
!MODEL.ignored_entities[k]) {
|
4842
4759
|
const
|
4843
|
-
p = MODEL.products[
|
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
|
-
//
|
4895
|
-
for(
|
4896
|
-
|
4897
|
-
|
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:
|
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
|
-
|
4918
|
-
|
4919
|
-
|
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
|
4934
|
-
!MODEL.ignored_entities[
|
4846
|
+
for(let k in MODEL.processes) if(MODEL.processes.hasOwnProperty(k) &&
|
4847
|
+
!MODEL.ignored_entities[k]) {
|
4935
4848
|
const
|
4936
|
-
p = MODEL.processes[
|
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(
|
4962
|
-
if(
|
4963
|
-
issue = Math.min(
|
4964
|
-
} else if(
|
4965
|
-
issue = Math.max(
|
4874
|
+
for(const ec of list) {
|
4875
|
+
if(ec <= VM.MINUS_INFINITY) {
|
4876
|
+
issue = Math.min(ec, issue);
|
4877
|
+
} else if(ec >= VM.PLUS_INFINITY) {
|
4878
|
+
issue = Math.max(ec, issue);
|
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
|
4989
|
-
|
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
|
4993
|
-
!MODEL.ignored_entities[
|
4994
|
-
l = MODEL.links[
|
4904
|
+
for(let k in MODEL.links) if(MODEL.links.hasOwnProperty(k) &&
|
4905
|
+
!MODEL.ignored_entities[k]) {
|
4906
|
+
const l = MODEL.links[k];
|
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
|
-
|
5003
|
-
|
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(
|
4998
|
+
for(const ll of p.inputs) {
|
5086
4999
|
const
|
5087
|
-
ipl =
|
5088
|
-
rr =
|
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
|
5126
|
-
const pg = MODEL.power_grids[
|
5037
|
+
for(let k in MODEL.power_grids) if(MODEL.power_grids.hasOwnProperty(k)) {
|
5038
|
+
const pg = MODEL.power_grids[k];
|
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
|
5142
|
-
!MODEL.ignored_entities[
|
5143
|
-
const c = MODEL.clusters[
|
5053
|
+
for(let k in MODEL.clusters) if(MODEL.clusters.hasOwnProperty(k) &&
|
5054
|
+
!MODEL.ignored_entities[k]) {
|
5055
|
+
const c = MODEL.clusters[k];
|
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
|
5150
|
-
!MODEL.ignored_entities[
|
5151
|
-
const p = MODEL.processes[
|
5152
|
-
let ci = 0,
|
5061
|
+
for(let k in MODEL.processes) if(MODEL.processes.hasOwnProperty(k) &&
|
5062
|
+
!MODEL.ignored_entities[k]) {
|
5063
|
+
const p = MODEL.processes[k];
|
5064
|
+
let ci = 0,
|
5065
|
+
co = 0;
|
5153
5066
|
// INPUT links from priced products generate cash OUT...
|
5154
|
-
for(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
5334
|
-
const ds = MODEL.datasets[
|
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(
|
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
|
5363
|
-
const c = MODEL.clusters[
|
5364
|
-
for(
|
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
|
-
|
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
|
-
//
|
5598
|
-
//
|
5599
|
-
for(
|
5600
|
-
const svl
|
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[
|
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(
|
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
|
5828
|
+
for(let i in row) if (isNumber(i)) {
|
5936
5829
|
const
|
5937
|
-
c = this.sig4Dig(row[
|
5938
|
-
vi =
|
5939
|
-
t = Math.floor(
|
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(
|
6229
|
+
for(const k of keys) {
|
6337
6230
|
const
|
6338
|
-
vi = parseInt(
|
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(
|
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(
|
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(
|
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
|
-
|
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:',
|
7050
|
-
|
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(
|
7090
|
-
if(
|
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:
|
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:
|
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:
|
7277
|
-
// better
|
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:
|
7441
|
-
for(
|
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 ?
|
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(
|
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(
|
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(
|
9152
|
-
const c = cb[i];
|
9056
|
+
for(const c of cb) {
|
9153
9057
|
let not_broken = true;
|
9154
9058
|
VMI_clear_coefficients();
|
9155
|
-
for(
|
9059
|
+
for(const e of c) {
|
9156
9060
|
const
|
9157
|
-
p =
|
9061
|
+
p = e.process,
|
9158
9062
|
x = p.length_in_km * p.grid.reactancePerKm,
|
9159
|
-
o =
|
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(
|
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(
|
9274
|
-
VM.lower_bounds[
|
9275
|
-
VM.upper_bounds[
|
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(
|
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,
|
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,
|
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(
|
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
|
}
|