linny-r 1.5.5 → 1.5.7
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/index.html +3 -1
- package/static/linny-r.css +9 -5
- package/static/scripts/linny-r-gui-chart-manager.js +26 -4
- package/static/scripts/linny-r-gui-controller.js +3 -1
- package/static/scripts/linny-r-gui-experiment-manager.js +6 -5
- package/static/scripts/linny-r-gui-monitor.js +5 -3
- package/static/scripts/linny-r-model.js +125 -108
- package/static/scripts/linny-r-vm.js +279 -238
@@ -2108,91 +2108,99 @@ class VirtualMachine {
|
|
2108
2108
|
}
|
2109
2109
|
|
2110
2110
|
reset() {
|
2111
|
-
//
|
2112
|
-
// First
|
2111
|
+
// Reset the virtual machine so that it can execute the model again.
|
2112
|
+
// First reset the expression attributes of all model entities.
|
2113
2113
|
MODEL.resetExpressions();
|
2114
|
-
// Clear slack use information for all constraints
|
2114
|
+
// Clear slack use information for all constraints.
|
2115
2115
|
for(let k in MODEL.constraints) if(MODEL.constraints.hasOwnProperty(k)) {
|
2116
2116
|
MODEL.constraints[k].slack_info = {};
|
2117
2117
|
}
|
2118
|
-
// Likewise, clear slack use information for all clusters
|
2118
|
+
// Likewise, clear slack use information for all clusters.
|
2119
2119
|
for(let k in MODEL.clusters) if(MODEL.clusters.hasOwnProperty(k)) {
|
2120
2120
|
MODEL.clusters[k].slack_info = {};
|
2121
2121
|
}
|
2122
|
-
// Clear the expression call stack -- used only for diagnostics
|
2122
|
+
// Clear the expression call stack -- used only for diagnostics.
|
2123
2123
|
this.call_stack.length = 0;
|
2124
|
-
// The out-of-bounds properties are set when the ARRAY_INDEX error
|
2124
|
+
// The out-of-bounds properties are set when the ARRAY_INDEX error
|
2125
|
+
// occurs to better inform the modeler.
|
2125
2126
|
this.out_of_bounds_array = '';
|
2126
2127
|
this.out_of_bounds_msg = '';
|
2127
2128
|
MODEL.set_up = false;
|
2128
|
-
// Let the model know that it should no longer display results in
|
2129
|
+
// Let the model know that it should no longer display results in
|
2130
|
+
// the model diagram.
|
2129
2131
|
MODEL.solved = false;
|
2130
2132
|
// "block start" is the first time step (relative to start) of the
|
2131
|
-
// optimization block
|
2133
|
+
// optimization block.
|
2132
2134
|
this.block_start = 0;
|
2133
2135
|
// "chunk length" is the number of time steps to solve
|
2134
|
-
// (block length + look-ahead)
|
2136
|
+
// (block length + look-ahead).
|
2135
2137
|
this.chunk_length = MODEL.block_length + MODEL.look_ahead;
|
2136
|
-
// Number of blocks is at least 1, and is based on the simulation
|
2137
|
-
//
|
2138
|
+
// Number of blocks is at least 1, and is based on the simulation
|
2139
|
+
// period divided by the block length (without look-ahead).
|
2140
|
+
// NOTES:
|
2141
|
+
// (1) MODEL.runLength = simulation period + look-ahead, so that
|
2142
|
+
// should not be used to compute the number of blocks.
|
2143
|
+
// (2) For each block, a chunk (block + lookahead) is optimized.
|
2138
2144
|
this.nr_of_blocks = Math.ceil(
|
2139
2145
|
(MODEL.end_period - MODEL.start_period + 1) / MODEL.block_length);
|
2140
2146
|
|
2141
|
-
// EXAMPLE:
|
2142
|
-
//
|
2143
|
-
//
|
2144
|
-
//
|
2145
|
-
//
|
2146
|
-
//
|
2147
|
-
//
|
2148
|
-
//
|
2149
|
-
|
2150
|
-
// Initialize error counters (error count will be reset to 0 for each
|
2147
|
+
// EXAMPLE: Simulation period of 55 time steps, block length of 10
|
2148
|
+
// time steps and no look-ahead => 6 chunks, and chunk length = block
|
2149
|
+
// length = 10. If look-ahead = 8, then STILL 6 blocks, but now the
|
2150
|
+
// *chunks* have 18 time steps, with the 5th *chunk* covering
|
2151
|
+
// t=41 - t=58. This is already beyond the end of the simulation period
|
2152
|
+
// (t=55), but with insufficient look-ahead (3), hence the 6th block
|
2153
|
+
// covering t=51 through t=68, of which only the first five time step
|
2154
|
+
// results will be used.
|
2155
|
+
|
2156
|
+
// Initialize error counters (error count will be reset to 0 for each
|
2157
|
+
// block).
|
2151
2158
|
this.error_count = 0;
|
2152
2159
|
this.block_issues = 0;
|
2153
|
-
// Clear issue list with warnings and hide issue panel
|
2160
|
+
// Clear issue list with warnings and hide issue panel.
|
2154
2161
|
this.issue_list.length = 0;
|
2155
2162
|
this.issue_index = -1;
|
2156
2163
|
UI.updateIssuePanel();
|
2157
|
-
// NOTE:
|
2164
|
+
// NOTE: Special tracking of potential solver license errors.
|
2158
2165
|
this.license_expired = 0;
|
2159
|
-
// Reset solver result arrays
|
2166
|
+
// Reset solver result arrays.
|
2160
2167
|
this.round_times.length = 0;
|
2161
2168
|
this.solver_times.length = 0;
|
2162
2169
|
this.round_secs.length = 0;
|
2163
2170
|
this.solver_secs.length = 0;
|
2164
2171
|
this.messages.length = 0;
|
2165
2172
|
this.equations.length = 0;
|
2166
|
-
// Initialize arrays to the expected number of blocks so that values
|
2167
|
-
// be stored asynchronously
|
2173
|
+
// Initialize arrays to the expected number of blocks so that values
|
2174
|
+
// can be stored asynchronously.
|
2168
2175
|
for(let i = 0; i < this.nr_of_blocks; i++) {
|
2169
2176
|
this.solver_times.push(0);
|
2170
2177
|
this.messages.push(this.no_messages);
|
2171
2178
|
this.equations.push(this.no_equations);
|
2172
2179
|
}
|
2173
|
-
// Reset the (graphical) controller
|
2180
|
+
// Reset the (graphical) controller.
|
2174
2181
|
MONITOR.reset();
|
2175
|
-
// Solver license expiry date will be set to ['YYYYMMDD'], or []
|
2182
|
+
// Solver license expiry date will be set to ['YYYYMMDD'], or to []
|
2183
|
+
// if none.
|
2176
2184
|
this.license_expires = [];
|
2177
2185
|
this.block_count = 1;
|
2178
|
-
// Use default
|
2186
|
+
// Use default round sequence unless it has been set.
|
2179
2187
|
if(MODEL.round_sequence === '') {
|
2180
2188
|
this.round_sequence = this.round_letters.slice(1, MODEL.rounds + 1);
|
2181
2189
|
} else {
|
2182
2190
|
this.round_sequence = MODEL.round_sequence;
|
2183
2191
|
}
|
2184
2192
|
this.current_round = 0;
|
2185
|
-
// Set the current time step, relative to start
|
2186
|
-
// (i.e., t = 0 corresponds with
|
2193
|
+
// Set the current time step, *relative* to the start of the simulation
|
2194
|
+
// period (i.e., t = 0 corresponds with the "from" time step t_0).
|
2187
2195
|
this.t = 0;
|
2188
|
-
// Prepare for halt
|
2196
|
+
// Prepare for halt.
|
2189
2197
|
this.halted = false;
|
2190
2198
|
UI.readyToSolve();
|
2191
2199
|
}
|
2192
2200
|
|
2193
2201
|
errorMessage(n) {
|
2194
2202
|
// VM errors are very big NEGATIVE numbers, so start comparing `n`
|
2195
|
-
// with the most negative one to return the correct message
|
2203
|
+
// with the most negative one to return the correct message.
|
2196
2204
|
if(n <= this.UNKNOWN_ERROR) return 'Unknown error';
|
2197
2205
|
if(n <= this.PARAMS) return 'Invalid (number of) parameters';
|
2198
2206
|
if(n <= this.INVALID) return 'Invalid expression';
|
@@ -2204,19 +2212,20 @@ class VirtualMachine {
|
|
2204
2212
|
if(n <= this.DIV_ZERO) return 'Division by zero';
|
2205
2213
|
if(n <= this.CYCLIC) return 'Cyclic reference';
|
2206
2214
|
if(n <= this.ERROR) return 'Unspecified error';
|
2207
|
-
// Large positive values denote exceptions
|
2215
|
+
// Large positive values denote exceptions.
|
2208
2216
|
if(n >= this.COMPUTING) return 'Cyclic reference while computing';
|
2209
2217
|
if(n >= this.NOT_COMPUTED) return 'Variable or expression not computed';
|
2210
2218
|
if(n >= this.UNDEFINED) return 'Undefined variable or expression';
|
2219
|
+
if(n === undefined) return 'Undefined Javascript value';
|
2211
2220
|
return n;
|
2212
2221
|
}
|
2213
2222
|
|
2214
2223
|
specialValue(n) {
|
2215
|
-
//
|
2224
|
+
// Return [FALSE, n] if number n is a NOT a special value,
|
2216
2225
|
// otherwise [TRUE, string] with string a readable representation
|
2217
|
-
// of Virtual Machine error values and other special values
|
2226
|
+
// of Virtual Machine error values and other special values.
|
2218
2227
|
// VM errors are very big NEGATIVE numbers, so start comparing `n`
|
2219
|
-
// with the most negative error code
|
2228
|
+
// with the most negative error code.
|
2220
2229
|
if(n <= this.UNKNOWN_ERROR) return [true, '#ERROR?'];
|
2221
2230
|
if(n <= this.PARAMS) return [true, '#PARAMS'];
|
2222
2231
|
if(n <= this.INVALID) return [true, '#INVALID'];
|
@@ -2228,12 +2237,12 @@ class VirtualMachine {
|
|
2228
2237
|
if(n <= this.DIV_ZERO) return [true, '#DIV0!'];
|
2229
2238
|
if(n <= this.CYCLIC) return [true, '#CYCLE!'];
|
2230
2239
|
// Any other number less than or equal to 10^30 is considered as
|
2231
|
-
// minus infinity
|
2240
|
+
// minus infinity.
|
2232
2241
|
if(n <= this.MINUS_INFINITY) return [true, '-\u221E'];
|
2233
2242
|
// Other special values are very big POSITIVE numbers, so start
|
2234
|
-
// comparing `n` with the highest value
|
2243
|
+
// comparing `n` with the highest value.
|
2235
2244
|
if(n >= this.COMPUTING) return [true, '\u25A6']; // Checkered square
|
2236
|
-
// NOTE: prettier circled bold X 2BBF does not display on macOS !!
|
2245
|
+
// NOTE: The prettier circled bold X 2BBF does not display on macOS !!
|
2237
2246
|
if(n >= this.NOT_COMPUTED) return [true, '\u2297']; // Circled X
|
2238
2247
|
if(n >= this.UNDEFINED) return [true, '\u2047']; // Double question mark ??
|
2239
2248
|
if(n >= this.PLUS_INFINITY) return [true, '\u221E'];
|
@@ -2242,15 +2251,15 @@ class VirtualMachine {
|
|
2242
2251
|
}
|
2243
2252
|
|
2244
2253
|
sig2Dig(n) {
|
2245
|
-
//
|
2254
|
+
// Return number `n` formatted so as to show 2-3 significant digits
|
2246
2255
|
// NOTE: as `n` should be a number, a warning sign will typically
|
2247
|
-
// indicate a bug in the software
|
2256
|
+
// indicate a bug in the software.
|
2248
2257
|
if(n === undefined) return '\u26A0'; // Warning sign
|
2249
2258
|
const sv = this.specialValue(n);
|
2250
|
-
// If `n` has a special value, return its representation
|
2259
|
+
// If `n` has a special value, return its representation.
|
2251
2260
|
if(sv[0]) return sv[1];
|
2252
2261
|
const a = Math.abs(n);
|
2253
|
-
// Signal small differences from true 0
|
2262
|
+
// Signal small differences from true 0 by leading + or - sign.
|
2254
2263
|
if(n !== 0 && a < 0.0005) return n > 0 ? '+0' : '-0';
|
2255
2264
|
if(a >= 999999.5) return n.toPrecision(2);
|
2256
2265
|
if(Math.abs(a-Math.round(a)) < 0.05) return Math.round(n);
|
@@ -2261,15 +2270,15 @@ class VirtualMachine {
|
|
2261
2270
|
}
|
2262
2271
|
|
2263
2272
|
sig4Dig(n) {
|
2264
|
-
//
|
2265
|
-
// NOTE:
|
2266
|
-
// indicate a bug in the software
|
2273
|
+
// Return number `n` formatted so as to show 4-5 significant digits.
|
2274
|
+
// NOTE: As `n` should be a number, a warning sign will typically
|
2275
|
+
// indicate a bug in the software.
|
2267
2276
|
if(n === undefined || isNaN(n)) return '\u26A0';
|
2268
2277
|
const sv = this.specialValue(n);
|
2269
|
-
// If `n` has a special value, return its representation
|
2278
|
+
// If `n` has a special value, return its representation.
|
2270
2279
|
if(sv[0]) return sv[1];
|
2271
2280
|
const a = Math.abs(n);
|
2272
|
-
// Signal small differences from true 0
|
2281
|
+
// Signal small differences from true 0 by a leading + or - sign.
|
2273
2282
|
if(n !== 0 && a < 0.0005) return n > 0 ? '+0' : '-0';
|
2274
2283
|
if(a >= 9999995) return n.toPrecision(4);
|
2275
2284
|
if(Math.abs(a-Math.round(a)) < 0.0005) return Math.round(n);
|
@@ -2281,65 +2290,66 @@ class VirtualMachine {
|
|
2281
2290
|
}
|
2282
2291
|
|
2283
2292
|
//
|
2284
|
-
// Vector scaling methods for datasets and experiment run results
|
2293
|
+
// Vector scaling methods for datasets and experiment run results.
|
2285
2294
|
//
|
2286
2295
|
|
2287
2296
|
keepException(test, result) {
|
2288
|
-
//
|
2297
|
+
// Return result only when test is *not* an exceptional value.
|
2289
2298
|
if(test >= VM.MINUS_INFINITY && test <= VM.PLUS_INFINITY) return result;
|
2290
|
-
// Otherwise, return the exceptional value
|
2299
|
+
// Otherwise, return the exceptional value.
|
2291
2300
|
return test;
|
2292
2301
|
}
|
2293
2302
|
|
2294
2303
|
scaleDataToVector(data, vector, ddt, vdt, vl=0, start=1, fill=VM.UNDEFINED,
|
2295
2304
|
periodic=false, method='nearest') {
|
2296
|
-
//
|
2305
|
+
// Convert array `data` with time step duration `ddt` to a vector with
|
2297
2306
|
// time step duration `vdt` with length `vl`, assuming that data[0]
|
2298
2307
|
// corresponds to vector[start] using the specified method, and filling out
|
2299
|
-
// with `fill` unless `periodic` is TRUE
|
2300
|
-
//
|
2301
|
-
// NOTE: do nothing if vector or data are not arrays
|
2308
|
+
// with `fill` unless `periodic` is TRUE.
|
2309
|
+
// NOTE: do nothing if vector or data are not arrays.
|
2302
2310
|
if(!(Array.isArray(vector) && Array.isArray(data))) return;
|
2311
|
+
// Initialize the vector.
|
2303
2312
|
vector.length = vl + 1;
|
2304
2313
|
vector.fill(fill);
|
2305
2314
|
const dl = data.length;
|
2306
|
-
// No data?
|
2315
|
+
// No data? Then return the vector with its `fill` values.
|
2307
2316
|
if(!dl) return;
|
2308
|
-
// Also compute the array lengths for data and model
|
2309
|
-
// NOTE:
|
2317
|
+
// Also compute the array lengths for data and model.
|
2318
|
+
// NOTE: Times are on "real" time scale, counting from t=1 onwards.
|
2310
2319
|
let period_length = dl * ddt, // no data beyond this time unless periodic
|
2311
2320
|
t_end = (start + vl) * vdt, // last time needing data for simulation
|
2312
2321
|
n = vl; // number of elements to calculate (by default: vector length)
|
2313
2322
|
if(!periodic) {
|
2314
2323
|
// If dataset is not periodic and ends before the vector's end time,
|
2315
|
-
// compute the vector only to the dataset end time
|
2324
|
+
// compute the vector only to the dataset end time.
|
2316
2325
|
if(t_end > period_length) {
|
2317
2326
|
t_end = period_length;
|
2318
|
-
// This then means fewer vector time steps to compute the vector for
|
2327
|
+
// This then means fewer vector time steps to compute the vector for.
|
2319
2328
|
n = Math.floor((t_end - start) / vdt) + 1;
|
2320
2329
|
}
|
2321
2330
|
}
|
2322
|
-
// NOTE: `vts` (vector time step), and `dts` (data time step) are
|
2323
|
-
// in the respective arrays
|
2331
|
+
// NOTE: `vts` (vector time step), and `dts` (data time step) are
|
2332
|
+
// indices in the respective arrays.
|
2324
2333
|
let dts = 1,
|
2325
2334
|
vts = 1;
|
2326
|
-
// The "use nearest corresponding data point" does not aggregate
|
2335
|
+
// The "use nearest corresponding data point" does not aggregate.
|
2327
2336
|
if(method === 'nearest') {
|
2328
|
-
// NOTE: data[0] by definition corresponds to vector time t=1,
|
2329
|
-
// vector[0] must contain the initial value (start - 1)
|
2330
|
-
// NOTE: t is time (*unrounded* step) at VECTOR time scale
|
2331
|
-
// For "nearest", start with data that corresponds to just below
|
2332
|
-
// vector time step before the first time step on the VECTOR
|
2337
|
+
// NOTE: data[0] by definition corresponds to vector time t=1,
|
2338
|
+
// whereas vector[0] must contain the initial value (start - 1).
|
2339
|
+
// NOTE: t is time (*unrounded* step) at VECTOR time scale.
|
2340
|
+
// For "nearest", start with data that corresponds to just below
|
2341
|
+
// half a vector time step before the first time step on the VECTOR
|
2342
|
+
// time scale.
|
2333
2343
|
let t = (start - 0.501) * vdt;
|
2334
2344
|
// t_end += 0.499 * vdt;
|
2335
|
-
// NOTE:
|
2336
|
-
// algorithm used for NEAREST, this works also if *not* periodic
|
2345
|
+
// NOTE: Always modulo data length to anticipate periodic. For the
|
2346
|
+
// algorithm used for NEAREST, this works also if *not* periodic.
|
2337
2347
|
dts = (Math.floor(t / ddt)) % dl;
|
2338
2348
|
/*
|
2339
2349
|
console.log(method, start, t, t_end, 'ddt vdt', ddt, vdt, 'dts vts vl',
|
2340
2350
|
dts, vts, vl, 'DATA', data.toString(), 'V', vector.toString());
|
2341
2351
|
*/
|
2342
|
-
// NOTE:
|
2352
|
+
// NOTE: For vector[0], use one data time step earlier.
|
2343
2353
|
if(dts > 0) {
|
2344
2354
|
vector[0] = data[dts - 1];
|
2345
2355
|
} else if(periodic) {
|
@@ -2476,15 +2486,16 @@ class VirtualMachine {
|
|
2476
2486
|
}
|
2477
2487
|
|
2478
2488
|
get lastRound() {
|
2479
|
-
//
|
2489
|
+
// Return the last round number in the round sequence.
|
2480
2490
|
const index = this.round_sequence.length - 1;
|
2481
2491
|
if(index < 0) return '';
|
2482
2492
|
return this.round_sequence[index];
|
2483
2493
|
}
|
2484
2494
|
|
2485
2495
|
get supRound() {
|
2486
|
-
//
|
2487
|
-
//
|
2496
|
+
// Return HTML for the current round letter as a superscript.
|
2497
|
+
// NOTE: Do not show round number as superscript of the step number
|
2498
|
+
// if the only round in the sequence is round a.
|
2488
2499
|
if(MODEL.rounds < 1 || this.round_sequence === 'a') {
|
2489
2500
|
return '';
|
2490
2501
|
} else {
|
@@ -2494,6 +2505,8 @@ class VirtualMachine {
|
|
2494
2505
|
}
|
2495
2506
|
|
2496
2507
|
get blockWithRound() {
|
2508
|
+
// Return block number plus round letter as plain text string.
|
2509
|
+
// NOTE: No round letter if no rounds, or only one round a.
|
2497
2510
|
if(MODEL.rounds < 1 || this.round_sequence === 'a') {
|
2498
2511
|
return this.block_count;
|
2499
2512
|
} else {
|
@@ -2502,44 +2515,45 @@ class VirtualMachine {
|
|
2502
2515
|
}
|
2503
2516
|
|
2504
2517
|
logCallStack(t) {
|
2505
|
-
// Similar to showCallStack, but simpler, and output only to console
|
2518
|
+
// Similar to showCallStack, but simpler, and output only to console.
|
2506
2519
|
console.log('Call stack:', this.call_stack.slice());
|
2507
2520
|
const csl = this.call_stack.length;
|
2508
2521
|
console.log(`ERROR at t=${t}: ` +
|
2509
2522
|
this.errorMessage(this.call_stack[csl - 1].vector[t]));
|
2510
|
-
// Make separate lists of variable names and their expressions
|
2523
|
+
// Make separate lists of variable names and their expressions.
|
2511
2524
|
const
|
2512
2525
|
vlist = [],
|
2513
2526
|
xlist = [];
|
2514
2527
|
for(let i = 0; i < csl; i++) {
|
2515
2528
|
const x = this.call_stack[i];
|
2516
2529
|
vlist.push(x.object.displayName + '|' + x.attribute);
|
2517
|
-
// Trim spaces around all object-attribute separators in the
|
2530
|
+
// Trim spaces around all object-attribute separators in the
|
2531
|
+
// expression as entered by the modeler.
|
2518
2532
|
xlist.push(x.text.replace(/\s*\|\s*/g, '|'));
|
2519
2533
|
}
|
2520
|
-
// Start without indentation
|
2534
|
+
// Start without indentation.
|
2521
2535
|
let pad = '';
|
2522
|
-
// First log the variable being computed
|
2536
|
+
// First log the variable being computed.
|
2523
2537
|
console.log('Computing:', vlist[0]);
|
2524
|
-
// Then iterate upwards over the call stack
|
2538
|
+
// Then iterate upwards over the call stack.
|
2525
2539
|
for(let i = 0; i < vlist.length - 1; i++) {
|
2526
|
-
// Log the expression, followed by the next computed variable
|
2540
|
+
// Log the expression, followed by the next computed variable.
|
2527
2541
|
console.log(pad + xlist[i] + '\u279C' + vlist[i+1]);
|
2528
2542
|
// Increase indentation
|
2529
2543
|
pad += ' ';
|
2530
2544
|
}
|
2531
|
-
// Log the last expression
|
2545
|
+
// Log the last expression.
|
2532
2546
|
console.log(pad + xlist[xlist.length - 1]);
|
2533
2547
|
}
|
2534
2548
|
|
2535
2549
|
logTrace(trc) {
|
2536
|
-
//
|
2550
|
+
// Log the trace string to the browser console when debugging.
|
2537
2551
|
if(DEBUGGING) console.log(trc);
|
2538
2552
|
}
|
2539
2553
|
|
2540
2554
|
logMessage(block, msg) {
|
2541
|
-
//
|
2542
|
-
// NOTE: block number minus 1, as array is zero-based
|
2555
|
+
// Add a solver message to the list.
|
2556
|
+
// NOTE: block number minus 1, as array is zero-based.
|
2543
2557
|
if(this.messages[block - 1] === this.no_messages) {
|
2544
2558
|
this.messages[block - 1] = '';
|
2545
2559
|
}
|
@@ -2548,12 +2562,12 @@ class VirtualMachine {
|
|
2548
2562
|
this.error_count++;
|
2549
2563
|
this.issue_list.push(msg);
|
2550
2564
|
}
|
2551
|
-
// Show message on console or in Monitor dialog
|
2565
|
+
// Show message on console or in Monitor dialog.
|
2552
2566
|
MONITOR.logMessage(block, msg);
|
2553
2567
|
}
|
2554
2568
|
|
2555
2569
|
setRunMessages(n) {
|
2556
|
-
//
|
2570
|
+
// Set the messages and solver times for experiment or SA run `n`.
|
2557
2571
|
let r = null;
|
2558
2572
|
if(EXPERIMENT_MANAGER.selected_experiment) {
|
2559
2573
|
if(n < EXPERIMENT_MANAGER.selected_experiment.runs.length) {
|
@@ -2590,27 +2604,27 @@ class VirtualMachine {
|
|
2590
2604
|
}
|
2591
2605
|
|
2592
2606
|
startTimer() {
|
2593
|
-
// Record time of this reset
|
2607
|
+
// Record time of this timer reset.
|
2594
2608
|
this.reset_time = new Date().getTime();
|
2595
2609
|
this.time_stamp = this.reset_time;
|
2596
|
-
// Activate the timer
|
2610
|
+
// Activate the timer.
|
2597
2611
|
this.timer_id = setInterval(() => MONITOR.updateMonitorTime(), 1000);
|
2598
2612
|
}
|
2599
2613
|
|
2600
2614
|
stopTimer() {
|
2601
|
-
// Deactivate the timer
|
2615
|
+
// Deactivate the timer.
|
2602
2616
|
clearInterval(this.timer_id);
|
2603
2617
|
}
|
2604
2618
|
|
2605
2619
|
get elapsedTime() {
|
2606
|
-
//
|
2620
|
+
// Return seconds since previous "elapsed time" check.
|
2607
2621
|
const ts = this.time_stamp;
|
2608
2622
|
this.time_stamp = new Date().getTime();
|
2609
2623
|
return (this.time_stamp - ts) / 1000;
|
2610
2624
|
}
|
2611
2625
|
|
2612
2626
|
addVariable(type, obj) {
|
2613
|
-
//
|
2627
|
+
// Add a variable that will need a column in the Simplex tableau.
|
2614
2628
|
const index = this.variables.push([type, obj]);
|
2615
2629
|
if((type === 'PL' || type === 'PiL') && obj.level_to_zero) {
|
2616
2630
|
this.sec_var_indices[index] = true;
|
@@ -2621,11 +2635,11 @@ class VirtualMachine {
|
|
2621
2635
|
this.bin_var_indices[index] = true;
|
2622
2636
|
}
|
2623
2637
|
if(obj instanceof Process && obj.pace > 1) {
|
2624
|
-
// NOTE:
|
2638
|
+
// NOTE: Binary variables can be "paced" just like level variables.
|
2625
2639
|
this.paced_var_indices[index] = obj.pace;
|
2626
2640
|
}
|
2627
|
-
// For constraint bound lines, add as many SOS variables as there
|
2628
|
-
// points on the bound line
|
2641
|
+
// For constraint bound lines, add as many SOS variables as there
|
2642
|
+
// are points on the bound line.
|
2629
2643
|
if(type === 'W1' && obj instanceof BoundLine) {
|
2630
2644
|
const n = obj.points.length;
|
2631
2645
|
for(let i = 2; i <= n; i++) {
|
@@ -2637,7 +2651,7 @@ class VirtualMachine {
|
|
2637
2651
|
}
|
2638
2652
|
|
2639
2653
|
resetVariableIndices(p) {
|
2640
|
-
// Set all variable indices to -1 ("no such variable") for node `p
|
2654
|
+
// Set all variable indices to -1 ("no such variable") for node `p`.
|
2641
2655
|
p.level_var_index = -1;
|
2642
2656
|
p.on_off_var_index = -1;
|
2643
2657
|
p.is_zero_var_index = -1;
|
@@ -2654,12 +2668,13 @@ class VirtualMachine {
|
|
2654
2668
|
}
|
2655
2669
|
|
2656
2670
|
addNodeVariables(p) {
|
2657
|
-
// Add tableau variables for process or product `p
|
2658
|
-
// NOTE:
|
2659
|
-
// This is done even if a product has no
|
2660
|
-
// simplifies the formulation of
|
2671
|
+
// Add tableau variables for process or product `p`.
|
2672
|
+
// NOTE: Every node (process or product) is represented by at least
|
2673
|
+
// one variable: its "level". This is done even if a product has no
|
2674
|
+
// storage capacity, because it simplifies the formulation of
|
2675
|
+
// product-related (data) constraints.
|
2661
2676
|
p.level_var_index = this.addVariable(p.integer_level ? 'PiL': 'PL', p);
|
2662
|
-
// Some "data-only" link multipliers require additional variables
|
2677
|
+
// Some "data-only" link multipliers require additional variables.
|
2663
2678
|
if(p.needsOnOffData) {
|
2664
2679
|
p.on_off_var_index = this.addVariable('OO', p);
|
2665
2680
|
p.is_zero_var_index = this.addVariable('IZ', p);
|
@@ -2682,11 +2697,12 @@ class VirtualMachine {
|
|
2682
2697
|
// (1) Processes have NO slack variables, because sufficient slack is
|
2683
2698
|
// provided by adding slack variables to products; these slack
|
2684
2699
|
// variables will have high cost penalty values in the objective
|
2685
|
-
// function, to serve as "last resort" to still obtain a solution
|
2686
|
-
// the "real" product levels are overconstrained
|
2687
|
-
// (2) The modeler may selectively disable slack to force the solver
|
2688
|
-
// respect certain constraints
|
2689
|
-
// problems
|
2700
|
+
// function, to serve as "last resort" to still obtain a solution
|
2701
|
+
// when the "real" product levels are overconstrained
|
2702
|
+
// (2) The modeler may selectively disable slack to force the solver
|
2703
|
+
// to respect certain constraints. This may result in infeasible
|
2704
|
+
// MILP problems. The solver will report this, but provide no
|
2705
|
+
// clue as to which constraints may be critical.
|
2690
2706
|
if(p instanceof Product && !p.no_slack) {
|
2691
2707
|
p.stock_LE_slack_var_index = this.addVariable('LE', p);
|
2692
2708
|
p.stock_GE_slack_var_index = this.addVariable('GE', p);
|
@@ -2694,13 +2710,13 @@ class VirtualMachine {
|
|
2694
2710
|
}
|
2695
2711
|
|
2696
2712
|
priorValue(tuple, t) {
|
2697
|
-
//
|
2698
|
-
// NOTE: tuple is a [type, object] VM variable specification
|
2713
|
+
// Return the value of a tableau variable calculated for a prior block.
|
2714
|
+
// NOTE: `tuple` is a [type, object] VM variable specification.
|
2699
2715
|
const
|
2700
2716
|
type = tuple[0],
|
2701
2717
|
obj = tuple[1];
|
2702
2718
|
if(type.indexOf('-peak') > 0) {
|
2703
|
-
// Peak level variables have an array as node property
|
2719
|
+
// Peak level variables have an array as node property.
|
2704
2720
|
const c = Math.trunc(t / this.block_length);
|
2705
2721
|
if(type.startsWith('b')) return obj.b_peak_inc[c];
|
2706
2722
|
return obj.la_peak_inc[c];
|
@@ -2708,9 +2724,11 @@ class VirtualMachine {
|
|
2708
2724
|
const prior_level = obj.actualLevel(t);
|
2709
2725
|
if(type === 'OO') return prior_level > 0 ? 1 : 0;
|
2710
2726
|
if(type === 'IZ') return prior_level === 0 ? 1 : 0;
|
2711
|
-
// Start-up at time t entails that t is in the list of start-up
|
2727
|
+
// Start-up at time t entails that t is in the list of start-up
|
2728
|
+
// time steps.
|
2712
2729
|
if(type === 'SU') return obj.start_ups.indexOf(t) < 0 ? 0 : 1;
|
2713
|
-
// Shut-down at time t entails that t is in the list of shut-down
|
2730
|
+
// Shut-down at time t entails that t is in the list of shut-down
|
2731
|
+
// time steps.
|
2714
2732
|
if(type === 'SD') return obj.shut_downs.indexOf(t) < 0 ? 0 : 1;
|
2715
2733
|
if(['SO', 'SC', 'FC'].indexOf(type) >= 0) {
|
2716
2734
|
let l = obj.start_ups.length;
|
@@ -2724,7 +2742,8 @@ class VirtualMachine {
|
|
2724
2742
|
}
|
2725
2743
|
|
2726
2744
|
variablesLegend(block) {
|
2727
|
-
//
|
2745
|
+
// Return a string with each variable code and full name on a
|
2746
|
+
// separate line.
|
2728
2747
|
const
|
2729
2748
|
vcnt = this.variables.length,
|
2730
2749
|
z = vcnt.toString().length;
|
@@ -2738,13 +2757,12 @@ class VirtualMachine {
|
|
2738
2757
|
l += v + ' [' + this.variables[i][0] + p + ']\n';
|
2739
2758
|
}
|
2740
2759
|
if(this.chunk_variables.length > 0) {
|
2741
|
-
|
2742
|
-
const chof = (block >= this.nr_of_blocks ? this.chunk_offset :
|
2743
|
-
this.cols * this.chunk_length + 1);
|
2760
|
+
const chof = this.cols * this.chunk_length + 1;
|
2744
2761
|
for(let i = 0; i < this.chunk_variables.length; i++) {
|
2745
2762
|
const
|
2746
2763
|
obj = this.chunk_variables[i][1],
|
2747
|
-
// NOTE: chunk offset takes into account that
|
2764
|
+
// NOTE: chunk offset takes into account that variable
|
2765
|
+
// indices are 0-based.
|
2748
2766
|
cvi = chof + i;
|
2749
2767
|
let v = 'X' + cvi.toString().padStart(z, '0');
|
2750
2768
|
v += ' '.slice(v.length) + obj.displayName;
|
@@ -2755,8 +2773,9 @@ class VirtualMachine {
|
|
2755
2773
|
}
|
2756
2774
|
|
2757
2775
|
setBoundConstraints(p) {
|
2758
|
-
//
|
2759
|
-
// NOTE:
|
2776
|
+
// Set LB and UB constraints for product `p`.
|
2777
|
+
// NOTE: This method affects the VM coefficient vector, so save it
|
2778
|
+
// (if needed) before calling this method.
|
2760
2779
|
const
|
2761
2780
|
vi = p.level_var_index,
|
2762
2781
|
lesvi = p.stock_LE_slack_var_index,
|
@@ -2764,13 +2783,13 @@ class VirtualMachine {
|
|
2764
2783
|
notsrc = !p.isSourceNode,
|
2765
2784
|
notsnk = !p.isSinkNode;
|
2766
2785
|
this.code.push(
|
2767
|
-
// Set coefficients vector to 0
|
2786
|
+
// Set coefficients vector to 0.
|
2768
2787
|
[VMI_clear_coefficients, null],
|
2769
|
-
// Always add the index of the variable-to-be-constrained
|
2788
|
+
// Always add the index of the variable-to-be-constrained.
|
2770
2789
|
[VMI_add_const_to_coefficient, [vi, 1]]
|
2771
2790
|
);
|
2772
|
-
// Get the lower bound as number (static LB) or expression (dynamic LB)
|
2773
|
-
// NOTE:
|
2791
|
+
// Get the lower bound as number (static LB) or expression (dynamic LB).
|
2792
|
+
// NOTE: By default, LB = 0 and UB = +INF.
|
2774
2793
|
let l = 0,
|
2775
2794
|
u = VM.PLUS_INFINITY;
|
2776
2795
|
if(p.hasBounds) {
|
@@ -4031,37 +4050,37 @@ class VirtualMachine {
|
|
4031
4050
|
}
|
4032
4051
|
|
4033
4052
|
scaleCashFlowConstraints() {
|
4034
|
-
// Scale cash flow coefficients per actor by dividing them by the
|
4035
|
-
// cash flow coefficient (in absolute value) within the
|
4036
|
-
// so that cash flows cannot easily "overrule" the
|
4037
|
-
// objective function
|
4038
|
-
// NOTE:
|
4053
|
+
// Scale cash flow coefficients per actor by dividing them by the
|
4054
|
+
// largest cash flow coefficient (in absolute value) within the
|
4055
|
+
// current block so that cash flows cannot easily "overrule" the
|
4056
|
+
// slack penalties in the objective function.
|
4057
|
+
// NOTE: No scaling needed if model features no cash flows.
|
4039
4058
|
if(this.no_cash_flows) return;
|
4040
4059
|
this.logMessage(this.block_count,
|
4041
4060
|
'Cash flows scaled by 1/' + this.cash_scalar);
|
4042
|
-
// Use reciprocal as multiplier to scale the constraint coefficients
|
4061
|
+
// Use reciprocal as multiplier to scale the constraint coefficients.
|
4043
4062
|
const m = 1 / this.cash_scalar;
|
4044
4063
|
let cv;
|
4045
4064
|
for(let i = 0; i < this.cash_constraints.length; i++) {
|
4046
4065
|
const cc = this.matrix[this.cash_constraints[i]];
|
4047
4066
|
for(let ci in cc) if(cc.hasOwnProperty(ci)) {
|
4048
4067
|
if(ci < this.chunk_offset) {
|
4049
|
-
// NOTE:
|
4068
|
+
// NOTE: Subtract 1 as variables array is zero-based.
|
4050
4069
|
cv = this.variables[(ci - 1) % this.cols];
|
4051
4070
|
} else {
|
4052
|
-
// Chunk variable array is zero-based
|
4071
|
+
// Chunk variable array is zero-based.
|
4053
4072
|
cv = this.chunk_variables[ci - this.chunk_offset];
|
4054
4073
|
}
|
4055
|
-
// NOTE:
|
4074
|
+
// NOTE: Do not scale the coefficient of the cash variable.
|
4056
4075
|
if(!cv[0].startsWith('C')) cc[ci] *= m;
|
4057
4076
|
}
|
4058
4077
|
}
|
4059
4078
|
}
|
4060
4079
|
|
4061
4080
|
checkForInfinity(n) {
|
4062
|
-
//
|
4081
|
+
// Return floating point number `n`, or +INF or -INF if the absolute
|
4063
4082
|
// value of `n` is relatively (!) close to the VM infinity constants
|
4064
|
-
// (since the solver may return imprecise values of such magnitude)
|
4083
|
+
// (since the solver may return imprecise values of such magnitude).
|
4065
4084
|
if(n > 0.5 * VM.PLUS_INFINITY && n < VM.BEYOND_PLUS_INFINITY) {
|
4066
4085
|
return VM.PLUS_INFINITY;
|
4067
4086
|
}
|
@@ -4072,23 +4091,40 @@ class VirtualMachine {
|
|
4072
4091
|
}
|
4073
4092
|
|
4074
4093
|
setLevels(block, round, x, err) {
|
4075
|
-
//
|
4076
|
-
// `x` holds the solver result, `err` is TRUE if the model was not
|
4077
|
-
// First deal with quirk of JSON, which turns
|
4094
|
+
// Copy the values of decision variables calculated by the solver.
|
4095
|
+
// `x` holds the solver result, `err` is TRUE if the model was not
|
4096
|
+
// computed. First deal with quirk of JSON, which turns a list with
|
4097
|
+
// one value into just that value as a number.
|
4078
4098
|
if(!(x instanceof Array)) x = [x];
|
4079
4099
|
// `bb` is first time step of this block (blocks are numbered 1, 2, ...)
|
4080
|
-
// `abl` is the actual block length, i.e., # time steps to set levels for
|
4100
|
+
// `abl` is the actual block length, i.e., # time steps to set levels for,
|
4101
|
+
// `cbl` is the cropped block length (applies only to last block).
|
4081
4102
|
let bb = (block - 1) * MODEL.block_length + 1,
|
4082
|
-
abl = this.chunk_length
|
4083
|
-
|
4103
|
+
abl = this.chunk_length,
|
4104
|
+
cbl = this.actualBlockLength;
|
4105
|
+
// For the last block, crop the actual block length so it does not
|
4106
|
+
// extend beyond the simulation period (these results should be ignored).
|
4107
|
+
// If no results computed, preserve those already computed for the
|
4108
|
+
// pervious chunk as "look-ahead".
|
4084
4109
|
if(err && block > 1 && MODEL.look_ahead > 0) {
|
4085
4110
|
bb += MODEL.look_ahead;
|
4086
4111
|
abl -= MODEL.look_ahead;
|
4087
|
-
|
4088
|
-
|
4089
|
-
|
4090
|
-
|
4091
|
-
|
4112
|
+
cbl -= MODEL.look_ahead;
|
4113
|
+
this.logMessage(block,
|
4114
|
+
'No results from solver -- retained results of ' +
|
4115
|
+
pluralS(MODEL.look_ahead, 'look-ahead time step'));
|
4116
|
+
}
|
4117
|
+
if(cbl <= 0) {
|
4118
|
+
this.logMessage(block, 'Results of last optimization could be discarded');
|
4119
|
+
abl = 0;
|
4120
|
+
} else if(cbl < abl) {
|
4121
|
+
this.logMessage(block, ['Last chunk (',
|
4122
|
+
pluralS(this.chunk_length, 'time step'), ') cropped to ',
|
4123
|
+
pluralS(cbl, 'time step')].join(''));
|
4124
|
+
abl = cbl;
|
4125
|
+
}
|
4126
|
+
// NOTE: Length of solution vector divided by number of columns should
|
4127
|
+
// be integer, and typically equal to the actual block length.
|
4092
4128
|
const
|
4093
4129
|
ncv = this.chunk_variables.length,
|
4094
4130
|
ncv_msg = (ncv ? ' minus ' + pluralS(ncv, 'singular variable') : ''),
|
@@ -4096,12 +4132,7 @@ class VirtualMachine {
|
|
4096
4132
|
xbl = Math.floor(xratio);
|
4097
4133
|
if(xbl < xratio) console.log('ANOMALY: solution vector length', x.length,
|
4098
4134
|
ncv_msg + ' is not a multiple of # columns', this.cols);
|
4099
|
-
|
4100
|
-
console.log('Cropping actual block length', abl,
|
4101
|
-
'to solved block length', xbl);
|
4102
|
-
abl = xbl;
|
4103
|
-
}
|
4104
|
-
// Assume no warnings or errors
|
4135
|
+
// Assume no warnings or errors.
|
4105
4136
|
this.error_count = 0;
|
4106
4137
|
// Set cash flows for all actors
|
4107
4138
|
// NOTE: all cash IN and cash OUT values should normally be non-negative,
|
@@ -4272,15 +4303,16 @@ class VirtualMachine {
|
|
4272
4303
|
}
|
4273
4304
|
|
4274
4305
|
calculateDependentVariables(block) {
|
4275
|
-
//
|
4276
|
-
// of the decision variables output by the solver
|
4277
|
-
// NOTE:
|
4278
|
-
// in the vectors of nodes and links that span the entire
|
4279
|
-
// hence start by calculating the offset `bb`
|
4280
|
-
//
|
4306
|
+
// Calculate the values of all model variables that depend on the
|
4307
|
+
// values of the decision variables output by the solver.
|
4308
|
+
// NOTE: Only for the block that was just solved, but the values are
|
4309
|
+
// stored in the vectors of nodes and links that span the entire
|
4310
|
+
// optimization period, hence start by calculating the offset `bb`
|
4311
|
+
// being the first time step of this block.
|
4312
|
+
// Blocks are numbered 1, 2, ...
|
4281
4313
|
const bb = (block - 1) * MODEL.block_length + 1;
|
4282
4314
|
|
4283
|
-
// FIRST:
|
4315
|
+
// FIRST: Calculate the actual flows on links.
|
4284
4316
|
let b, bt, p, pl, ld;
|
4285
4317
|
for(let l in MODEL.links) if(MODEL.links.hasOwnProperty(l) &&
|
4286
4318
|
!MODEL.ignored_entities[l]) {
|
@@ -4473,11 +4505,10 @@ class VirtualMachine {
|
|
4473
4505
|
|
4474
4506
|
showSetUpProgress(next_start, abl) {
|
4475
4507
|
if(this.show_progress) {
|
4476
|
-
//
|
4508
|
+
// Display 1 more segment progress so that the bar reaches 100%
|
4477
4509
|
UI.setProgressNeedle((next_start + this.tsl) / abl);
|
4478
4510
|
}
|
4479
|
-
setTimeout(
|
4480
|
-
function(t, n) { VM.addTableauSegment(t, n); }, 0, next_start, abl);
|
4511
|
+
setTimeout((t, n) => { VM.addTableauSegment(t, n); }, 0, next_start, abl);
|
4481
4512
|
}
|
4482
4513
|
|
4483
4514
|
hideSetUpOrWriteProgress() {
|
@@ -4486,7 +4517,7 @@ class VirtualMachine {
|
|
4486
4517
|
}
|
4487
4518
|
|
4488
4519
|
logCode() {
|
4489
|
-
//
|
4520
|
+
// Print VM instructions to console.
|
4490
4521
|
const arg = (a) => {
|
4491
4522
|
if(a === null) return '';
|
4492
4523
|
if(typeof a === 'number') return a + '';
|
@@ -4513,12 +4544,12 @@ class VirtualMachine {
|
|
4513
4544
|
setupBlock() {
|
4514
4545
|
if(DEBUGGING) this.logCode();
|
4515
4546
|
const abl = this.actualBlockLength;
|
4516
|
-
// NOTE:
|
4547
|
+
// NOTE: Tableau segment length is the number of time steps between
|
4517
4548
|
// updates of the progress needle. The default progress needle interval
|
4518
|
-
// is calibrated for 1000 VMI instructions
|
4549
|
+
// is calibrated for 1000 VMI instructions.
|
4519
4550
|
this.tsl = Math.ceil(CONFIGURATION.progress_needle_interval *
|
4520
4551
|
1000 / this.code.length);
|
4521
|
-
if(abl > this.tsl * 5) {
|
4552
|
+
if(true||abl > this.tsl * 5) {
|
4522
4553
|
UI.setMessage('Constructing the Simplex tableau');
|
4523
4554
|
UI.setProgressNeedle(0);
|
4524
4555
|
this.show_progress = true;
|
@@ -4706,6 +4737,17 @@ class VirtualMachine {
|
|
4706
4737
|
setTimeout(() => VM.solveBlock(), 0);
|
4707
4738
|
}
|
4708
4739
|
|
4740
|
+
get actualBlockLength() {
|
4741
|
+
// The actual block length is the number of time steps to be considered
|
4742
|
+
// by the solver; the abl of the last block is likely to be shorter
|
4743
|
+
// than the standard, as it should not go beyond the end time plus
|
4744
|
+
// look-ahead.
|
4745
|
+
if(this.block_count < this.nr_of_blocks) return this.chunk_length;
|
4746
|
+
let rem = MODEL.runLength % MODEL.block_length;
|
4747
|
+
if(rem === 0) rem = MODEL.block_length;
|
4748
|
+
return rem + MODEL.look_ahead;
|
4749
|
+
}
|
4750
|
+
|
4709
4751
|
setNumericIssue(n, p, where) {
|
4710
4752
|
let vbl;
|
4711
4753
|
if(p >= this.chunk_offset) {
|
@@ -4715,41 +4757,31 @@ class VirtualMachine {
|
|
4715
4757
|
vbl = this.variables[(p-1) % this.cols];
|
4716
4758
|
}
|
4717
4759
|
this.numeric_issue = where + ' for ' + vbl[1].name +
|
4718
|
-
' (' + vbl[0] + ', bt=' + Math.floor((p-1) / this.cols + 1) + ')';
|
4719
|
-
// NOTE:
|
4720
|
-
//
|
4721
|
-
// values
|
4722
|
-
|
4723
|
-
|
4724
|
-
|
4725
|
-
|
4726
|
-
|
4727
|
-
|
4728
|
-
}
|
4729
|
-
this.logMessage(this.block_count, err);
|
4730
|
-
UI.alert(err);
|
4731
|
-
}
|
4732
|
-
|
4733
|
-
get actualBlockLength() {
|
4734
|
-
// The actual block length is the number of time steps to be considered by
|
4735
|
-
// the solver; the abl of the last block is likely to be shorter than the
|
4736
|
-
// standard, as it should not go beyond the end time, assuming that
|
4737
|
-
// parameter data are undefined beyond this end time
|
4738
|
-
if(this.block_count < this.nr_of_blocks) return this.chunk_length;
|
4739
|
-
return (MODEL.end_period - MODEL.start_period + 1) -
|
4740
|
-
(this.block_count - 1) * MODEL.block_length;
|
4760
|
+
' (' + vbl[0] + ', bt=' + Math.floor((p-1) / this.cols + 1) + ') ';
|
4761
|
+
// NOTE: Numeric issues may be detected on negated values, because
|
4762
|
+
// coefficients may be transformed algebraically. Exception and
|
4763
|
+
// error codes are extremely high + or - values => negate them when
|
4764
|
+
// they exceed the negated exception or error threshold.
|
4765
|
+
if(n <= -VM.EXCEPTION || n >= -VM.ERROR) n = -n;
|
4766
|
+
const msg = ['Tableau error: ', this.numeric_issue, ' - ',
|
4767
|
+
this.errorMessage(n), ' (value = ', this.sig2Dig(n), ')'].join('');
|
4768
|
+
this.logMessage(this.block_count, msg);
|
4769
|
+
UI.alert(msg);
|
4741
4770
|
}
|
4742
4771
|
|
4743
4772
|
get columnsInBlock() {
|
4744
|
-
//
|
4745
|
-
return this.
|
4773
|
+
// Return the chunk length plus the number of chunk variables.
|
4774
|
+
return this.chunk_length * this.cols + this.chunk_variables.length;
|
4746
4775
|
}
|
4747
4776
|
|
4748
4777
|
writeLpFormat(cplex=false) {
|
4749
|
-
// NOTE: actual block length
|
4750
|
-
// shorter than the
|
4751
|
-
|
4752
|
-
|
4778
|
+
// NOTE: Up to version 1.5.6, actual block length of last block used
|
4779
|
+
// to be shorter than the chunk length so as not to go beyond the
|
4780
|
+
// simulation end time. The look-ahead is now *always* part of the
|
4781
|
+
// chunk, even if this extends beyond the simulation period. The
|
4782
|
+
// model is expected to provide the necessary data. The former model
|
4783
|
+
// behavior can still be generated by limiting time series length to
|
4784
|
+
// the simulation period.
|
4753
4785
|
const
|
4754
4786
|
abl = this.actualBlockLength,
|
4755
4787
|
// Get the number digits for variable names
|
@@ -4982,9 +5014,9 @@ class VirtualMachine {
|
|
4982
5014
|
}
|
4983
5015
|
|
4984
5016
|
writeMPSFormat() {
|
4985
|
-
// Write model code lines in MPS format
|
4986
|
-
//
|
4987
|
-
// NOTE:
|
5017
|
+
// Write model code lines in MPS format. This format is column-based
|
5018
|
+
// instead of row-based, hence for each column a separate string list.
|
5019
|
+
// NOTE: Columns are numbered from 1 to N, hence a dummy list for c=0.
|
4988
5020
|
const
|
4989
5021
|
abl = this.actualBlockLength,
|
4990
5022
|
cols = [[]],
|
@@ -5165,7 +5197,7 @@ class VirtualMachine {
|
|
5165
5197
|
|
5166
5198
|
writeLastMPSLines() {
|
5167
5199
|
this.hideSetUpOrWriteProgress();
|
5168
|
-
// Add the SOS section
|
5200
|
+
// Add the SOS section.
|
5169
5201
|
if(this.sos_var_indices.length > 0) {
|
5170
5202
|
this.lines += 'SOS\n';
|
5171
5203
|
const abl = this.actualBlockLength;
|
@@ -5185,14 +5217,14 @@ class VirtualMachine {
|
|
5185
5217
|
}
|
5186
5218
|
}
|
5187
5219
|
}
|
5188
|
-
// Add the end-of-model marker
|
5220
|
+
// Add the end-of-model marker.
|
5189
5221
|
this.lines += 'ENDATA';
|
5190
5222
|
setTimeout(() => VM.submitFile(), 0);
|
5191
5223
|
}
|
5192
5224
|
|
5193
5225
|
get noSolutionStatus() {
|
5194
|
-
//
|
5195
|
-
// a solution (so look-ahead should be conserved)
|
5226
|
+
// Return the set of status codes that indicate that solver did not
|
5227
|
+
// return a solution (so look-ahead should be conserved).
|
5196
5228
|
if(this.solver_name === 'lp_solve') {
|
5197
5229
|
return [-2, 2, 6];
|
5198
5230
|
} else if(this.solver_name === 'gurobi') {
|
@@ -5203,10 +5235,10 @@ class VirtualMachine {
|
|
5203
5235
|
}
|
5204
5236
|
|
5205
5237
|
checkLicense() {
|
5206
|
-
//
|
5207
|
-
// when three days or less remain
|
5238
|
+
// Compare license expiry date (if set) with current time, and notify
|
5239
|
+
// when three days or less remain.
|
5208
5240
|
if(this.license_expires && this.license_expires.length) {
|
5209
|
-
// NOTE:
|
5241
|
+
// NOTE: Expiry date has YYYY-MM-DD format.
|
5210
5242
|
const
|
5211
5243
|
xds = this.license_expires[0].slice(-10).split('-'),
|
5212
5244
|
y = parseInt(xds[0]),
|
@@ -5217,7 +5249,8 @@ class VirtualMachine {
|
|
5217
5249
|
three_days = 3*24*3.6e+6;
|
5218
5250
|
if(time_left < three_days) {
|
5219
5251
|
const
|
5220
|
-
opts = {weekday: 'long', year: 'numeric', month: 'long',
|
5252
|
+
opts = {weekday: 'long', year: 'numeric', month: 'long',
|
5253
|
+
day: 'numeric'},
|
5221
5254
|
lds = ' (' + xdate.toLocaleDateString(undefined, opts) + ')';
|
5222
5255
|
UI.notify('Solver license will expire in less than 3 days' + lds);
|
5223
5256
|
}
|
@@ -5236,7 +5269,7 @@ class VirtualMachine {
|
|
5236
5269
|
// - model: the MILP equations in LP format
|
5237
5270
|
// - data: data object {block, round, x}
|
5238
5271
|
let msg = '';
|
5239
|
-
// NOTE:
|
5272
|
+
// NOTE: Block number is passed as string => convert to integer.
|
5240
5273
|
const
|
5241
5274
|
bnr = safeStrToInt(json.data.block),
|
5242
5275
|
rl = json.data.round,
|
@@ -5247,7 +5280,7 @@ class VirtualMachine {
|
|
5247
5280
|
Solver status = ${json.status}`);
|
5248
5281
|
if(json.messages) {
|
5249
5282
|
msg = json.messages.join('\n');
|
5250
|
-
// Check whether Gurobi version is at least version 9.5
|
5283
|
+
// Check whether Gurobi version is at least version 9.5.
|
5251
5284
|
let gv = msg.match(/Gurobi \d+\.\d+\.\d+/);
|
5252
5285
|
if(gv) {
|
5253
5286
|
gv = gv[0].split(' ')[1].split('.');
|
@@ -5256,7 +5289,8 @@ Solver status = ${json.status}`);
|
|
5256
5289
|
UI.alert('Gurobi version is too old -- upgrade to 9.5 or higher');
|
5257
5290
|
}
|
5258
5291
|
}
|
5259
|
-
// NOTE:
|
5292
|
+
// NOTE: Server script adds a license expiry notice for the Gurobi
|
5293
|
+
// solver.
|
5260
5294
|
this.license_expires = msg.match(/ expires \d{4}\-\d{2}\-\d{2}/);
|
5261
5295
|
}
|
5262
5296
|
if(json.error) {
|
@@ -5270,42 +5304,44 @@ Solver status = ${json.status}`);
|
|
5270
5304
|
this.logMessage(bnr, msg);
|
5271
5305
|
this.equations[bnr - 1] = json.model;
|
5272
5306
|
if(DEBUGGING) console.log(json.data);
|
5273
|
-
// Store the results in the decision variable vectors (production
|
5274
|
-
// and stock level), but do NOT overwrite "look-ahead" levels
|
5275
|
-
// was not solved (indicated by the 4th parameter that
|
5276
|
-
//
|
5307
|
+
// Store the results in the decision variable vectors (production
|
5308
|
+
// levels and stock level), but do NOT overwrite "look-ahead" levels
|
5309
|
+
// if this block was not solved (indicated by the 4th parameter that
|
5310
|
+
// tests the status).
|
5311
|
+
// NOTE: Appropriate status codes are solver-dependent.
|
5277
5312
|
this.setLevels(bnr, rl, json.data.x,
|
5278
5313
|
this.noSolutionStatus.indexOf(json.status) >= 0);
|
5279
5314
|
// NOTE: Post-process levels only AFTER the last round!
|
5280
5315
|
if(rl === this.lastRound) {
|
5281
|
-
// Calculate data for all other dependent variables
|
5316
|
+
// Calculate data for all other dependent variables.
|
5282
5317
|
this.calculateDependentVariables(bnr);
|
5283
|
-
// Add progress bar segment only now, knowing status AND slack use
|
5318
|
+
// Add progress bar segment only now, knowing status AND slack use.
|
5284
5319
|
const issue = json.status !== 0 || this.error_count > 0;
|
5285
5320
|
if(issue) this.block_issues++;
|
5286
|
-
// NOTE: in case of multiple rounds, use the sum of the round times
|
5321
|
+
// NOTE: in case of multiple rounds, use the sum of the round times.
|
5287
5322
|
const time = this.round_times.reduce((a, b) => a + b, 0);
|
5288
5323
|
this.round_times.length = 0;
|
5289
5324
|
this.solver_times[bnr - 1] = time;
|
5290
|
-
|
5325
|
+
const ssecs = this.round_secs.reduce((a, b) => a + b, 0);
|
5326
|
+
this.solver_secs[bnr - 1] = (ssecs ? VM.sig4Dig(ssecs) : '');
|
5291
5327
|
this.round_secs.length = 0;
|
5292
5328
|
MONITOR.addProgressBlock(bnr, issue, time);
|
5293
5329
|
}
|
5294
|
-
// Free up memory
|
5330
|
+
// Free up memory.
|
5295
5331
|
json = null;
|
5296
5332
|
}
|
5297
5333
|
|
5298
5334
|
solveBlocks() {
|
5299
|
-
// Check if blocks remain to be done
|
5300
|
-
// NOTE:
|
5335
|
+
// Check if blocks remain to be done. If not, redraw the graph and exit.
|
5336
|
+
// NOTE: Set IF-condition to TRUE for testing WITHOUT computation.
|
5301
5337
|
if(this.halted || this.block_count > this.nr_of_blocks) {
|
5302
|
-
// Set current time step to 1 (= first time step of simulation period)
|
5338
|
+
// Set current time step to 1 (= first time step of simulation period).
|
5303
5339
|
MODEL.t = 1;
|
5304
5340
|
this.stopSolving();
|
5305
5341
|
MODEL.solved = true;
|
5306
5342
|
this.checkLicense();
|
5307
5343
|
UI.drawDiagram(MODEL);
|
5308
|
-
// Show the reset button (GUI only)
|
5344
|
+
// Show the reset button (GUI only).
|
5309
5345
|
UI.readyToReset();
|
5310
5346
|
if(MODEL.running_experiment) {
|
5311
5347
|
// If experiment is active, signal the manager.
|
@@ -5314,7 +5350,7 @@ Solver status = ${json.status}`);
|
|
5314
5350
|
// Otherwise report results now, if applicable.
|
5315
5351
|
RECEIVER.report();
|
5316
5352
|
}
|
5317
|
-
// Warn modeler if any issues occurred
|
5353
|
+
// Warn modeler if any issues occurred.
|
5318
5354
|
if(this.block_issues) {
|
5319
5355
|
let msg = 'Issues occurred in ' +
|
5320
5356
|
pluralS(this.block_issues, 'block') +
|
@@ -5326,17 +5362,22 @@ Solver status = ${json.status}`);
|
|
5326
5362
|
UI.updateIssuePanel();
|
5327
5363
|
}
|
5328
5364
|
if(this.license_expired > 0) {
|
5329
|
-
// Special message to draw attention to this critical error
|
5365
|
+
// Special message to draw attention to this critical error.
|
5330
5366
|
UI.alert('SOLVER LICENSE EXPIRED: Please check!');
|
5331
5367
|
}
|
5332
|
-
// Call back to the console (if callback hook has been set)
|
5368
|
+
// Call back to the console (if callback hook has been set).
|
5333
5369
|
if(this.callback) this.callback(this);
|
5334
5370
|
return;
|
5335
5371
|
}
|
5336
|
-
const
|
5372
|
+
const
|
5373
|
+
bwr = this.blockWithRound,
|
5374
|
+
fromt = (this.block_count - 1) * MODEL.block_length + 1,
|
5375
|
+
abl = this.actualBlockLength;
|
5337
5376
|
MONITOR.updateBlockNumber(bwr);
|
5338
|
-
// NOTE:
|
5339
|
-
this.logMessage(this.block_count, '\nSetting up block #'
|
5377
|
+
// NOTE: Add blank line to message to visually separate rounds.
|
5378
|
+
this.logMessage(this.block_count, ['\nSetting up block #', bwr,
|
5379
|
+
' (t=', fromt, '-', fromt + abl - 1, '; ',
|
5380
|
+
pluralS(abl, 'time step'), ')'].join(''));
|
5340
5381
|
UI.logHeapSize('Before set-up of block #' + bwr);
|
5341
5382
|
this.setupBlock();
|
5342
5383
|
}
|
@@ -5361,13 +5402,13 @@ Solver status = ${json.status}`);
|
|
5361
5402
|
} else {
|
5362
5403
|
this.show_progress = false;
|
5363
5404
|
}
|
5364
|
-
// Generate lines of code in format that should be accepted by solver
|
5405
|
+
// Generate lines of code in format that should be accepted by solver.
|
5365
5406
|
if(this.solver_name === 'gurobi') {
|
5366
5407
|
this.writeMPSFormat();
|
5367
5408
|
} else if(this.solver_name === 'scip' || this.solver_name === 'cplex') {
|
5368
|
-
// NOTE:
|
5369
|
-
// the LP_solve format that was used by the first versions of Linny-R
|
5370
|
-
// TRUE indicates "CPLEX format"
|
5409
|
+
// NOTE: The CPLEX LP format that is also used by SCIP differs from
|
5410
|
+
// the LP_solve format that was used by the first versions of Linny-R.
|
5411
|
+
// TRUE indicates "CPLEX format".
|
5371
5412
|
this.writeLpFormat(true);
|
5372
5413
|
} else if(this.solver_name === 'lp_solve') {
|
5373
5414
|
this.writeLpFormat(false);
|
@@ -5377,8 +5418,8 @@ Solver status = ${json.status}`);
|
|
5377
5418
|
}
|
5378
5419
|
|
5379
5420
|
submitFile() {
|
5380
|
-
// Prepares to POST the model file (LP or MPS) to the Linny-R server
|
5381
|
-
// NOTE:
|
5421
|
+
// Prepares to POST the model file (LP or MPS) to the Linny-R server.
|
5422
|
+
// NOTE: The tableau is no longer needed, so free up its memory.
|
5382
5423
|
this.resetTableau();
|
5383
5424
|
if(this.numeric_issue) {
|
5384
5425
|
const msg = 'Invalid ' + this.numeric_issue;
|
@@ -5386,16 +5427,16 @@ Solver status = ${json.status}`);
|
|
5386
5427
|
UI.alert(msg);
|
5387
5428
|
this.stopSolving();
|
5388
5429
|
} else {
|
5389
|
-
// Log the time it took to create the code lines
|
5430
|
+
// Log the time it took to create the code lines.
|
5390
5431
|
this.logMessage(this.block_count,
|
5391
5432
|
'Model file creation (' + UI.sizeInBytes(this.lines.length) +
|
5392
5433
|
') took ' + this.elapsedTime + ' seconds.');
|
5393
|
-
// NOTE:
|
5394
|
-
// to pass it on as parameter
|
5434
|
+
// NOTE: Monitor will use (and then clear) VM.lines, so no need
|
5435
|
+
// to pass it on as parameter.
|
5395
5436
|
MONITOR.submitBlockToSolver();
|
5396
5437
|
// Now the round number can be increased...
|
5397
5438
|
this.current_round++;
|
5398
|
-
// ... and also the blocknumber if all rounds have been played
|
5439
|
+
// ... and also the blocknumber if all rounds have been played.
|
5399
5440
|
if(this.current_round >= this.round_sequence.length) {
|
5400
5441
|
this.current_round = 0;
|
5401
5442
|
this.block_count++;
|
@@ -5404,7 +5445,7 @@ Solver status = ${json.status}`);
|
|
5404
5445
|
}
|
5405
5446
|
|
5406
5447
|
solve() {
|
5407
|
-
//
|
5448
|
+
// Compile model to VM code; then start sequence of solving blocks.
|
5408
5449
|
UI.logHeapSize('Before model reset');
|
5409
5450
|
this.reset();
|
5410
5451
|
UI.logHeapSize('After model reset');
|