linny-r 1.9.2 → 2.0.1
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/LICENSE +1 -1
- package/README.md +4 -4
- package/package.json +1 -1
- package/server.js +1 -1
- package/static/images/eq-negated.png +0 -0
- package/static/images/power.png +0 -0
- package/static/images/tex.png +0 -0
- package/static/index.html +225 -10
- package/static/linny-r.css +458 -8
- package/static/scripts/linny-r-ctrl.js +6 -4
- package/static/scripts/linny-r-gui-actor-manager.js +1 -1
- package/static/scripts/linny-r-gui-chart-manager.js +20 -13
- package/static/scripts/linny-r-gui-constraint-editor.js +410 -50
- package/static/scripts/linny-r-gui-controller.js +127 -12
- package/static/scripts/linny-r-gui-dataset-manager.js +28 -20
- package/static/scripts/linny-r-gui-documentation-manager.js +11 -3
- package/static/scripts/linny-r-gui-equation-manager.js +1 -1
- package/static/scripts/linny-r-gui-experiment-manager.js +1 -1
- package/static/scripts/linny-r-gui-expression-editor.js +7 -1
- package/static/scripts/linny-r-gui-file-manager.js +31 -13
- package/static/scripts/linny-r-gui-finder.js +1 -1
- package/static/scripts/linny-r-gui-model-autosaver.js +1 -1
- package/static/scripts/linny-r-gui-monitor.js +1 -1
- package/static/scripts/linny-r-gui-paper.js +108 -25
- package/static/scripts/linny-r-gui-power-grid-manager.js +529 -0
- package/static/scripts/linny-r-gui-receiver.js +1 -1
- package/static/scripts/linny-r-gui-repository-browser.js +1 -1
- package/static/scripts/linny-r-gui-scale-unit-manager.js +1 -1
- package/static/scripts/linny-r-gui-sensitivity-analysis.js +1 -1
- package/static/scripts/linny-r-gui-tex-manager.js +110 -0
- package/static/scripts/linny-r-gui-undo-redo.js +1 -1
- package/static/scripts/linny-r-milp.js +1 -1
- package/static/scripts/linny-r-model.js +1016 -155
- package/static/scripts/linny-r-utils.js +3 -3
- package/static/scripts/linny-r-vm.js +714 -248
- package/static/show-diff.html +1 -1
- package/static/show-png.html +1 -1
@@ -12,7 +12,7 @@ executed by the VM, construct the Simplex tableau that can be sent to the
|
|
12
12
|
MILP solver.
|
13
13
|
*/
|
14
14
|
/*
|
15
|
-
Copyright (c) 2017-
|
15
|
+
Copyright (c) 2017-2024 Delft University of Technology
|
16
16
|
|
17
17
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
18
18
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -136,7 +136,7 @@ class Expression {
|
|
136
136
|
if(this.object) return this.object.displayName + UI.OA_SEPARATOR + this.attribute;
|
137
137
|
return 'Unknown variable (no object)';
|
138
138
|
}
|
139
|
-
|
139
|
+
|
140
140
|
get timeStepDuration() {
|
141
141
|
// Return dt for dataset if this is a dataset modifier expression;
|
142
142
|
// otherwise dt for the current model.
|
@@ -2024,7 +2024,7 @@ class ExpressionParser {
|
|
2024
2024
|
this.error = 'Missing operand';
|
2025
2025
|
} else if(this.sym_stack > 1) {
|
2026
2026
|
this.error = 'Missing operator';
|
2027
|
-
} else if(this.concatenating) {
|
2027
|
+
} else if(this.concatenating && !(this.owner instanceof BoundLine)) {
|
2028
2028
|
this.error = 'Invalid parameter list';
|
2029
2029
|
}
|
2030
2030
|
}
|
@@ -2121,6 +2121,7 @@ class VirtualMachine {
|
|
2121
2121
|
this.solver_secs = [];
|
2122
2122
|
this.messages = [];
|
2123
2123
|
this.equations = [];
|
2124
|
+
|
2124
2125
|
// Default texts to display for (still) empty results.
|
2125
2126
|
this.no_messages = '(no messages)';
|
2126
2127
|
this.no_variables = '(no variables)';
|
@@ -2134,18 +2135,23 @@ class VirtualMachine {
|
|
2134
2135
|
// decision variables reach +INF (1e+30) or -INF (-1e+30), and a solution
|
2135
2136
|
// inaccurate if extreme values get too close to +/-INF. The higher
|
2136
2137
|
// values have been chosen arbitrarily.
|
2137
|
-
this.
|
2138
|
-
this.
|
2138
|
+
this.SOLVER_PLUS_INFINITY = 1e+25;
|
2139
|
+
this.SOLVER_MINUS_INFINITY = -1e+25;
|
2139
2140
|
this.BEYOND_PLUS_INFINITY = 1e+35;
|
2140
2141
|
this.BEYOND_MINUS_INFINITY = -1e+35;
|
2141
|
-
// The
|
2142
|
-
//
|
2143
|
-
|
2144
|
-
this.
|
2142
|
+
// The VM properties "PLUS_INFINITY" and "MINUS_INFINITY" are used
|
2143
|
+
// when evaluating expressions. These propeties may be changed for
|
2144
|
+
// diagnostic purposes -- see below.
|
2145
|
+
this.PLUS_INFINITY = 1e+25;
|
2146
|
+
this.MINUS_INFINITY = -1e+25;
|
2145
2147
|
// As of version 1.8.0, Linny-R imposes no +INF bounds on processes
|
2146
2148
|
// unless diagnosing an unbounded problem. For such diagnosis, the
|
2147
2149
|
// (relatively) low value 9.999999999e+9 is used.
|
2148
2150
|
this.DIAGNOSIS_UPPER_BOUND = 9.999999999e+9;
|
2151
|
+
// For processes representing grid elements, upper bounds of +INF are
|
2152
|
+
// "capped" to 9999 grid element capacity units (typically MW for
|
2153
|
+
// high voltage grids).
|
2154
|
+
this.UNLIMITED_POWER_FLOW = 9999;
|
2149
2155
|
// NOTE: Below the "near zero" limit, a number is considered zero
|
2150
2156
|
// (this is to timely detect division-by-zero errors).
|
2151
2157
|
this.NEAR_ZERO = 1e-10;
|
@@ -2257,6 +2263,7 @@ class VirtualMachine {
|
|
2257
2263
|
this.LE = 1;
|
2258
2264
|
this.GE = 2;
|
2259
2265
|
this.EQ = 3;
|
2266
|
+
this.ACTOR_CASH = 4;
|
2260
2267
|
|
2261
2268
|
this.constraint_codes = ['FR', 'LE', 'GE', 'EQ'];
|
2262
2269
|
this.constraint_symbols = ['', '<=', '>=', '='];
|
@@ -2392,14 +2399,18 @@ class VirtualMachine {
|
|
2392
2399
|
// Reset the virtual machine so that it can execute the model again.
|
2393
2400
|
// First reset the expression attributes of all model entities.
|
2394
2401
|
MODEL.resetExpressions();
|
2395
|
-
// Clear slack use information for all
|
2402
|
+
// Clear slack use information and boundline point coordinates for all
|
2403
|
+
// constraints.
|
2396
2404
|
for(let k in MODEL.constraints) if(MODEL.constraints.hasOwnProperty(k)) {
|
2397
|
-
MODEL.constraints[k].
|
2405
|
+
MODEL.constraints[k].reset();
|
2398
2406
|
}
|
2399
2407
|
// Likewise, clear slack use information for all clusters.
|
2400
2408
|
for(let k in MODEL.clusters) if(MODEL.clusters.hasOwnProperty(k)) {
|
2401
2409
|
MODEL.clusters[k].slack_info = {};
|
2402
2410
|
}
|
2411
|
+
if(MODEL.with_power_flow) {
|
2412
|
+
POWER_GRID_MANAGER.checkLengths();
|
2413
|
+
}
|
2403
2414
|
// Clear the expression call stack -- used only for diagnostics.
|
2404
2415
|
this.call_stack.length = 0;
|
2405
2416
|
// The out-of-bounds properties are set when the ARRAY_INDEX error
|
@@ -2559,12 +2570,20 @@ class VirtualMachine {
|
|
2559
2570
|
const a = Math.abs(n);
|
2560
2571
|
// Signal small differences from true 0 by leading + or - sign.
|
2561
2572
|
if(n !== 0 && a <= this.ON_OFF_THRESHOLD) return n > 0 ? '+0' : '-0';
|
2562
|
-
|
2573
|
+
/*
|
2574
|
+
if(a >= 9999.5) return n.toPrecision(2);
|
2563
2575
|
if(Math.abs(a-Math.round(a)) < 0.05) return Math.round(n);
|
2564
2576
|
if(a < 1) return Math.round(n*100) / 100;
|
2565
2577
|
if(a < 10) return Math.round(n*10) / 10;
|
2566
2578
|
if(a < 100) return Math.round(n*10) / 10;
|
2567
2579
|
return Math.round(n);
|
2580
|
+
*/
|
2581
|
+
let s = n.toString();
|
2582
|
+
const prec = n.toPrecision(2);
|
2583
|
+
if(prec.length < s.length) s = prec;
|
2584
|
+
const expo = n.toExponential(1);
|
2585
|
+
if(expo.length < s.length) s = expo;
|
2586
|
+
return s;
|
2568
2587
|
}
|
2569
2588
|
|
2570
2589
|
sig4Dig(n, tiny=false) {
|
@@ -2577,19 +2596,25 @@ class VirtualMachine {
|
|
2577
2596
|
if(sv[0]) return sv[1];
|
2578
2597
|
const a = Math.abs(n);
|
2579
2598
|
if(a === 0) return 0;
|
2580
|
-
// Signal small differences from
|
2581
|
-
|
2582
|
-
|
2583
|
-
|
2584
|
-
|
2585
|
-
}
|
2586
|
-
if(a >= 9999995) return n.toPrecision(4);
|
2599
|
+
// Signal small differences from exactly 0 by a leading + or - sign
|
2600
|
+
// except when the `tiny` flag is set.
|
2601
|
+
if(a <= this.ON_OFF_THRESHOLD && !tiny) return n > 0 ? '+0' : '-0';
|
2602
|
+
/*
|
2603
|
+
if(a >= 9999.5) return n.toPrecision(4);
|
2587
2604
|
if(Math.abs(a-Math.round(a)) < 0.0005) return Math.round(n);
|
2588
2605
|
if(a < 1) return Math.round(n*10000) / 10000;
|
2589
2606
|
if(a < 10) return Math.round(n*1000) / 1000;
|
2590
2607
|
if(a < 100) return Math.round(n*100) / 100;
|
2591
2608
|
if(a < 1000) return Math.round(n*10) / 10;
|
2592
2609
|
return Math.round(n);
|
2610
|
+
*/
|
2611
|
+
let s = n.toString();
|
2612
|
+
const prec = n.toPrecision(4);
|
2613
|
+
if(prec.length < s.length) s = prec;
|
2614
|
+
const expo = n.toExponential(2);
|
2615
|
+
if(expo.length < s.length) s = expo;
|
2616
|
+
if(s.indexOf('e') < 0) s = parseFloat(s).toString();
|
2617
|
+
return s;
|
2593
2618
|
}
|
2594
2619
|
|
2595
2620
|
//
|
@@ -2934,7 +2959,7 @@ class VirtualMachine {
|
|
2934
2959
|
}
|
2935
2960
|
if(type === 'I' || type === 'PiL') {
|
2936
2961
|
this.int_var_indices[index] = true;
|
2937
|
-
} else if('OO|IZ|SU|SD|SO|FC|SB'.indexOf(type) >= 0) {
|
2962
|
+
} else if('OO|IZ|SU|SD|SO|FC|SB|UO1|DO1|UO2|DO2|UO3|DO3'.indexOf(type) >= 0) {
|
2938
2963
|
this.bin_var_indices[index] = true;
|
2939
2964
|
}
|
2940
2965
|
if(obj instanceof Process && obj.pace > 1) {
|
@@ -2944,7 +2969,7 @@ class VirtualMachine {
|
|
2944
2969
|
// For constraint bound lines, add as many SOS variables as there
|
2945
2970
|
// are points on the bound line.
|
2946
2971
|
if(type === 'W1' && obj instanceof BoundLine) {
|
2947
|
-
const n = obj.
|
2972
|
+
const n = obj.maxPoints;
|
2948
2973
|
for(let i = 2; i <= n; i++) {
|
2949
2974
|
this.variables.push(['W' + i, obj]);
|
2950
2975
|
}
|
@@ -2967,6 +2992,33 @@ class VirtualMachine {
|
|
2967
2992
|
return index;
|
2968
2993
|
}
|
2969
2994
|
|
2995
|
+
gridProcessVarIndices(p, offset=0) {
|
2996
|
+
// Return an object with lists of 1, 2 or 3 slope variable indices.
|
2997
|
+
if(p.up_1_var_index <= 0) return null;
|
2998
|
+
const gpv = {
|
2999
|
+
slopes: 1,
|
3000
|
+
up: [p.up_1_var_index + offset],
|
3001
|
+
up_on: [p.up_1_on_var_index + offset],
|
3002
|
+
down: [p.down_1_var_index + offset],
|
3003
|
+
down_on: [p.down_1_on_var_index + offset]
|
3004
|
+
};
|
3005
|
+
if(p.up_2_var_index >= 0) {
|
3006
|
+
gpv.slopes++;
|
3007
|
+
gpv.up.push(p.up_2_var_index + offset);
|
3008
|
+
gpv.up_on.push(p.up_2_on_var_index + offset);
|
3009
|
+
gpv.down.push(p.down_2_var_index + offset);
|
3010
|
+
gpv.down_on.push(p.down_2_on_var_index + offset);
|
3011
|
+
if(p.up_3_var_index >= 0) {
|
3012
|
+
gpv.slopes++;
|
3013
|
+
gpv.up.push(p.up_3_var_index + offset);
|
3014
|
+
gpv.up_on.push(p.up_3_on_var_index + offset);
|
3015
|
+
gpv.down.push(p.down_3_var_index + offset);
|
3016
|
+
gpv.down_on.push(p.down_3_on_var_index + offset);
|
3017
|
+
}
|
3018
|
+
}
|
3019
|
+
return gpv;
|
3020
|
+
}
|
3021
|
+
|
2970
3022
|
resetVariableIndices(p) {
|
2971
3023
|
// Set all variable indices to -1 ("no such variable") for node `p`.
|
2972
3024
|
p.level_var_index = -1;
|
@@ -2983,6 +3035,19 @@ class VirtualMachine {
|
|
2983
3035
|
p.stock_GE_slack_var_index = -1;
|
2984
3036
|
} else {
|
2985
3037
|
p.semic_var_index = -1;
|
3038
|
+
// Additional indices for grid elements.
|
3039
|
+
p.up_1_var_index = -1;
|
3040
|
+
p.up_1_on_var_index = -1;
|
3041
|
+
p.down_1_var_index = -1;
|
3042
|
+
p.down_1_on_var_index = -1;
|
3043
|
+
p.up_2_var_index = -1;
|
3044
|
+
p.up_2_on_var_index = -1;
|
3045
|
+
p.down_2_var_index = -1;
|
3046
|
+
p.down_2_on_var_index = -1;
|
3047
|
+
p.up_3_var_index = -1;
|
3048
|
+
p.up_3_on_var_index = -1;
|
3049
|
+
p.down_3_var_index = -1;
|
3050
|
+
p.down_3_on_var_index = -1;
|
2986
3051
|
}
|
2987
3052
|
}
|
2988
3053
|
|
@@ -3001,7 +3066,9 @@ class VirtualMachine {
|
|
3001
3066
|
// Some "data-only" link multipliers require additional variables.
|
3002
3067
|
if(p.needsOnOffData) {
|
3003
3068
|
p.on_off_var_index = this.addVariable('OO', p);
|
3004
|
-
p.
|
3069
|
+
if(p.needsIsZeroData) {
|
3070
|
+
p.is_zero_var_index = this.addVariable('IZ', p);
|
3071
|
+
}
|
3005
3072
|
// To detect startup, one more variable is needed
|
3006
3073
|
if(p.needsStartUpData) {
|
3007
3074
|
p.start_up_var_index = this.addVariable('SU', p);
|
@@ -3017,6 +3084,27 @@ class VirtualMachine {
|
|
3017
3084
|
p.shut_down_var_index = this.addVariable('SD', p);
|
3018
3085
|
}
|
3019
3086
|
}
|
3087
|
+
if(p.grid) {
|
3088
|
+
// Processes representing power grid elements are bi-directional
|
3089
|
+
// and hence need separate UP and DOWN flow variables.
|
3090
|
+
p.up_1_var_index = this.addVariable('U1', p);
|
3091
|
+
p.up_1_on_var_index = this.addVariable('UO1', p);
|
3092
|
+
p.down_1_var_index = this.addVariable('D1', p);
|
3093
|
+
p.down_1_on_var_index = this.addVariable('DO1', p);
|
3094
|
+
// Additional UP and DOWN is needed for each additional loss slope.
|
3095
|
+
if(p.grid.loss_approximation > 1) {
|
3096
|
+
p.up_2_var_index = this.addVariable('U2', p);
|
3097
|
+
p.up_2_on_var_index = this.addVariable('UO2', p);
|
3098
|
+
p.down_2_var_index = this.addVariable('D2', p);
|
3099
|
+
p.down_2_on_var_index = this.addVariable('DO2', p);
|
3100
|
+
if(p.grid.loss_approximation > 2) {
|
3101
|
+
p.up_3_var_index = this.addVariable('U3', p);
|
3102
|
+
p.up_3_on_var_index = this.addVariable('UO3', p);
|
3103
|
+
p.down_3_var_index = this.addVariable('D3', p);
|
3104
|
+
p.down_3_on_var_index = this.addVariable('DO3', p);
|
3105
|
+
}
|
3106
|
+
}
|
3107
|
+
}
|
3020
3108
|
// NOTES:
|
3021
3109
|
// (1) Processes have NO slack variables, because sufficient slack is
|
3022
3110
|
// provided by adding slack variables to products; these slack
|
@@ -3222,7 +3310,7 @@ class VirtualMachine {
|
|
3222
3310
|
|
3223
3311
|
// Log if run is performed in "diagnosis" mode.
|
3224
3312
|
if(this.diagnose) {
|
3225
|
-
this.logMessage(
|
3313
|
+
this.logMessage(1, 'DIAGNOSTIC RUN' +
|
3226
3314
|
(MODEL.always_diagnose ? ' (default -- see model settings)': '') +
|
3227
3315
|
'\n- slack variables on products and constraints' +
|
3228
3316
|
'\n- finite bounds on all processes');
|
@@ -3232,10 +3320,25 @@ class VirtualMachine {
|
|
3232
3320
|
MODEL.inferIgnoredEntities();
|
3233
3321
|
const n = Object.keys(MODEL.ignored_entities).length;
|
3234
3322
|
if(n > 0) {
|
3235
|
-
this.logMessage(
|
3323
|
+
this.logMessage(1,
|
3236
3324
|
pluralS(n, 'entity', 'entities') + ' will be ignored');
|
3237
3325
|
}
|
3238
3326
|
|
3327
|
+
// Infer cycle basis for combined power grids for which Kirchhoff's
|
3328
|
+
// voltage law must be enforced.
|
3329
|
+
if(MODEL.with_power_flow) {
|
3330
|
+
MONITOR.logMessage(1, 'POWER FLOW: ' +
|
3331
|
+
pluralS(Object.keys(MODEL.power_grids).length, 'grid'));
|
3332
|
+
POWER_GRID_MANAGER.inferCycleBasis();
|
3333
|
+
if(POWER_GRID_MANAGER.messages.length > 1) {
|
3334
|
+
UI.warn('Check monitor for power grid warnings');
|
3335
|
+
}
|
3336
|
+
MONITOR.logMessage(1, POWER_GRID_MANAGER.messages.join('\n'));
|
3337
|
+
if(POWER_GRID_MANAGER.cycle_basis.length) this.logMessage(1,
|
3338
|
+
'Enforcing Kirchhoff\'s voltage law for ' +
|
3339
|
+
POWER_GRID_MANAGER.cycleBasisAsString);
|
3340
|
+
}
|
3341
|
+
|
3239
3342
|
// FIRST: Define indices for all variables (index = Simplex tableau
|
3240
3343
|
// column number).
|
3241
3344
|
|
@@ -3288,7 +3391,7 @@ class VirtualMachine {
|
|
3288
3391
|
for(l = 0; l < c.bound_lines.length; l++) {
|
3289
3392
|
const bl = c.bound_lines[l];
|
3290
3393
|
bl.sos_var_indices = [];
|
3291
|
-
if(bl.
|
3394
|
+
if(bl.constrainsY) {
|
3292
3395
|
// Define SOS2 variables w[i] (plus associated binaries if
|
3293
3396
|
// solver does not support special ordered sets).
|
3294
3397
|
// NOTE: `addVariable` will add as many as there are points!
|
@@ -3379,9 +3482,16 @@ class VirtualMachine {
|
|
3379
3482
|
// NOTE: If UB = LB, set UB to LB only if LB is defined,
|
3380
3483
|
// because LB expressions default to -INF while UB expressions
|
3381
3484
|
// default to +INF.
|
3382
|
-
ubx = (p.equal_bounds && lbx.defined ? lbx : p.upper_bound);
|
3485
|
+
ubx = (!p.grid && p.equal_bounds && lbx.defined ? lbx : p.upper_bound);
|
3383
3486
|
if(lbx.isStatic) lbx = lbx.result(0);
|
3384
|
-
if(ubx.isStatic)
|
3487
|
+
if(ubx.isStatic) {
|
3488
|
+
ubx = ubx.result(0);
|
3489
|
+
if(p.grid) lbx = -ubx;
|
3490
|
+
} else if (p.grid) {
|
3491
|
+
// When UB is dynamic, pass NULL as LB; the VM instruction will
|
3492
|
+
// interpret this as "LB = -UB".
|
3493
|
+
lbx = null;
|
3494
|
+
}
|
3385
3495
|
// NOTE: When semic_var_index is set, the lower bound must be
|
3386
3496
|
// zero, as the semi-continuous lower bound is implemented with
|
3387
3497
|
// a binary variable.
|
@@ -3482,39 +3592,50 @@ class VirtualMachine {
|
|
3482
3592
|
const p = MODEL.processes[k];
|
3483
3593
|
// Only consider processes owned by this actor.
|
3484
3594
|
if(p.actor === a) {
|
3485
|
-
|
3486
|
-
|
3487
|
-
|
3488
|
-
|
3489
|
-
|
3490
|
-
|
3491
|
-
|
3492
|
-
|
3493
|
-
|
3494
|
-
|
3495
|
-
|
3496
|
-
|
3497
|
-
|
3498
|
-
|
3499
|
-
//
|
3595
|
+
if(p.grid) {
|
3596
|
+
// Grid processes are a special case, as they can have a
|
3597
|
+
// negative level and potentially multiple slopes. Hence a
|
3598
|
+
// special VM instruction.
|
3599
|
+
this.code.push([VMI_update_grid_process_cash_coefficients, p]);
|
3600
|
+
} else {
|
3601
|
+
// Iterate over links IN, but only consider consumed products
|
3602
|
+
// having a market price.
|
3603
|
+
for(j = 0; j < p.inputs.length; j++) {
|
3604
|
+
l = p.inputs[j];
|
3605
|
+
if(!MODEL.ignored_entities[l.identifier] &&
|
3606
|
+
l.from_node.price.defined) {
|
3607
|
+
if(l.from_node.price.isStatic && l.relative_rate.isStatic) {
|
3608
|
+
k = l.from_node.price.result(0) * l.relative_rate.result(0);
|
3609
|
+
// NOTE: VMI_update_cash_coefficient has at least 4 arguments:
|
3610
|
+
// flow (CONSUME or PRODUCE), type (specifies the number and
|
3611
|
+
// type of arguments), the level_var_index of the process,
|
3612
|
+
// and the delay.
|
3613
|
+
// NOTE: Input links cannot have delay, so then delay = 0.
|
3614
|
+
if(Math.abs(k) > VM.NEAR_ZERO) {
|
3615
|
+
// Consumption rate & price are static: pass one constant.
|
3616
|
+
this.code.push([VMI_update_cash_coefficient,
|
3617
|
+
[VM.CONSUME, VM.ONE_C, p.level_var_index, 0, k]]);
|
3618
|
+
}
|
3619
|
+
} else {
|
3620
|
+
// No further optimization: assume two dynamic expressions.
|
3500
3621
|
this.code.push([VMI_update_cash_coefficient,
|
3501
|
-
[VM.CONSUME, VM.
|
3622
|
+
[VM.CONSUME, VM.TWO_X, p.level_var_index, 0,
|
3623
|
+
l.from_node.price, l.relative_rate]]);
|
3502
3624
|
}
|
3503
|
-
} else {
|
3504
|
-
// No further optimization: assume two dynamic expressions.
|
3505
|
-
this.code.push([VMI_update_cash_coefficient,
|
3506
|
-
[VM.CONSUME, VM.TWO_X, p.level_var_index, 0,
|
3507
|
-
l.from_node.price, l.relative_rate]]);
|
3508
3625
|
}
|
3509
|
-
}
|
3510
|
-
}
|
3511
|
-
|
3512
|
-
//
|
3513
|
-
//
|
3626
|
+
} // END of FOR ALL input links
|
3627
|
+
}
|
3628
|
+
// Now iterate over links OUT, but only consider produced
|
3629
|
+
// products having a (non-zero) market price.
|
3630
|
+
// NOTE: Grid processes can have output links to *data* products,
|
3631
|
+
// so do NOT skip this iteration...
|
3514
3632
|
for(j = 0; j < p.outputs.length; j++) {
|
3515
3633
|
l = p.outputs[j];
|
3516
|
-
const
|
3517
|
-
|
3634
|
+
const
|
3635
|
+
tnpx = l.to_node.price,
|
3636
|
+
// ... but DO skip links from grid processes to regular products.
|
3637
|
+
skip = p.grid && !l.to_node.is_data;
|
3638
|
+
if(!(skip || MODEL.ignored_entities[l.identifier]) && tnpx.defined &&
|
3518
3639
|
!(tnpx.isStatic && Math.abs(tnpx.result(0)) < VM.NEAR_ZERO)) {
|
3519
3640
|
// By default, use the process level as multiplier.
|
3520
3641
|
vi = p.level_var_index;
|
@@ -3609,8 +3730,10 @@ class VirtualMachine {
|
|
3609
3730
|
// Check whether any VMI_update_cash_coefficient instructions have
|
3610
3731
|
// been added. If so, the objective function will maximze weighted
|
3611
3732
|
// sum of actor cash flows, otherwise minimize sum of process levels.
|
3733
|
+
const last_vmi = this.code[this.code.length - 1][0];
|
3612
3734
|
this.no_cash_flows = this.no_cash_flows &&
|
3613
|
-
|
3735
|
+
last_vmi !== VMI_update_cash_coefficient &&
|
3736
|
+
last_vmi !== VMI_update_grid_process_cash_coefficients;
|
3614
3737
|
|
3615
3738
|
// ALWAYS add the two cash flow constraints for this actor, as both
|
3616
3739
|
// cash flow variables must be computed (will be 0 if no cash flows).
|
@@ -3749,6 +3872,21 @@ class VirtualMachine {
|
|
3749
3872
|
}
|
3750
3873
|
}
|
3751
3874
|
}
|
3875
|
+
|
3876
|
+
// NEXT: Add constraints for processes representing grid elements.
|
3877
|
+
if(MODEL.with_power_flow) {
|
3878
|
+
for(i = 0; i < process_keys.length; i++) {
|
3879
|
+
k = process_keys[i];
|
3880
|
+
if(!MODEL.ignored_entities[k]) {
|
3881
|
+
p = MODEL.processes[k];
|
3882
|
+
if(p.grid) {
|
3883
|
+
this.code.push([VMI_add_grid_process_constraints, p]);
|
3884
|
+
}
|
3885
|
+
}
|
3886
|
+
}
|
3887
|
+
this.code.push(
|
3888
|
+
[VMI_add_kirchhoff_constraints, POWER_GRID_MANAGER.cycle_basis]);
|
3889
|
+
}
|
3752
3890
|
|
3753
3891
|
// NEXT: Add product constraints to calculate (and constrain) their stock.
|
3754
3892
|
|
@@ -3786,7 +3924,9 @@ class VirtualMachine {
|
|
3786
3924
|
// Add coefficient -1 for level index variable of `p`.
|
3787
3925
|
this.code.push([VMI_add_const_to_coefficient,
|
3788
3926
|
[p.level_var_index, -1, 0]]);
|
3789
|
-
|
3927
|
+
// NOTE: Pass special constraint type parameter to indicate
|
3928
|
+
// that this constraint must be scaled by the cash scalar.
|
3929
|
+
this.code.push([VMI_add_constraint, VM.ACTOR_CASH]);
|
3790
3930
|
} else {
|
3791
3931
|
console.log('ANOMALY: no actor for cash flow product', p.displayName);
|
3792
3932
|
}
|
@@ -3818,8 +3958,20 @@ class VirtualMachine {
|
|
3818
3958
|
} else {
|
3819
3959
|
vi = fn.level_var_index;
|
3820
3960
|
}
|
3821
|
-
// First check
|
3822
|
-
if(l.multiplier === VM.
|
3961
|
+
// First check whether the link is a power flow.
|
3962
|
+
if(l.multiplier === VM.LM_LEVEL && !p.is_data && fn.grid) {
|
3963
|
+
// If so, pass the grid process to a special VM instruction
|
3964
|
+
// that will add coefficients that account for losses.
|
3965
|
+
// NOTES:
|
3966
|
+
// (1) The second parameter (+1) indicates that the
|
3967
|
+
// coefficients of the UP flows should be positive
|
3968
|
+
// and those of the DOWN flows should be negative
|
3969
|
+
// (because it is a P -> Q link).
|
3970
|
+
// (2) The rate and delay properties of the link are ignored.
|
3971
|
+
this.code.push(
|
3972
|
+
[VMI_add_power_flow_to_coefficients, [fn, 1]]);
|
3973
|
+
// Then check for throughput links, as these are elaborate.
|
3974
|
+
} else if(l.multiplier === VM.LM_THROUGHPUT) {
|
3823
3975
|
// Link `l` is Y-->Z and "reads" the total inflow into Y
|
3824
3976
|
// over links Xi-->Y having rate Ri and when Y is a
|
3825
3977
|
// product potentially also delay Di.
|
@@ -3936,19 +4088,35 @@ class VirtualMachine {
|
|
3936
4088
|
} // END IF not ignored
|
3937
4089
|
} // END FOR all inputs
|
3938
4090
|
|
3939
|
-
//
|
4091
|
+
// Subtract outflow from product P to consuming processes (outputs)
|
3940
4092
|
for(i = 0; i < p.outputs.length; i++) {
|
3941
|
-
// NOTE: only consider outputs to processes; data outflows do not subtract
|
3942
4093
|
l = p.outputs[i];
|
3943
4094
|
if(!MODEL.ignored_entities[l.identifier]) {
|
3944
|
-
|
3945
|
-
|
3946
|
-
|
3947
|
-
|
3948
|
-
|
4095
|
+
const tn = l.to_node;
|
4096
|
+
// NOTE: Only consider outputs to processes; data flows do
|
4097
|
+
// not subtract from their tail nodes.
|
4098
|
+
if(tn instanceof Process) {
|
4099
|
+
if(tn.grid) {
|
4100
|
+
// If the link is a power flow, pass the grid process to
|
4101
|
+
// a special VM instruction that will add coefficients that
|
4102
|
+
// account for losses.
|
4103
|
+
// NOTES:
|
4104
|
+
// (1) The second parameter (-1) indicates that the
|
4105
|
+
// coefficients of the UP flows should be negative
|
4106
|
+
// and those of the DOWN flows should be positive
|
4107
|
+
// (because it is a Q -> P link).
|
4108
|
+
// (2) The rate and delay properties of the link are ignored.
|
4109
|
+
this.code.push(
|
4110
|
+
[VMI_add_power_flow_to_coefficients, [tn, -1]]);
|
3949
4111
|
} else {
|
3950
|
-
|
3951
|
-
|
4112
|
+
const rr = l.relative_rate;
|
4113
|
+
if(rr.isStatic) {
|
4114
|
+
this.code.push([VMI_subtract_const_from_coefficient,
|
4115
|
+
[tn.level_var_index, rr.result(0), l.flow_delay]]);
|
4116
|
+
} else {
|
4117
|
+
this.code.push([VMI_subtract_var_from_coefficient,
|
4118
|
+
[tn.level_var_index, rr, l.flow_delay]]);
|
4119
|
+
}
|
3952
4120
|
}
|
3953
4121
|
}
|
3954
4122
|
}
|
@@ -4090,88 +4258,94 @@ class VirtualMachine {
|
|
4090
4258
|
// To deal with this, the default equations will NOT be set when UB <= 0,
|
4091
4259
|
// while the "exceptional" equations (q.v.) will NOT be set when UB > 0.
|
4092
4260
|
// This can be realized using a special VM instruction:
|
4093
|
-
ubx = (p.equal_bounds && p.lower_bound.defined
|
4261
|
+
ubx = (p.equal_bounds && p.lower_bound.defined && !p.grid ?
|
4262
|
+
p.lower_bound : p.upper_bound);
|
4094
4263
|
this.code.push([VMI_set_add_constraints_flag, [ubx, '>', 0]]);
|
4095
|
-
|
4096
|
-
//
|
4097
|
-
|
4098
|
-
|
4099
|
-
|
4100
|
-
|
4101
|
-
|
4102
|
-
|
4103
|
-
|
4104
|
-
|
4105
|
-
|
4106
|
-
|
4107
|
-
|
4108
|
-
|
4109
|
-
|
4110
|
-
|
4111
|
-
|
4112
|
-
|
4113
|
-
|
4114
|
-
|
4115
|
-
|
4116
|
-
|
4117
|
-
// Set coefficients vector to 0
|
4118
|
-
[VMI_clear_coefficients, null],
|
4119
|
-
// (b) L[t] - UB[t]*OO[t] <= 0
|
4120
|
-
[VMI_add_const_to_coefficient, [p.level_var_index, 1]]
|
4121
|
-
);
|
4122
|
-
if(ubx.isStatic) {
|
4123
|
-
// If UB is very high (typically: undefined, so +INF), try to
|
4124
|
-
// infer a lower value for UB to use for the ON/OFF binary.
|
4125
|
-
let ub = ubx.result(0),
|
4126
|
-
hub = ub;
|
4127
|
-
if(ub > VM.MEGA_UPPER_BOUND) {
|
4128
|
-
hub = p.highestUpperBound([]);
|
4129
|
-
// If UB still very high, warn modeler on infoline and in monitor
|
4130
|
-
if(hub > VM.MEGA_UPPER_BOUND) {
|
4131
|
-
const msg = 'High upper bound (' + this.sig4Dig(hub) +
|
4132
|
-
') for <strong>' + p.displayName + '</strong>' +
|
4133
|
-
' will compromise computation of its binary variables';
|
4134
|
-
UI.warn(msg);
|
4135
|
-
this.logMessage(this.block_count,
|
4136
|
-
'WARNING: ' + msg.replace(/<\/?strong>/g, '"'));
|
4137
|
-
}
|
4264
|
+
// This instruction ensures that when UB <= 0, the constraints for
|
4265
|
+
// binaries will be prepared, but NOT added to the MILP problem.
|
4266
|
+
|
4267
|
+
// NOTE: For grid element processes, the ON/OFF binary is set by
|
4268
|
+
// the VMI_add_grid_process_constraints.
|
4269
|
+
if(!p.grid) {
|
4270
|
+
this.code.push(
|
4271
|
+
// Set coefficients vector to 0
|
4272
|
+
[VMI_clear_coefficients, null],
|
4273
|
+
// (a) L[t] - LB[t]*OO[t] >= 0
|
4274
|
+
[VMI_add_const_to_coefficient, [p.level_var_index, 1]]
|
4275
|
+
);
|
4276
|
+
if(p.lower_bound.isStatic) {
|
4277
|
+
let lb = p.lower_bound.result(0);
|
4278
|
+
if(Math.abs(lb) < VM.NEAR_ZERO) lb = VM.ON_OFF_THRESHOLD;
|
4279
|
+
this.code.push([VMI_subtract_const_from_coefficient,
|
4280
|
+
[p.on_off_var_index, lb]]);
|
4281
|
+
} else {
|
4282
|
+
this.code.push([VMI_subtract_var_from_coefficient,
|
4283
|
+
// NOTE: the 3rd parameter signals VM to use the ON/OFF threshold
|
4284
|
+
// value when the LB evaluates as near-zero
|
4285
|
+
[p.on_off_var_index, p.lower_bound, VM.ON_OFF_THRESHOLD]]);
|
4138
4286
|
}
|
4139
|
-
|
4140
|
-
|
4141
|
-
|
4142
|
-
|
4287
|
+
this.code.push(
|
4288
|
+
[VMI_add_constraint, VM.GE], // >= 0 as default RHS = 0
|
4289
|
+
// Set coefficients vector to 0
|
4290
|
+
[VMI_clear_coefficients, null],
|
4291
|
+
// (b) L[t] - UB[t]*OO[t] <= 0
|
4292
|
+
[VMI_add_const_to_coefficient, [p.level_var_index, 1]]
|
4293
|
+
);
|
4294
|
+
if(ubx.isStatic) {
|
4295
|
+
// If UB is very high (typically: undefined, so +INF), try to
|
4296
|
+
// infer a lower value for UB to use for the ON/OFF binary.
|
4297
|
+
let ub = ubx.result(0),
|
4298
|
+
hub = ub;
|
4299
|
+
if(ub > VM.MEGA_UPPER_BOUND) {
|
4300
|
+
hub = p.highestUpperBound([]);
|
4301
|
+
// If UB still very high, warn modeler on infoline and in monitor
|
4302
|
+
if(hub > VM.MEGA_UPPER_BOUND) {
|
4303
|
+
const msg = 'High upper bound (' + this.sig4Dig(hub) +
|
4304
|
+
') for <strong>' + p.displayName + '</strong>' +
|
4305
|
+
' will compromise computation of its binary variables';
|
4306
|
+
UI.warn(msg);
|
4307
|
+
this.logMessage(this.block_count,
|
4308
|
+
'WARNING: ' + msg.replace(/<\/?strong>/g, '"'));
|
4309
|
+
}
|
4310
|
+
}
|
4311
|
+
if(hub !== ub) {
|
4312
|
+
ub = hub;
|
4313
|
+
this.logMessage(1,
|
4314
|
+
`Inferred upper bound for ${p.displayName}: ${this.sig4Dig(ub)}`);
|
4315
|
+
}
|
4316
|
+
this.code.push([VMI_subtract_const_from_coefficient,
|
4317
|
+
[p.on_off_var_index, ub]]);
|
4318
|
+
} else {
|
4319
|
+
// NOTE: no check (yet) for high values when UB is an expression
|
4320
|
+
// (this could be achieved by a special VM instruction)
|
4321
|
+
this.code.push([VMI_subtract_var_from_coefficient,
|
4322
|
+
[p.on_off_var_index, ubx]]);
|
4143
4323
|
}
|
4144
|
-
this.code.push([
|
4145
|
-
[p.on_off_var_index, ub]]);
|
4146
|
-
} else {
|
4147
|
-
// NOTE: no check (yet) for high values when UB is an expression
|
4148
|
-
// (this could be achieved by a special VM instruction)
|
4149
|
-
this.code.push([VMI_subtract_var_from_coefficient,
|
4150
|
-
[p.on_off_var_index, ubx]]);
|
4324
|
+
this.code.push([VMI_add_constraint, VM.LE]); // <= 0 as default RHS = 0
|
4151
4325
|
}
|
4152
|
-
|
4153
|
-
[VMI_add_constraint, VM.LE], // <= 0 as default RHS = 0
|
4326
|
+
if(p.is_zero_var_index >= 0) {
|
4154
4327
|
// Also add the constraints for is-zero
|
4155
|
-
|
4156
|
-
|
4157
|
-
|
4158
|
-
|
4159
|
-
|
4160
|
-
|
4161
|
-
|
4162
|
-
|
4163
|
-
|
4164
|
-
|
4165
|
-
|
4166
|
-
|
4167
|
-
|
4168
|
-
|
4169
|
-
|
4170
|
-
|
4171
|
-
|
4328
|
+
this.code.push(
|
4329
|
+
[VMI_clear_coefficients, null],
|
4330
|
+
// (c) OO[t] + IZ[t] = 1
|
4331
|
+
[VMI_add_const_to_coefficient, [p.is_zero_var_index, 1]],
|
4332
|
+
[VMI_add_const_to_coefficient, [p.on_off_var_index, 1]],
|
4333
|
+
[VMI_set_const_rhs, 1],
|
4334
|
+
[VMI_add_constraint, VM.EQ],
|
4335
|
+
// (d) L[t] + IZ[t] >= LB[t]
|
4336
|
+
[VMI_clear_coefficients, null],
|
4337
|
+
[VMI_add_const_to_coefficient, [p.level_var_index, 1]],
|
4338
|
+
[VMI_add_const_to_coefficient, [p.is_zero_var_index, 1]]
|
4339
|
+
);
|
4340
|
+
// NOTE: for semicontinuous variable, always use LB = 0
|
4341
|
+
if(p.lower_bound.isStatic || p.level_to_zero) {
|
4342
|
+
const plb = (p.level_to_zero ? 0 : p.lower_bound.result(0));
|
4343
|
+
this.code.push([VMI_set_const_rhs, plb]);
|
4344
|
+
} else {
|
4345
|
+
this.code.push([VMI_set_var_rhs, p.lower_bound]);
|
4346
|
+
}
|
4347
|
+
this.code.push([VMI_add_constraint, VM.GE]);
|
4172
4348
|
}
|
4173
|
-
this.code.push([VMI_add_constraint, VM.GE]);
|
4174
|
-
|
4175
4349
|
// Also add constraints for start-up (if needed)
|
4176
4350
|
if(p.start_up_var_index >= 0) {
|
4177
4351
|
this.code.push(
|
@@ -4271,18 +4445,22 @@ class VirtualMachine {
|
|
4271
4445
|
// NOTE: toggle the flag so that if UB <= 0, the following constraints
|
4272
4446
|
// for setting the binary variables WILL be added
|
4273
4447
|
this.code.push(
|
4274
|
-
|
4275
|
-
|
4276
|
-
|
4277
|
-
|
4278
|
-
|
4279
|
-
|
4280
|
-
|
4281
|
-
|
4282
|
-
|
4283
|
-
|
4284
|
-
|
4285
|
-
|
4448
|
+
[VMI_toggle_add_constraints_flag, null],
|
4449
|
+
// When UB <= 0, add these much simpler "exceptional" constraints:
|
4450
|
+
// OO[t] = 0
|
4451
|
+
[VMI_clear_coefficients, null],
|
4452
|
+
[VMI_add_const_to_coefficient, [p.on_off_var_index, 1]],
|
4453
|
+
[VMI_add_constraint, VM.EQ]
|
4454
|
+
);
|
4455
|
+
if(p.is_zero_var_index >= 0) {
|
4456
|
+
this.code.push(
|
4457
|
+
// IZ[t] = 1
|
4458
|
+
[VMI_clear_coefficients, null],
|
4459
|
+
[VMI_add_const_to_coefficient, [p.is_zero_var_index, 1]],
|
4460
|
+
[VMI_set_const_rhs, 1], // RHS = 1
|
4461
|
+
[VMI_add_constraint, VM.EQ]
|
4462
|
+
);
|
4463
|
+
}
|
4286
4464
|
// Add constraints for start-up and first commit only if needed
|
4287
4465
|
if(p.start_up_var_index >= 0) {
|
4288
4466
|
this.code.push(
|
@@ -4328,7 +4506,7 @@ class VirtualMachine {
|
|
4328
4506
|
}
|
4329
4507
|
}
|
4330
4508
|
|
4331
|
-
// NEXT: Add constraints.
|
4509
|
+
// NEXT: Add composite constraints.
|
4332
4510
|
// NOTE: As of version 1.0.10, constraints are implemented using special
|
4333
4511
|
// ordered sets (SOS2). This is effectuated with a dedicated VM instruction
|
4334
4512
|
// for each of its "active" bound lines. This instruction requires these
|
@@ -4336,10 +4514,6 @@ class VirtualMachine {
|
|
4336
4514
|
// - variable indices for the constraining node X, the constrained node Y
|
4337
4515
|
// - expressions for the LB and UB of X and Y
|
4338
4516
|
// - the bound line object, as this provides all further information
|
4339
|
-
// NOTE: For efficiency, the useBinaries flag is also passed, as it can
|
4340
|
-
// be determined at compile time whether a SOS constraint is needed
|
4341
|
-
// (bound lines are static), and whether the solver does not support
|
4342
|
-
// SOS, in which case binary variables must be used.
|
4343
4517
|
for(i = 0; i < constraint_keys.length; i++) {
|
4344
4518
|
k = constraint_keys[i];
|
4345
4519
|
if(!MODEL.ignored_entities[k]) {
|
@@ -4349,20 +4523,16 @@ class VirtualMachine {
|
|
4349
4523
|
x = c.from_node,
|
4350
4524
|
y = c.to_node;
|
4351
4525
|
for(j = 0; j < c.bound_lines.length; j++) {
|
4352
|
-
|
4353
|
-
|
4354
|
-
|
4355
|
-
|
4356
|
-
this.code.push([VMI_add_bound_line_constraint,
|
4357
|
-
[x.level_var_index, x.lower_bound, x.upper_bound,
|
4358
|
-
y.level_var_index, y.lower_bound, y.upper_bound,
|
4359
|
-
bl, this.noSupportForSOS && !bl.needsNoSOS]]);
|
4360
|
-
}
|
4526
|
+
this.code.push([VMI_add_bound_line_constraint,
|
4527
|
+
[x.level_var_index, x.lower_bound, x.upper_bound,
|
4528
|
+
y.level_var_index, y.lower_bound, y.upper_bound,
|
4529
|
+
c.bound_lines[j]]]);
|
4361
4530
|
}
|
4362
4531
|
}
|
4363
4532
|
} // end FOR all constraints
|
4533
|
+
|
4364
4534
|
MODEL.set_up = true;
|
4365
|
-
this.logMessage(
|
4535
|
+
this.logMessage(1,
|
4366
4536
|
`Problem formulation took ${this.elapsedTime} seconds.`);
|
4367
4537
|
} // END of setup_problem function
|
4368
4538
|
|
@@ -4464,6 +4634,24 @@ class VirtualMachine {
|
|
4464
4634
|
if(cv && !cv[0].startsWith('C')) cc[ci] *= m;
|
4465
4635
|
}
|
4466
4636
|
}
|
4637
|
+
// In case the model contains data products that represent an actor
|
4638
|
+
// cash flow, the coefficients of the constraint that equates the
|
4639
|
+
// product level to the cash flow must be *multiplied* by the cash
|
4640
|
+
// scalar so that they equal the cash flow in the model's monetary unit.
|
4641
|
+
for(let i = 0; i < this.actor_cash_constraints.length; i++) {
|
4642
|
+
const cc = this.matrix[this.actor_cash_constraints[i]];
|
4643
|
+
for(let ci in cc) if(cc.hasOwnProperty(ci)) {
|
4644
|
+
if(ci < this.chunk_offset) {
|
4645
|
+
// NOTE: Subtract 1 as variables array is zero-based.
|
4646
|
+
cv = this.variables[(ci - 1) % this.cols];
|
4647
|
+
} else {
|
4648
|
+
// Chunk variable array is zero-based.
|
4649
|
+
cv = this.chunk_variables[ci - this.chunk_offset];
|
4650
|
+
}
|
4651
|
+
// NOTE: Scale coefficients of cash variables only.
|
4652
|
+
if(cv && cv[0].startsWith('C')) cc[ci] *= this.cash_scalar;
|
4653
|
+
}
|
4654
|
+
}
|
4467
4655
|
}
|
4468
4656
|
|
4469
4657
|
checkForInfinity(n) {
|
@@ -4549,7 +4737,7 @@ class VirtualMachine {
|
|
4549
4737
|
}
|
4550
4738
|
if(b <= this.nr_of_time_steps && a.cash_out[b] < -0.005) {
|
4551
4739
|
this.logMessage(block, `${this.WARNING}(t=${b}${round}) ` +
|
4552
|
-
a.displayName + ' cash
|
4740
|
+
a.displayName + ' cash OUT = ' + a.cash_out[b].toPrecision(2));
|
4553
4741
|
}
|
4554
4742
|
// Advance column offset in tableau by the # cols per time step.
|
4555
4743
|
j += this.cols;
|
@@ -4564,7 +4752,8 @@ class VirtualMachine {
|
|
4564
4752
|
p = MODEL.processes[o],
|
4565
4753
|
has_OO = (p.on_off_var_index >= 0),
|
4566
4754
|
has_SU = (p.start_up_var_index >= 0),
|
4567
|
-
has_SD = (p.shut_down_var_index >= 0)
|
4755
|
+
has_SD = (p.shut_down_var_index >= 0),
|
4756
|
+
grid = p.grid;
|
4568
4757
|
// Clear all start-ups and shut-downs at t >= bb.
|
4569
4758
|
if(has_SU) p.resetStartUps(bb);
|
4570
4759
|
if(has_SD) p.resetShutDowns(bb);
|
@@ -4747,6 +4936,9 @@ class VirtualMachine {
|
|
4747
4936
|
this.variables_to_fixate = {};
|
4748
4937
|
// FIRST: Calculate the actual flows on links.
|
4749
4938
|
let b, bt, p, pl, ld, ci;
|
4939
|
+
for(let g in MODEL.power_grids) if(MODEL.power_grids.hasOwnProperty(g)) {
|
4940
|
+
MODEL.power_grids[g].total_losses = 0;
|
4941
|
+
}
|
4750
4942
|
for(let l in MODEL.links) if(MODEL.links.hasOwnProperty(l) &&
|
4751
4943
|
!MODEL.ignored_entities[l]) {
|
4752
4944
|
l = MODEL.links[l];
|
@@ -4756,7 +4948,7 @@ class VirtualMachine {
|
|
4756
4948
|
b = bb;
|
4757
4949
|
// Iterate over all time steps in this chunk.
|
4758
4950
|
for(let i = 0; i < cbl; i++) {
|
4759
|
-
// NOTE: Flows may have a delay
|
4951
|
+
// NOTE: Flows may have a delay (but will be 0 for grid processes).
|
4760
4952
|
ld = l.actualDelay(b);
|
4761
4953
|
bt = b - ld;
|
4762
4954
|
latest_time_step = Math.max(latest_time_step, bt);
|
@@ -4857,13 +5049,38 @@ class VirtualMachine {
|
|
4857
5049
|
}
|
4858
5050
|
}
|
4859
5051
|
// Preserve special values such as INF, UNDEFINED and VM error codes.
|
4860
|
-
|
4861
|
-
|
4862
|
-
|
5052
|
+
let rr = l.relative_rate.result(bt);
|
5053
|
+
if(p.grid) {
|
5054
|
+
// For grid processes, rates depend on losses, which depend on
|
5055
|
+
// the process level, and whether the link is P -> Q or Q -> P.
|
5056
|
+
rr = 1;
|
5057
|
+
if(p.grid.loss_approximation > 0 &&
|
5058
|
+
((pl > 0 && p === l.from_node) ||
|
5059
|
+
(pl < 0 && p === l.to_node))) {
|
5060
|
+
const alr = p.actualLossRate(bt);
|
5061
|
+
rr = 1 - alr;
|
5062
|
+
p.grid.total_losses += alr * Math.abs(pl);
|
5063
|
+
}
|
5064
|
+
}
|
5065
|
+
const af = this.severestIssue([pl, rr], rr * pl);
|
4863
5066
|
l.actual_flow[b] = (Math.abs(af) > VM.NEAR_ZERO ? af : 0);
|
4864
5067
|
b++;
|
4865
5068
|
}
|
4866
5069
|
}
|
5070
|
+
// Report power losses per grid, if applicable.
|
5071
|
+
if(MODEL.with_power_flow) {
|
5072
|
+
const ll = [];
|
5073
|
+
for(let g in MODEL.power_grids) if(MODEL.power_grids.hasOwnProperty(g)) {
|
5074
|
+
const pg = MODEL.power_grids[g];
|
5075
|
+
if(pg.loss_approximation > 0) {
|
5076
|
+
ll.push(`${pg.name}: ${VM.sig4Dig(pg.total_losses / cbl)} ${pg.power_unit}`);
|
5077
|
+
}
|
5078
|
+
}
|
5079
|
+
if(ll.length) {
|
5080
|
+
this.logMessage(block, 'Average power grid losses per time step:\n ' +
|
5081
|
+
ll.join('\n ') + '\n');
|
5082
|
+
}
|
5083
|
+
}
|
4867
5084
|
|
4868
5085
|
// THEN: Calculate cash flows one step at a time because of delays.
|
4869
5086
|
b = bb;
|
@@ -5188,6 +5405,12 @@ class VirtualMachine {
|
|
5188
5405
|
// of matrix rows that then need to be scaled.
|
5189
5406
|
this.cash_scalar = 1;
|
5190
5407
|
this.cash_constraints = [];
|
5408
|
+
// NOTE: The model may contain data products that represent a cash
|
5409
|
+
// flow property of an actor. To calculate the actual value of such
|
5410
|
+
// properties, the coefficients in the effectuating constraint must
|
5411
|
+
// be *multiplied* by the scalar to compensate for the downscaling
|
5412
|
+
// explained above.
|
5413
|
+
this.actor_cash_constraints = [];
|
5191
5414
|
// Vector for the objective function coefficients.
|
5192
5415
|
this.objective = {};
|
5193
5416
|
// Vectors for the bounds on decision variables.
|
@@ -5447,6 +5670,7 @@ class VirtualMachine {
|
|
5447
5670
|
}
|
5448
5671
|
let c,
|
5449
5672
|
p,
|
5673
|
+
v,
|
5450
5674
|
line = '';
|
5451
5675
|
// NOTE: Iterate over ALL columns to maintain variable order.
|
5452
5676
|
let n = abl * this.cols + this.chunk_variables.length;
|
@@ -5527,31 +5751,23 @@ class VirtualMachine {
|
|
5527
5751
|
break;
|
5528
5752
|
}
|
5529
5753
|
}
|
5530
|
-
|
5754
|
+
v = vbl(p);
|
5531
5755
|
if(lb === ub) {
|
5532
|
-
|
5756
|
+
line = (lb !== null ? ` ${v} = ${lb}` : '');
|
5533
5757
|
} else {
|
5758
|
+
const
|
5759
|
+
lbfree = (lb === null || lb <= VM.SOLVER_MINUS_INFINITY),
|
5760
|
+
ubfree = (ub === null || ub >= VM.SOLVER_PLUS_INFINITY);
|
5534
5761
|
// NOTE: By default, lower bound of variables is 0.
|
5535
|
-
|
5536
|
-
|
5537
|
-
// Explicitly denote free variables.
|
5538
|
-
if(lb === null && ub === null && !this.is_binary[p]) {
|
5539
|
-
line += ' free';
|
5540
|
-
} else {
|
5541
|
-
// Separate lines for LB and UB if specified.
|
5542
|
-
if(ub !== null) line += ' <= ' + ub;
|
5543
|
-
if(lb !== null && lb !== 0) line += `\n ${vbl(p)} >= ${lb}`;
|
5544
|
-
}
|
5762
|
+
if(cplex && lbfree && ubfree) {
|
5763
|
+
line = ` ${v} ${this.is_binary[p] ? '<= 1' : 'free'}`;
|
5545
5764
|
} else {
|
5546
5765
|
// Bounds can be specified on a single line: lb <= X001 <= ub.
|
5547
|
-
|
5548
|
-
|
5549
|
-
|
5550
|
-
line =
|
5766
|
+
if(lb || lb === 0) {
|
5767
|
+
line = ` ${lb} <= ${v}${ubfree ? '' : ' <= ' + ub}`;
|
5768
|
+
} else {
|
5769
|
+
line = (ubfree ? '' : ` ${v} <= ${ub}`);
|
5551
5770
|
}
|
5552
|
-
if(ub !== null && ub < 1e+25) line += ' <= ' + ub;
|
5553
|
-
// NOTE: Do not add line if both bounds are infinite.
|
5554
|
-
if(line.indexOf('<=') < 0) line = '';
|
5555
5771
|
}
|
5556
5772
|
}
|
5557
5773
|
if(line) this.lines += line + EOL;
|
@@ -6190,8 +6406,8 @@ Solver status = ${json.status}`);
|
|
6190
6406
|
this.MINUS_INFINITY = -this.DIAGNOSIS_UPPER_BOUND;
|
6191
6407
|
console.log('DIAGNOSIS ON');
|
6192
6408
|
} else {
|
6193
|
-
this.PLUS_INFINITY =
|
6194
|
-
this.MINUS_INFINITY =
|
6409
|
+
this.PLUS_INFINITY = this.SOLVER_PLUS_INFINITY;
|
6410
|
+
this.MINUS_INFINITY = this.SOLVER_MINUS_INFINITY;
|
6195
6411
|
console.log('DIAGNOSIS OFF');
|
6196
6412
|
}
|
6197
6413
|
// The "propt to diagnose" flag is set when some block posed an
|
@@ -7799,6 +8015,18 @@ function randomBinomial(n, p) {
|
|
7799
8015
|
}
|
7800
8016
|
}
|
7801
8017
|
|
8018
|
+
// Function that computes the cumulative probability P(X <= x) when X
|
8019
|
+
// has a N(mu, sigma) distribution. Accuracy is about 1e-6.
|
8020
|
+
function normalCumulativeProbability(mu, sigma, x) {
|
8021
|
+
const
|
8022
|
+
t = 1 / (1 + 0.2316419 * Math.abs(x)),
|
8023
|
+
d = 0.3989423 * Math.exp(-0.5 * x * x),
|
8024
|
+
p = d * t * (0.3193815 + t * (-0.3565638 + t * (1.781478 +
|
8025
|
+
t * (-1.821256 + T * 1.330274))));
|
8026
|
+
if(x > 0) return 1 - p;
|
8027
|
+
return p;
|
8028
|
+
}
|
8029
|
+
|
7802
8030
|
// Global array as cache for computation of factorial numbers.
|
7803
8031
|
const FACTORIALS = [0, 1];
|
7804
8032
|
|
@@ -7903,8 +8131,12 @@ function VMI_set_bounds(args) {
|
|
7903
8131
|
// When diagnosing an unbounded problem, use low value for INFINITY,
|
7904
8132
|
// but the optional fourth parameter indicates whether the solver's
|
7905
8133
|
// infinity values should override the diagnosis INFINITY.
|
7906
|
-
|
7907
|
-
|
8134
|
+
// NOTE: For grid processes, the bounds are always "capped" so as
|
8135
|
+
// to permit Big M constraints for the associated binary variables.
|
8136
|
+
inf_is_free = (args.length > 3 && args[3]),
|
8137
|
+
inf_val = (vbl.grid ? VM.UNLIMITED_POWER_FLOW :
|
8138
|
+
(VM.diagnose && !inf_is_free ?
|
8139
|
+
VM.DIAGNOSIS_UPPER_BOUND : VM.SOLVER_PLUS_INFINITY));
|
7908
8140
|
let l,
|
7909
8141
|
u,
|
7910
8142
|
fixed = (vi in VM.fixed_var_indices[r - 1]);
|
@@ -7924,26 +8156,35 @@ function VMI_set_bounds(args) {
|
|
7924
8156
|
} else {
|
7925
8157
|
// Set bounds as specified by the two arguments.
|
7926
8158
|
l = args[1];
|
7927
|
-
if(l instanceof Expression) l = l.result(VM.t);
|
7928
|
-
if(l === VM.UNDEFINED) l = 0;
|
7929
8159
|
u = args[2];
|
7930
8160
|
if(u instanceof Expression) u = u.result(VM.t);
|
7931
|
-
u = Math.min(u,
|
7932
|
-
|
7933
|
-
if(
|
8161
|
+
u = Math.min(u, inf_val);
|
8162
|
+
// When LB is passed as NULL, this indicates: LB = -UB.
|
8163
|
+
if(l === null) {
|
8164
|
+
l = -u;
|
8165
|
+
} else {
|
8166
|
+
if(l instanceof Expression) l = l.result(VM.t);
|
8167
|
+
if(l === VM.UNDEFINED) {
|
8168
|
+
l = 0;
|
8169
|
+
} else {
|
8170
|
+
l = Math.max(l, -inf_val);
|
8171
|
+
}
|
8172
|
+
}
|
7934
8173
|
fixed = '';
|
7935
8174
|
}
|
7936
8175
|
// NOTE: To see in the console whether fixing across rounds works, insert
|
7937
8176
|
// "fixed !== '' || " before DEBUGGING below.
|
7938
|
-
if(
|
7939
|
-
|
7940
|
-
|
8177
|
+
if(isNaN(l) || isNaN(u) ||
|
8178
|
+
typeof l !== 'number' || typeof u !== 'number' || DEBUGGING) {
|
8179
|
+
console.log(['set_bounds [', k, '] ', vbl.displayName, '[',
|
8180
|
+
VM.variables[vi - 1][0],'] t = ', VM.t, ' LB = ', VM.sig4Dig(l),
|
8181
|
+
', UB = ', VM.sig4Dig(u), fixed].join(''), l, u, inf_val);
|
7941
8182
|
}
|
7942
8183
|
// NOTE: Since the VM vectors for lower bounds and upper bounds are
|
7943
|
-
// initialized with default values (0 for LB, +INF for UB),
|
7944
|
-
//
|
7945
|
-
if(l !== 0
|
7946
|
-
|
8184
|
+
// initialized with default values (0 for LB, +INF for UB), the bounds
|
8185
|
+
// need only be set when they differ from these default values.
|
8186
|
+
if(l !== 0) VM.lower_bounds[k] = l;
|
8187
|
+
if(u < VM.SOLVER_PLUS_INFINITY) {
|
7947
8188
|
VM.upper_bounds[k] = u;
|
7948
8189
|
// If associated node is FROM-node of a "peak increase" link, then
|
7949
8190
|
// the "peak increase" variables of this node must have the highest
|
@@ -7960,6 +8201,26 @@ function VMI_set_bounds(args) {
|
|
7960
8201
|
VM.upper_bounds[cvi] = u;
|
7961
8202
|
VM.upper_bounds[cvi + 1] = u;
|
7962
8203
|
}
|
8204
|
+
// For grid elements, bounds must be set on UP and DOWN variables.
|
8205
|
+
if(vbl.grid) {
|
8206
|
+
// When considering losses, partition range 0...UB in sections.
|
8207
|
+
const step = (vbl.grid.loss_approximation < 2 ? u :
|
8208
|
+
u / vbl.grid.loss_approximation);
|
8209
|
+
VM.upper_bounds[VM.offset + vbl.up_1_var_index] = step;
|
8210
|
+
VM.upper_bounds[VM.offset + vbl.down_1_var_index] = step;
|
8211
|
+
if(vbl.grid.loss_approximation > 1) {
|
8212
|
+
// Set UB for semi-contiuous variables Up & Down slope 2.
|
8213
|
+
VM.upper_bounds[VM.offset + vbl.up_2_var_index] = 2 * step;
|
8214
|
+
VM.upper_bounds[VM.offset + vbl.down_2_var_index] = 2 * step;
|
8215
|
+
if(vbl.grid.loss_approximation > 2) {
|
8216
|
+
// Set UB for semi-contiuous variables Up & Down slope 3.
|
8217
|
+
VM.upper_bounds[VM.offset + vbl.up_3_var_index] = 3 * step;
|
8218
|
+
VM.upper_bounds[VM.offset + vbl.down_3_var_index] = 3 * step;
|
8219
|
+
}
|
8220
|
+
}
|
8221
|
+
// NOTE: lower bounds are 0 for all variables; their semi-continuous
|
8222
|
+
// ranges are set by VMI_add_grid_process_constraints.
|
8223
|
+
}
|
7963
8224
|
}
|
7964
8225
|
}
|
7965
8226
|
|
@@ -8191,6 +8452,28 @@ function VMI_subtract_var_from_coefficient(args) {
|
|
8191
8452
|
}
|
8192
8453
|
}
|
8193
8454
|
|
8455
|
+
/* AUXILIARY FUNCTIONS for setting cash flow coefficients */
|
8456
|
+
|
8457
|
+
function addCashIn(index, value) {
|
8458
|
+
if(index in VM.cash_in_coefficients) {
|
8459
|
+
// Add value to coefficient if it already exists...
|
8460
|
+
VM.cash_in_coefficients[index] += value;
|
8461
|
+
} else {
|
8462
|
+
// ... and set it if it is new.
|
8463
|
+
VM.cash_in_coefficients[index] = value;
|
8464
|
+
}
|
8465
|
+
}
|
8466
|
+
|
8467
|
+
function addCashOut(index, value) {
|
8468
|
+
if(index in VM.cash_out_coefficients) {
|
8469
|
+
// Add value to coefficient if it already exists...
|
8470
|
+
VM.cash_out_coefficients[index] += value;
|
8471
|
+
} else {
|
8472
|
+
// ... and set it if it is new.
|
8473
|
+
VM.cash_out_coefficients[index] = value;
|
8474
|
+
}
|
8475
|
+
}
|
8476
|
+
|
8194
8477
|
function VMI_update_cash_coefficient(args) {
|
8195
8478
|
// `args`: [flow, type, level_var_index, delay, x1, x2, ...]
|
8196
8479
|
// NOTE: Flow is either CONSUME or PRODUCE; type can be ONE_C (one
|
@@ -8265,17 +8548,9 @@ function VMI_update_cash_coefficient(args) {
|
|
8265
8548
|
VM.cash_out_rhs += pl * price_rate;
|
8266
8549
|
}
|
8267
8550
|
} else if(r > 0) {
|
8268
|
-
|
8269
|
-
VM.cash_in_coefficients[plk] += price_rate;
|
8270
|
-
} else {
|
8271
|
-
VM.cash_in_coefficients[plk] = price_rate;
|
8272
|
-
}
|
8551
|
+
addCashIn(plk, price_rate);
|
8273
8552
|
} else if(r < 0) {
|
8274
|
-
|
8275
|
-
VM.cash_out_coefficients[plk] -= price_rate;
|
8276
|
-
} else {
|
8277
|
-
VM.cash_out_coefficients[plk] = -price_rate;
|
8278
|
-
}
|
8553
|
+
addCashOut(plk, -price_rate);
|
8279
8554
|
}
|
8280
8555
|
}
|
8281
8556
|
// NOTE: For spinning reserve and highest increment, flow will always
|
@@ -8303,21 +8578,111 @@ function VMI_update_cash_coefficient(args) {
|
|
8303
8578
|
VM.cash_out_rhs -= knownValue(vi, VM.t - d) * r;
|
8304
8579
|
}
|
8305
8580
|
} else if(r > 0) {
|
8306
|
-
|
8307
|
-
VM.cash_in_coefficients[k] -= r;
|
8308
|
-
} else {
|
8309
|
-
VM.cash_in_coefficients[k] = -r;
|
8310
|
-
}
|
8581
|
+
addCashIn(k, -r);
|
8311
8582
|
} else if(r < 0) {
|
8312
8583
|
// NOTE: Test for r < 0 because no action is needed if r = 0.
|
8313
|
-
|
8314
|
-
|
8315
|
-
|
8316
|
-
|
8584
|
+
addCashOut(k, r);
|
8585
|
+
}
|
8586
|
+
}
|
8587
|
+
|
8588
|
+
function VMI_update_grid_process_cash_coefficients(p) {
|
8589
|
+
// Update cash flow coefficients for process `p` that relate to its
|
8590
|
+
// regular input and output link (data links are handled by means of
|
8591
|
+
// VMI_update_cash_coefficient).
|
8592
|
+
let fn = null,
|
8593
|
+
tn = null;
|
8594
|
+
for(let i = 0; i <= p.inputs.length; i++) {
|
8595
|
+
const l = p.inputs[i];
|
8596
|
+
if(l.multiplier === VM.LM_LEVEL &&
|
8597
|
+
!MODEL.ignored_entities[l.identifier]) {
|
8598
|
+
fn = l.from_node;
|
8599
|
+
break;
|
8600
|
+
}
|
8601
|
+
}
|
8602
|
+
for(let i = 0; i <= p.outputs.length; i++) {
|
8603
|
+
const l = p.outputs[i];
|
8604
|
+
if(l.multiplier === VM.LM_LEVEL &&
|
8605
|
+
!MODEL.ignored_entities[l.identifier]) {
|
8606
|
+
tn = l.to_node;
|
8607
|
+
break;
|
8608
|
+
}
|
8609
|
+
}
|
8610
|
+
const
|
8611
|
+
fp = (fn && fn.price.defined ? fn.price.result(VM.t) : 0),
|
8612
|
+
tp = (tn && tn.price.defined ? tn.price.result(VM.t) : 0);
|
8613
|
+
// Only proceed if process links to a product with a non-zero price.
|
8614
|
+
if(fp || tp) {
|
8615
|
+
const
|
8616
|
+
gpv = VM.gridProcessVarIndices(p, VM.offset),
|
8617
|
+
lr = p.lossRates(VM.t);
|
8618
|
+
if(fp > 0) {
|
8619
|
+
// If FROM node has price > 0, then all UP flows generate cash OUT
|
8620
|
+
// *without* loss while all DOWN flows generate cash IN *with* loss.
|
8621
|
+
for(let i = 0; i < gpv.slopes; i++) {
|
8622
|
+
addCashOut(gpv.up[i], -fp);
|
8623
|
+
addCashIn(gpv.down[i], (1 - lr[i]) * -fp);
|
8624
|
+
}
|
8625
|
+
} else if(fp < 0) {
|
8626
|
+
// If FROM node has price < 0, then all UP flows generate cash IN
|
8627
|
+
// *without* loss while all DOWN flows generate cash OUT *with* loss.
|
8628
|
+
for(let i = 0; i < gpv.slopes; i++) {
|
8629
|
+
addCashIn(gpv.up[i], fp);
|
8630
|
+
addCashOut(gpv.down[i], (1 - lr[i]) * fp);
|
8631
|
+
}
|
8632
|
+
}
|
8633
|
+
if(tp > 0) {
|
8634
|
+
// If TO node has price > 0, then all UP flows generate cash IN *with*
|
8635
|
+
// loss while all DOWN flows generate cash OUT *without* loss.
|
8636
|
+
for(let i = 0; i < gpv.slopes; i++) {
|
8637
|
+
addCashIn(gpv.up[i], (1 - lr[i]) * -tp);
|
8638
|
+
addCashOut(gpv.down[i], -tp);
|
8639
|
+
}
|
8640
|
+
} else if(tp < 0) {
|
8641
|
+
// If TO node has price < 0, then all UP flows generate cash OUT
|
8642
|
+
// *with* loss while all DOWN flows generate cash IN *without* loss.
|
8643
|
+
for(let i = 0; i < gpv.slopes; i++) {
|
8644
|
+
addCashOut(gpv.up[i], (1 - lr[i]) * tp);
|
8645
|
+
addCashIn(gpv.down[i], tp);
|
8646
|
+
}
|
8317
8647
|
}
|
8318
8648
|
}
|
8319
8649
|
}
|
8320
8650
|
|
8651
|
+
function VMI_add_power_flow_to_coefficients(args) {
|
8652
|
+
// Special instruction to add power flow rates represented by process
|
8653
|
+
// P to the coefficient vector that is being constructed to compute the
|
8654
|
+
// level for product Q.
|
8655
|
+
// The instruction is added once for the link P -> Q (then UP flows
|
8656
|
+
// add to the level of Q, while DOWN flows subtract) and once for the
|
8657
|
+
// link Q -> P (then UP flows *subtract* from the level of Q while
|
8658
|
+
// DOWN flows *add*).
|
8659
|
+
// The instruction expects two arguments: a grid process and an integer
|
8660
|
+
// indicating the direction: P -> Q (1) or Q -> P (-1).
|
8661
|
+
const
|
8662
|
+
p = args[0],
|
8663
|
+
up = args[1] > 0,
|
8664
|
+
gpv = VM.gridProcessVarIndices(p, VM.offset),
|
8665
|
+
lr = p.lossRates(VM.t);
|
8666
|
+
for(let i = 0; i < gpv.slopes; i++) {
|
8667
|
+
// Losses must be subtracted only from flows *into* P.
|
8668
|
+
const
|
8669
|
+
uv = (up ? 1 - lr[i] : -1),
|
8670
|
+
dv = (up ? -1 : 1 - lr[i]);
|
8671
|
+
let k = gpv.up[i];
|
8672
|
+
if(k in VM.coefficients) {
|
8673
|
+
VM.coefficients[k] += uv;
|
8674
|
+
} else {
|
8675
|
+
VM.coefficients[k] = uv;
|
8676
|
+
}
|
8677
|
+
k = gpv.down[i];
|
8678
|
+
if(k in VM.coefficients) {
|
8679
|
+
VM.coefficients[k] += dv;
|
8680
|
+
} else {
|
8681
|
+
VM.coefficients[k] = dv;
|
8682
|
+
}
|
8683
|
+
}
|
8684
|
+
}
|
8685
|
+
|
8321
8686
|
function VMI_add_throughput_to_coefficient(args) {
|
8322
8687
|
// Special instruction to deal with throughput calculation.
|
8323
8688
|
// Function: to add the contribution of variable X to the level of
|
@@ -8442,17 +8807,29 @@ function VMI_add_constraint(ct) {
|
|
8442
8807
|
row[i] = c;
|
8443
8808
|
}
|
8444
8809
|
}
|
8445
|
-
|
8810
|
+
// Special case:
|
8811
|
+
if(ct === VM.ACTOR_CASH) {
|
8812
|
+
VM.actor_cash_constraints.push(VM.matrix.length);
|
8813
|
+
ct = VM.EQ;
|
8814
|
+
}
|
8446
8815
|
let rhs = VM.rhs;
|
8447
|
-
|
8448
|
-
|
8449
|
-
|
8450
|
-
|
8451
|
-
|
8452
|
-
|
8453
|
-
|
8454
|
-
|
8455
|
-
|
8816
|
+
// Check for <= (near) +infinity and >= (near) -infinity: such
|
8817
|
+
// constraints should not be added to the model.
|
8818
|
+
if((ct === VM.LE && rhs >= 0.1 * VM.PLUS_INFINITY) ||
|
8819
|
+
(ct === VM.GE && rhs < 0.1 * VM.MINUS_INFINITY)) {
|
8820
|
+
if(DEBUGGING) console.log('Ignored infinite bound constraint');
|
8821
|
+
} else {
|
8822
|
+
VM.matrix.push(row);
|
8823
|
+
if(rhs >= VM.PLUS_INFINITY) {
|
8824
|
+
rhs = (VM.diagnose ? VM.DIAGNOSIS_UPPER_BOUND :
|
8825
|
+
VM.SOLVER_PLUS_INFINITY);
|
8826
|
+
} else if(rhs <= VM.MINUS_INFINITY) {
|
8827
|
+
rhs = (VM.diagnose ? -VM.DIAGNOSIS_UPPER_BOUND :
|
8828
|
+
VM.SOLVER_MINUS_INFINITY);
|
8829
|
+
}
|
8830
|
+
VM.right_hand_side.push(rhs);
|
8831
|
+
VM.constraint_types.push(ct);
|
8832
|
+
}
|
8456
8833
|
} else if(DEBUGGING) {
|
8457
8834
|
console.log('Constraint NOT added!');
|
8458
8835
|
}
|
@@ -8523,13 +8900,90 @@ function VMI_add_cash_constraints(args) {
|
|
8523
8900
|
VM.cash_out_rhs = 0;
|
8524
8901
|
}
|
8525
8902
|
|
8903
|
+
function VMI_add_grid_process_constraints(p) {
|
8904
|
+
// Add constraints that will ensure that power flows either UP or DOWN,
|
8905
|
+
// and that loss slopes properties are set.
|
8906
|
+
const gpv = VM.gridProcessVarIndices(p, VM.offset);
|
8907
|
+
if(!gpv) return;
|
8908
|
+
// Now the variable index lists all contain 1, 2 or 3 indices,
|
8909
|
+
// depending on the loss approximation level.
|
8910
|
+
let ub = p.upper_bound.result(VM.t);
|
8911
|
+
if(ub >= VM.PLUS_INFINITY) {
|
8912
|
+
// When UB = +INF, this is interpreted as "unlimited", which is
|
8913
|
+
// implemented as 99999 grid power units.
|
8914
|
+
ub = VM.UNLIMITED_POWER_FLOW;
|
8915
|
+
}
|
8916
|
+
const
|
8917
|
+
step = ub / gpv.slopes,
|
8918
|
+
// NOTE: For slope 1 use a small positive number as LB.
|
8919
|
+
lbs = [VM.ON_OFF_THRESHOLD, step, 2*step],
|
8920
|
+
ubs = [step, 2*step, 3*step];
|
8921
|
+
for(let i = 0; i < gpv.slopes; i++) {
|
8922
|
+
// Add constraints to set the ON/OFF binary for each slope:
|
8923
|
+
VMI_clear_coefficients();
|
8924
|
+
// level - UB*binary <= 0
|
8925
|
+
VM.coefficients[gpv.up[i]] = 1;
|
8926
|
+
VM.coefficients[gpv.up_on[i]] = -ubs[i];
|
8927
|
+
VMI_add_constraint(VM.LE);
|
8928
|
+
// level - LB*binary >= 0
|
8929
|
+
VM.coefficients[gpv.up_on[i]] = -lbs[i];
|
8930
|
+
VMI_add_constraint(VM.GE);
|
8931
|
+
// Two similar constraints for the Down slope
|
8932
|
+
VMI_clear_coefficients();
|
8933
|
+
VM.coefficients[gpv.down[i]] = 1;
|
8934
|
+
VM.coefficients[gpv.down_on[i]] = -ubs[i];
|
8935
|
+
VMI_add_constraint(VM.LE);
|
8936
|
+
VM.coefficients[gpv.down_on[i]] = -lbs[i];
|
8937
|
+
VMI_add_constraint(VM.GE);
|
8938
|
+
}
|
8939
|
+
// Set level to sum of all Up variables minus sum of all Down variables.
|
8940
|
+
VMI_clear_coefficients();
|
8941
|
+
VM.coefficients[VM.offset + p.level_var_index] = -1;
|
8942
|
+
for(let i = 0; i < gpv.slopes; i++) {
|
8943
|
+
VM.coefficients[gpv.up[i]] = 1;
|
8944
|
+
VM.coefficients[gpv.down[i]] = -1;
|
8945
|
+
}
|
8946
|
+
VMI_add_constraint(VM.EQ);
|
8947
|
+
// Set OO to the sum of all binary ON variables. This not only makes
|
8948
|
+
// OO available for read-out but also ensures that at most one slope
|
8949
|
+
// can be active (because OO is binary).
|
8950
|
+
VMI_clear_coefficients();
|
8951
|
+
VM.coefficients[VM.offset + p.on_off_var_index] = -1;
|
8952
|
+
for(let i = 0; i < gpv.slopes; i++) {
|
8953
|
+
VM.coefficients[gpv.up_on[i]] = 1;
|
8954
|
+
VM.coefficients[gpv.down_on[i]] = 1;
|
8955
|
+
}
|
8956
|
+
VMI_add_constraint(VM.EQ);
|
8957
|
+
}
|
8958
|
+
|
8959
|
+
function VMI_add_kirchhoff_constraints(cb) {
|
8960
|
+
// Add Kirchhoff's voltage law constraint for each cycle in `cb`.
|
8961
|
+
// NOTE: Do not add a constraint for cyles that have been "broken"
|
8962
|
+
// because one or more of its processes have UB = 0.
|
8963
|
+
for(let i = 0; i < cb.length; i++) {
|
8964
|
+
const c = cb[i];
|
8965
|
+
let not_broken = true;
|
8966
|
+
VMI_clear_coefficients();
|
8967
|
+
for(let j = 0; j < c.length; j++) {
|
8968
|
+
const
|
8969
|
+
p = c[j].process,
|
8970
|
+
x = p.length_in_km * p.grid.reactancePerKm,
|
8971
|
+
o = c[j].orientation,
|
8972
|
+
ub = p.upper_bound.result(VM.t);
|
8973
|
+
if(ub <= VM.NEAR_ZERO) {
|
8974
|
+
not_broken = false;
|
8975
|
+
break;
|
8976
|
+
}
|
8977
|
+
VM.coefficients[VM.offset + p.level_var_index] = x * o;
|
8978
|
+
}
|
8979
|
+
if(not_broken) VMI_add_constraint(VM.EQ);
|
8980
|
+
}
|
8981
|
+
}
|
8982
|
+
|
8526
8983
|
function VMI_add_bound_line_constraint(args) {
|
8527
8984
|
// `args`: [variable index for X, LB expression for X, UB expression for X,
|
8528
8985
|
// variable index for Y, LB expression for Y, UB expression for Y,
|
8529
|
-
// boundline object
|
8530
|
-
// The `use_binaries` flag can be determined at compile time, as bound
|
8531
|
-
// lines are not dynamic. When use_binaries = TRUE, additional constraints
|
8532
|
-
// on binary variables are needed (see below).
|
8986
|
+
// boundline object]
|
8533
8987
|
const
|
8534
8988
|
vix = args[0],
|
8535
8989
|
vx = VM.variables[vix - 1], // `variables` is zero-based!
|
@@ -8540,14 +8994,22 @@ function VMI_add_bound_line_constraint(args) {
|
|
8540
8994
|
objy= vy[1],
|
8541
8995
|
uby = args[5].result(VM.t),
|
8542
8996
|
bl = args[6],
|
8543
|
-
use_binaries = args[7],
|
8544
8997
|
n = bl.points.length,
|
8545
8998
|
x = new Array(n),
|
8546
8999
|
y = new Array(n),
|
8547
9000
|
w = new Array(n);
|
9001
|
+
// Set bound line point coordinates for current run and time step.
|
9002
|
+
bl.setDynamicPoints(VM.t);
|
8548
9003
|
if(DEBUGGING) {
|
8549
9004
|
console.log('add_bound_line_constraint:', bl.displayName);
|
8550
9005
|
}
|
9006
|
+
// Do not add constraints for bound lines that set no infeasible area.
|
9007
|
+
if(!bl.constrainsY) {
|
9008
|
+
if(DEBUGGING) {
|
9009
|
+
console.log('SKIP because bound line does not constrain');
|
9010
|
+
}
|
9011
|
+
return;
|
9012
|
+
}
|
8551
9013
|
// NOTE: For semi-continuous processes, lower bounds > 0 should to be
|
8552
9014
|
// adjusted to 0, as then 0 is part of the process level range.
|
8553
9015
|
let lbx = args[1].result(VM.t),
|
@@ -8572,6 +9034,11 @@ function VMI_add_bound_line_constraint(args) {
|
|
8572
9034
|
// For LE and GE type bound lines, one slack variable suffices, and = 0 must
|
8573
9035
|
// be, respectively, <= 0 or >= 0
|
8574
9036
|
|
9037
|
+
// Since version 2.0.0, the `use_binaries` flag can no longer be determined
|
9038
|
+
// at compile time, as bound lines may be dynamic. When use_binaries = TRUE,
|
9039
|
+
// additional constraints on binary variables are needed (see below).
|
9040
|
+
let use_binaries = VM.noSupportForSOS && !bl.needsNoSOS;
|
9041
|
+
|
8575
9042
|
// Scale X and Y and compute the block indices of w[i]
|
8576
9043
|
let wi = VM.offset + bl.first_sos_var_index;
|
8577
9044
|
const
|
@@ -8629,8 +9096,7 @@ function VMI_add_bound_line_constraint(args) {
|
|
8629
9096
|
// and then to ensure that at most 2 binaries can be 1:
|
8630
9097
|
// b[1] + ... + b[N] <= 2
|
8631
9098
|
// NOTE: These additional variables and constraints are not needed
|
8632
|
-
// when a bound line defines a convex feasible area.
|
8633
|
-
// parameter takes this into account.
|
9099
|
+
// when a bound line defines a convex feasible area.
|
8634
9100
|
if(use_binaries) {
|
8635
9101
|
// Add the constraints mentioned above. The index of b[i] is the
|
8636
9102
|
// index of w[i] plus the number of points on the boundline N.
|