linny-r 1.7.0 → 1.7.2
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/console.js +150 -89
- package/package.json +1 -1
- package/server.js +1 -1
- package/static/scripts/linny-r-ctrl.js +16 -13
- package/static/scripts/linny-r-gui-paper.js +43 -38
- package/static/scripts/linny-r-gui-receiver.js +5 -5
- package/static/scripts/linny-r-model.js +188 -155
- package/static/scripts/linny-r-utils.js +20 -3
- package/static/scripts/linny-r-vm.js +581 -327
@@ -2044,29 +2044,29 @@ class ExpressionParser {
|
|
2044
2044
|
// CLASS VirtualMachine
|
2045
2045
|
class VirtualMachine {
|
2046
2046
|
constructor() {
|
2047
|
-
// Set user name to default as configured in file `linny-r-config.js
|
2048
|
-
// This will be an empty string for local host servers
|
2047
|
+
// Set user name to default as configured in file `linny-r-config.js`.
|
2048
|
+
// This will be an empty string for local host servers.
|
2049
2049
|
this.solver_user = SOLVER.user_id;
|
2050
|
-
// NOTE:
|
2050
|
+
// NOTE: If not empty, then authentication is needed.
|
2051
2051
|
if(this.solver_user) {
|
2052
|
-
// If URL contains ?u=, set user name to the passed parameter
|
2052
|
+
// If URL contains ?u=, set user name to the passed parameter.
|
2053
2053
|
let url = decodeURI(window.location.href);
|
2054
|
-
// NOTE:
|
2054
|
+
// NOTE: Trim cache buster suffix that may have been added.
|
2055
2055
|
if(url.indexOf('?x=') > 0) url = url.split('?x=')[0].trim();
|
2056
2056
|
if(url.indexOf('?u=') > 0) {
|
2057
2057
|
this.solver_user = url.split('?u=')[1].trim();
|
2058
2058
|
}
|
2059
2059
|
}
|
2060
|
-
// NOTE:
|
2061
|
-
// finished a run
|
2060
|
+
// NOTE: If not null, the callback function is called when the VM has
|
2061
|
+
// finished a run. This is used by the console version of Linny-R.
|
2062
2062
|
this.callback = null;
|
2063
|
-
// Solver limits may be set in file `linny-r-config.js` (0 => unlimited)
|
2063
|
+
// Solver limits may be set in file `linny-r-config.js` (0 => unlimited).
|
2064
2064
|
this.max_solver_time = SOLVER.max_solver_time;
|
2065
2065
|
this.max_blocks = SOLVER.max_nr_of_blocks;
|
2066
2066
|
this.max_tableau_size = SOLVER.max_tableau_size;
|
2067
|
-
// Standard variables: array of tuples [type, object]
|
2067
|
+
// Standard variables: array of tuples [type, object].
|
2068
2068
|
this.variables = [];
|
2069
|
-
// Indices for special types
|
2069
|
+
// Indices for special variable types.
|
2070
2070
|
this.int_var_indices = [];
|
2071
2071
|
this.bin_var_indices = [];
|
2072
2072
|
this.sec_var_indices = [];
|
@@ -2075,35 +2075,35 @@ class VirtualMachine {
|
|
2075
2075
|
this.fixed_var_indices = [];
|
2076
2076
|
// Chunk variables: also an array of tuples [type, object], but
|
2077
2077
|
// so far, type is always HI (highest increment); object can be
|
2078
|
-
// a process or a product
|
2078
|
+
// a process or a product.
|
2079
2079
|
this.chunk_variables = [];
|
2080
|
-
// Array for VM instructions
|
2080
|
+
// Array for VM instructions.
|
2081
2081
|
this.code = [];
|
2082
|
-
// The Simplex tableau: matrix, rhs and ct will have same length
|
2082
|
+
// The Simplex tableau: matrix, rhs and ct will have same length.
|
2083
2083
|
this.matrix = [];
|
2084
2084
|
this.right_hand_side = [];
|
2085
2085
|
this.constraint_types = [];
|
2086
|
-
// String to hold lines of (solver-dependent) model equations
|
2086
|
+
// String to hold lines of (solver-dependent) model equations.
|
2087
2087
|
this.lines = '';
|
2088
|
-
// String specifying a numeric issue (empty if none)
|
2088
|
+
// String specifying a numeric issue (empty if none).
|
2089
2089
|
this.numeric_issue = '';
|
2090
|
-
// Warnings are stored in a list to permit browsing through them
|
2090
|
+
// Warnings are stored in a list to permit browsing through them.
|
2091
2091
|
this.issue_list = [];
|
2092
|
-
// The call stack tracks evaluation of "nested" expression variables
|
2092
|
+
// The call stack tracks evaluation of "nested" expression variables.
|
2093
2093
|
this.call_stack = [];
|
2094
2094
|
this.block_count = 0;
|
2095
|
-
// Sequence of round numbers (set by default or as experiment parameter)
|
2095
|
+
// Sequence of round numbers (set by default or as experiment parameter).
|
2096
2096
|
this.round_sequence = '';
|
2097
|
-
// NOTE:
|
2097
|
+
// NOTE: Current round is index in round sequence.
|
2098
2098
|
this.current_round = 0;
|
2099
|
-
// Add arrays for solver results per block
|
2099
|
+
// Add arrays for solver results per block.
|
2100
2100
|
this.round_times = [];
|
2101
2101
|
this.round_secs = [];
|
2102
2102
|
this.solver_times = [];
|
2103
2103
|
this.solver_secs = [];
|
2104
2104
|
this.messages = [];
|
2105
2105
|
this.equations = [];
|
2106
|
-
// Default texts to display for (still) empty results
|
2106
|
+
// Default texts to display for (still) empty results.
|
2107
2107
|
this.no_messages = '(no messages)';
|
2108
2108
|
this.no_variables = '(no variables)';
|
2109
2109
|
this.no_equations = '(select block in progress bar)';
|
@@ -2122,26 +2122,29 @@ class VirtualMachine {
|
|
2122
2122
|
this.BEYOND_MINUS_INFINITY = -1e+35;
|
2123
2123
|
this.SOLVER_PLUS_INFINITY = 1e+30;
|
2124
2124
|
this.SOLVER_MINUS_INFINITY = -1e+30;
|
2125
|
-
// NOTE:
|
2126
|
-
// (this is to timely detect division-by-zero errors)
|
2125
|
+
// NOTE: Below the "near zero" limit, a number is considered zero
|
2126
|
+
// (this is to timely detect division-by-zero errors).
|
2127
2127
|
this.NEAR_ZERO = 1e-10;
|
2128
2128
|
// Use a specific constant smaller than near-zero to denote "no cost"
|
2129
|
-
// to differentiate "no cost" form cost prices that really are 0
|
2129
|
+
// to differentiate "no cost" form cost prices that really are 0.
|
2130
2130
|
this.NO_COST = 0.987654321e-10;
|
2131
2131
|
|
2132
|
-
// NOTE:
|
2133
|
-
// target without displaying them in red or blue to signal
|
2132
|
+
// NOTE: Allow for an accuracy margin: stocks may differ 0.1% from
|
2133
|
+
// their target without displaying them in red or blue to signal
|
2134
|
+
// shortage or surplus.
|
2134
2135
|
this.SIG_DIF_LIMIT = 0.001;
|
2135
2136
|
this.SIG_DIF_FROM_ZERO = 1e-6;
|
2136
|
-
//
|
2137
|
-
// (will be displayed as +0)
|
2137
|
+
// ON/OFF threshold is used to differentiate between level = 0 and
|
2138
|
+
// still "ON" (will be displayed as +0).
|
2138
2139
|
this.ON_OFF_THRESHOLD = 1.5e-4;
|
2139
|
-
// Limit for upper bounds beyond which binaries cannot be computed
|
2140
|
+
// Limit for upper bounds beyond which binaries cannot be computed
|
2141
|
+
// correctly. Modeler is warned when this occurs (typically when
|
2142
|
+
// ON/OFF variables are needed for a process having infinite bounds.
|
2140
2143
|
this.MEGA_UPPER_BOUND = 1e6;
|
2141
|
-
// Limit slack penalty to one order of magnitude below +INF
|
2144
|
+
// Limit slack penalty to one order of magnitude below +INF.
|
2142
2145
|
this.MAX_SLACK_PENALTY = 0.1 * this.PLUS_INFINITY;
|
2143
2146
|
|
2144
|
-
// VM constants for specifying the type of cash flow operation
|
2147
|
+
// VM constants for specifying the type of cash flow operation.
|
2145
2148
|
this.CONSUME = 0;
|
2146
2149
|
this.PRODUCE = 1;
|
2147
2150
|
this.ONE_C = 2;
|
@@ -2149,16 +2152,16 @@ class VirtualMachine {
|
|
2149
2152
|
this.THREE_X = 4;
|
2150
2153
|
this.SPIN_RES = 5;
|
2151
2154
|
this.PEAK_INC = 6;
|
2152
|
-
// Array of corrsponding strings for more readable debugging information
|
2155
|
+
// Array of corrsponding strings for more readable debugging information.
|
2153
2156
|
this.CF_CONSTANTS = ['CONSUME', 'PRODUCE', 'ONE_C', 'TWO_X',
|
2154
2157
|
'THREE_X', 'SPIN_RES'];
|
2155
2158
|
|
2156
|
-
// Constraint cost price transfer direction
|
2159
|
+
// Constraint cost price transfer direction.
|
2157
2160
|
this.SOC_X_Y = 1;
|
2158
2161
|
this.SOC_Y_X = -1;
|
2159
2162
|
|
2160
|
-
// Link multiplier type numbers
|
2161
|
-
// NOTE:
|
2163
|
+
// Link multiplier type numbers.
|
2164
|
+
// NOTE: Do *NOT* change existing values, as this will cause legacy issues!
|
2162
2165
|
this.LM_LEVEL = 0; // No symbol
|
2163
2166
|
this.LM_THROUGHPUT = 1; // Symbol: two parallel right-pointing arrows
|
2164
2167
|
this.LM_INCREASE = 2; // Symbol: Delta
|
@@ -2176,11 +2179,11 @@ class VirtualMachine {
|
|
2176
2179
|
this.LM_SYMBOLS = ['', '\u21C9', '\u0394', '\u03A3', '\u03BC', '\u25B2',
|
2177
2180
|
'+', '0', '\u2934', '\u2732', '\u25BC', '\u2A39'];
|
2178
2181
|
|
2179
|
-
// VM max. expression stack size
|
2182
|
+
// VM max. expression stack size.
|
2180
2183
|
this.MAX_STACK = 200;
|
2181
2184
|
|
2182
|
-
// Base penalty of 10 is high relative to the (scaled) coefficients of
|
2183
|
-
// cash flows in the objective function (typically +/- 1)
|
2185
|
+
// Base penalty of 10 is high relative to the (scaled) coefficients of
|
2186
|
+
// the cash flows in the objective function (typically +/- 1).
|
2184
2187
|
this.BASE_PENALTY = 10;
|
2185
2188
|
// Peak variable penalty is added to make solver choose the *smallest*
|
2186
2189
|
// value that is greater than or equal to X[t] for all t as "peak value".
|
@@ -2189,17 +2192,17 @@ class VirtualMachine {
|
|
2189
2192
|
// solution (highest total cash flow).
|
2190
2193
|
this.PEAK_VAR_PENALTY = 0.1;
|
2191
2194
|
|
2192
|
-
// NOTE:
|
2195
|
+
// NOTE: The VM uses numbers >> +INF to denote special computation results.
|
2193
2196
|
this.EXCEPTION = 1e+36; // to test for any exceptional value
|
2194
2197
|
this.UNDEFINED = 1e+37; // to denote "unspecified by the user"
|
2195
2198
|
this.NOT_COMPUTED = 1e+38; // initial value for VM variables (to distinguish from UNDEFINED)
|
2196
2199
|
this.COMPUTING = 1e+39; // used by the VM to implement lazy evaluation
|
2197
2200
|
|
2198
2201
|
// NOTES:
|
2199
|
-
// (1)
|
2200
|
-
// (2) JavaScript exponents can go up to +/- 308 (IEEE 754 standard)
|
2201
|
-
// (3) when adding/modifying these values, ALSO update the VM methods
|
2202
|
-
// representing these values as human-readable strings!
|
2202
|
+
// (1) Computation errors are signalled by NEGATIVE values << -10^35.
|
2203
|
+
// (2) JavaScript exponents can go up to +/- 308 (IEEE 754 standard).
|
2204
|
+
// (3) when adding/modifying these values, ALSO update the VM methods
|
2205
|
+
// for representing these values as human-readable strings!
|
2203
2206
|
|
2204
2207
|
this.ERROR = -1e+40; // Any lower value indicates a computation error
|
2205
2208
|
this.CYCLIC = -1e+41;
|
@@ -2218,12 +2221,12 @@ class VirtualMachine {
|
|
2218
2221
|
this.BAD_REF, this.UNDERFLOW, this.OVERFLOW, this.INVALID, this.PARAMS,
|
2219
2222
|
this.UNKNOWN_ERROR, this.UNDEFINED, this.NOT_COMPUTED, this.COMPUTING];
|
2220
2223
|
|
2221
|
-
// Prefix for warning messages that are logged in the monitor
|
2224
|
+
// Prefix for warning messages that are logged in the monitor.
|
2222
2225
|
this.WARNING = '-- Warning: ';
|
2223
2226
|
|
2224
|
-
// Solver constants indicating constraint type
|
2225
|
-
// NOTE:
|
2226
|
-
// MPS files, other constants are used
|
2227
|
+
// Solver constants indicating constraint type.
|
2228
|
+
// NOTE: These correspond to the codes used in the LP format. When
|
2229
|
+
// generating MPS files, other constants are used.
|
2227
2230
|
this.FR = 0;
|
2228
2231
|
this.LE = 1;
|
2229
2232
|
this.GE = 2;
|
@@ -2239,18 +2242,18 @@ class VirtualMachine {
|
|
2239
2242
|
'hour': 1, 'minute': 1/60, 'second': 1/3600
|
2240
2243
|
};
|
2241
2244
|
// More or less standard time unit abbreviations.
|
2242
|
-
// NOTE:
|
2243
|
-
// that can be used in expressions. There, `min` already
|
2244
|
-
// operator.
|
2245
|
+
// NOTE: Minute is abbreviated to `m` to remain consistent with the
|
2246
|
+
// constants that can be used in expressions. There, `min` already
|
2247
|
+
// denotes the "minimum" operator.
|
2245
2248
|
this.time_unit_shorthand = {
|
2246
2249
|
'year': 'yr', 'week': 'wk', 'day': 'd',
|
2247
2250
|
'hour': 'h', 'minute': 'm', 'second': 's'
|
2248
2251
|
};
|
2249
2252
|
// Number of rounds limited to 31 because JavaScript performs bitwise
|
2250
|
-
// operations on 32 bit integers, and the sign bit may be troublesome
|
2253
|
+
// operations on 32 bit integers, and the sign bit may be troublesome.
|
2251
2254
|
this.max_rounds = 31;
|
2252
2255
|
this.round_letters = '?abcdefghijklmnopqrstuvwxyzABCDE';
|
2253
|
-
// Standard 1-letter codes for Linny-R entities
|
2256
|
+
// Standard 1-letter codes for Linny-R entities.
|
2254
2257
|
this.entity_names = {
|
2255
2258
|
A: 'actor',
|
2256
2259
|
B: 'constraint',
|
@@ -2262,7 +2265,7 @@ class VirtualMachine {
|
|
2262
2265
|
Q: 'product'
|
2263
2266
|
};
|
2264
2267
|
this.entity_letters = 'ABCDELPQ';
|
2265
|
-
// Standard attributes of Linny-R entities
|
2268
|
+
// Standard attributes of Linny-R entities.
|
2266
2269
|
this.attribute_names = {
|
2267
2270
|
'LB': 'lower bound',
|
2268
2271
|
'UB': 'upper bound',
|
@@ -2282,18 +2285,18 @@ class VirtualMachine {
|
|
2282
2285
|
'SOC': 'share of cost',
|
2283
2286
|
'A': 'active'
|
2284
2287
|
};
|
2285
|
-
// NOTE:
|
2286
|
-
// actor cash flow (CF); dataset value (no attribute)
|
2287
|
-
// NOTE:
|
2288
|
+
// NOTE: Defaults are level (L), link flow (F), cluster cash flow (CF),
|
2289
|
+
// actor cash flow (CF); dataset value (no attribute).
|
2290
|
+
// NOTE: Exogenous properties first, then the computed properties.
|
2288
2291
|
this.process_attr = ['LB', 'UB', 'IL', 'LCF', 'L', 'CI', 'CO', 'CF', 'CP'];
|
2289
2292
|
this.product_attr = ['LB', 'UB', 'IL', 'P', 'L', 'CP', 'HCP'];
|
2290
2293
|
this.cluster_attr = ['CI', 'CO', 'CF'];
|
2291
2294
|
this.link_attr = ['R', 'D', 'SOC', 'F'];
|
2292
2295
|
this.constraint_attr = ['SOC', 'A'];
|
2293
2296
|
this.actor_attr = ['W', 'CI', 'CO', 'CF'];
|
2294
|
-
// Only expression attributes can be used for sensitivity analysis
|
2297
|
+
// Only expression attributes can be used for sensitivity analysis.
|
2295
2298
|
this.expression_attr = ['LB', 'UB', 'IL', 'LCF', 'P', 'R', 'D', 'W'];
|
2296
|
-
// Attributes per entity type letter
|
2299
|
+
// Attributes per entity type letter.
|
2297
2300
|
this.attribute_codes = {
|
2298
2301
|
A: this.actor_attr,
|
2299
2302
|
B: this.constraint_attr,
|
@@ -2314,18 +2317,19 @@ class VirtualMachine {
|
|
2314
2317
|
this.entity_attribute_names[el].push(ac[j]);
|
2315
2318
|
}
|
2316
2319
|
}
|
2317
|
-
// Level-based attributes are computed only AFTER optimization
|
2320
|
+
// Level-based attributes are computed only AFTER optimization.
|
2318
2321
|
this.level_based_attr = ['L', 'CP', 'HCP', 'CF', 'CI', 'CO', 'F', 'A'];
|
2319
2322
|
this.object_types = ['Process', 'Product', 'Cluster', 'Link', 'Constraint',
|
2320
2323
|
'Actor', 'Dataset', 'Equation'];
|
2321
2324
|
this.type_attributes = [this.process_attr, this.product_attr,
|
2322
2325
|
this.cluster_attr, this.link_attr, this.constraint_attr,
|
2323
2326
|
this.actor_attr, [], []];
|
2324
|
-
// Statistics that can be calculated for sets of variables
|
2327
|
+
// Statistics that can be calculated for sets of variables.
|
2325
2328
|
this.statistic_operators =
|
2326
2329
|
['MAX', 'MEAN', 'MIN', 'N', 'SD', 'SUM', 'VAR',
|
2327
2330
|
'MAXNZ', 'MEANNZ', 'MINNZ', 'NNZ', 'SDNZ', 'SUMNZ', 'VARNZ'];
|
2328
|
-
// Statistics that can be calculated for outcomes and experiment run
|
2331
|
+
// Statistics that can be calculated for outcomes and experiment run
|
2332
|
+
// results.
|
2329
2333
|
this.outcome_statistics =
|
2330
2334
|
['LAST', 'MAX', 'MEAN', 'MIN', 'N', 'NZ', 'SD', 'SUM', 'VAR'];
|
2331
2335
|
this.solver_names = {
|
@@ -2370,8 +2374,9 @@ class VirtualMachine {
|
|
2370
2374
|
// (1) MODEL.runLength = simulation period + look-ahead, so that
|
2371
2375
|
// should not be used to compute the number of blocks.
|
2372
2376
|
// (2) For each block, a chunk (block + lookahead) is optimized.
|
2377
|
+
this.nr_of_time_steps = MODEL.end_period - MODEL.start_period + 1;
|
2373
2378
|
this.nr_of_blocks = Math.ceil(
|
2374
|
-
|
2379
|
+
this.nr_of_time_steps / MODEL.block_length);
|
2375
2380
|
|
2376
2381
|
// EXAMPLE: Simulation period of 55 time steps, block length of 10
|
2377
2382
|
// time steps and no look-ahead => 6 chunks, and chunk length = block
|
@@ -2390,8 +2395,13 @@ class VirtualMachine {
|
|
2390
2395
|
this.issue_list.length = 0;
|
2391
2396
|
this.issue_index = -1;
|
2392
2397
|
UI.updateIssuePanel();
|
2393
|
-
//
|
2398
|
+
// Special tracking of potential solver license errors.
|
2394
2399
|
this.license_expired = 0;
|
2400
|
+
// Variables that will be decided by the solver again in the next
|
2401
|
+
// block must be "fixated" when, due to a negative link delay, they
|
2402
|
+
// would have consequences for the previous block (and these will be
|
2403
|
+
// ignored).
|
2404
|
+
this.variables_to_fixate = {};
|
2395
2405
|
// Reset solver result arrays.
|
2396
2406
|
this.round_times.length = 0;
|
2397
2407
|
this.solver_times.length = 0;
|
@@ -2951,7 +2961,6 @@ class VirtualMachine {
|
|
2951
2961
|
return obj.la_peak_inc[c];
|
2952
2962
|
}
|
2953
2963
|
const prior_level = obj.actualLevel(t);
|
2954
|
-
//console.log('HERE obj prilev t', obj.displayName, prior_level, t, obj.level);
|
2955
2964
|
if(type === 'OO') return prior_level > 0 ? 1 : 0;
|
2956
2965
|
if(type === 'IZ') return prior_level === 0 ? 1 : 0;
|
2957
2966
|
// Start-up at time t entails that t is in the list of start-up
|
@@ -3136,7 +3145,7 @@ class VirtualMachine {
|
|
3136
3145
|
|
3137
3146
|
// FIRST: define indices for all variables (index = Simplex tableau column number)
|
3138
3147
|
|
3139
|
-
// Each actor has a variable to compute its cash in and its cash out
|
3148
|
+
// Each actor has a variable to compute its cash in and its cash out.
|
3140
3149
|
const actor_keys = Object.keys(MODEL.actors).sort();
|
3141
3150
|
for(i = 0; i < actor_keys.length; i++) {
|
3142
3151
|
const a = MODEL.actors[actor_keys[i]];
|
@@ -3243,9 +3252,9 @@ class VirtualMachine {
|
|
3243
3252
|
// NOTE: Chunk variables of node `p` have LB = 0 and UB = UB of `p`.
|
3244
3253
|
// This is effectuated by the VM "set bounds" instructions at run time.
|
3245
3254
|
|
3246
|
-
// NOTE: Under normal assumptions (all processes having LB >= 0), bounds
|
3247
|
-
// actor cash flow variables need NOT be set because cash IN and
|
3248
|
-
// will then always be >= 0 (solver's default bounds).
|
3255
|
+
// NOTE: Under normal assumptions (all processes having LB >= 0), bounds
|
3256
|
+
// on actor cash flow variables need NOT be set because cash IN and
|
3257
|
+
// cash OUT will then always be >= 0 (solver's default bounds).
|
3249
3258
|
// However, Linny-R does not prohibit negative bounds on processes, nor
|
3250
3259
|
// negative rates on links. To be consistently permissive, cash IN and
|
3251
3260
|
// cash OUT of all actors are both allowed to become negative.
|
@@ -3271,18 +3280,18 @@ class VirtualMachine {
|
|
3271
3280
|
if(!MODEL.ignored_entities[k]) {
|
3272
3281
|
p = MODEL.processes[k];
|
3273
3282
|
lbx = p.lower_bound;
|
3274
|
-
// NOTE:
|
3283
|
+
// NOTE: If UB = LB, set UB to LB only if LB is defined,
|
3275
3284
|
// because LB expressions default to -INF while UB expressions
|
3276
|
-
// default to +
|
3285
|
+
// default to +INF.
|
3277
3286
|
ubx = (p.equal_bounds && lbx.defined ? lbx : p.upper_bound);
|
3278
3287
|
if(lbx.isStatic) lbx = lbx.result(0);
|
3279
3288
|
if(ubx.isStatic) ubx = ubx.result(0);
|
3280
|
-
// NOTE:
|
3289
|
+
// NOTE: Pass TRUE as fourth parameter to indicate that +INF
|
3281
3290
|
// and -INF can be coded as the infinity values used by the
|
3282
3291
|
// solver, rather than the Linny-R values used to detect
|
3283
|
-
// unbounded problems
|
3292
|
+
// unbounded problems.
|
3284
3293
|
this.code.push([VMI_set_bounds, [p.level_var_index, lbx, ubx, true]]);
|
3285
|
-
// Add level variable index to "fixed" list for specified rounds
|
3294
|
+
// Add level variable index to "fixed" list for specified rounds.
|
3286
3295
|
const rf = p.actor.round_flags;
|
3287
3296
|
if(rf != 0) {
|
3288
3297
|
// Note: 32-bit integer `b` is used for bit-wise AND
|
@@ -3333,9 +3342,9 @@ class VirtualMachine {
|
|
3333
3342
|
|
3334
3343
|
// NOTE: Each process generates cash flow proportional to its production
|
3335
3344
|
// level if it produces and/or consumes a product having a price.
|
3336
|
-
// Cash flow is negative (cash OUT) if a product is consumed AND
|
3337
|
-
// price > 0, but positive (cash IN) if a product is produced
|
3338
|
-
// price < 0. Likewise for the other two cases.
|
3345
|
+
// Cash flow is negative (cash OUT) if a product is consumed AND
|
3346
|
+
// has price > 0, but positive (cash IN) if a product is produced
|
3347
|
+
// and has price < 0. Likewise for the other two cases.
|
3339
3348
|
// To calculate the coefficient for the process variable, the
|
3340
3349
|
// multiplier rates of the links in and out must be calculated (at
|
3341
3350
|
// run time when dynamic expressions) such that they will add to the
|
@@ -3357,21 +3366,24 @@ class VirtualMachine {
|
|
3357
3366
|
// on this vector. If expressions for process properties are
|
3358
3367
|
// static, more efficient VM instructions are used.
|
3359
3368
|
|
3360
|
-
// Initially assume "no cash flows for any actor to be considered"
|
3369
|
+
// Initially assume "no cash flows for any actor to be considered".
|
3370
|
+
// This flag will be set to FALSE when some actor cash flow constraint
|
3371
|
+
// has a non-zero coefficient.
|
3361
3372
|
this.no_cash_flows = true;
|
3362
3373
|
|
3363
|
-
// Iterate over all actors to add the cash flow computation constraints
|
3374
|
+
// Iterate over all actors to add the cash flow computation constraints.
|
3364
3375
|
for(let ai = 0; ai < actor_keys.length; ai++) {
|
3365
3376
|
const a = MODEL.actors[actor_keys[ai]];
|
3366
|
-
|
3377
|
+
// NOTE: No need for VMI_clear_coefficients because the cash flow
|
3378
|
+
// coefficients operate on two special "registers" of the VM.
|
3367
3379
|
for(i = 0; i < process_keys.length; i++) {
|
3368
3380
|
k = process_keys[i];
|
3369
3381
|
if(!MODEL.ignored_entities[k]) {
|
3370
3382
|
const p = MODEL.processes[k];
|
3371
|
-
// Only consider processes owned by this actor
|
3383
|
+
// Only consider processes owned by this actor.
|
3372
3384
|
if(p.actor === a) {
|
3373
|
-
// Iterate over links IN, but only consider consumed products
|
3374
|
-
// a market price
|
3385
|
+
// Iterate over links IN, but only consider consumed products
|
3386
|
+
// having a market price.
|
3375
3387
|
for(j = 0; j < p.inputs.length; j++) {
|
3376
3388
|
l = p.inputs[j];
|
3377
3389
|
if(!MODEL.ignored_entities[l.identifier] &&
|
@@ -3379,18 +3391,17 @@ class VirtualMachine {
|
|
3379
3391
|
if(l.from_node.price.isStatic && l.relative_rate.isStatic) {
|
3380
3392
|
k = l.from_node.price.result(0) * l.relative_rate.result(0);
|
3381
3393
|
// NOTE: VMI_update_cash_coefficient has at least 4 arguments:
|
3382
|
-
// flow (CONSUME or PRODUCE), type (specifies the number and
|
3383
|
-
// of arguments), the level_var_index of the process,
|
3384
|
-
// delay.
|
3394
|
+
// flow (CONSUME or PRODUCE), type (specifies the number and
|
3395
|
+
// type of arguments), the level_var_index of the process,
|
3396
|
+
// and the delay.
|
3397
|
+
// NOTE: Input links cannot have delay, so then delay = 0.
|
3385
3398
|
if(Math.abs(k) > VM.NEAR_ZERO) {
|
3386
|
-
// Consumption rate & price are static: pass one constant
|
3387
|
-
// NOTE: input links cannot have delay, so delay = 0
|
3399
|
+
// Consumption rate & price are static: pass one constant.
|
3388
3400
|
this.code.push([VMI_update_cash_coefficient,
|
3389
3401
|
[VM.CONSUME, VM.ONE_C, p.level_var_index, 0, k]]);
|
3390
3402
|
}
|
3391
3403
|
} else {
|
3392
|
-
// No further optimization: assume two dynamic expressions
|
3393
|
-
// NOTE: input links cannot have delay, so delay = 0
|
3404
|
+
// No further optimization: assume two dynamic expressions.
|
3394
3405
|
this.code.push([VMI_update_cash_coefficient,
|
3395
3406
|
[VM.CONSUME, VM.TWO_X, p.level_var_index, 0,
|
3396
3407
|
l.from_node.price, l.relative_rate]]);
|
@@ -3399,15 +3410,16 @@ class VirtualMachine {
|
|
3399
3410
|
} // END of FOR ALL input links
|
3400
3411
|
|
3401
3412
|
// Iterate over links OUT, but only consider produced products
|
3402
|
-
// having a (non-zero) market price
|
3413
|
+
// having a (non-zero) market price.
|
3403
3414
|
for(j = 0; j < p.outputs.length; j++) {
|
3404
3415
|
l = p.outputs[j];
|
3405
3416
|
const tnpx = l.to_node.price;
|
3406
3417
|
if(!MODEL.ignored_entities[l.identifier] && tnpx.defined &&
|
3407
3418
|
!(tnpx.isStatic && Math.abs(tnpx.result(0)) < VM.NEAR_ZERO)) {
|
3408
|
-
// By default, use the process level as multiplier
|
3419
|
+
// By default, use the process level as multiplier.
|
3409
3420
|
vi = p.level_var_index;
|
3410
|
-
// For "binary data links", use the correct binary variable
|
3421
|
+
// For "binary data links", use the correct binary variable
|
3422
|
+
// instead of the level.
|
3411
3423
|
if(l.multiplier === VM.LM_STARTUP) {
|
3412
3424
|
vi = p.start_up_var_index;
|
3413
3425
|
} else if(l.multiplier === VM.LM_FIRST_COMMIT) {
|
@@ -3428,57 +3440,63 @@ class VirtualMachine {
|
|
3428
3440
|
// times the rate of `l`.
|
3429
3441
|
for(k = 0; k < l.from_node.inputs.length; j++) {
|
3430
3442
|
ll = l.from_node.inputs[k];
|
3431
|
-
// NOTE:
|
3432
|
-
// price and both rates are dynamic
|
3443
|
+
// NOTE: No attempt for efficiency -- assume that
|
3444
|
+
// price and both rates are dynamic.
|
3433
3445
|
this.code.push([VMI_update_cash_coefficient, [
|
3434
3446
|
VM.PRODUCE, VM.THREE_X, vi, l.flow_delay, tnpx,
|
3435
3447
|
l.relative_rate, ll.relative_rate]]);
|
3436
3448
|
}
|
3437
3449
|
} else if(l.multiplier === VM.LM_SPINNING_RESERVE) {
|
3438
|
-
// "spinning reserve" equals UB - level if level > 0,
|
3439
|
-
// The cash flow then equals
|
3440
|
-
//
|
3441
|
-
//
|
3442
|
-
//
|
3443
|
-
//
|
3450
|
+
// "spinning reserve" equals UB - level if level > 0,
|
3451
|
+
// and otherwise 0. The cash flow then equals
|
3452
|
+
// ON/OFF * UB * price * rate MINUS level * price * rate,
|
3453
|
+
// hence a special instruction type.
|
3454
|
+
// NOTE: Only the ON/OFF variable determines whether
|
3455
|
+
// there will be any cash flow, hence it is passed as
|
3456
|
+
// the primary variable, and the process level as the
|
3457
|
+
// secondary variable.
|
3444
3458
|
this.code.push([VMI_update_cash_coefficient, [
|
3445
|
-
VM.PRODUCE, VM.SPIN_RES, p.on_off_var_index,
|
3446
|
-
l.from_node.upper_bound, tnpx,
|
3459
|
+
VM.PRODUCE, VM.SPIN_RES, p.on_off_var_index,
|
3460
|
+
l.flow_delay, vi, l.from_node.upper_bound, tnpx,
|
3461
|
+
l.relative_rate]]);
|
3447
3462
|
} else if(l.multiplier === VM.LM_PEAK_INC) {
|
3448
|
-
// NOTE: "peak increase" may be > 0 only in the first
|
3449
|
-
// of the block being optimized, and in the
|
3450
|
-
// look-ahead period (if peak rises
|
3451
|
-
// be 0 in all other time steps
|
3463
|
+
// NOTE: "peak increase" may be > 0 only in the first
|
3464
|
+
// time step of the block being optimized, and in the
|
3465
|
+
// first step of the look-ahead period (if peak rises
|
3466
|
+
// in that period), and will be 0 in all other time steps.
|
3467
|
+
// The VM instruction handles this.
|
3452
3468
|
// NOTE: Delay is always 0 for this link flow.
|
3453
3469
|
this.code.push([VMI_update_cash_coefficient, [
|
3454
3470
|
VM.PRODUCE, VM.PEAK_INC, p.peak_inc_var_index, 0,
|
3455
3471
|
tnpx, l.relative_rate]]);
|
3456
3472
|
} else if(tnpx.isStatic && l.relative_rate.isStatic) {
|
3457
|
-
// If link rate and product price are static, only add
|
3458
|
-
// if rate*price is non-zero (and
|
3473
|
+
// If link rate and product price are static, only add
|
3474
|
+
// the variable if rate*price is non-zero (and then pass
|
3475
|
+
// the constant rate*price to the VM instruction.
|
3459
3476
|
k = tnpx.result(0) * l.relative_rate.result(0);
|
3460
3477
|
if(Math.abs(k) > VM.NEAR_ZERO) {
|
3461
|
-
// Production rate & price are static: pass one constant
|
3478
|
+
// Production rate & price are static: pass one constant.
|
3462
3479
|
this.code.push([VMI_update_cash_coefficient,
|
3463
3480
|
[VM.PRODUCE, VM.ONE_C, vi, l.flow_delay, k]]);
|
3464
3481
|
// When multiplier is Delta, subtract level in previous t
|
3465
|
-
// (so add 1 to flow delay, and consume, rather than
|
3482
|
+
// (so add 1 to flow delay, and consume, rather than
|
3483
|
+
// produce).
|
3466
3484
|
if(l.multiplier === VM.LM_INCREASE) {
|
3467
3485
|
this.code.push([VMI_update_cash_coefficient,
|
3468
|
-
// NOTE: 6th argument = 1 indicates "delay + 1"
|
3486
|
+
// NOTE: 6th argument = 1 indicates "delay + 1".
|
3469
3487
|
[VM.CONSUME, VM.ONE_C, vi, l.flow_delay, k, 1]]);
|
3470
3488
|
}
|
3471
3489
|
}
|
3472
3490
|
} else {
|
3473
|
-
// Production rate or price are dynamic: pass two expressions
|
3491
|
+
// Production rate or price are dynamic: pass two expressions.
|
3474
3492
|
this.code.push([VMI_update_cash_coefficient, [
|
3475
3493
|
VM.PRODUCE, VM.TWO_X, vi, l.flow_delay,
|
3476
3494
|
tnpx, l.relative_rate]]);
|
3477
|
-
// When multiplier is Delta, consume level in previous t
|
3495
|
+
// When multiplier is Delta, consume level in previous t.
|
3478
3496
|
if(l.multiplier === VM.LM_INCREASE) {
|
3479
3497
|
this.code.push([VMI_update_cash_coefficient, [
|
3480
3498
|
VM.CONSUME, VM.TWO_X, vi, l.flow_delay,
|
3481
|
-
// NOTE:
|
3499
|
+
// NOTE: Now 7th argument indicates "delay + 1".
|
3482
3500
|
tnpx, l.relative_rate, 1]]);
|
3483
3501
|
}
|
3484
3502
|
}
|
@@ -3487,33 +3505,26 @@ class VirtualMachine {
|
|
3487
3505
|
} // END of FOR ALL output links
|
3488
3506
|
} // END of IF process not ignored
|
3489
3507
|
} // END of FOR ALL processes
|
3490
|
-
|
3491
|
-
//
|
3492
|
-
//
|
3493
|
-
|
3494
|
-
|
3495
|
-
|
3496
|
-
|
3497
|
-
|
3498
|
-
//
|
3499
|
-
|
3500
|
-
|
3501
|
-
|
3502
|
-
[VMI_add_const_to_coefficient, [a.cash_in_var_index, 1]],
|
3503
|
-
[VMI_add_constraint, VM.EQ],
|
3504
|
-
[VMI_copy_cash_coefficients, VM.CONSUME],
|
3505
|
-
[VMI_add_const_to_coefficient, [a.cash_out_var_index, 1]],
|
3506
|
-
[VMI_add_constraint, VM.EQ]
|
3507
|
-
);
|
3508
|
-
|
3508
|
+
|
3509
|
+
// Check whether any VMI_update_cash_coefficient instructions have
|
3510
|
+
// been added. If so, the objective function will maximze weighted
|
3511
|
+
// sum of actor cash flows, otherwise minimize sum of process levels.
|
3512
|
+
this.no_cash_flows = this.no_cash_flows &&
|
3513
|
+
this.code[this.code.length - 1][0] !== VMI_update_cash_coefficient;
|
3514
|
+
|
3515
|
+
// ALWAYS add the two cash flow constraints for this actor, as both
|
3516
|
+
// cash flow variables must be computed (will be 0 if no cash flows).
|
3517
|
+
this.code.push([VMI_add_cash_constraints,
|
3518
|
+
[a.cash_in_var_index, a.cash_out_var_index]]);
|
3519
|
+
|
3509
3520
|
} // END of FOR loop iterating over all actors
|
3510
3521
|
|
3511
|
-
// NEXT:
|
3522
|
+
// NEXT: Define the coefficients for the objective function.
|
3512
3523
|
this.code.push([VMI_clear_coefficients, null]);
|
3513
3524
|
|
3514
|
-
// NOTE:
|
3515
|
-
// considered, no cash flows have been detected, the solver should aim
|
3516
|
-
// minimal effort, i.e., lowest weighted sum of process levels
|
3525
|
+
// NOTE: If, after all actors -- this includes (no actor) -- have been
|
3526
|
+
// considered, no cash flows have been detected, the solver should aim
|
3527
|
+
// for minimal effort, i.e., lowest weighted sum of process levels.
|
3517
3528
|
if(this.no_cash_flows) {
|
3518
3529
|
for(i = 0; i < process_keys.length; i++) {
|
3519
3530
|
k = process_keys[i];
|
@@ -4285,8 +4296,9 @@ class VirtualMachine {
|
|
4285
4296
|
// largest cash flow coefficient (in absolute value) within the
|
4286
4297
|
// current block so that cash flows cannot easily "overrule" the
|
4287
4298
|
// slack penalties in the objective function.
|
4288
|
-
// NOTE: No scaling needed if model features no cash flows
|
4289
|
-
|
4299
|
+
// NOTE: No scaling needed if model features no cash flows, or if
|
4300
|
+
// cash scalar equals 1.
|
4301
|
+
if(this.no_cash_flows || this.cash_scalar === 1) return;
|
4290
4302
|
this.logMessage(this.block_count,
|
4291
4303
|
'Cash flows scaled by 1/' + this.cash_scalar);
|
4292
4304
|
// Use reciprocal as multiplier to scale the constraint coefficients.
|
@@ -4307,7 +4319,7 @@ class VirtualMachine {
|
|
4307
4319
|
}
|
4308
4320
|
}
|
4309
4321
|
}
|
4310
|
-
|
4322
|
+
|
4311
4323
|
checkForInfinity(n) {
|
4312
4324
|
// Return floating point number `n`, or +INF or -INF if the absolute
|
4313
4325
|
// value of `n` is relatively (!) close to the VM infinity constants
|
@@ -4333,8 +4345,6 @@ class VirtualMachine {
|
|
4333
4345
|
let bb = (block - 1) * MODEL.block_length + 1,
|
4334
4346
|
abl = this.chunk_length,
|
4335
4347
|
cbl = this.actualBlockLength(block);
|
4336
|
-
// For the last block, crop the actual block length so it does not
|
4337
|
-
// extend beyond the simulation period (these results should be ignored).
|
4338
4348
|
// If no results computed, preserve those already computed for the
|
4339
4349
|
// pervious chunk as "look-ahead".
|
4340
4350
|
if(err && block > 1 && MODEL.look_ahead > 0) {
|
@@ -4345,7 +4355,9 @@ class VirtualMachine {
|
|
4345
4355
|
'No results from solver -- retained results of ' +
|
4346
4356
|
pluralS(MODEL.look_ahead, 'look-ahead time step'));
|
4347
4357
|
}
|
4348
|
-
|
4358
|
+
// For the last block, crop the actual block length so it does not
|
4359
|
+
// extend beyond the simulation period (these results should be ignored).
|
4360
|
+
if(cbl < 0) {
|
4349
4361
|
this.logMessage(block, 'Results of last optimization could be discarded');
|
4350
4362
|
abl = 0;
|
4351
4363
|
} else if(cbl < abl) {
|
@@ -4365,41 +4377,41 @@ class VirtualMachine {
|
|
4365
4377
|
ncv_msg + ' is not a multiple of # columns', this.cols);
|
4366
4378
|
// Assume no warnings or errors.
|
4367
4379
|
this.error_count = 0;
|
4368
|
-
// Set cash flows for all actors
|
4369
|
-
// NOTE:
|
4380
|
+
// Set cash flows for all actors.
|
4381
|
+
// NOTE: All cash IN and cash OUT values should normally be non-negative,
|
4370
4382
|
// but since Linny-R permits negative lower bounds on processes, and also
|
4371
4383
|
// negative link rates, cash flows may become negative. If that occurs,
|
4372
4384
|
// the modeler should be warned.
|
4373
4385
|
for(let o in MODEL.actors) if(MODEL.actors.hasOwnProperty(o)) {
|
4374
4386
|
const a = MODEL.actors[o];
|
4375
|
-
// NOTE: `b` is the index to be used for the vectors
|
4387
|
+
// NOTE: `b` is the index to be used for the vectors.
|
4376
4388
|
let b = bb;
|
4377
|
-
// Iterate over all time steps in this block
|
4378
|
-
// NOTE: -1 because indices start at 1, but list is zero-based
|
4389
|
+
// Iterate over all time steps in this block.
|
4390
|
+
// NOTE: -1 because indices start at 1, but list is zero-based.
|
4379
4391
|
let j = -1;
|
4380
4392
|
for(let i = 0; i < abl; i++) {
|
4381
|
-
// NOTE:
|
4393
|
+
// NOTE: Cash coefficients computed by the solver must be scaled back.
|
4382
4394
|
a.cash_in[b] = this.checkForInfinity(
|
4383
4395
|
x[a.cash_in_var_index + j] * this.cash_scalar);
|
4384
4396
|
a.cash_out[b] = this.checkForInfinity(
|
4385
4397
|
x[a.cash_out_var_index + j] * this.cash_scalar);
|
4386
4398
|
a.cash_flow[b] = a.cash_in[b] - a.cash_out[b];
|
4387
|
-
// Count occurrences of a negative cash flow (threshold -0.5 cent)
|
4388
|
-
if(a.cash_in[b] < -0.005) {
|
4399
|
+
// Count occurrences of a negative cash flow (threshold -0.5 cent).
|
4400
|
+
if(b <= this.nr_of_time_steps && a.cash_in[b] < -0.005) {
|
4389
4401
|
this.logMessage(block, `${this.WARNING}(t=${b}${round}) ` +
|
4390
4402
|
a.displayName + ' cash IN = ' + a.cash_in[b].toPrecision(2));
|
4391
4403
|
}
|
4392
|
-
if(a.cash_out[b] < -0.005) {
|
4404
|
+
if(b <= this.nr_of_time_steps && a.cash_out[b] < -0.005) {
|
4393
4405
|
this.logMessage(block, `${this.WARNING}(t=${b}${round}) ` +
|
4394
4406
|
a.displayName + ' cash IN = ' + a.cash_out[b].toPrecision(2));
|
4395
4407
|
}
|
4396
|
-
// Advance column offset in tableau by the # cols per time step
|
4408
|
+
// Advance column offset in tableau by the # cols per time step.
|
4397
4409
|
j += this.cols;
|
4398
|
-
// Advance to the next time step in this block
|
4410
|
+
// Advance to the next time step in this block.
|
4399
4411
|
b++;
|
4400
4412
|
}
|
4401
4413
|
}
|
4402
|
-
// Set production levels and start-up moments for all processes
|
4414
|
+
// Set production levels and start-up moments for all processes.
|
4403
4415
|
for(let o in MODEL.processes) if(MODEL.processes.hasOwnProperty(o) &&
|
4404
4416
|
!MODEL.ignored_entities[o]) {
|
4405
4417
|
const
|
@@ -4490,10 +4502,10 @@ class VirtualMachine {
|
|
4490
4502
|
// NOTE: Only check after the last round has been evaluated.
|
4491
4503
|
if(round === this.lastRound) {
|
4492
4504
|
let b = bb;
|
4493
|
-
// Iterate over all time steps in this block
|
4505
|
+
// Iterate over all time steps in this block.
|
4494
4506
|
let j = -1;
|
4495
4507
|
for(let i = 0; i < abl; i++) {
|
4496
|
-
// Index `svt` iterates over types of slack variable (0 - 2)
|
4508
|
+
// Index `svt` iterates over types of slack variable (0 - 2).
|
4497
4509
|
for(let svt = 0; svt <= 2; svt++) {
|
4498
4510
|
const
|
4499
4511
|
svl = this.slack_variables[svt],
|
@@ -4505,12 +4517,12 @@ class VirtualMachine {
|
|
4505
4517
|
absl = Math.abs(slack);
|
4506
4518
|
if(absl > VM.NEAR_ZERO) {
|
4507
4519
|
const v = this.variables[vi - 1];
|
4508
|
-
// NOTE: for constraints, add 'UB' or 'LB' to its vector for
|
4509
|
-
// time step where slack was used
|
4520
|
+
// NOTE: for constraints, add 'UB' or 'LB' to its vector for
|
4521
|
+
// the time step where slack was used.
|
4510
4522
|
if(v[1] instanceof BoundLine) {
|
4511
4523
|
v[1].constraint.slack_info[b] = v[0];
|
4512
4524
|
}
|
4513
|
-
if(absl > VM.SIG_DIF_FROM_ZERO) {
|
4525
|
+
if(b <= this.nr_of_time_steps && absl > VM.SIG_DIF_FROM_ZERO) {
|
4514
4526
|
this.logMessage(block, `${this.WARNING}(t=${b}${round}) ` +
|
4515
4527
|
`${v[1].displayName} ${v[0]} slack = ${this.sig4Dig(slack)}`);
|
4516
4528
|
if(v[1] instanceof Product) {
|
@@ -4556,10 +4568,13 @@ class VirtualMachine {
|
|
4556
4568
|
// optimization period, hence start by calculating the offset `bb`
|
4557
4569
|
// being the first time step of this block.
|
4558
4570
|
// Blocks are numbered 1, 2, ...
|
4571
|
+
let latest_time_step = 0;
|
4559
4572
|
const
|
4560
4573
|
bb = (block - 1) * MODEL.block_length + 1,
|
4561
4574
|
cbl = this.actualBlockLength(block);
|
4562
4575
|
|
4576
|
+
// Start with an empty list of variables to "fixate" in the next block.
|
4577
|
+
this.variables_to_fixate = {};
|
4563
4578
|
// FIRST: Calculate the actual flows on links.
|
4564
4579
|
let b, bt, p, pl, ld, ci;
|
4565
4580
|
for(let l in MODEL.links) if(MODEL.links.hasOwnProperty(l) &&
|
@@ -4574,6 +4589,23 @@ class VirtualMachine {
|
|
4574
4589
|
// NOTE: Flows may have a delay!
|
4575
4590
|
ld = l.actualDelay(b);
|
4576
4591
|
bt = b - ld;
|
4592
|
+
latest_time_step = Math.max(latest_time_step, bt);
|
4593
|
+
// If delay < 0 AND this results in a block time beyond the
|
4594
|
+
// block length, this means that the level of the FROM node
|
4595
|
+
// must be "fixated" in the next block.
|
4596
|
+
const nbt = bt - bb - MODEL.block_length + 1;
|
4597
|
+
// NOTE: `nbt` (next block time) cannot be beyond the look-ahead
|
4598
|
+
// period, as for those time steps the levels are still undefined,
|
4599
|
+
// NOR can it be later than the duration of the (negative) delay
|
4600
|
+
// on the link.
|
4601
|
+
if(ld < 0 && nbt > 0 && nbt <= MODEL.look_ahead && nbt <= -ld) {
|
4602
|
+
this.addNodeToFixate(l.from_node, nbt,
|
4603
|
+
// NOTE: Use the level at time `bt` (i.e., in the future)
|
4604
|
+
// because that is the optimal level computed for this chunk
|
4605
|
+
// (in its look-ahead period) that should be maintained in
|
4606
|
+
// the next block.
|
4607
|
+
l.from_node.nonZeroLevel(bt));
|
4608
|
+
}
|
4577
4609
|
// NOTE: Block index may fall beyond actual chunk length.
|
4578
4610
|
ci = i - ld;
|
4579
4611
|
// NOTE: Use non-zero level here to ignore non-zero values that
|
@@ -4682,9 +4714,10 @@ class VirtualMachine {
|
|
4682
4714
|
// INPUT links from priced products generate cash OUT...
|
4683
4715
|
for(let j = 0; j < p.inputs.length; j++) {
|
4684
4716
|
// NOTE: Input links do NOT have a delay.
|
4685
|
-
const
|
4686
|
-
|
4687
|
-
|
4717
|
+
const
|
4718
|
+
l = p.inputs[j],
|
4719
|
+
af = l.actual_flow[b],
|
4720
|
+
fnp = l.from_node.price;
|
4688
4721
|
if(af > VM.NEAR_ZERO && fnp.defined) {
|
4689
4722
|
const pp = fnp.result(b);
|
4690
4723
|
if(pp > 0 && pp < VM.PLUS_INFINITY) {
|
@@ -4697,14 +4730,14 @@ class VirtualMachine {
|
|
4697
4730
|
}
|
4698
4731
|
// OUTPUT links to priced products generate cash IN ...
|
4699
4732
|
for(let j = 0; j < p.outputs.length; j++) {
|
4700
|
-
// NOTE:
|
4701
|
-
const
|
4702
|
-
|
4703
|
-
|
4704
|
-
|
4733
|
+
// NOTE: actualFlows already consider delay!
|
4734
|
+
const
|
4735
|
+
l = p.outputs[j],
|
4736
|
+
af = l.actualFlow(b),
|
4737
|
+
tnp = l.to_node.price;
|
4705
4738
|
if(af > VM.NEAR_ZERO && tnp.defined) {
|
4706
|
-
// NOTE:
|
4707
|
-
const pp = tnp.result(b
|
4739
|
+
// NOTE: Use the price at the time of the actual flow.
|
4740
|
+
const pp = tnp.result(b);
|
4708
4741
|
if(pp > 0 && pp < VM.PLUS_INFINITY) {
|
4709
4742
|
ci += pp * af;
|
4710
4743
|
// ... unless the product price is negative; then cash OUT.
|
@@ -4733,15 +4766,128 @@ class VirtualMachine {
|
|
4733
4766
|
// at a time because of delays, and also because expressions may refer
|
4734
4767
|
// to values for earlier time steps.
|
4735
4768
|
if(MODEL.infer_cost_prices) {
|
4769
|
+
// Keep track of products for which CP will not be computed correctly
|
4770
|
+
// due to negative delays.
|
4771
|
+
MODEL.products_with_negative_delays = {};
|
4736
4772
|
b = bb;
|
4737
4773
|
for(let i = 0; i < cbl; i++) {
|
4738
|
-
|
4774
|
+
// NOTE: Issues with cost price calculation beyond simulation
|
4775
|
+
// period need not be reported.
|
4776
|
+
if(b <= this.nr_of_time_steps && !MODEL.calculateCostPrices(b)) {
|
4739
4777
|
this.logMessage(block, `${this.WARNING}(t=${b}) ` +
|
4740
4778
|
'Invalid cost prices due to negative flow(s)');
|
4741
4779
|
}
|
4742
4780
|
// Move on to the next time step of the block.
|
4743
4781
|
b++;
|
4744
4782
|
}
|
4783
|
+
// NOTE: Links with negative delays will not have correct cost
|
4784
|
+
// prices as these occur in the future. Having calculated (insofar
|
4785
|
+
// as possibe) cost prices of nodes, those on links with negative
|
4786
|
+
// delays can now be set to these "future" cost prices.
|
4787
|
+
let ts = Object.keys(MODEL.products_with_negative_delays);
|
4788
|
+
// Make a separate list of stocks, as these require post-processing.
|
4789
|
+
const stocks = [];
|
4790
|
+
// NOTE: Proceed backwards in time, using CP = 0 when time step
|
4791
|
+
// falls beyond block length + look-ahead.
|
4792
|
+
for(let i = ts.length - 1; i >= 0; i--) {
|
4793
|
+
const
|
4794
|
+
t = parseInt(ts[i]),
|
4795
|
+
pwnd = MODEL.products_with_negative_delays[t];
|
4796
|
+
// @@TO DO: Sort products in order of precedence to avoid that
|
4797
|
+
// when Q1 --> Q2 the CP of Q2 is computed first, and remains
|
4798
|
+
// "undefined" while the CP of Q1 can be known.
|
4799
|
+
for(let j = 0; j < pwnd.length; j++) {
|
4800
|
+
const p = pwnd[j];
|
4801
|
+
if(p.is_buffer) addDistinct(p, stocks);
|
4802
|
+
// Compute total cost price as sum of inflow * unit cost price.
|
4803
|
+
let tcp = 0,
|
4804
|
+
taf = 0;
|
4805
|
+
for(let k = 0; k < p.inputs.length; k++) {
|
4806
|
+
const
|
4807
|
+
l = p.inputs[k],
|
4808
|
+
d = l.actualDelay(t),
|
4809
|
+
td = t - d;
|
4810
|
+
// Only compute if t lies within the optimization period.
|
4811
|
+
if(td < bb + cbl) {
|
4812
|
+
const
|
4813
|
+
// NOTE: actualFlow already considers delays.
|
4814
|
+
af = l.actualFlow(t),
|
4815
|
+
// Get the cost price for the delayed time step.
|
4816
|
+
qcp = l.from_node.costPrice(td);
|
4817
|
+
if(qcp > VM.EXCEPTION) {
|
4818
|
+
tcp = VM.UNDEFINED;
|
4819
|
+
break;
|
4820
|
+
}
|
4821
|
+
if(af > VM.NEAR_ZERO) {
|
4822
|
+
tcp += af * qcp * l.share_of_cost;
|
4823
|
+
taf += af;
|
4824
|
+
}
|
4825
|
+
}
|
4826
|
+
}
|
4827
|
+
// CP beyond time horizon
|
4828
|
+
if(tcp >= VM.EXCEPTION) {
|
4829
|
+
p.cost_price[t] = VM.UNDEFINED;
|
4830
|
+
} else if(taf > 0) {
|
4831
|
+
// Product cost price is "per unit".
|
4832
|
+
p.cost_price[t] = tcp / taf;
|
4833
|
+
} else if(p.inputs.length === 1) {
|
4834
|
+
// If product has only one input, its cost price can be
|
4835
|
+
// inferred from this input even if there is no inflow.
|
4836
|
+
const
|
4837
|
+
l = p.inputs[0],
|
4838
|
+
d = l.actualDelay(t),
|
4839
|
+
rr = l.relative_rate.result[t - d];
|
4840
|
+
// When t - d is outside period, `rr` is undefined => CP = 0.
|
4841
|
+
p.cost_price[t] = (rr ? l.from_node.costPrice(t - d) *
|
4842
|
+
rr * l.share_of_cost : 0);
|
4843
|
+
} else {
|
4844
|
+
p.cost_price[t] = 0;
|
4845
|
+
}
|
4846
|
+
}
|
4847
|
+
}
|
4848
|
+
// NOTE: Stocks require special treatment due to working backwards
|
4849
|
+
// in time.
|
4850
|
+
for(let i = 0; i < stocks.length; i++) {
|
4851
|
+
const p = stocks[i];
|
4852
|
+
// Get previous stock level and stock price (prior to block start).
|
4853
|
+
let sl = p.actualLevel(bb - 1),
|
4854
|
+
psp = p.stock_price[bb - 1] || 0;
|
4855
|
+
for(let t = bb; t < bb + cbl; t++) {
|
4856
|
+
// Subtract outflows from stock level.
|
4857
|
+
for(let k = 0; k < p.outputs.length; k++) {
|
4858
|
+
const
|
4859
|
+
l = p.outputs[k],
|
4860
|
+
af = l.actualFlow(t);
|
4861
|
+
if(af > VM.NEAR_ZERO) sl -= af;
|
4862
|
+
}
|
4863
|
+
// Start with total CP = remaining old stock times old price.
|
4864
|
+
let tcp = sl * psp;
|
4865
|
+
// Add inflows to both level and total CP.
|
4866
|
+
for(let k = 0; k < p.inputs.length; k++) {
|
4867
|
+
const
|
4868
|
+
l = p.inputs[k],
|
4869
|
+
af = l.actualFlow(t),
|
4870
|
+
d = l.actualDelay(t);
|
4871
|
+
if(af > VM.NEAR_ZERO) {
|
4872
|
+
const cp = l.from_node.costPrice(t - d);
|
4873
|
+
if(cp >= VM.EXCEPTION) {
|
4874
|
+
tcp = VM.UNDEFINED;
|
4875
|
+
break;
|
4876
|
+
}
|
4877
|
+
sl += af;
|
4878
|
+
tcp += af * cp;
|
4879
|
+
}
|
4880
|
+
}
|
4881
|
+
if(tcp >= VM.EXCEPTION) {
|
4882
|
+
psp = VM.UNDEFINED;
|
4883
|
+
} else if(sl > VM.NEAR_ZERO) {
|
4884
|
+
psp = tcp / sl;
|
4885
|
+
} else {
|
4886
|
+
psp = 0;
|
4887
|
+
}
|
4888
|
+
p.stock_price[t] = psp;
|
4889
|
+
}
|
4890
|
+
}
|
4745
4891
|
}
|
4746
4892
|
|
4747
4893
|
// THEN: Reset all datasets that are outcomes or serve as "formulas".
|
@@ -4839,41 +4985,45 @@ class VirtualMachine {
|
|
4839
4985
|
}
|
4840
4986
|
|
4841
4987
|
resetTableau() {
|
4842
|
-
// Clears tableau data: matrix, rhs and constraint types
|
4843
|
-
// NOTE:
|
4844
|
-
// memory after posting a block to the server
|
4988
|
+
// Clears tableau data: matrix, rhs and constraint types.
|
4989
|
+
// NOTE: This reset is called when initializing, and to free up
|
4990
|
+
// memory after posting a block to the server.
|
4845
4991
|
this.matrix.length = 0;
|
4846
4992
|
this.right_hand_side.length = 0;
|
4847
4993
|
this.constraint_types.length = 0;
|
4848
4994
|
}
|
4849
4995
|
|
4850
4996
|
initializeTableau(abl) {
|
4851
|
-
// `offset` is used to calculate the actual column index for variables
|
4997
|
+
// `offset` is used to calculate the actual column index for variables.
|
4852
4998
|
this.offset = 0;
|
4853
|
-
// NOTE:
|
4854
|
-
// represented as arrays but as objects, e.g., {4:1.5, 8:0.3} to
|
4855
|
-
// an array [0, 0, 0, 1.5, 0, 0, 0, 0.3, 0, 0, 0, ...]
|
4856
|
-
// The keys can span the full chunk, so the objects represent vectors
|
4857
|
-
// have a "virtual length" of cols * abl
|
4999
|
+
// NOTE: Vectors are "sparse" (i.e., will contain many 0) and are hence
|
5000
|
+
// not represented as arrays but as objects, e.g., {4:1.5, 8:0.3} to
|
5001
|
+
// represent an array [0, 0, 0, 1.5, 0, 0, 0, 0.3, 0, 0, 0, ...].
|
5002
|
+
// The keys can span the full chunk, so the objects represent vectors
|
5003
|
+
// that have a "virtual length" of cols * abl.
|
4858
5004
|
this.coefficients = {};
|
5005
|
+
this.rhs = 0;
|
5006
|
+
// NOTE: VM needs separate "registers" for cash IN and cash OUT
|
5007
|
+
// coefficients and RHS because the direction of the cash flow is
|
5008
|
+
// dynamic.
|
4859
5009
|
this.cash_in_coefficients = {};
|
5010
|
+
this.cash_in_rhs = 0;
|
4860
5011
|
this.cash_out_coefficients = {};
|
4861
|
-
|
4862
|
-
//
|
4863
|
-
//
|
4864
|
-
//
|
4865
|
-
//
|
4866
|
-
//
|
5012
|
+
this.cash_out_rhs = 0;
|
5013
|
+
// NOTE: Cash flow equation coefficients may be divided by a scalar to
|
5014
|
+
// keep them amply below the base slack penalty; the scalar is increased
|
5015
|
+
// by the VM instruction VMI_add_cash_constraints so that at the end of
|
5016
|
+
// the block setup it equals the highest absolute coefficient in the
|
5017
|
+
// cash flow constraint equations. The VM maintains a list of indices
|
5018
|
+
// of matrix rows that then need to be scaled.
|
4867
5019
|
this.cash_scalar = 1;
|
4868
5020
|
this.cash_constraints = [];
|
5021
|
+
// Vector for the objective function coefficients.
|
4869
5022
|
this.objective = {};
|
5023
|
+
// Vectors for the bounds on decision variables.
|
4870
5024
|
this.lower_bounds = {};
|
4871
5025
|
this.upper_bounds = {};
|
4872
|
-
//
|
4873
|
-
// actors' cash flows are calculated as C - a1P1 - a2P2 - ... anPn = 0
|
4874
|
-
this.rhs = 0;
|
4875
|
-
// NOTE: the constraint coefficient matrix and the rhs and ct vectors
|
4876
|
-
// have equal length (#rows); the matrix is a list of sparse vectors
|
5026
|
+
// Clear the tableau matrix and constraint type and RHS columns.
|
4877
5027
|
this.resetTableau();
|
4878
5028
|
// NOTE: setupBlock only works properly if setupProblem was successful
|
4879
5029
|
// Every variable gets one column per time step => tableau is organized
|
@@ -4899,7 +5049,7 @@ class VirtualMachine {
|
|
4899
5049
|
this.is_binary[parseInt(i) + j*this.cols] = true;
|
4900
5050
|
}
|
4901
5051
|
}
|
4902
|
-
// Set list with indices of semi-contiuous variables
|
5052
|
+
// Set list with indices of semi-contiuous variables.
|
4903
5053
|
this.is_semi_continuous = {};
|
4904
5054
|
for(let i in this.sec_var_indices) if(Number(i)) {
|
4905
5055
|
for(let j = 0; j < abl; j++) {
|
@@ -5028,7 +5178,7 @@ class VirtualMachine {
|
|
5028
5178
|
let rem = (MODEL.runLength - MODEL.look_ahead) % MODEL.block_length;
|
5029
5179
|
// If this remainder equals 0, the last block is a full chunk.
|
5030
5180
|
if(rem === 0) return this.chunk_length;
|
5031
|
-
// Otherwise, the last block
|
5181
|
+
// Otherwise, the last block has remainder + look-ahead time steps.
|
5032
5182
|
return rem + MODEL.look_ahead;
|
5033
5183
|
}
|
5034
5184
|
|
@@ -5058,6 +5208,33 @@ class VirtualMachine {
|
|
5058
5208
|
return this.chunk_length * this.cols + this.chunk_variables.length;
|
5059
5209
|
}
|
5060
5210
|
|
5211
|
+
addNodeToFixate(n, bt, level) {
|
5212
|
+
// Record that level of node `n` must be fixated for block time `bt`
|
5213
|
+
// in the next block by setting it to the specified level.
|
5214
|
+
const
|
5215
|
+
vi = n.level_var_index,
|
5216
|
+
oo = n.on_off_var_index,
|
5217
|
+
iz = n.is_zero_var_index;
|
5218
|
+
if(!this.variables_to_fixate.hasOwnProperty(vi)) {
|
5219
|
+
this.variables_to_fixate[vi] = {};
|
5220
|
+
}
|
5221
|
+
this.variables_to_fixate[vi][bt] = level;
|
5222
|
+
if(oo >= 0) {
|
5223
|
+
if(!this.variables_to_fixate.hasOwnProperty(oo)) {
|
5224
|
+
this.variables_to_fixate[oo] = {};
|
5225
|
+
}
|
5226
|
+
this.variables_to_fixate[oo][bt] = (
|
5227
|
+
Math.abs(level) < VM.NEAR_ZERO ? 0 : 1);
|
5228
|
+
}
|
5229
|
+
if(iz >= 0) {
|
5230
|
+
if(!this.variables_to_fixate.hasOwnProperty(iz)) {
|
5231
|
+
this.variables_to_fixate[iz] = {};
|
5232
|
+
}
|
5233
|
+
this.variables_to_fixate[iz][bt] = (
|
5234
|
+
Math.abs(level) < VM.NEAR_ZERO ? 1 : 0);
|
5235
|
+
}
|
5236
|
+
}
|
5237
|
+
|
5061
5238
|
writeLpFormat(cplex=false) {
|
5062
5239
|
// NOTE: Up to version 1.5.6, actual block length of last block used
|
5063
5240
|
// to be shorter than the chunk length so as not to go beyond the
|
@@ -5701,6 +5878,37 @@ Solver status = ${json.status}`);
|
|
5701
5878
|
this.logMessage(this.block_count, 'Zero columns -- nothing to solve');
|
5702
5879
|
return;
|
5703
5880
|
}
|
5881
|
+
// If negative delays require "fixating" variables for some number
|
5882
|
+
// of time steps, this must be logged in the monitor.
|
5883
|
+
const keys = Object.keys(this.variables_to_fixate);
|
5884
|
+
if(keys.length) {
|
5885
|
+
const msg = ['NOTE: Due to negative link delays, levels for ' +
|
5886
|
+
pluralS(keys.length, 'variable') + ' are pre-set:'];
|
5887
|
+
for(let i = 0; i < keys.length; i++) {
|
5888
|
+
const
|
5889
|
+
vi = parseInt(keys[i]),
|
5890
|
+
// NOTE: Subtract 1 because variable list is zero-based.
|
5891
|
+
vbl = this.variables[vi - 1],
|
5892
|
+
fv = this.variables_to_fixate[vi],
|
5893
|
+
fvk = Object.keys(fv),
|
5894
|
+
fvl = [];
|
5895
|
+
// Add constraints that fixate the levels directly to the tableau.
|
5896
|
+
for(let i = 0; i < fvk.length; i++) {
|
5897
|
+
const
|
5898
|
+
bt = fvk[i],
|
5899
|
+
pl = fv[bt],
|
5900
|
+
k = (bt - 1) * VM.cols + vi,
|
5901
|
+
row = {};
|
5902
|
+
row[k] = 1;
|
5903
|
+
VM.matrix.push(row);
|
5904
|
+
VM.right_hand_side.push(pl);
|
5905
|
+
VM.constraint_types.push(VM.EQ);
|
5906
|
+
fvl.push(pl + ' for bt=' + bt);
|
5907
|
+
}
|
5908
|
+
msg.push(`- ${vbl[1].displayName} [${vbl[0]}]: ${fvl.join(', ')}`);
|
5909
|
+
}
|
5910
|
+
this.logMessage(this.block_count, msg.join('\n'));
|
5911
|
+
}
|
5704
5912
|
this.logMessage(this.block_count,
|
5705
5913
|
'Creating model for block #' + this.blockWithRound);
|
5706
5914
|
this.cbl = CONFIGURATION.progress_needle_interval * 200;
|
@@ -5898,7 +6106,7 @@ function VMI_push_block_number(x) {
|
|
5898
6106
|
|
5899
6107
|
function VMI_push_run_length(x) {
|
5900
6108
|
// Push the run length (excl. look-ahead!).
|
5901
|
-
const n =
|
6109
|
+
const n = VM.nr_of_time_steps;
|
5902
6110
|
if(DEBUGGING) console.log('push run length N = ' + n);
|
5903
6111
|
x.push(n);
|
5904
6112
|
}
|
@@ -6177,7 +6385,7 @@ function relativeTimeStep(t, anchor, offset, dtm, x) {
|
|
6177
6385
|
}
|
6178
6386
|
if(anchor === 'l') {
|
6179
6387
|
// Last: offset relative to the last index in the vector.
|
6180
|
-
return
|
6388
|
+
return VM.nr_of_time_steps + offset;
|
6181
6389
|
}
|
6182
6390
|
if(anchor === 's') {
|
6183
6391
|
// Scaled: offset is scaled to time unit of run.
|
@@ -6565,7 +6773,7 @@ function VMI_push_run_result(x, args) {
|
|
6565
6773
|
} else if(rrspec.nr !== false) {
|
6566
6774
|
// Run number inferred from local time step of expression
|
6567
6775
|
const
|
6568
|
-
rl =
|
6776
|
+
rl = VM.nr_of_time_steps,
|
6569
6777
|
range = rangeToList(rrspec.nr, xp.runs.length - 1);
|
6570
6778
|
if(range) {
|
6571
6779
|
const
|
@@ -6694,7 +6902,7 @@ function VMI_push_statistic(x, args) {
|
|
6694
6902
|
}
|
6695
6903
|
// Negative time step is evaluated as t = 0 (initial value) t beyond
|
6696
6904
|
// optimization period is evaluated as its last time step
|
6697
|
-
const tmax =
|
6905
|
+
const tmax = VM.nr_of_time_steps;
|
6698
6906
|
t1 = Math.max(0, Math.min(tmax, t1));
|
6699
6907
|
t2 = Math.max(0, Math.min(tmax, t2));
|
6700
6908
|
// Trace only now that time step range has been computed
|
@@ -7467,19 +7675,19 @@ function VMI_set_bounds(args) {
|
|
7467
7675
|
k = VM.offset + vi,
|
7468
7676
|
r = VM.round_letters.indexOf(VM.round_sequence[VM.current_round]),
|
7469
7677
|
// Optional fourth parameter indicates whether the solver's
|
7470
|
-
// infinity values should be used
|
7678
|
+
// infinity values should be used.
|
7471
7679
|
solver_inf = args.length > 3 && args[3],
|
7472
7680
|
inf_val = (solver_inf ? VM.SOLVER_PLUS_INFINITY : VM.PLUS_INFINITY);
|
7473
7681
|
let l,
|
7474
7682
|
u,
|
7475
7683
|
fixed = (vi in VM.fixed_var_indices[r - 1]);
|
7476
7684
|
if(fixed) {
|
7477
|
-
// Set both bounds equal to the level set in the previous round,
|
7478
|
-
// if this is the first round
|
7685
|
+
// Set both bounds equal to the level set in the previous round,
|
7686
|
+
// or to 0 if this is the first round.
|
7479
7687
|
if(VM.current_round) {
|
7480
7688
|
l = vbl.actualLevel(VM.t);
|
7481
|
-
// QUICK PATCH!
|
7482
|
-
// computed in prior round make problem infeasible
|
7689
|
+
// QUICK PATCH! Should resolve that small non-zero process levels
|
7690
|
+
// computed in prior round make problem infeasible.
|
7483
7691
|
if(l < 0.0005) l = 0;
|
7484
7692
|
} else {
|
7485
7693
|
l = 0;
|
@@ -7487,7 +7695,7 @@ function VMI_set_bounds(args) {
|
|
7487
7695
|
u = l;
|
7488
7696
|
fixed = ' (FIXED ' + vbl.displayName + ')';
|
7489
7697
|
} else {
|
7490
|
-
// Set bounds as specified by the two arguments
|
7698
|
+
// Set bounds as specified by the two arguments.
|
7491
7699
|
l = args[1];
|
7492
7700
|
if(l instanceof Expression) l = l.result(VM.t);
|
7493
7701
|
if(l === VM.UNDEFINED) l = 0;
|
@@ -7500,22 +7708,22 @@ function VMI_set_bounds(args) {
|
|
7500
7708
|
}
|
7501
7709
|
fixed = '';
|
7502
7710
|
}
|
7503
|
-
// NOTE:
|
7504
|
-
// "fixed !== '' || " before DEBUGGING below
|
7711
|
+
// NOTE: To see in the console whether fixing across rounds works, insert
|
7712
|
+
// "fixed !== '' || " before DEBUGGING below.
|
7505
7713
|
if(DEBUGGING) {
|
7506
7714
|
console.log(['set_bounds [', k, '] ', vbl.displayName, ' t = ', VM.t,
|
7507
7715
|
' LB = ', VM.sig4Dig(l), ', UB = ', VM.sig4Dig(u), fixed].join(''));
|
7508
7716
|
}
|
7509
|
-
// NOTE:
|
7717
|
+
// NOTE: Since the VM vectors for lower bounds and upper bounds are
|
7510
7718
|
// initialized with default values (0 for LB, +INF for UB), there is
|
7511
|
-
// no need to set them
|
7719
|
+
// no need to set them.
|
7512
7720
|
if(l !== 0 || u < inf_val) {
|
7513
7721
|
VM.lower_bounds[k] = l;
|
7514
7722
|
VM.upper_bounds[k] = u;
|
7515
7723
|
// If associated node is FROM-node of a "peak increase" link, then
|
7516
7724
|
// the "peak increase" variables of this node must have the highest
|
7517
7725
|
// UB of the node (for all t in this block, hence MAX) MINUS their
|
7518
|
-
// peak level in previous block
|
7726
|
+
// peak level in previous block.
|
7519
7727
|
if(vbl.peak_inc_var_index >= 0) {
|
7520
7728
|
u = Math.max(0, u - vbl.b_peak[VM.block_count - 1]);
|
7521
7729
|
const
|
@@ -7531,13 +7739,28 @@ function VMI_set_bounds(args) {
|
|
7531
7739
|
}
|
7532
7740
|
|
7533
7741
|
function VMI_clear_coefficients() {
|
7742
|
+
// Clear the coefficients register and set RHS to zero.
|
7534
7743
|
if(DEBUGGING) console.log('clear_coefficients');
|
7535
7744
|
VM.coefficients = {};
|
7536
|
-
VM.cash_in_coefficients = {};
|
7537
|
-
VM.cash_out_coefficients = {};
|
7538
7745
|
VM.rhs = 0;
|
7539
7746
|
}
|
7540
7747
|
|
7748
|
+
// AUXILIARY FUNCTION (added to support debugging)
|
7749
|
+
function knownValue(vi, t) {
|
7750
|
+
// Return the value of decision variable X that has already been
|
7751
|
+
// computed while optimizing a previous block.
|
7752
|
+
const
|
7753
|
+
// `vi` is the variable index for X, so use vi-1 for the zero-based
|
7754
|
+
// list VM.variables list.
|
7755
|
+
vbl = VM.variables[vi - 1],
|
7756
|
+
// NOTE: priorValue deals with special cases for binary variables.
|
7757
|
+
pv = VM.priorValue(vbl, t);
|
7758
|
+
if(DEBUGGING) {
|
7759
|
+
console.log(`--known value: ${vbl[0]} ${vbl[1].displayName} @ ${t} = ${pv}`);
|
7760
|
+
}
|
7761
|
+
return pv;
|
7762
|
+
}
|
7763
|
+
|
7541
7764
|
function VMI_add_const_to_coefficient(args) {
|
7542
7765
|
// `args`: [var_index, number (, delay (, 1))]
|
7543
7766
|
const
|
@@ -7553,15 +7776,13 @@ function VMI_add_const_to_coefficient(args) {
|
|
7553
7776
|
d = args[2];
|
7554
7777
|
}
|
7555
7778
|
}
|
7556
|
-
const
|
7557
|
-
k = VM.offset + vi - d*VM.cols,
|
7558
|
-
t = VM.t - d;
|
7779
|
+
const k = VM.offset + vi - d*VM.cols;
|
7559
7780
|
if(DEBUGGING) {
|
7560
7781
|
console.log(`add_const_to_coefficient [${k}]: ${VM.sig4Dig(n)}`);
|
7561
7782
|
}
|
7562
7783
|
// A negative delay may result in a variable index beyond the tableau
|
7563
|
-
// column range. Such "future variables" should be ignored.
|
7564
|
-
if(
|
7784
|
+
// column range. Such "future variables" should always be ignored.
|
7785
|
+
if(k > VM.chunk_offset) return;
|
7565
7786
|
if(k <= 0) {
|
7566
7787
|
// NOTE: If `k` falls PRIOR to the start of the block being solved,
|
7567
7788
|
// this means that the value of the decision variable X for which the
|
@@ -7569,16 +7790,7 @@ function VMI_add_const_to_coefficient(args) {
|
|
7569
7790
|
// while solving a previous block. Since the value of X is known,
|
7570
7791
|
// adding n to C is implemented as subtracting n*X from the right hand
|
7571
7792
|
// side of the constraint.
|
7572
|
-
|
7573
|
-
// array.
|
7574
|
-
const
|
7575
|
-
vbl = VM.variables[vi - 1],
|
7576
|
-
pv = VM.priorValue(vbl, t);
|
7577
|
-
if(DEBUGGING) {
|
7578
|
-
console.log(`--lookup[${k}]: ${vbl[0]} ${vbl[1].displayName} @ ${t} = ${pv}`);
|
7579
|
-
}
|
7580
|
-
// NOTE: special cases for binary variables!
|
7581
|
-
VM.rhs -= pv * n;
|
7793
|
+
VM.rhs -= knownValue(vi, VM.t - d) * n;
|
7582
7794
|
} else if(k in VM.coefficients) {
|
7583
7795
|
VM.coefficients[k] += n;
|
7584
7796
|
} else {
|
@@ -7587,7 +7799,7 @@ function VMI_add_const_to_coefficient(args) {
|
|
7587
7799
|
}
|
7588
7800
|
|
7589
7801
|
function VMI_add_const_to_sum_coefficients(args) {
|
7590
|
-
// NOTE:
|
7802
|
+
// NOTE: Used to implement data links with SUM multiplier.
|
7591
7803
|
// `args`: [var_index, number, delay (, 1)]
|
7592
7804
|
const vi = args[0];
|
7593
7805
|
let d = args[2].object.actualDelay(VM.t),
|
@@ -7606,16 +7818,11 @@ function VMI_add_const_to_sum_coefficients(args) {
|
|
7606
7818
|
d = -d;
|
7607
7819
|
}
|
7608
7820
|
for(let i = 0; i <= d; i++) {
|
7609
|
-
//
|
7610
|
-
// column range. Such "future variables" should be ignored.
|
7821
|
+
// Variables beyond the chunk length should be ignored.
|
7611
7822
|
if(k > VM.chunk_offset) return;
|
7612
7823
|
if(k <= 0) {
|
7613
|
-
// See NOTE in VMI_add_const_to_coefficient instruction
|
7614
|
-
|
7615
|
-
if(DEBUGGING) {
|
7616
|
-
console.log('--lookup[' + k + ']: ' + vbl[0] + ' ' + vbl[1].displayName);
|
7617
|
-
}
|
7618
|
-
VM.rhs -= VM.priorValue(vbl, t) * n;
|
7824
|
+
// See NOTE in VMI_add_const_to_coefficient instruction.
|
7825
|
+
VM.rhs -= knownValue(vi, t) * n;
|
7619
7826
|
} else if(k in VM.coefficients) {
|
7620
7827
|
VM.coefficients[k] += n;
|
7621
7828
|
} else {
|
@@ -7632,29 +7839,24 @@ function VMI_add_var_to_coefficient(args) {
|
|
7632
7839
|
let d = 0;
|
7633
7840
|
if(args.length > 2 && args[2] instanceof Expression) {
|
7634
7841
|
d = args[2].object.actualDelay(VM.t);
|
7635
|
-
// 4th argument = 1 indicates "delay + 1"
|
7842
|
+
// 4th argument = 1 indicates "delay + 1".
|
7636
7843
|
if(args.length > 3 && args[3]) d++;
|
7637
7844
|
}
|
7638
7845
|
const
|
7639
7846
|
k = VM.offset + vi - d*VM.cols,
|
7640
7847
|
t = VM.t - d;
|
7641
7848
|
let r = args[1].result(t);
|
7642
|
-
// Optional 5th parameter is a constant multiplier
|
7849
|
+
// Optional 5th parameter is a constant multiplier.
|
7643
7850
|
if(args.length > 4) r *= args[4];
|
7644
7851
|
if(DEBUGGING) {
|
7645
7852
|
console.log('add_var_to_coefficient [' + k + ']: ' +
|
7646
7853
|
args[1].variableName + ' (t = ' + t + ')');
|
7647
7854
|
}
|
7648
|
-
//
|
7649
|
-
// column range. Such "future variables" should be ignored.
|
7855
|
+
// Ignore "future variables".
|
7650
7856
|
if(k > VM.chunk_offset) return;
|
7651
7857
|
if(k <= 0) {
|
7652
|
-
// See NOTE in VMI_add_const_to_coefficient instruction
|
7653
|
-
|
7654
|
-
if(DEBUGGING) {
|
7655
|
-
console.log('--lookup[' + k + ']: ' + vbl[0] + ' ' + vbl[1].displayName);
|
7656
|
-
}
|
7657
|
-
VM.rhs -= VM.priorValue(vbl, t) * r;
|
7858
|
+
// See NOTE in VMI_add_const_to_coefficient instruction.
|
7859
|
+
VM.rhs -= knownValue(vi, t) * r;
|
7658
7860
|
} else if(k in VM.coefficients) {
|
7659
7861
|
VM.coefficients[k] += r;
|
7660
7862
|
} else {
|
@@ -7682,18 +7884,13 @@ function VMI_add_var_to_weighted_sum_coefficients(args) {
|
|
7682
7884
|
d = -d;
|
7683
7885
|
}
|
7684
7886
|
for(let i = 0; i <= d; i++) {
|
7685
|
-
//
|
7686
|
-
// column range. Such "future variables" should be ignored.
|
7887
|
+
// Ignore "future variables".
|
7687
7888
|
if(k > VM.chunk_offset) return;
|
7688
7889
|
let r = v.result(t);
|
7689
7890
|
if(args.length > 3) r /= (d + 1);
|
7690
7891
|
if(k <= 0) {
|
7691
7892
|
// See NOTE in VMI_add_const_to_coefficient instruction
|
7692
|
-
|
7693
|
-
if(DEBUGGING) {
|
7694
|
-
console.log('--lookup[' + k + ']: ' + vbl[0] + ' ' + vbl[1].displayName);
|
7695
|
-
}
|
7696
|
-
VM.rhs -= VM.priorValue(vbl, t) * r;
|
7893
|
+
VM.rhs -= knownValue(vi, t) * r;
|
7697
7894
|
} else if(k in VM.coefficients) {
|
7698
7895
|
VM.coefficients[k] += r;
|
7699
7896
|
} else {
|
@@ -7715,22 +7912,15 @@ function VMI_subtract_const_from_coefficient(args) {
|
|
7715
7912
|
// 4th argument indicates "delay + 1"
|
7716
7913
|
if(args.length > 3) d++;
|
7717
7914
|
}
|
7718
|
-
const
|
7719
|
-
k = VM.offset + vi - d*VM.cols,
|
7720
|
-
t = VM.t - d;
|
7915
|
+
const k = VM.offset + vi - d*VM.cols;
|
7721
7916
|
if(DEBUGGING) {
|
7722
7917
|
console.log('subtract_const_from_coefficient [' + k + ']: ' + VM.sig4Dig(n));
|
7723
7918
|
}
|
7724
|
-
//
|
7725
|
-
// column range. Such "future variables" should be ignored.
|
7919
|
+
// Ignore "future variables".
|
7726
7920
|
if(k > VM.chunk_offset) return;
|
7727
7921
|
if(k <= 0) {
|
7728
7922
|
// See NOTE in VMI_add_const_to_coefficient instruction
|
7729
|
-
|
7730
|
-
if(DEBUGGING) {
|
7731
|
-
console.log('--lookup[' + k + ']: ' + vbl[0] + ' ' + vbl[1].displayName);
|
7732
|
-
}
|
7733
|
-
VM.rhs += VM.priorValue(vbl, t) * n;
|
7923
|
+
VM.rhs += knownValue(vi, VM.t - d) * n;
|
7734
7924
|
} else if(k in VM.coefficients) {
|
7735
7925
|
VM.coefficients[k] -= n;
|
7736
7926
|
} else {
|
@@ -7764,16 +7954,11 @@ function VMI_subtract_var_from_coefficient(args) {
|
|
7764
7954
|
console.log('subtract_var_from_coefficient [' + k + ']: ' +
|
7765
7955
|
args[1].variableName + ' (t = ' + t + ')');
|
7766
7956
|
}
|
7767
|
-
//
|
7768
|
-
// column range. Such "future variables" should be ignored.
|
7957
|
+
// Ignore "future variables".
|
7769
7958
|
if(k > VM.chunk_offset) return;
|
7770
7959
|
if(k <= 0) {
|
7771
|
-
// See NOTE in VMI_add_const_to_coefficient instruction
|
7772
|
-
|
7773
|
-
if(DEBUGGING) {
|
7774
|
-
console.log('--lookup[' + k + ']: ' + vbl[0] + ' ' + vbl[1].displayName);
|
7775
|
-
}
|
7776
|
-
VM.rhs += VM.priorValue(vbl, t) * r;
|
7960
|
+
// See NOTE in VMI_add_const_to_coefficient instruction.
|
7961
|
+
VM.rhs += knownValue(vi, t) * r;
|
7777
7962
|
} else if(k in VM.coefficients) {
|
7778
7963
|
VM.coefficients[k] -= r;
|
7779
7964
|
} else {
|
@@ -7783,9 +7968,9 @@ function VMI_subtract_var_from_coefficient(args) {
|
|
7783
7968
|
|
7784
7969
|
function VMI_update_cash_coefficient(args) {
|
7785
7970
|
// `args`: [flow, type, level_var_index, delay, x1, x2, ...]
|
7786
|
-
// NOTE:
|
7971
|
+
// NOTE: Flow is either CONSUME or PRODUCE; type can be ONE_C (one
|
7787
7972
|
// constant parameter x1), TWO_X (two expressions x1 and x2), THREE_X
|
7788
|
-
// (three expressions x1, x2 and x3) or SPIN_RES or PEAK_INC (see below)
|
7973
|
+
// (three expressions x1, x2 and x3) or SPIN_RES or PEAK_INC (see below).
|
7789
7974
|
let d = 0;
|
7790
7975
|
const
|
7791
7976
|
flow = args[0],
|
@@ -7793,52 +7978,68 @@ function VMI_update_cash_coefficient(args) {
|
|
7793
7978
|
vi = args[2],
|
7794
7979
|
dx = args[3];
|
7795
7980
|
if(dx instanceof Expression) {
|
7981
|
+
// When delay is an expression, `dx.object` is the delayed link.
|
7796
7982
|
d = dx.object.actualDelay(VM.t);
|
7797
|
-
// Extra argument indicates "delay + 1"
|
7983
|
+
// Extra argument indicates "delay + 1" (for delta-multipliers).
|
7798
7984
|
if((type === VM.ONE_C && args.length === 6) ||
|
7799
7985
|
(type === VM.TWO_X && args.length === 7)) d++;
|
7800
7986
|
}
|
7987
|
+
// NOTE: When delay < 0, the coefficient must be set for a matrix row
|
7988
|
+
// that will be added to the tableau for some later VM.t.
|
7801
7989
|
// `k` is the tableau column index of the variable that affects the CF
|
7802
7990
|
let k = (type === VM.PEAK_INC ? VM.chunk_offset + vi :
|
7803
7991
|
VM.offset + vi - d*VM.cols);
|
7804
|
-
// NOTE:
|
7805
|
-
//
|
7806
|
-
|
7807
|
-
// NOTE:
|
7808
|
-
// coefficients; previously computed decision variables and variables
|
7809
|
-
// beyond the tableau column range (when delay < 0) can be ignored.
|
7810
|
-
if(k <= 0 || k > VM.chunk_offset) return;
|
7811
|
-
// NOTE: peak increase can generate cash only at the first time
|
7992
|
+
// NOTE: Variables beyond the tableau column range (when delay < 0) can
|
7993
|
+
// be ignored.
|
7994
|
+
if(k > VM.chunk_offset) return;
|
7995
|
+
// NOTE: Peak increase can generate cash only at the first time
|
7812
7996
|
// step of a block (when VM.offset = 0) and at the first time step
|
7813
|
-
// of the look-ahead period (when VM.offset = block length)
|
7997
|
+
// of the look-ahead period (when VM.offset = block length).
|
7814
7998
|
if(type === VM.PEAK_INC &&
|
7815
7999
|
VM.offset > 0 && VM.offset !== MODEL.block_length) return;
|
7816
|
-
//
|
8000
|
+
// NOTE: Delay affects only which variable is to be used. The level of
|
8001
|
+
// this variable is then the delayed production volume to be priced at
|
8002
|
+
// the *current* time.
|
8003
|
+
// First compute the result to be processed.
|
7817
8004
|
let r = 0;
|
7818
8005
|
if(type === VM.ONE_C) {
|
7819
8006
|
r = args[4];
|
7820
8007
|
} else if(type === VM.TWO_X || type === VM.PEAK_INC) {
|
7821
|
-
// NOTE: "peak increase" always passes two expressions
|
8008
|
+
// NOTE: "peak increase" always passes two expressions.
|
8009
|
+
// The first expression determines the price.
|
7822
8010
|
r = args[4].result(VM.t) * args[5].result(VM.t);
|
7823
8011
|
} else if(type === VM.THREE_X) {
|
8012
|
+
// NOTE: Here, too, the first expression determines the price.
|
7824
8013
|
r = args[4].result(VM.t) * args[5].result(VM.t) * args[6].result(VM.t);
|
7825
8014
|
} else if(type === VM.SPIN_RES) {
|
7826
|
-
// "spinning reserve" equals UB - level if level > 0, or 0
|
8015
|
+
// "spinning reserve" equals UB - level if level > 0, or 0.
|
7827
8016
|
// The cash flow then equals ON/OFF*UB*price*rate - level*price*rate.
|
7828
8017
|
// The ON/OFF variable index is passed as third argument, hence `plvi`
|
7829
8018
|
// (process level variable index) as first extra parameter, plus three
|
7830
|
-
// expressions (UB, price, rate)
|
8019
|
+
// expressions (UB, price, rate).
|
7831
8020
|
const
|
7832
8021
|
plvi = args[4],
|
7833
|
-
// NOTE:
|
8022
|
+
// NOTE: Column of second variable will be relative to same offset.
|
7834
8023
|
plk = k + plvi - vi,
|
7835
8024
|
ub = args[5].result(VM.t),
|
7836
8025
|
price_rate = args[6].result(VM.t) * args[7].result(VM.t);
|
7837
8026
|
r = ub * price_rate;
|
7838
|
-
// NOTE:
|
7839
|
-
// generate cash IN or cash OUT
|
7840
|
-
//
|
7841
|
-
if
|
8027
|
+
// NOTE: The sign of r determines whether this spinning reserve will
|
8028
|
+
// generate cash IN or cash OUT. The *subtracted* part (production level
|
8029
|
+
// times price_rate) should hence be ADDED if r > 0, and SUBTRACTED
|
8030
|
+
// if r < 0 (unlike the "primary" part r itself).
|
8031
|
+
if(plk <= 0) {
|
8032
|
+
// NOTE: If `plk` falls PRIOR to the start of the block being solved,
|
8033
|
+
// update the right hand side of the cash flow constraint.
|
8034
|
+
// Assume that the known production level is be non-negative, so its
|
8035
|
+
// sign does not alter the flow direction.
|
8036
|
+
const pl = knownValue(plvi, VM.t - d);
|
8037
|
+
if(r > 0) {
|
8038
|
+
VM.cash_in_rhs -= pl * price_rate;
|
8039
|
+
} else {
|
8040
|
+
VM.cash_out_rhs += pl * price_rate;
|
8041
|
+
}
|
8042
|
+
} else if(r > 0) {
|
7842
8043
|
if(plk in VM.cash_in_coefficients) {
|
7843
8044
|
VM.cash_in_coefficients[plk] += price_rate;
|
7844
8045
|
} else {
|
@@ -7852,27 +8053,38 @@ function VMI_update_cash_coefficient(args) {
|
|
7852
8053
|
}
|
7853
8054
|
}
|
7854
8055
|
}
|
7855
|
-
// NOTE:
|
7856
|
-
// be PRODUCE
|
8056
|
+
// NOTE: For spinning reserve and highest increment, flow will always
|
8057
|
+
// be PRODUCE.
|
7857
8058
|
if(flow === VM.CONSUME) r = -r;
|
7858
8059
|
if(DEBUGGING) {
|
7859
|
-
const vbl = (vi <=
|
7860
|
-
VM.chunk_variables[vi -
|
7861
|
-
|
7862
|
-
|
7863
|
-
|
7864
|
-
|
7865
|
-
|
8060
|
+
const vbl = (vi <= VM.cols ? VM.variables[vi - 1] :
|
8061
|
+
VM.chunk_variables[vi - VM.cols]);
|
8062
|
+
//@@@ MESSAGE TO MAKE CORRECT FOR chunk vars!
|
8063
|
+
console.log(['update_cash_coefficient [', k, ']: ',
|
8064
|
+
vbl[1].displayName, '[' + vbl[0] + '] (t = ', VM.t, ') ',
|
8065
|
+
VM.CF_CONSTANTS[type], ' ', VM.CF_CONSTANTS[flow], ' r = ',
|
8066
|
+
VM.sig4Dig(r)].join(''));
|
8067
|
+
}
|
8068
|
+
// Use look-ahead peak increase when offset > 0.
|
7866
8069
|
if(type === VM.PEAK_INC && VM.offset) k++;
|
7867
|
-
// Then update the cash flow: cash IN if r > 0, otherwise cash OUT
|
7868
|
-
if(
|
8070
|
+
// Then update the cash flow: cash IN if r > 0, otherwise cash OUT.
|
8071
|
+
if(k <= 0) {
|
8072
|
+
// NOTE: If `k` falls PRIOR to the start of the block being solved,
|
8073
|
+
// subtract the known prior value of the decision variable from the
|
8074
|
+
// right hand side of the cash flow constraint.
|
8075
|
+
if(r > 0) {
|
8076
|
+
VM.cash_in_rhs += knownValue(vi, VM.t - d) * r;
|
8077
|
+
} else {
|
8078
|
+
VM.cash_out_rhs -= knownValue(vi, VM.t - d) * r;
|
8079
|
+
}
|
8080
|
+
} else if(r > 0) {
|
7869
8081
|
if(k in VM.cash_in_coefficients) {
|
7870
8082
|
VM.cash_in_coefficients[k] -= r;
|
7871
8083
|
} else {
|
7872
8084
|
VM.cash_in_coefficients[k] = -r;
|
7873
8085
|
}
|
7874
8086
|
} else if(r < 0) {
|
7875
|
-
// NOTE: Test for r < 0 because no action is needed if r = 0
|
8087
|
+
// NOTE: Test for r < 0 because no action is needed if r = 0.
|
7876
8088
|
if(k in VM.cash_out_coefficients) {
|
7877
8089
|
VM.cash_out_coefficients[k] += r;
|
7878
8090
|
} else {
|
@@ -7882,7 +8094,7 @@ function VMI_update_cash_coefficient(args) {
|
|
7882
8094
|
}
|
7883
8095
|
|
7884
8096
|
function VMI_add_throughput_to_coefficient(args) {
|
7885
|
-
// Special instruction to deal with throughput calculation
|
8097
|
+
// Special instruction to deal with throughput calculation.
|
7886
8098
|
// Function: to add the contribution of variable X to the level of
|
7887
8099
|
// variable Z when Z depends (a.o.) on the throughput of variable Y, i.e.,
|
7888
8100
|
// X --(r2,d2)--> Y --(r1,d1)--> Z
|
@@ -7892,8 +8104,9 @@ function VMI_add_throughput_to_coefficient(args) {
|
|
7892
8104
|
vi = args[0],
|
7893
8105
|
d1 = args[2].object.actualDelay(VM.t),
|
7894
8106
|
d2 = (args[4] ? args[4].object.actualDelay(VM.t) : 0),
|
7895
|
-
|
7896
|
-
|
8107
|
+
dsum = d1 + d2,
|
8108
|
+
k = VM.offset + vi - dsum * VM.cols,
|
8109
|
+
t = VM.t - dsum,
|
7897
8110
|
// Compute the value to be added to the coefficient
|
7898
8111
|
v = args[1].result(VM.t) * args[3].result(VM.t - d1);
|
7899
8112
|
if(DEBUGGING) {
|
@@ -7901,8 +8114,7 @@ function VMI_add_throughput_to_coefficient(args) {
|
|
7901
8114
|
args[1].variableName + ' * ' + args[3].variableName +
|
7902
8115
|
' (t = ' + VM.t + ')');
|
7903
8116
|
}
|
7904
|
-
//
|
7905
|
-
// column range. Such "future variables" should be ignored.
|
8117
|
+
// Ignore "future variables".
|
7906
8118
|
if(k > VM.chunk_offset) return;
|
7907
8119
|
if(k <= 0) {
|
7908
8120
|
const vbl = VM.variables[vi - 1];
|
@@ -8012,27 +8224,69 @@ function VMI_add_constraint(ct) {
|
|
8012
8224
|
}
|
8013
8225
|
}
|
8014
8226
|
|
8015
|
-
function
|
8016
|
-
//
|
8017
|
-
//
|
8227
|
+
function VMI_add_cash_constraints(args) {
|
8228
|
+
// args = [cash IN variable index, cash OUT variable index]
|
8229
|
+
// Overwrites the coefficients vector with the cash coefficients
|
8230
|
+
// vector specified by the first argument (cash IN for production,
|
8231
|
+
// cash OUT for consumption). The second argument is passed only for
|
8232
|
+
// tracing purposes.
|
8018
8233
|
if(DEBUGGING) {
|
8019
|
-
console.log('
|
8234
|
+
console.log('add cash constraints for ',
|
8235
|
+
VM.variables[args[0]][1].displayName, '(t = ' + VM.t + ')');
|
8020
8236
|
}
|
8021
|
-
if(
|
8022
|
-
|
8023
|
-
|
8024
|
-
VM.coefficients = Object.assign({}, VM.cash_out_coefficients);
|
8237
|
+
if(!VM.add_constraints_flag) {
|
8238
|
+
console.log('Constraint NOT added!');
|
8239
|
+
return;
|
8025
8240
|
}
|
8026
|
-
//
|
8027
|
-
|
8028
|
-
for(let i in VM.
|
8029
|
-
|
8241
|
+
// Add a constraint for cash IN.
|
8242
|
+
let row = {};
|
8243
|
+
for(let i in VM.cash_in_coefficients) if(VM.cash_in_coefficients.hasOwnProperty(i)) {
|
8244
|
+
const
|
8245
|
+
c = VM.cash_in_coefficients[i],
|
8246
|
+
ac = Math.abs(c);
|
8247
|
+
// Do not add variables having near-zero coefficients.
|
8248
|
+
if(ac > VM.NEAR_ZERO) {
|
8249
|
+
row[i] = c;
|
8250
|
+
// NOTE: This instruction also keeps track of the highest absolute
|
8251
|
+
// cash flow constraint coefficient, so it can be used for scaling
|
8252
|
+
// these constraint equations.
|
8253
|
+
VM.cash_scalar = Math.max(VM.cash_scalar, ac);
|
8254
|
+
}
|
8255
|
+
}
|
8256
|
+
// To permit such scaling, this instruction maintains a list of cash
|
8257
|
+
// constraint row indices, as these are the equations that need to be
|
8258
|
+
// scaled once the tableau is complete.
|
8259
|
+
VM.cash_constraints.push(VM.matrix.length);
|
8260
|
+
// Set coefficient for the cash IN variable to 1.
|
8261
|
+
row[VM.offset + args[0]] = 1;
|
8262
|
+
// Add the constraint to the tableau.
|
8263
|
+
VM.matrix.push(row);
|
8264
|
+
VM.right_hand_side.push(VM.cash_in_rhs);
|
8265
|
+
VM.constraint_types.push(VM.EQ);
|
8266
|
+
// Clear the cash IN coefficient register and RHS.
|
8267
|
+
VM.cash_in_coefficients = {};
|
8268
|
+
VM.cash_in_rhs = 0;
|
8269
|
+
// Now likewise add a constraint for cash OUT.
|
8270
|
+
row = {};
|
8271
|
+
for(let i in VM.cash_out_coefficients) if(VM.cash_out_coefficients.hasOwnProperty(i)) {
|
8272
|
+
const
|
8273
|
+
c = VM.cash_out_coefficients[i],
|
8274
|
+
ac = Math.abs(c);
|
8275
|
+
if(ac > VM.NEAR_ZERO) {
|
8276
|
+
row[i] = c;
|
8277
|
+
VM.cash_scalar = Math.max(VM.cash_scalar, ac);
|
8278
|
+
}
|
8030
8279
|
}
|
8031
|
-
// NOTE: To permit such scaling, this instruction creates a list of constraint
|
8032
|
-
// row indices, as these are the equations that need to be scaled
|
8033
8280
|
VM.cash_constraints.push(VM.matrix.length);
|
8034
|
-
//
|
8035
|
-
VM.
|
8281
|
+
// Add the cash OUT variable index.
|
8282
|
+
row[VM.offset + args[1]] = 1;
|
8283
|
+
// Add the constraint to the tableau.
|
8284
|
+
VM.matrix.push(row);
|
8285
|
+
VM.right_hand_side.push(VM.cash_out_rhs);
|
8286
|
+
VM.constraint_types.push(VM.EQ);
|
8287
|
+
// Clear the cash OUT coefficients register and RHS (just to be sure).
|
8288
|
+
VM.cash_out_coefficients = {};
|
8289
|
+
VM.cash_out_rhs = 0;
|
8036
8290
|
}
|
8037
8291
|
|
8038
8292
|
function VMI_add_bound_line_constraint(args) {
|
@@ -8285,7 +8539,7 @@ function VMI_profitable_units(x) {
|
|
8285
8539
|
mpa = d[3].attribute,
|
8286
8540
|
pt = (d.length > 4 ? d[4] : 0), // the profit threshold (0 by default)
|
8287
8541
|
// the time horizon (by default the length of the simulation period)
|
8288
|
-
nt = (d.length > 5 ? d[5] :
|
8542
|
+
nt = (d.length > 5 ? d[5] : VM.nr_of_time_steps);
|
8289
8543
|
// Handle exceptional values of `uc` and `mc`.
|
8290
8544
|
if(uc <= VM.BEYOND_MINUS_INFINITY || mc <= VM.BEYOND_MINUS_INFINITY) {
|
8291
8545
|
x.retop(Math.min(uc, mc));
|
@@ -8423,7 +8677,7 @@ function VMI_highest_cumulative_consecutive_deviation(x) {
|
|
8423
8677
|
// NOTE: an expression may not have been (fully) computed yet.
|
8424
8678
|
x.compute(0);
|
8425
8679
|
if(!x.isStatic) {
|
8426
|
-
const nt =
|
8680
|
+
const nt = VM.nr_of_time_steps;
|
8427
8681
|
for(let t = 1; t <= nt; t++) x.result(t);
|
8428
8682
|
}
|
8429
8683
|
vector = x.vector;
|
@@ -8574,7 +8828,7 @@ function correlation_or_slope(x, c_or_s) {
|
|
8574
8828
|
// NOTE: An equation may not have been (fully) computed yet.
|
8575
8829
|
eq.compute(0, x.wildcard_vector_index);
|
8576
8830
|
if(!eq.isStatic) {
|
8577
|
-
const nt =
|
8831
|
+
const nt = VM.nr_of_time_steps;
|
8578
8832
|
for(let t = 1; t <= nt; t++) eq.result(t, x.wildcard_vector_index);
|
8579
8833
|
}
|
8580
8834
|
vector[k].v = eq.vector;
|
@@ -8601,7 +8855,7 @@ function correlation_or_slope(x, c_or_s) {
|
|
8601
8855
|
x.retop(x.cache[cache_key]);
|
8602
8856
|
return;
|
8603
8857
|
}
|
8604
|
-
if(
|
8858
|
+
if(DEBUGGING) {
|
8605
8859
|
console.log(`-- ${vmi}(${vector.x.name}, ${vector.y.name})`);
|
8606
8860
|
}
|
8607
8861
|
// NOTE: Vectors should have equal length.
|