linny-r 1.8.2 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -317,6 +317,7 @@ class Expression {
317
317
  if((typeof number !== 'number' ||
318
318
  (this.isStatic && !this.isWildcardExpression)) &&
319
319
  !this.isMethod) return this.vector;
320
+ //console.log('HERE choosing wcnr', number, this);
320
321
  // Method expressions are not "numbered" but differentiate by the
321
322
  // entity to which they are applied. Their "vector number" is then
322
323
  // inferred by looking up this entity in a method object list.
@@ -331,7 +332,9 @@ class Expression {
331
332
  }
332
333
  // Use the vector for the wildcard number (create it if necessary).
333
334
  if(!this.wildcard_vectors.hasOwnProperty(number)) {
335
+ //console.log('HERE adding wc vector', number, this);
334
336
  this.wildcard_vectors[number] = [];
337
+ //console.log('HERE adding wc vector', number, this.wildcard_vectors);
335
338
  if(this.isStatic) {
336
339
  this.wildcard_vectors[number][0] = VM.NOT_COMPUTED;
337
340
  } else {
@@ -427,20 +430,25 @@ class Expression {
427
430
  }
428
431
 
429
432
  result(t, number=false) {
430
- // Computes (only if needed) and then returns result for time step t
433
+ // Compute (only if needed) and then returns result for time step t.
431
434
  // The `number` is passed only by the VMI_push_dataset_modifier
432
- // instruction so as to force recomputation of the expression
433
- // NOTE: for t < 1 return the value for t = 1, since expressions have no
434
- // "initial value" (these follow from the variables used in the expression)
435
- // Select the vector to use
435
+ // instruction so as to force recomputation of the expression.
436
+ // Select the vector to use.
436
437
  const v = this.chooseVector(number);
437
438
  if(!Array.isArray(v)) {
438
439
  console.log('ANOMALY: No vector for result(t)');
439
440
  return VM.UNDEFINED;
440
441
  }
442
+ // NOTE: For t < 1 return the value for t = 1, since expressions have
443
+ // no "initial value" (these follow from the variables used in the
444
+ // expression).
441
445
  if(t < 0 || this.isStatic) t = 0;
442
446
  if(t >= v.length) return VM.UNDEFINED;
443
- if(v[t] === VM.NOT_COMPUTED || v[t] === VM.COMPUTING) {
447
+ // NOTE: When VM is setting up a tableau, values computed for the
448
+ // look-ahead period must be recomputed.
449
+ if(v[t] === VM.NOT_COMPUTED || v[t] === VM.COMPUTING ||
450
+ (!this.isStatic && VM.inLookAhead(t))) {
451
+ v[t] = VM.NOT_COMPUTED;
444
452
  this.compute(t, number);
445
453
  }
446
454
  // NOTE: when this expression is the "active" parameter for sensitivity
@@ -2136,8 +2144,8 @@ class VirtualMachine {
2136
2144
  this.SOLVER_MINUS_INFINITY = -1e+30;
2137
2145
  // As of version 1.8.0, Linny-R imposes no +INF bounds on processes
2138
2146
  // unless diagnosing an unbounded problem. For such diagnosis, the
2139
- // (relatively) low value 9.99999999e+9 is used.
2140
- this.DIAGNOSIS_UPPER_BOUND = 9.99999999e+9;
2147
+ // (relatively) low value 9.999999999e+9 is used.
2148
+ this.DIAGNOSIS_UPPER_BOUND = 9.999999999e+9;
2141
2149
  // NOTE: Below the "near zero" limit, a number is considered zero
2142
2150
  // (this is to timely detect division-by-zero errors).
2143
2151
  this.NEAR_ZERO = 1e-10;
@@ -2149,7 +2157,8 @@ class VirtualMachine {
2149
2157
  // their target without displaying them in red or blue to signal
2150
2158
  // shortage or surplus.
2151
2159
  this.SIG_DIF_LIMIT = 0.001;
2152
- this.SIG_DIF_FROM_ZERO = 1e-6;
2160
+ // Numbers near zero are displayed as +0 or -0.
2161
+ this.SIG_DIF_FROM_ZERO = 5e-5;
2153
2162
  // ON/OFF threshold is used to differentiate between level = 0 and
2154
2163
  // still "ON" (will be displayed as +0).
2155
2164
  this.ON_OFF_THRESHOLD = 1.5e-4;
@@ -2194,6 +2203,7 @@ class VirtualMachine {
2194
2203
  this.LM_NEEDING_ON_OFF = [5, 6, 7, 8, 9, 10];
2195
2204
  this.LM_SYMBOLS = ['', '\u21C9', '\u0394', '\u03A3', '\u03BC', '\u25B2',
2196
2205
  '+', '0', '\u2934', '\u2732', '\u25BC', '\u2A39'];
2206
+ this.LM_LETTERS = ' TDSMU+0RFDP';
2197
2207
 
2198
2208
  // VM max. expression stack size.
2199
2209
  this.MAX_STACK = 200;
@@ -2472,8 +2482,19 @@ class VirtualMachine {
2472
2482
  this.t = 0;
2473
2483
  // Prepare for halt.
2474
2484
  this.halted = false;
2485
+ // Flag to indicate that VM is executing its tableau construction code.
2486
+ // This affects how chunk time (ct) is computed, and whether expression
2487
+ // results must be recomputed (see inLookAhead below).
2488
+ this.executing_tableau_code = false;
2475
2489
  UI.readyToSolve();
2476
2490
  }
2491
+
2492
+ inLookAhead(t) {
2493
+ // Return TRUE if VM is executing its tableau construction code AND
2494
+ // time step `t` falls in the look-ahead period of the previous block.
2495
+ return this.executing_tableau_code &&
2496
+ t - (this.block_count - 1) * MODEL.block_length <= MODEL.look_ahead;
2497
+ }
2477
2498
 
2478
2499
  errorMessage(n) {
2479
2500
  // VM errors are very big NEGATIVE numbers, so start comparing `n`
@@ -2537,7 +2558,7 @@ class VirtualMachine {
2537
2558
  if(sv[0]) return sv[1];
2538
2559
  const a = Math.abs(n);
2539
2560
  // Signal small differences from true 0 by leading + or - sign.
2540
- if(n !== 0 && a < 0.0005) return n > 0 ? '+0' : '-0';
2561
+ if(n !== 0 && a <= this.ON_OFF_THRESHOLD) return n > 0 ? '+0' : '-0';
2541
2562
  if(a >= 999999.5) return n.toPrecision(2);
2542
2563
  if(Math.abs(a-Math.round(a)) < 0.05) return Math.round(n);
2543
2564
  if(a < 1) return Math.round(n*100) / 100;
@@ -2556,7 +2577,7 @@ class VirtualMachine {
2556
2577
  if(sv[0]) return sv[1];
2557
2578
  const a = Math.abs(n);
2558
2579
  // Signal small differences from true 0 by a leading + or - sign.
2559
- if(n !== 0 && a < 0.0005) return n > 0 ? '+0' : '-0';
2580
+ if(n !== 0 && a <= this.ON_OFF_THRESHOLD) return n > 0 ? '+0' : '-0';
2560
2581
  if(a >= 9999995) return n.toPrecision(4);
2561
2582
  if(Math.abs(a-Math.round(a)) < 0.0005) return Math.round(n);
2562
2583
  if(a < 1) return Math.round(n*10000) / 10000;
@@ -2866,7 +2887,7 @@ class VirtualMachine {
2866
2887
  const
2867
2888
  bm = r.block_messages[i],
2868
2889
  err = (bm.messages.indexOf('Solver status = 0') < 0 ||
2869
- bm.messages.indexOf('Warning') >= 0);
2890
+ bm.messages.indexOf(this.WARNING) >= 0);
2870
2891
  this.solver_times.push(bm.solver_time);
2871
2892
  this.messages.push(bm.messages);
2872
2893
  this.variables.push(this.no_variables);
@@ -2922,19 +2943,20 @@ class VirtualMachine {
2922
2943
  for(let i = 2; i <= n; i++) {
2923
2944
  this.variables.push(['W' + i, obj]);
2924
2945
  }
2925
- // NOTE: Some solvers do not support SOS. To ensure that only 2
2926
- // adjacent w[i]-variables are non-zero (they range from 0 to 1),
2927
- // as many binary variables b[i] must be defined, and additional
2928
- // constraints must be added (see function VMI_add_boundline).
2929
- // NOTE: These additional variables and constraints are not needed
2930
- // when a bound line defines a convex feasible area.
2931
- const sos_with_bin = this.noSupportForSOS && !obj.needsNoSOS;
2932
- this.sos_var_indices.push([index, n, sos_with_bin]);
2933
- if(sos_with_bin) {
2934
- for(let i = 1; i <= n; i++) {
2935
- const bi = this.variables.push(['b' + i, obj]);
2936
- this.bin_var_indices[bi] = true;
2937
- }
2946
+ // NOTE: SOS constraints are not needed when a bound line defines
2947
+ // a convex feasible area.
2948
+ if(!obj.needsNoSOS) {
2949
+ this.sos_var_indices.push([index, n]);
2950
+ // NOTE: Some solvers do not support SOS. To ensure that only 2
2951
+ // adjacent w[i]-variables are non-zero (they range from 0 to 1),
2952
+ // as many binary variables b[i] must be defined, and additional
2953
+ // constraints must be added (see VMI_add_bound_line_constraint).
2954
+ if(this.noSupportForSOS) {
2955
+ for(let i = 1; i <= n; i++) {
2956
+ const bi = this.variables.push(['b' + i, obj]);
2957
+ this.bin_var_indices[bi] = true;
2958
+ }
2959
+ }
2938
2960
  }
2939
2961
  }
2940
2962
  return index;
@@ -4044,7 +4066,7 @@ class VirtualMachine {
4044
4066
  these variables can take on higher values. The modeler must ensure
4045
4067
  that there is a cost associated with the actual flow, not a revenue.
4046
4068
  */
4047
- // NOTE: as of 20 June 2021, binary attributes of products are also computed
4069
+ // NOTE: As of 20 June 2021, binary attributes of products are also computed.
4048
4070
  const pp_nodes = [];
4049
4071
  for(i = 0; i < process_keys.length; i++) {
4050
4072
  k = process_keys[i];
@@ -4093,8 +4115,8 @@ class VirtualMachine {
4093
4115
  [VMI_add_const_to_coefficient, [p.level_var_index, 1]]
4094
4116
  );
4095
4117
  if(ubx.isStatic) {
4096
- // If UB is very high (typically: undefined, so +INF), try to infer
4097
- // a lower value for UB to use for the ON/OFF binary
4118
+ // If UB is very high (typically: undefined, so +INF), try to
4119
+ // infer a lower value for UB to use for the ON/OFF binary.
4098
4120
  let ub = ubx.result(0),
4099
4121
  hub = ub;
4100
4122
  if(ub > VM.MEGA_UPPER_BOUND) {
@@ -4642,7 +4664,7 @@ class VirtualMachine {
4642
4664
  if(v[1] instanceof BoundLine) {
4643
4665
  v[1].constraint.slack_info[b] = v[0];
4644
4666
  }
4645
- if(b <= this.nr_of_time_steps && absl > VM.SIG_DIF_FROM_ZERO) {
4667
+ if(b <= this.nr_of_time_steps && absl > VM.ON_OFF_THRESHOLD) {
4646
4668
  this.logMessage(block, `${this.WARNING}(t=${b}${round}) ` +
4647
4669
  `${v[1].displayName} ${v[0]} slack = ${this.sig4Dig(slack)}`);
4648
4670
  if(v[1] instanceof Product) {
@@ -4651,10 +4673,10 @@ class VirtualMachine {
4651
4673
  ppc[ci].usesSlack(b, v[1], v[0]);
4652
4674
  }
4653
4675
  }
4654
- } else if(CONFIGURATION.slight_slack_notices) {
4655
- this.logMessage(block, '-- Notice: (t=' + b + round + ') ' +
4676
+ } else if(MODEL.show_notices) {
4677
+ this.logMessage(block, '---- Notice: (t=' + b + round + ') ' +
4656
4678
  v[1].displayName + ' ' + v[0] + ' slack = ' +
4657
- slack.toPrecision(2));
4679
+ slack.toPrecision(1));
4658
4680
  }
4659
4681
  }
4660
4682
  }
@@ -5217,7 +5239,7 @@ class VirtualMachine {
5217
5239
  }
5218
5240
 
5219
5241
  addTableauSegment(start, abl) {
5220
- if(VM.halted) {
5242
+ if(this.halted) {
5221
5243
  this.hideSetUpOrWriteProgress();
5222
5244
  this.stopSolving();
5223
5245
  return;
@@ -5227,6 +5249,7 @@ class VirtualMachine {
5227
5249
  var l;
5228
5250
  const next_start = (start + this.tsl * 1.2 < abl ? start + this.tsl : abl);
5229
5251
  for(let i = start; i < next_start; i++) {
5252
+ this.executing_tableau_code = true;
5230
5253
  this.logTrace('EXECUTE for t=' + this.t);
5231
5254
  l = this.code.length;
5232
5255
  for(let j = 0; j < l; j++) {
@@ -5238,6 +5261,7 @@ class VirtualMachine {
5238
5261
  this.logTrace([(' ' + j).slice(-5), ': coeff = ',
5239
5262
  JSON.stringify(this.coefficients), '; rhs = ', this.rhs].join(''));
5240
5263
  }
5264
+ this.executing_tableau_code = false;
5241
5265
  this.logTrace('STOP executing block code');
5242
5266
  // Add constraints for paced process variables.
5243
5267
  // NOTE: This is effectuated by *executing* VM instructions.
@@ -5410,7 +5434,7 @@ class VirtualMachine {
5410
5434
  this.numeric_issue = '';
5411
5435
  // First add the objective (always MAXimize).
5412
5436
  if(cplex) {
5413
- this.lines = 'Maximize\n';
5437
+ this.lines = `\\${this.solver_id}\nMaximize\n`;
5414
5438
  } else {
5415
5439
  this.lines = '/* Objective function */\nmax:\n';
5416
5440
  }
@@ -5461,6 +5485,12 @@ class VirtualMachine {
5461
5485
  }
5462
5486
  }
5463
5487
  c = this.right_hand_side[r];
5488
+ // NOTE: When previous block was infeasible or unbounded (no solution),
5489
+ // expressions for RHS may not evaluate as a number.
5490
+ if(Number.isNaN(c)) {
5491
+ this.setNumericIssue(c, r, 'constraint RHS');
5492
+ c = 0;
5493
+ }
5464
5494
  this.lines += line + ' ' +
5465
5495
  this.constraint_symbols[this.constraint_types[r]] + ' ' + c + EOL;
5466
5496
  line = '';
@@ -5507,8 +5537,14 @@ class VirtualMachine {
5507
5537
  }
5508
5538
  } else {
5509
5539
  // Bounds can be specified on a single line: lb <= X001 <= ub.
5510
- if(lb !== null && lb !== 0) line = lb + ' <= ' + line;
5511
- if(ub !== null) line += ' <= ' + ub;
5540
+ // NOTE: LP_solve has Infinity value 1e+25. Use this literal
5541
+ // because VM.PLUS_INFINITY may be set to *diagnostic* value.
5542
+ if(lb !== null && lb !== 0 && lb > -1e+25) {
5543
+ line = lb + ' <= ' + line;
5544
+ }
5545
+ if(ub !== null && ub < 1e+25) line += ' <= ' + ub;
5546
+ // NOTE: Do not add line if both bounds are infinite.
5547
+ if(line.indexOf('<=') < 0) line = '';
5512
5548
  }
5513
5549
  }
5514
5550
  if(line) this.lines += line + EOL;
@@ -5568,19 +5604,17 @@ class VirtualMachine {
5568
5604
  // NOTE: Add SOS section only if the solver supports SOS.
5569
5605
  if(this.sos_var_indices.length > 0 && !this.noSupportForSOS) {
5570
5606
  this.lines += 'SOS\n';
5571
- let sos = 0;
5572
5607
  const v_set = [];
5573
5608
  for(let j = 0; j < abl; j++) {
5574
5609
  for(let i = 0; i < this.sos_var_indices.length; i++) {
5610
+ const svi = this.sos_var_indices[i];
5575
5611
  v_set.length = 0;
5576
- let vi = this.sos_var_indices[i][0] + j * this.cols;
5577
- const n = this.sos_var_indices[i][1];
5578
- for(let j = 1; j <= n; j++) {
5612
+ let vi = svi[0] + j * this.cols;
5613
+ for(let j = 1; j <= svi[1]; j++) {
5579
5614
  v_set.push(`${vbl(vi)}:${j}`);
5580
5615
  vi++;
5581
5616
  }
5582
- this.lines += ` s${sos}: S2:: ${v_set.join(' ')}\n`;
5583
- sos++;
5617
+ this.lines += ` s${i}: S2:: ${v_set.join(' ')}\n`;
5584
5618
  }
5585
5619
  }
5586
5620
  }
@@ -5605,18 +5639,16 @@ class VirtualMachine {
5605
5639
  // LP_solve supports SOS, so add the SOS section if needed.
5606
5640
  if(this.sos_var_indices.length > 0) {
5607
5641
  this.lines += 'sos\n';
5608
- let sos = 1;
5609
5642
  for(let j = 0; j < abl; j++) {
5610
5643
  for(let i = 0; i < this.sos_var_indices.length; i++) {
5644
+ const svi = this.sos_var_indices[i];
5611
5645
  v_set.length = 0;
5612
- let vi = this.sos_var_indices[i][0] + j * this.cols;
5613
- const n = this.sos_var_indices[i][1];
5614
- for(let j = 1; j <= n; j++) {
5646
+ let vi = svi[0] + j * this.cols;
5647
+ for(let j = 1; j <= svi[1]; j++) {
5615
5648
  v_set.push(vbl(vi));
5616
5649
  vi++;
5617
5650
  }
5618
5651
  this.lines += `SOS${sos}: ${v_set.join(',')} <= 2;\n`;
5619
- sos++;
5620
5652
  }
5621
5653
  }
5622
5654
  }
@@ -5825,19 +5857,18 @@ class VirtualMachine {
5825
5857
  if(this.sos_var_indices.length > 0) {
5826
5858
  this.lines += 'SOS\n';
5827
5859
  const abl = this.actualBlockLength(this.block_count);
5828
- let sos = 1;
5829
5860
  for(let j = 0; j < abl; j++) {
5830
5861
  for(let i = 0; i < this.sos_var_indices.length; i++) {
5831
- this.lines += ' S2 sos' + sos + '\n';
5832
- let vi = this.sos_var_indices[i][0] + j * this.cols;
5833
- const n = this.sos_var_indices[i][1];
5834
- for(let j = 1; j <= n; j++) {
5835
- const s = ' X' + vi.toString().padStart(this.decimals, '0') +
5862
+ const svi = this.sos_var_indices[i];
5863
+ this.lines += ` S2 sos${i + 1}\n`;
5864
+ let vi = svi[0] + j * this.cols;
5865
+ for(let j = 1; j <= svi[1]; j++) {
5866
+ const s = ' X' +
5867
+ vi.toString().padStart(this.decimals, '0') +
5836
5868
  ' ';
5837
5869
  this.lines += s.substring(0, 15) + j + '\n';
5838
5870
  vi++;
5839
5871
  }
5840
- sos++;
5841
5872
  }
5842
5873
  }
5843
5874
  }
@@ -6145,7 +6176,7 @@ Solver status = ${json.status}`);
6145
6176
  }
6146
6177
  // Diagnosis (by adding slack variables and finite bounds on processes)
6147
6178
  // is activated when Alt-clicking the "run" button, or by clicking the
6148
- // "clicke HERE to diagnose" link on the infoline.
6179
+ // "clicke *here* to diagnose" link on the infoline.
6149
6180
  this.diagnose = diagnose || MODEL.always_diagnose;
6150
6181
  if(this.diagnose) {
6151
6182
  this.PLUS_INFINITY = this.DIAGNOSIS_UPPER_BOUND;
@@ -6255,11 +6286,26 @@ function VMI_push_block_time(x) {
6255
6286
  const
6256
6287
  lt = x.step[x.step.length - 1] - 1,
6257
6288
  bnr = Math.floor(lt / MODEL.block_length),
6258
- t = lt - bnr * MODEL.block_length + 1;
6289
+ t = lt - bnr * MODEL.block_length + 1;
6259
6290
  if(DEBUGGING) console.log('push block time bt = ' + t);
6260
6291
  x.push(t);
6261
6292
  }
6262
6293
 
6294
+ function VMI_push_chunk_time(x) {
6295
+ // Push the time step for which the VM is preparing the tableau.
6296
+ // NOTE: Chunk time is meaningful only while the VM is solving a block.
6297
+ // If not, the block time is pushed.
6298
+ if(VM.executing_tableau_code) {
6299
+ const
6300
+ ct = VM.t - (VM.block_count - 1) * MODEL.block_length;
6301
+ if(DEBUGGING) console.log('push chunk time ct = ' + ct);
6302
+ x.push(ct);
6303
+ } else {
6304
+ if(DEBUGGING) console.log('push chunk time: NOT constructing tableau');
6305
+ VMI_push_block_time(x);
6306
+ }
6307
+ }
6308
+
6263
6309
  function VMI_push_block_number(x) {
6264
6310
  // Push the number of the block currently being optimized.
6265
6311
  // NOTE: Block numbering starts at 1.
@@ -8329,10 +8375,11 @@ function VMI_set_const_rhs(c) {
8329
8375
  }
8330
8376
 
8331
8377
  function VMI_set_var_rhs(x) {
8378
+ VM.rhs = x.result(VM.t);
8332
8379
  if(DEBUGGING) {
8333
- console.log('set_var_rhs: ' + x.variableName + ' (t = ' + VM.t + ')');
8380
+ console.log(`set_var_rhs: ${x.variableName} (t = ${VM.t}) = ` +
8381
+ VM.sig4Dig(VM.rhs));
8334
8382
  }
8335
- VM.rhs = x.result(VM.t);
8336
8383
  }
8337
8384
 
8338
8385
  function VMI_add_const_to_rhs(c) {
@@ -8669,11 +8716,12 @@ const
8669
8716
  SEPARATOR_CHARS = PARENTHESES + OPERATOR_CHARS + "[ '",
8670
8717
  COMPOUND_OPERATORS = ['!=', '<>', '>=', '<='],
8671
8718
  CONSTANT_SYMBOLS = [
8672
- 't', 'rt', 'bt', 'b', 'N', 'n', 'l', 'r', 'lr', 'nr', 'x', 'nx',
8719
+ 't', 'rt', 'bt', 'ct', 'b', 'N', 'n', 'l', 'r', 'lr', 'nr', 'x', 'nx',
8673
8720
  'random', 'dt', 'true', 'false', 'pi', 'infinity', '#',
8674
8721
  'i', 'j', 'k', 'yr', 'wk', 'd', 'h', 'm', 's'],
8675
8722
  CONSTANT_CODES = [
8676
8723
  VMI_push_time_step, VMI_push_relative_time, VMI_push_block_time,
8724
+ VMI_push_chunk_time,
8677
8725
  VMI_push_block_number, VMI_push_run_length, VMI_push_block_length,
8678
8726
  VMI_push_look_ahead, VMI_push_round, VMI_push_last_round,
8679
8727
  VMI_push_number_of_rounds, VMI_push_run_number, VMI_push_number_of_runs,
@@ -8682,7 +8730,7 @@ const
8682
8730
  VMI_push_i, VMI_push_j, VMI_push_k,
8683
8731
  VMI_push_year, VMI_push_week, VMI_push_day, VMI_push_hour,
8684
8732
  VMI_push_minute, VMI_push_second],
8685
- DYNAMIC_SYMBOLS = ['t', 'rt', 'bt', 'b', 'r', 'random', 'i', 'j', 'k'],
8733
+ DYNAMIC_SYMBOLS = ['t', 'rt', 'bt', 'ct', 'b', 'r', 'random', 'i', 'j', 'k'],
8686
8734
  MONADIC_OPERATORS = [
8687
8735
  '~', 'not', 'abs', 'sin', 'cos', 'atan', 'ln',
8688
8736
  'exp', 'sqrt', 'round', 'int', 'fract', 'min', 'max',