linny-r 1.5.6 → 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.
@@ -2108,91 +2108,99 @@ class VirtualMachine {
2108
2108
  }
2109
2109
 
2110
2110
  reset() {
2111
- // Resets the virtual machine so that it can execute the model again
2112
- // First: reset the expression attributes of all model entities
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 occurs
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 the graph
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 period
2137
- // (not MODEL.runLength!) divided by the block length (without look-ahead)
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: simulation period of 55 time steps, and optimization period of
2142
- // 10 time steps => 6 blocks of 10, and chunk length = block length = 10
2143
- // if no look-ahead.
2144
- // But if look-ahead = 8, then STILL 6 blocks, but now the *chunks* have
2145
- // 18 time steps, with the 5th *chunk* covering t=41 - t=58. This is already
2146
- // beyond the end of the simulation period (t=55), but with insufficient
2147
- // look-ahead (3), hence the 6th block covering t=51 - t=68, of which only
2148
- // the first five time step results will be used.
2149
-
2150
- // Initialize error counters (error count will be reset to 0 for each block)
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: special tracking of potential solver license errors
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 can
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 [] if none
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 block sequence unless it has been set
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 start)
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
- // Returns [FALSE, n] if number n is a NOT a special value,
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
- // Returns number `n` formatted so as to show 2-3 significant digits
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
- // Returns number `n` formatted so as to show 4-5 significant digits
2265
- // NOTE: as `n` should be a number, a warning sign will typically
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
- // Returns result only when test is *not* an exceptional value
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
- // Converts array `data` with time step duration `ddt` to a vector with
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
- // Initialize the vector
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? then return the vector with its `fill` values
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: times are on "real" time scale, counting from t=1 onwards
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 indices
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, whereas
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 half a
2332
- // vector time step before the first time step on the VECTOR time scale
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: always modulo data length to anticipate periodic; for the
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: for vector[0], use one data time step earlier
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
- // Returns the last round number in the round sequence
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
- // NOTE: do not show round number as superscript of the step number if the
2487
- // only round in the sequence is round a
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 expression
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
- // Logs the trace string to the browser console
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
- // Adds a solver message to the list
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
- // Sets the messages and solver times for experiment or SA run `n`
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
- // Returns seconds since previous "elapsed time" check
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
- // Adds a variable that will need a column in the Simplex tableau
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: binary variables can be "paced" just like level variables
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 are
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: every node is represented by at least one variable: its "level"
2659
- // This is done even if a product has no storage capacity, because it
2660
- // simplifies the formulation of product-related (data) constraints
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 when
2686
- // the "real" product levels are overconstrained
2687
- // (2) The modeler may selectively disable slack to force the solver to
2688
- // respect certain constraints; this may result in infeasible MILP
2689
- // problems; the solver will report this
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
- // Returns value of a tableau variable calculated for a prior block
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 time steps
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 time steps
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
- // Returns a string with each variable code and full name on a separate line
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
- // NOTE: chunk offset for last block may be lower than standard
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 indices are 0-based
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
- // Sets LB and UB constraints for product `p`
2759
- // NOTE: this method affects the VM coefficient vector, so save it if needed!
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: by default, LB = 0 and UB = +INF
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 largest
4035
- // cash flow coefficient (in absolute value) within the current block
4036
- // so that cash flows cannot easily "overrule" the slack penalties in the
4037
- // objective function
4038
- // NOTE: no scaling needed if model features no cash flows
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: subtract 1 as variables array is zero-based
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: do not scale the coefficient of the cash variable
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
- // Returns floating point number `n`, or +INF or -INF if the absolute
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
- // Copies the values of decision variables calculated by the solver
4076
- // `x` holds the solver result, `err` is TRUE if the model was not computed
4077
- // First deal with quirk of JSON, which turns [one value] into value
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
- // If no results computed, preserve those already computed as "look-ahead"
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
- // NOTE: length of solution vector divided by number of columns should
4089
- // be integer, and typically equal to the actual block length, except for
4090
- // the last block when look-ahead > 0 (as Linny-R never "looks" beyond the
4091
- // simulation end time)
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
- if(xbl < abl) {
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
- // Calculates the values of all model variables that depend on the values
4276
- // of the decision variables output by the solver
4277
- // NOTE: only for the block that was just solved, but the values are stored
4278
- // in the vectors of nodes and links that span the entire optimization period,
4279
- // hence start by calculating the offset `bb` being the first time step of
4280
- // this block (blocks are numbered 1, 2, ...)
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: calculate the actual flows on links
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
- // NOTE: display 1 more segment progress so that the bar reaches 100%
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
- // Prints VM instructions to console
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: tableau segment length is the number of time steps between
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: numeric issues are detected on ABSOLUTE values, while error codes
4720
- // are extreme negative values => negate when greater than the special
4721
- // values VM.UNDEFINED, VM.NOT_COMPUTED and VM.COMPUTING
4722
- if(-n <= VM.ERROR) n = -n;
4723
- let err = this.errorMessage(n);
4724
- if(err === n) {
4725
- err = 'value = ' + n;
4726
- } else if(this.error_codes.indexOf(n) < 0) {
4727
- err += '? value = ' + n;
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
- // Returns the actual block length plus the number of chunk variables
4745
- return this.actualBlockLength * this.cols + this.chunk_variables.length;
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 `abl` of last block is likely to be
4750
- // shorter than the standard, as it should not go beyond the end time
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
- // NOTE: for each column a separate list
4987
- // NOTE: columns are numbered from 1 to N, hence dummy list for c=0
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
- // Returns set of status codes that indicate that solver did not return
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
- // Compares license expiry date (if set) with current time, and notifies
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: expiry date has YYYY-MM-DD format
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', day: 'numeric'},
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: block number is passed as string => convert to integer
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: server script adds a license expiry notice for the Gurobi solver
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 levels
5274
- // and stock level), but do NOT overwrite "look-ahead" levels if this block
5275
- // was not solved (indicated by the 4th parameter that tests the status)
5276
- // NOTE: appropriate status codes are solver-dependent
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
- this.solver_secs[bnr - 1] = this.round_secs.reduce((a, b) => a + b, 0);
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; if not, redraw the graph and exit
5300
- // NOTE: set IF-condition to TRUE for testing WITHOUT computation
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 bwr = this.blockWithRound;
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: add blank line to message to visually separate rounds
5339
- this.logMessage(this.block_count, '\nSetting up block #' + bwr);
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: the CPLEX LP format that is also used by SCIP differs from
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: the tableau is no longer needed, so free up its memory
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: monitor will use (and then clear) VM.lines, so no need
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
- // Compiles model to VM code and starts sequence of solving blocks
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');