linny-r 1.7.1 → 1.7.3
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/package.json +1 -1
- package/static/scripts/linny-r-gui-paper.js +25 -20
- package/static/scripts/linny-r-model.js +171 -138
- package/static/scripts/linny-r-vm.js +441 -262
@@ -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
|
@@ -3140,7 +3145,7 @@ class VirtualMachine {
|
|
3140
3145
|
|
3141
3146
|
// FIRST: define indices for all variables (index = Simplex tableau column number)
|
3142
3147
|
|
3143
|
-
// 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.
|
3144
3149
|
const actor_keys = Object.keys(MODEL.actors).sort();
|
3145
3150
|
for(i = 0; i < actor_keys.length; i++) {
|
3146
3151
|
const a = MODEL.actors[actor_keys[i]];
|
@@ -3247,9 +3252,9 @@ class VirtualMachine {
|
|
3247
3252
|
// NOTE: Chunk variables of node `p` have LB = 0 and UB = UB of `p`.
|
3248
3253
|
// This is effectuated by the VM "set bounds" instructions at run time.
|
3249
3254
|
|
3250
|
-
// NOTE: Under normal assumptions (all processes having LB >= 0), bounds
|
3251
|
-
// actor cash flow variables need NOT be set because cash IN and
|
3252
|
-
// 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).
|
3253
3258
|
// However, Linny-R does not prohibit negative bounds on processes, nor
|
3254
3259
|
// negative rates on links. To be consistently permissive, cash IN and
|
3255
3260
|
// cash OUT of all actors are both allowed to become negative.
|
@@ -3337,9 +3342,9 @@ class VirtualMachine {
|
|
3337
3342
|
|
3338
3343
|
// NOTE: Each process generates cash flow proportional to its production
|
3339
3344
|
// level if it produces and/or consumes a product having a price.
|
3340
|
-
// Cash flow is negative (cash OUT) if a product is consumed AND
|
3341
|
-
// price > 0, but positive (cash IN) if a product is produced
|
3342
|
-
// 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.
|
3343
3348
|
// To calculate the coefficient for the process variable, the
|
3344
3349
|
// multiplier rates of the links in and out must be calculated (at
|
3345
3350
|
// run time when dynamic expressions) such that they will add to the
|
@@ -3361,21 +3366,24 @@ class VirtualMachine {
|
|
3361
3366
|
// on this vector. If expressions for process properties are
|
3362
3367
|
// static, more efficient VM instructions are used.
|
3363
3368
|
|
3364
|
-
// 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.
|
3365
3372
|
this.no_cash_flows = true;
|
3366
3373
|
|
3367
|
-
// Iterate over all actors to add the cash flow computation constraints
|
3374
|
+
// Iterate over all actors to add the cash flow computation constraints.
|
3368
3375
|
for(let ai = 0; ai < actor_keys.length; ai++) {
|
3369
3376
|
const a = MODEL.actors[actor_keys[ai]];
|
3370
|
-
|
3377
|
+
// NOTE: No need for VMI_clear_coefficients because the cash flow
|
3378
|
+
// coefficients operate on two special "registers" of the VM.
|
3371
3379
|
for(i = 0; i < process_keys.length; i++) {
|
3372
3380
|
k = process_keys[i];
|
3373
3381
|
if(!MODEL.ignored_entities[k]) {
|
3374
3382
|
const p = MODEL.processes[k];
|
3375
|
-
// Only consider processes owned by this actor
|
3383
|
+
// Only consider processes owned by this actor.
|
3376
3384
|
if(p.actor === a) {
|
3377
|
-
// Iterate over links IN, but only consider consumed products
|
3378
|
-
// a market price
|
3385
|
+
// Iterate over links IN, but only consider consumed products
|
3386
|
+
// having a market price.
|
3379
3387
|
for(j = 0; j < p.inputs.length; j++) {
|
3380
3388
|
l = p.inputs[j];
|
3381
3389
|
if(!MODEL.ignored_entities[l.identifier] &&
|
@@ -3383,18 +3391,17 @@ class VirtualMachine {
|
|
3383
3391
|
if(l.from_node.price.isStatic && l.relative_rate.isStatic) {
|
3384
3392
|
k = l.from_node.price.result(0) * l.relative_rate.result(0);
|
3385
3393
|
// NOTE: VMI_update_cash_coefficient has at least 4 arguments:
|
3386
|
-
// flow (CONSUME or PRODUCE), type (specifies the number and
|
3387
|
-
// of arguments), the level_var_index of the process,
|
3388
|
-
// 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.
|
3389
3398
|
if(Math.abs(k) > VM.NEAR_ZERO) {
|
3390
|
-
// Consumption rate & price are static: pass one constant
|
3391
|
-
// NOTE: input links cannot have delay, so delay = 0
|
3399
|
+
// Consumption rate & price are static: pass one constant.
|
3392
3400
|
this.code.push([VMI_update_cash_coefficient,
|
3393
3401
|
[VM.CONSUME, VM.ONE_C, p.level_var_index, 0, k]]);
|
3394
3402
|
}
|
3395
3403
|
} else {
|
3396
|
-
// No further optimization: assume two dynamic expressions
|
3397
|
-
// NOTE: input links cannot have delay, so delay = 0
|
3404
|
+
// No further optimization: assume two dynamic expressions.
|
3398
3405
|
this.code.push([VMI_update_cash_coefficient,
|
3399
3406
|
[VM.CONSUME, VM.TWO_X, p.level_var_index, 0,
|
3400
3407
|
l.from_node.price, l.relative_rate]]);
|
@@ -3403,15 +3410,16 @@ class VirtualMachine {
|
|
3403
3410
|
} // END of FOR ALL input links
|
3404
3411
|
|
3405
3412
|
// Iterate over links OUT, but only consider produced products
|
3406
|
-
// having a (non-zero) market price
|
3413
|
+
// having a (non-zero) market price.
|
3407
3414
|
for(j = 0; j < p.outputs.length; j++) {
|
3408
3415
|
l = p.outputs[j];
|
3409
3416
|
const tnpx = l.to_node.price;
|
3410
3417
|
if(!MODEL.ignored_entities[l.identifier] && tnpx.defined &&
|
3411
3418
|
!(tnpx.isStatic && Math.abs(tnpx.result(0)) < VM.NEAR_ZERO)) {
|
3412
|
-
// By default, use the process level as multiplier
|
3419
|
+
// By default, use the process level as multiplier.
|
3413
3420
|
vi = p.level_var_index;
|
3414
|
-
// For "binary data links", use the correct binary variable
|
3421
|
+
// For "binary data links", use the correct binary variable
|
3422
|
+
// instead of the level.
|
3415
3423
|
if(l.multiplier === VM.LM_STARTUP) {
|
3416
3424
|
vi = p.start_up_var_index;
|
3417
3425
|
} else if(l.multiplier === VM.LM_FIRST_COMMIT) {
|
@@ -3432,57 +3440,63 @@ class VirtualMachine {
|
|
3432
3440
|
// times the rate of `l`.
|
3433
3441
|
for(k = 0; k < l.from_node.inputs.length; j++) {
|
3434
3442
|
ll = l.from_node.inputs[k];
|
3435
|
-
// NOTE:
|
3436
|
-
// price and both rates are dynamic
|
3443
|
+
// NOTE: No attempt for efficiency -- assume that
|
3444
|
+
// price and both rates are dynamic.
|
3437
3445
|
this.code.push([VMI_update_cash_coefficient, [
|
3438
3446
|
VM.PRODUCE, VM.THREE_X, vi, l.flow_delay, tnpx,
|
3439
3447
|
l.relative_rate, ll.relative_rate]]);
|
3440
3448
|
}
|
3441
3449
|
} else if(l.multiplier === VM.LM_SPINNING_RESERVE) {
|
3442
|
-
// "spinning reserve" equals UB - level if level > 0,
|
3443
|
-
// The cash flow then equals
|
3444
|
-
//
|
3445
|
-
//
|
3446
|
-
//
|
3447
|
-
//
|
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.
|
3448
3458
|
this.code.push([VMI_update_cash_coefficient, [
|
3449
|
-
VM.PRODUCE, VM.SPIN_RES, p.on_off_var_index,
|
3450
|
-
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]]);
|
3451
3462
|
} else if(l.multiplier === VM.LM_PEAK_INC) {
|
3452
|
-
// NOTE: "peak increase" may be > 0 only in the first
|
3453
|
-
// of the block being optimized, and in the
|
3454
|
-
// look-ahead period (if peak rises
|
3455
|
-
// 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.
|
3456
3468
|
// NOTE: Delay is always 0 for this link flow.
|
3457
3469
|
this.code.push([VMI_update_cash_coefficient, [
|
3458
3470
|
VM.PRODUCE, VM.PEAK_INC, p.peak_inc_var_index, 0,
|
3459
3471
|
tnpx, l.relative_rate]]);
|
3460
3472
|
} else if(tnpx.isStatic && l.relative_rate.isStatic) {
|
3461
|
-
// If link rate and product price are static, only add
|
3462
|
-
// 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.
|
3463
3476
|
k = tnpx.result(0) * l.relative_rate.result(0);
|
3464
3477
|
if(Math.abs(k) > VM.NEAR_ZERO) {
|
3465
|
-
// Production rate & price are static: pass one constant
|
3478
|
+
// Production rate & price are static: pass one constant.
|
3466
3479
|
this.code.push([VMI_update_cash_coefficient,
|
3467
3480
|
[VM.PRODUCE, VM.ONE_C, vi, l.flow_delay, k]]);
|
3468
3481
|
// When multiplier is Delta, subtract level in previous t
|
3469
|
-
// (so add 1 to flow delay, and consume, rather than
|
3482
|
+
// (so add 1 to flow delay, and consume, rather than
|
3483
|
+
// produce).
|
3470
3484
|
if(l.multiplier === VM.LM_INCREASE) {
|
3471
3485
|
this.code.push([VMI_update_cash_coefficient,
|
3472
|
-
// NOTE: 6th argument = 1 indicates "delay + 1"
|
3486
|
+
// NOTE: 6th argument = 1 indicates "delay + 1".
|
3473
3487
|
[VM.CONSUME, VM.ONE_C, vi, l.flow_delay, k, 1]]);
|
3474
3488
|
}
|
3475
3489
|
}
|
3476
3490
|
} else {
|
3477
|
-
// Production rate or price are dynamic: pass two expressions
|
3491
|
+
// Production rate or price are dynamic: pass two expressions.
|
3478
3492
|
this.code.push([VMI_update_cash_coefficient, [
|
3479
3493
|
VM.PRODUCE, VM.TWO_X, vi, l.flow_delay,
|
3480
3494
|
tnpx, l.relative_rate]]);
|
3481
|
-
// When multiplier is Delta, consume level in previous t
|
3495
|
+
// When multiplier is Delta, consume level in previous t.
|
3482
3496
|
if(l.multiplier === VM.LM_INCREASE) {
|
3483
3497
|
this.code.push([VMI_update_cash_coefficient, [
|
3484
3498
|
VM.CONSUME, VM.TWO_X, vi, l.flow_delay,
|
3485
|
-
// NOTE:
|
3499
|
+
// NOTE: Now 7th argument indicates "delay + 1".
|
3486
3500
|
tnpx, l.relative_rate, 1]]);
|
3487
3501
|
}
|
3488
3502
|
}
|
@@ -3491,33 +3505,26 @@ class VirtualMachine {
|
|
3491
3505
|
} // END of FOR ALL output links
|
3492
3506
|
} // END of IF process not ignored
|
3493
3507
|
} // END of FOR ALL processes
|
3494
|
-
|
3495
|
-
//
|
3496
|
-
//
|
3497
|
-
|
3498
|
-
|
3499
|
-
|
3500
|
-
|
3501
|
-
|
3502
|
-
//
|
3503
|
-
|
3504
|
-
|
3505
|
-
|
3506
|
-
[VMI_add_const_to_coefficient, [a.cash_in_var_index, 1]],
|
3507
|
-
[VMI_add_constraint, VM.EQ],
|
3508
|
-
[VMI_copy_cash_coefficients, VM.CONSUME],
|
3509
|
-
[VMI_add_const_to_coefficient, [a.cash_out_var_index, 1]],
|
3510
|
-
[VMI_add_constraint, VM.EQ]
|
3511
|
-
);
|
3512
|
-
|
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
|
+
|
3513
3520
|
} // END of FOR loop iterating over all actors
|
3514
3521
|
|
3515
|
-
// NEXT:
|
3522
|
+
// NEXT: Define the coefficients for the objective function.
|
3516
3523
|
this.code.push([VMI_clear_coefficients, null]);
|
3517
3524
|
|
3518
|
-
// NOTE:
|
3519
|
-
// considered, no cash flows have been detected, the solver should aim
|
3520
|
-
// 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.
|
3521
3528
|
if(this.no_cash_flows) {
|
3522
3529
|
for(i = 0; i < process_keys.length; i++) {
|
3523
3530
|
k = process_keys[i];
|
@@ -4289,8 +4296,9 @@ class VirtualMachine {
|
|
4289
4296
|
// largest cash flow coefficient (in absolute value) within the
|
4290
4297
|
// current block so that cash flows cannot easily "overrule" the
|
4291
4298
|
// slack penalties in the objective function.
|
4292
|
-
// NOTE: No scaling needed if model features no cash flows
|
4293
|
-
|
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;
|
4294
4302
|
this.logMessage(this.block_count,
|
4295
4303
|
'Cash flows scaled by 1/' + this.cash_scalar);
|
4296
4304
|
// Use reciprocal as multiplier to scale the constraint coefficients.
|
@@ -4311,7 +4319,7 @@ class VirtualMachine {
|
|
4311
4319
|
}
|
4312
4320
|
}
|
4313
4321
|
}
|
4314
|
-
|
4322
|
+
|
4315
4323
|
checkForInfinity(n) {
|
4316
4324
|
// Return floating point number `n`, or +INF or -INF if the absolute
|
4317
4325
|
// value of `n` is relatively (!) close to the VM infinity constants
|
@@ -4337,8 +4345,6 @@ class VirtualMachine {
|
|
4337
4345
|
let bb = (block - 1) * MODEL.block_length + 1,
|
4338
4346
|
abl = this.chunk_length,
|
4339
4347
|
cbl = this.actualBlockLength(block);
|
4340
|
-
// For the last block, crop the actual block length so it does not
|
4341
|
-
// extend beyond the simulation period (these results should be ignored).
|
4342
4348
|
// If no results computed, preserve those already computed for the
|
4343
4349
|
// pervious chunk as "look-ahead".
|
4344
4350
|
if(err && block > 1 && MODEL.look_ahead > 0) {
|
@@ -4349,7 +4355,9 @@ class VirtualMachine {
|
|
4349
4355
|
'No results from solver -- retained results of ' +
|
4350
4356
|
pluralS(MODEL.look_ahead, 'look-ahead time step'));
|
4351
4357
|
}
|
4352
|
-
|
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) {
|
4353
4361
|
this.logMessage(block, 'Results of last optimization could be discarded');
|
4354
4362
|
abl = 0;
|
4355
4363
|
} else if(cbl < abl) {
|
@@ -4389,11 +4397,11 @@ class VirtualMachine {
|
|
4389
4397
|
x[a.cash_out_var_index + j] * this.cash_scalar);
|
4390
4398
|
a.cash_flow[b] = a.cash_in[b] - a.cash_out[b];
|
4391
4399
|
// Count occurrences of a negative cash flow (threshold -0.5 cent).
|
4392
|
-
if(a.cash_in[b] < -0.005) {
|
4400
|
+
if(b <= this.nr_of_time_steps && a.cash_in[b] < -0.005) {
|
4393
4401
|
this.logMessage(block, `${this.WARNING}(t=${b}${round}) ` +
|
4394
4402
|
a.displayName + ' cash IN = ' + a.cash_in[b].toPrecision(2));
|
4395
4403
|
}
|
4396
|
-
if(a.cash_out[b] < -0.005) {
|
4404
|
+
if(b <= this.nr_of_time_steps && a.cash_out[b] < -0.005) {
|
4397
4405
|
this.logMessage(block, `${this.WARNING}(t=${b}${round}) ` +
|
4398
4406
|
a.displayName + ' cash IN = ' + a.cash_out[b].toPrecision(2));
|
4399
4407
|
}
|
@@ -4494,10 +4502,10 @@ class VirtualMachine {
|
|
4494
4502
|
// NOTE: Only check after the last round has been evaluated.
|
4495
4503
|
if(round === this.lastRound) {
|
4496
4504
|
let b = bb;
|
4497
|
-
// Iterate over all time steps in this block
|
4505
|
+
// Iterate over all time steps in this block.
|
4498
4506
|
let j = -1;
|
4499
4507
|
for(let i = 0; i < abl; i++) {
|
4500
|
-
// Index `svt` iterates over types of slack variable (0 - 2)
|
4508
|
+
// Index `svt` iterates over types of slack variable (0 - 2).
|
4501
4509
|
for(let svt = 0; svt <= 2; svt++) {
|
4502
4510
|
const
|
4503
4511
|
svl = this.slack_variables[svt],
|
@@ -4509,12 +4517,12 @@ class VirtualMachine {
|
|
4509
4517
|
absl = Math.abs(slack);
|
4510
4518
|
if(absl > VM.NEAR_ZERO) {
|
4511
4519
|
const v = this.variables[vi - 1];
|
4512
|
-
// NOTE: for constraints, add 'UB' or 'LB' to its vector for
|
4513
|
-
// 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.
|
4514
4522
|
if(v[1] instanceof BoundLine) {
|
4515
4523
|
v[1].constraint.slack_info[b] = v[0];
|
4516
4524
|
}
|
4517
|
-
if(absl > VM.SIG_DIF_FROM_ZERO) {
|
4525
|
+
if(b <= this.nr_of_time_steps && absl > VM.SIG_DIF_FROM_ZERO) {
|
4518
4526
|
this.logMessage(block, `${this.WARNING}(t=${b}${round}) ` +
|
4519
4527
|
`${v[1].displayName} ${v[0]} slack = ${this.sig4Dig(slack)}`);
|
4520
4528
|
if(v[1] instanceof Product) {
|
@@ -4560,6 +4568,7 @@ class VirtualMachine {
|
|
4560
4568
|
// optimization period, hence start by calculating the offset `bb`
|
4561
4569
|
// being the first time step of this block.
|
4562
4570
|
// Blocks are numbered 1, 2, ...
|
4571
|
+
let latest_time_step = 0;
|
4563
4572
|
const
|
4564
4573
|
bb = (block - 1) * MODEL.block_length + 1,
|
4565
4574
|
cbl = this.actualBlockLength(block);
|
@@ -4580,6 +4589,7 @@ class VirtualMachine {
|
|
4580
4589
|
// NOTE: Flows may have a delay!
|
4581
4590
|
ld = l.actualDelay(b);
|
4582
4591
|
bt = b - ld;
|
4592
|
+
latest_time_step = Math.max(latest_time_step, bt);
|
4583
4593
|
// If delay < 0 AND this results in a block time beyond the
|
4584
4594
|
// block length, this means that the level of the FROM node
|
4585
4595
|
// must be "fixated" in the next block.
|
@@ -4704,9 +4714,10 @@ class VirtualMachine {
|
|
4704
4714
|
// INPUT links from priced products generate cash OUT...
|
4705
4715
|
for(let j = 0; j < p.inputs.length; j++) {
|
4706
4716
|
// NOTE: Input links do NOT have a delay.
|
4707
|
-
const
|
4708
|
-
|
4709
|
-
|
4717
|
+
const
|
4718
|
+
l = p.inputs[j],
|
4719
|
+
af = l.actual_flow[b],
|
4720
|
+
fnp = l.from_node.price;
|
4710
4721
|
if(af > VM.NEAR_ZERO && fnp.defined) {
|
4711
4722
|
const pp = fnp.result(b);
|
4712
4723
|
if(pp > 0 && pp < VM.PLUS_INFINITY) {
|
@@ -4719,14 +4730,14 @@ class VirtualMachine {
|
|
4719
4730
|
}
|
4720
4731
|
// OUTPUT links to priced products generate cash IN ...
|
4721
4732
|
for(let j = 0; j < p.outputs.length; j++) {
|
4722
|
-
// NOTE:
|
4723
|
-
const
|
4724
|
-
|
4725
|
-
|
4726
|
-
|
4733
|
+
// NOTE: actualFlows already consider delay!
|
4734
|
+
const
|
4735
|
+
l = p.outputs[j],
|
4736
|
+
af = l.actualFlow(b),
|
4737
|
+
tnp = l.to_node.price;
|
4727
4738
|
if(af > VM.NEAR_ZERO && tnp.defined) {
|
4728
|
-
// NOTE:
|
4729
|
-
const pp = tnp.result(b
|
4739
|
+
// NOTE: Use the price at the time of the actual flow.
|
4740
|
+
const pp = tnp.result(b);
|
4730
4741
|
if(pp > 0 && pp < VM.PLUS_INFINITY) {
|
4731
4742
|
ci += pp * af;
|
4732
4743
|
// ... unless the product price is negative; then cash OUT.
|
@@ -4755,15 +4766,128 @@ class VirtualMachine {
|
|
4755
4766
|
// at a time because of delays, and also because expressions may refer
|
4756
4767
|
// to values for earlier time steps.
|
4757
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 = {};
|
4758
4772
|
b = bb;
|
4759
4773
|
for(let i = 0; i < cbl; i++) {
|
4760
|
-
|
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)) {
|
4761
4777
|
this.logMessage(block, `${this.WARNING}(t=${b}) ` +
|
4762
4778
|
'Invalid cost prices due to negative flow(s)');
|
4763
4779
|
}
|
4764
4780
|
// Move on to the next time step of the block.
|
4765
4781
|
b++;
|
4766
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
|
+
}
|
4767
4891
|
}
|
4768
4892
|
|
4769
4893
|
// THEN: Reset all datasets that are outcomes or serve as "formulas".
|
@@ -4861,41 +4985,45 @@ class VirtualMachine {
|
|
4861
4985
|
}
|
4862
4986
|
|
4863
4987
|
resetTableau() {
|
4864
|
-
// Clears tableau data: matrix, rhs and constraint types
|
4865
|
-
// NOTE:
|
4866
|
-
// 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.
|
4867
4991
|
this.matrix.length = 0;
|
4868
4992
|
this.right_hand_side.length = 0;
|
4869
4993
|
this.constraint_types.length = 0;
|
4870
4994
|
}
|
4871
4995
|
|
4872
4996
|
initializeTableau(abl) {
|
4873
|
-
// `offset` is used to calculate the actual column index for variables
|
4997
|
+
// `offset` is used to calculate the actual column index for variables.
|
4874
4998
|
this.offset = 0;
|
4875
|
-
// NOTE:
|
4876
|
-
// represented as arrays but as objects, e.g., {4:1.5, 8:0.3} to
|
4877
|
-
// an array [0, 0, 0, 1.5, 0, 0, 0, 0.3, 0, 0, 0, ...]
|
4878
|
-
// The keys can span the full chunk, so the objects represent vectors
|
4879
|
-
// 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.
|
4880
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.
|
4881
5009
|
this.cash_in_coefficients = {};
|
5010
|
+
this.cash_in_rhs = 0;
|
4882
5011
|
this.cash_out_coefficients = {};
|
4883
|
-
|
4884
|
-
//
|
4885
|
-
//
|
4886
|
-
//
|
4887
|
-
//
|
4888
|
-
//
|
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.
|
4889
5019
|
this.cash_scalar = 1;
|
4890
5020
|
this.cash_constraints = [];
|
5021
|
+
// Vector for the objective function coefficients.
|
4891
5022
|
this.objective = {};
|
5023
|
+
// Vectors for the bounds on decision variables.
|
4892
5024
|
this.lower_bounds = {};
|
4893
5025
|
this.upper_bounds = {};
|
4894
|
-
//
|
4895
|
-
// actors' cash flows are calculated as C - a1P1 - a2P2 - ... anPn = 0
|
4896
|
-
this.rhs = 0;
|
4897
|
-
// NOTE: the constraint coefficient matrix and the rhs and ct vectors
|
4898
|
-
// have equal length (#rows); the matrix is a list of sparse vectors
|
5026
|
+
// Clear the tableau matrix and constraint type and RHS columns.
|
4899
5027
|
this.resetTableau();
|
4900
5028
|
// NOTE: setupBlock only works properly if setupProblem was successful
|
4901
5029
|
// Every variable gets one column per time step => tableau is organized
|
@@ -4921,7 +5049,7 @@ class VirtualMachine {
|
|
4921
5049
|
this.is_binary[parseInt(i) + j*this.cols] = true;
|
4922
5050
|
}
|
4923
5051
|
}
|
4924
|
-
// Set list with indices of semi-contiuous variables
|
5052
|
+
// Set list with indices of semi-contiuous variables.
|
4925
5053
|
this.is_semi_continuous = {};
|
4926
5054
|
for(let i in this.sec_var_indices) if(Number(i)) {
|
4927
5055
|
for(let j = 0; j < abl; j++) {
|
@@ -5978,7 +6106,7 @@ function VMI_push_block_number(x) {
|
|
5978
6106
|
|
5979
6107
|
function VMI_push_run_length(x) {
|
5980
6108
|
// Push the run length (excl. look-ahead!).
|
5981
|
-
const n =
|
6109
|
+
const n = VM.nr_of_time_steps;
|
5982
6110
|
if(DEBUGGING) console.log('push run length N = ' + n);
|
5983
6111
|
x.push(n);
|
5984
6112
|
}
|
@@ -6257,7 +6385,7 @@ function relativeTimeStep(t, anchor, offset, dtm, x) {
|
|
6257
6385
|
}
|
6258
6386
|
if(anchor === 'l') {
|
6259
6387
|
// Last: offset relative to the last index in the vector.
|
6260
|
-
return
|
6388
|
+
return VM.nr_of_time_steps + offset;
|
6261
6389
|
}
|
6262
6390
|
if(anchor === 's') {
|
6263
6391
|
// Scaled: offset is scaled to time unit of run.
|
@@ -6645,7 +6773,7 @@ function VMI_push_run_result(x, args) {
|
|
6645
6773
|
} else if(rrspec.nr !== false) {
|
6646
6774
|
// Run number inferred from local time step of expression
|
6647
6775
|
const
|
6648
|
-
rl =
|
6776
|
+
rl = VM.nr_of_time_steps,
|
6649
6777
|
range = rangeToList(rrspec.nr, xp.runs.length - 1);
|
6650
6778
|
if(range) {
|
6651
6779
|
const
|
@@ -6774,7 +6902,7 @@ function VMI_push_statistic(x, args) {
|
|
6774
6902
|
}
|
6775
6903
|
// Negative time step is evaluated as t = 0 (initial value) t beyond
|
6776
6904
|
// optimization period is evaluated as its last time step
|
6777
|
-
const tmax =
|
6905
|
+
const tmax = VM.nr_of_time_steps;
|
6778
6906
|
t1 = Math.max(0, Math.min(tmax, t1));
|
6779
6907
|
t2 = Math.max(0, Math.min(tmax, t2));
|
6780
6908
|
// Trace only now that time step range has been computed
|
@@ -7611,13 +7739,28 @@ function VMI_set_bounds(args) {
|
|
7611
7739
|
}
|
7612
7740
|
|
7613
7741
|
function VMI_clear_coefficients() {
|
7742
|
+
// Clear the coefficients register and set RHS to zero.
|
7614
7743
|
if(DEBUGGING) console.log('clear_coefficients');
|
7615
7744
|
VM.coefficients = {};
|
7616
|
-
VM.cash_in_coefficients = {};
|
7617
|
-
VM.cash_out_coefficients = {};
|
7618
7745
|
VM.rhs = 0;
|
7619
7746
|
}
|
7620
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
|
+
|
7621
7764
|
function VMI_add_const_to_coefficient(args) {
|
7622
7765
|
// `args`: [var_index, number (, delay (, 1))]
|
7623
7766
|
const
|
@@ -7633,9 +7776,7 @@ function VMI_add_const_to_coefficient(args) {
|
|
7633
7776
|
d = args[2];
|
7634
7777
|
}
|
7635
7778
|
}
|
7636
|
-
const
|
7637
|
-
k = VM.offset + vi - d*VM.cols,
|
7638
|
-
t = VM.t - d;
|
7779
|
+
const k = VM.offset + vi - d*VM.cols;
|
7639
7780
|
if(DEBUGGING) {
|
7640
7781
|
console.log(`add_const_to_coefficient [${k}]: ${VM.sig4Dig(n)}`);
|
7641
7782
|
}
|
@@ -7649,16 +7790,7 @@ function VMI_add_const_to_coefficient(args) {
|
|
7649
7790
|
// while solving a previous block. Since the value of X is known,
|
7650
7791
|
// adding n to C is implemented as subtracting n*X from the right hand
|
7651
7792
|
// side of the constraint.
|
7652
|
-
|
7653
|
-
// array.
|
7654
|
-
const
|
7655
|
-
vbl = VM.variables[vi - 1],
|
7656
|
-
pv = VM.priorValue(vbl, t);
|
7657
|
-
if(DEBUGGING) {
|
7658
|
-
console.log(`--lookup[${k}]: ${vbl[0]} ${vbl[1].displayName} @ ${t} = ${pv}`);
|
7659
|
-
}
|
7660
|
-
// NOTE: Special cases for binary variables!
|
7661
|
-
VM.rhs -= pv * n;
|
7793
|
+
VM.rhs -= knownValue(vi, VM.t - d) * n;
|
7662
7794
|
} else if(k in VM.coefficients) {
|
7663
7795
|
VM.coefficients[k] += n;
|
7664
7796
|
} else {
|
@@ -7690,11 +7822,7 @@ function VMI_add_const_to_sum_coefficients(args) {
|
|
7690
7822
|
if(k > VM.chunk_offset) return;
|
7691
7823
|
if(k <= 0) {
|
7692
7824
|
// See NOTE in VMI_add_const_to_coefficient instruction.
|
7693
|
-
|
7694
|
-
if(DEBUGGING) {
|
7695
|
-
console.log('--lookup[' + k + ']: ' + vbl[0] + ' ' + vbl[1].displayName);
|
7696
|
-
}
|
7697
|
-
VM.rhs -= VM.priorValue(vbl, t) * n;
|
7825
|
+
VM.rhs -= knownValue(vi, t) * n;
|
7698
7826
|
} else if(k in VM.coefficients) {
|
7699
7827
|
VM.coefficients[k] += n;
|
7700
7828
|
} else {
|
@@ -7728,11 +7856,7 @@ function VMI_add_var_to_coefficient(args) {
|
|
7728
7856
|
if(k > VM.chunk_offset) return;
|
7729
7857
|
if(k <= 0) {
|
7730
7858
|
// See NOTE in VMI_add_const_to_coefficient instruction.
|
7731
|
-
|
7732
|
-
if(DEBUGGING) {
|
7733
|
-
console.log('--lookup[' + k + ']: ' + vbl[0] + ' ' + vbl[1].displayName);
|
7734
|
-
}
|
7735
|
-
VM.rhs -= VM.priorValue(vbl, t) * r;
|
7859
|
+
VM.rhs -= knownValue(vi, t) * r;
|
7736
7860
|
} else if(k in VM.coefficients) {
|
7737
7861
|
VM.coefficients[k] += r;
|
7738
7862
|
} else {
|
@@ -7766,11 +7890,7 @@ function VMI_add_var_to_weighted_sum_coefficients(args) {
|
|
7766
7890
|
if(args.length > 3) r /= (d + 1);
|
7767
7891
|
if(k <= 0) {
|
7768
7892
|
// See NOTE in VMI_add_const_to_coefficient instruction
|
7769
|
-
|
7770
|
-
if(DEBUGGING) {
|
7771
|
-
console.log('--lookup[' + k + ']: ' + vbl[0] + ' ' + vbl[1].displayName);
|
7772
|
-
}
|
7773
|
-
VM.rhs -= VM.priorValue(vbl, t) * r;
|
7893
|
+
VM.rhs -= knownValue(vi, t) * r;
|
7774
7894
|
} else if(k in VM.coefficients) {
|
7775
7895
|
VM.coefficients[k] += r;
|
7776
7896
|
} else {
|
@@ -7792,9 +7912,7 @@ function VMI_subtract_const_from_coefficient(args) {
|
|
7792
7912
|
// 4th argument indicates "delay + 1"
|
7793
7913
|
if(args.length > 3) d++;
|
7794
7914
|
}
|
7795
|
-
const
|
7796
|
-
k = VM.offset + vi - d*VM.cols,
|
7797
|
-
t = VM.t - d;
|
7915
|
+
const k = VM.offset + vi - d*VM.cols;
|
7798
7916
|
if(DEBUGGING) {
|
7799
7917
|
console.log('subtract_const_from_coefficient [' + k + ']: ' + VM.sig4Dig(n));
|
7800
7918
|
}
|
@@ -7802,11 +7920,7 @@ function VMI_subtract_const_from_coefficient(args) {
|
|
7802
7920
|
if(k > VM.chunk_offset) return;
|
7803
7921
|
if(k <= 0) {
|
7804
7922
|
// See NOTE in VMI_add_const_to_coefficient instruction
|
7805
|
-
|
7806
|
-
if(DEBUGGING) {
|
7807
|
-
console.log('--lookup[' + k + ']: ' + vbl[0] + ' ' + vbl[1].displayName);
|
7808
|
-
}
|
7809
|
-
VM.rhs += VM.priorValue(vbl, t) * n;
|
7923
|
+
VM.rhs += knownValue(vi, VM.t - d) * n;
|
7810
7924
|
} else if(k in VM.coefficients) {
|
7811
7925
|
VM.coefficients[k] -= n;
|
7812
7926
|
} else {
|
@@ -7844,11 +7958,7 @@ function VMI_subtract_var_from_coefficient(args) {
|
|
7844
7958
|
if(k > VM.chunk_offset) return;
|
7845
7959
|
if(k <= 0) {
|
7846
7960
|
// See NOTE in VMI_add_const_to_coefficient instruction.
|
7847
|
-
|
7848
|
-
if(DEBUGGING) {
|
7849
|
-
console.log('--lookup[' + k + ']: ' + vbl[0] + ' ' + vbl[1].displayName);
|
7850
|
-
}
|
7851
|
-
VM.rhs += VM.priorValue(vbl, t) * r;
|
7961
|
+
VM.rhs += knownValue(vi, t) * r;
|
7852
7962
|
} else if(k in VM.coefficients) {
|
7853
7963
|
VM.coefficients[k] -= r;
|
7854
7964
|
} else {
|
@@ -7858,9 +7968,9 @@ function VMI_subtract_var_from_coefficient(args) {
|
|
7858
7968
|
|
7859
7969
|
function VMI_update_cash_coefficient(args) {
|
7860
7970
|
// `args`: [flow, type, level_var_index, delay, x1, x2, ...]
|
7861
|
-
// NOTE:
|
7971
|
+
// NOTE: Flow is either CONSUME or PRODUCE; type can be ONE_C (one
|
7862
7972
|
// constant parameter x1), TWO_X (two expressions x1 and x2), THREE_X
|
7863
|
-
// (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).
|
7864
7974
|
let d = 0;
|
7865
7975
|
const
|
7866
7976
|
flow = args[0],
|
@@ -7868,34 +7978,38 @@ function VMI_update_cash_coefficient(args) {
|
|
7868
7978
|
vi = args[2],
|
7869
7979
|
dx = args[3];
|
7870
7980
|
if(dx instanceof Expression) {
|
7981
|
+
// When delay is an expression, `dx.object` is the delayed link.
|
7871
7982
|
d = dx.object.actualDelay(VM.t);
|
7872
|
-
// Extra argument indicates "delay + 1"
|
7983
|
+
// Extra argument indicates "delay + 1" (for delta-multipliers).
|
7873
7984
|
if((type === VM.ONE_C && args.length === 6) ||
|
7874
7985
|
(type === VM.TWO_X && args.length === 7)) d++;
|
7875
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.
|
7876
7989
|
// `k` is the tableau column index of the variable that affects the CF
|
7877
7990
|
let k = (type === VM.PEAK_INC ? VM.chunk_offset + vi :
|
7878
7991
|
VM.offset + vi - d*VM.cols);
|
7879
|
-
// NOTE:
|
7880
|
-
//
|
7881
|
-
|
7882
|
-
// NOTE: This instruction is used only for objective function
|
7883
|
-
// coefficients. Previously computed decision variables and variables
|
7884
|
-
// beyond the tableau column range (when delay < 0) can be ignored.
|
7885
|
-
if(k <= 0 || k > VM.chunk_offset) return;
|
7992
|
+
// NOTE: Variables beyond the tableau column range (when delay < 0) can
|
7993
|
+
// be ignored.
|
7994
|
+
if(k > VM.chunk_offset) return;
|
7886
7995
|
// NOTE: Peak increase can generate cash only at the first time
|
7887
7996
|
// step of a block (when VM.offset = 0) and at the first time step
|
7888
7997
|
// of the look-ahead period (when VM.offset = block length).
|
7889
7998
|
if(type === VM.PEAK_INC &&
|
7890
7999
|
VM.offset > 0 && VM.offset !== MODEL.block_length) return;
|
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.
|
7891
8003
|
// First compute the result to be processed.
|
7892
8004
|
let r = 0;
|
7893
8005
|
if(type === VM.ONE_C) {
|
7894
8006
|
r = args[4];
|
7895
8007
|
} else if(type === VM.TWO_X || type === VM.PEAK_INC) {
|
7896
8008
|
// NOTE: "peak increase" always passes two expressions.
|
8009
|
+
// The first expression determines the price.
|
7897
8010
|
r = args[4].result(VM.t) * args[5].result(VM.t);
|
7898
8011
|
} else if(type === VM.THREE_X) {
|
8012
|
+
// NOTE: Here, too, the first expression determines the price.
|
7899
8013
|
r = args[4].result(VM.t) * args[5].result(VM.t) * args[6].result(VM.t);
|
7900
8014
|
} else if(type === VM.SPIN_RES) {
|
7901
8015
|
// "spinning reserve" equals UB - level if level > 0, or 0.
|
@@ -7905,15 +8019,27 @@ function VMI_update_cash_coefficient(args) {
|
|
7905
8019
|
// expressions (UB, price, rate).
|
7906
8020
|
const
|
7907
8021
|
plvi = args[4],
|
7908
|
-
// NOTE:
|
8022
|
+
// NOTE: Column of second variable will be relative to same offset.
|
7909
8023
|
plk = k + plvi - vi,
|
7910
8024
|
ub = args[5].result(VM.t),
|
7911
8025
|
price_rate = args[6].result(VM.t) * args[7].result(VM.t);
|
7912
8026
|
r = ub * price_rate;
|
7913
8027
|
// NOTE: The sign of r determines whether this spinning reserve will
|
7914
|
-
// generate cash IN or cash OUT. The *subtracted* part
|
7915
|
-
//
|
7916
|
-
if
|
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) {
|
7917
8043
|
if(plk in VM.cash_in_coefficients) {
|
7918
8044
|
VM.cash_in_coefficients[plk] += price_rate;
|
7919
8045
|
} else {
|
@@ -7931,16 +8057,27 @@ function VMI_update_cash_coefficient(args) {
|
|
7931
8057
|
// be PRODUCE.
|
7932
8058
|
if(flow === VM.CONSUME) r = -r;
|
7933
8059
|
if(DEBUGGING) {
|
7934
|
-
const vbl = (vi <=
|
7935
|
-
VM.chunk_variables[vi -
|
7936
|
-
|
7937
|
-
|
7938
|
-
|
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(''));
|
7939
8067
|
}
|
7940
8068
|
// Use look-ahead peak increase when offset > 0.
|
7941
8069
|
if(type === VM.PEAK_INC && VM.offset) k++;
|
7942
|
-
// Then update the cash flow: cash IN if r > 0, otherwise cash OUT
|
7943
|
-
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) {
|
7944
8081
|
if(k in VM.cash_in_coefficients) {
|
7945
8082
|
VM.cash_in_coefficients[k] -= r;
|
7946
8083
|
} else {
|
@@ -8087,27 +8224,69 @@ function VMI_add_constraint(ct) {
|
|
8087
8224
|
}
|
8088
8225
|
}
|
8089
8226
|
|
8090
|
-
function
|
8091
|
-
//
|
8092
|
-
//
|
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.
|
8093
8233
|
if(DEBUGGING) {
|
8094
|
-
console.log('
|
8234
|
+
console.log('add cash constraints for ',
|
8235
|
+
VM.variables[args[0]][1].displayName, '(t = ' + VM.t + ')');
|
8095
8236
|
}
|
8096
|
-
if(
|
8097
|
-
|
8098
|
-
|
8099
|
-
VM.coefficients = Object.assign({}, VM.cash_out_coefficients);
|
8237
|
+
if(!VM.add_constraints_flag) {
|
8238
|
+
console.log('Constraint NOT added!');
|
8239
|
+
return;
|
8100
8240
|
}
|
8101
|
-
//
|
8102
|
-
|
8103
|
-
for(let i in VM.
|
8104
|
-
|
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
|
+
}
|
8105
8279
|
}
|
8106
|
-
// NOTE: To permit such scaling, this instruction creates a list of constraint
|
8107
|
-
// row indices, as these are the equations that need to be scaled
|
8108
8280
|
VM.cash_constraints.push(VM.matrix.length);
|
8109
|
-
//
|
8110
|
-
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;
|
8111
8290
|
}
|
8112
8291
|
|
8113
8292
|
function VMI_add_bound_line_constraint(args) {
|
@@ -8360,7 +8539,7 @@ function VMI_profitable_units(x) {
|
|
8360
8539
|
mpa = d[3].attribute,
|
8361
8540
|
pt = (d.length > 4 ? d[4] : 0), // the profit threshold (0 by default)
|
8362
8541
|
// the time horizon (by default the length of the simulation period)
|
8363
|
-
nt = (d.length > 5 ? d[5] :
|
8542
|
+
nt = (d.length > 5 ? d[5] : VM.nr_of_time_steps);
|
8364
8543
|
// Handle exceptional values of `uc` and `mc`.
|
8365
8544
|
if(uc <= VM.BEYOND_MINUS_INFINITY || mc <= VM.BEYOND_MINUS_INFINITY) {
|
8366
8545
|
x.retop(Math.min(uc, mc));
|
@@ -8498,7 +8677,7 @@ function VMI_highest_cumulative_consecutive_deviation(x) {
|
|
8498
8677
|
// NOTE: an expression may not have been (fully) computed yet.
|
8499
8678
|
x.compute(0);
|
8500
8679
|
if(!x.isStatic) {
|
8501
|
-
const nt =
|
8680
|
+
const nt = VM.nr_of_time_steps;
|
8502
8681
|
for(let t = 1; t <= nt; t++) x.result(t);
|
8503
8682
|
}
|
8504
8683
|
vector = x.vector;
|
@@ -8649,7 +8828,7 @@ function correlation_or_slope(x, c_or_s) {
|
|
8649
8828
|
// NOTE: An equation may not have been (fully) computed yet.
|
8650
8829
|
eq.compute(0, x.wildcard_vector_index);
|
8651
8830
|
if(!eq.isStatic) {
|
8652
|
-
const nt =
|
8831
|
+
const nt = VM.nr_of_time_steps;
|
8653
8832
|
for(let t = 1; t <= nt; t++) eq.result(t, x.wildcard_vector_index);
|
8654
8833
|
}
|
8655
8834
|
vector[k].v = eq.vector;
|