linny-r 1.9.0 → 1.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linny-r",
3
- "version": "1.9.0",
3
+ "version": "1.9.2",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
package/static/index.html CHANGED
@@ -750,6 +750,13 @@ NOTE: Unit symbols are case-sensitive, so BTU ≠ Btu">
750
750
  placeholder="1e-4" type="text" autocomplete="off">
751
751
  </td>
752
752
  </tr>
753
+ <tr title="When checked, small slack uses are reported in monitor">
754
+ <td style="padding: 0px; width: 20px">
755
+ <div id="solver-show-notices" class="box clear"></div>
756
+ </td>
757
+ <td style="padding-bottom:4px">Report small slack uses</td>
758
+ </td>
759
+ </tr>
753
760
  </table>
754
761
  </div>
755
762
  </div>
@@ -3689,6 +3689,7 @@ console.log('HERE name conflicts', name_conflicts, mapping);
3689
3689
  md.element('preference').innerHTML = html.join('');
3690
3690
  md.element('int-feasibility').value = MODEL.integer_tolerance;
3691
3691
  md.element('mip-gap').value = MODEL.MIP_gap;
3692
+ this.setBox('solver-show-notices', MODEL.show_notices);
3692
3693
  md.show();
3693
3694
  }
3694
3695
 
@@ -3717,6 +3718,7 @@ console.log('HERE name conflicts', name_conflicts, mapping);
3717
3718
  }
3718
3719
  MODEL.integer_tolerance = Math.max(1e-9, Math.min(0.1, itol));
3719
3720
  MODEL.MIP_gap = Math.max(0, Math.min(0.5, mgap));
3721
+ MODEL.show_notices = this.boxChecked('solver-show-notices');
3720
3722
  // Close the dialog.
3721
3723
  md.hide();
3722
3724
  }
@@ -66,6 +66,7 @@ Attributes, however, are case sensitive!">[Actor X|CF]</code> for cash flow.
66
66
  <code title="Relative time step (t &minus; t&#8320; + 1)">rt</code>,
67
67
  <code title="Number of current block">b</code>,
68
68
  <code title="Time step within current block">bt</code>,
69
+ <code title="Time step within current chunk">ct</code>,
69
70
  <code title="Duration of 1 time step (in hours)">dt</code>,
70
71
  <code title="Run length (# time steps)">N</code>,
71
72
  <code title="Block length (# time steps)">n</code>,
@@ -107,6 +107,7 @@ class LinnyRModel {
107
107
  this.integer_tolerance = 5e-7; // integer feasibility tolerance
108
108
  this.MIP_gap = 1e-4; // relative MIP gap
109
109
  this.always_diagnose = true;
110
+ this.show_notices = true;
110
111
 
111
112
  // Sensitivity-related properties
112
113
  this.base_case_selectors = '';
@@ -2670,6 +2671,7 @@ class LinnyRModel {
2670
2671
  this.report_results = nodeParameterValue(node, 'report-results') === '1';
2671
2672
  this.show_block_arrows = nodeParameterValue(node, 'block-arrows') === '1';
2672
2673
  this.always_diagnose = nodeParameterValue(node, 'diagnose') === '1';
2674
+ this.show_notices = nodeParameterValue(node, 'show-notices') === '1';
2673
2675
  this.name = xmlDecoded(nodeContentByTag(node, 'name'));
2674
2676
  this.author = xmlDecoded(nodeContentByTag(node, 'author'));
2675
2677
  this.comments = xmlDecoded(nodeContentByTag(node, 'notes'));
@@ -3023,6 +3025,7 @@ class LinnyRModel {
3023
3025
  if(this.report_results) p += ' report-results="1"';
3024
3026
  if(this.show_block_arrows) p += ' block-arrows="1"';
3025
3027
  if(this.always_diagnose) p += ' diagnose="1"';
3028
+ if(this.show_notices) p += ' show-notices="1"';
3026
3029
  let xml = this.xml_header + ['<model', p, '><name>', xmlEncoded(this.name),
3027
3030
  '</name><author>', xmlEncoded(this.author),
3028
3031
  '</author><notes>', xmlEncoded(this.comments),
@@ -430,20 +430,25 @@ class Expression {
430
430
  }
431
431
 
432
432
  result(t, number=false) {
433
- // 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.
434
434
  // The `number` is passed only by the VMI_push_dataset_modifier
435
- // instruction so as to force recomputation of the expression
436
- // NOTE: for t < 1 return the value for t = 1, since expressions have no
437
- // "initial value" (these follow from the variables used in the expression)
438
- // Select the vector to use
435
+ // instruction so as to force recomputation of the expression.
436
+ // Select the vector to use.
439
437
  const v = this.chooseVector(number);
440
438
  if(!Array.isArray(v)) {
441
439
  console.log('ANOMALY: No vector for result(t)');
442
440
  return VM.UNDEFINED;
443
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).
444
445
  if(t < 0 || this.isStatic) t = 0;
445
446
  if(t >= v.length) return VM.UNDEFINED;
446
- 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;
447
452
  this.compute(t, number);
448
453
  }
449
454
  // NOTE: when this expression is the "active" parameter for sensitivity
@@ -2477,8 +2482,19 @@ class VirtualMachine {
2477
2482
  this.t = 0;
2478
2483
  // Prepare for halt.
2479
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;
2480
2489
  UI.readyToSolve();
2481
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
+ }
2482
2498
 
2483
2499
  errorMessage(n) {
2484
2500
  // VM errors are very big NEGATIVE numbers, so start comparing `n`
@@ -2551,7 +2567,7 @@ class VirtualMachine {
2551
2567
  return Math.round(n);
2552
2568
  }
2553
2569
 
2554
- sig4Dig(n) {
2570
+ sig4Dig(n, tiny=false) {
2555
2571
  // Return number `n` formatted so as to show 4-5 significant digits.
2556
2572
  // NOTE: As `n` should be a number, a warning sign will typically
2557
2573
  // indicate a bug in the software.
@@ -2560,8 +2576,13 @@ class VirtualMachine {
2560
2576
  // If `n` has a special value, return its representation.
2561
2577
  if(sv[0]) return sv[1];
2562
2578
  const a = Math.abs(n);
2579
+ if(a === 0) return 0;
2563
2580
  // Signal small differences from true 0 by a leading + or - sign.
2564
- if(n !== 0 && a <= this.ON_OFF_THRESHOLD) return n > 0 ? '+0' : '-0';
2581
+ if(a <= this.ON_OFF_THRESHOLD) {
2582
+ // The `tiny` flag indicates: display small number in E-notation.
2583
+ if(tiny) return n.toPrecision(1);
2584
+ return n > 0 ? '+0' : '-0';
2585
+ }
2565
2586
  if(a >= 9999995) return n.toPrecision(4);
2566
2587
  if(Math.abs(a-Math.round(a)) < 0.0005) return Math.round(n);
2567
2588
  if(a < 1) return Math.round(n*10000) / 10000;
@@ -2871,7 +2892,7 @@ class VirtualMachine {
2871
2892
  const
2872
2893
  bm = r.block_messages[i],
2873
2894
  err = (bm.messages.indexOf('Solver status = 0') < 0 ||
2874
- bm.messages.indexOf('Warning') >= 0);
2895
+ bm.messages.indexOf(this.WARNING) >= 0);
2875
2896
  this.solver_times.push(bm.solver_time);
2876
2897
  this.messages.push(bm.messages);
2877
2898
  this.variables.push(this.no_variables);
@@ -4650,14 +4671,16 @@ class VirtualMachine {
4650
4671
  }
4651
4672
  if(b <= this.nr_of_time_steps && absl > VM.ON_OFF_THRESHOLD) {
4652
4673
  this.logMessage(block, `${this.WARNING}(t=${b}${round}) ` +
4653
- `${v[1].displayName} ${v[0]} slack = ${this.sig4Dig(slack)}`);
4674
+ `${v[1].displayName} ${v[0]} slack = ` +
4675
+ // NOTE: TRUE denotes "show tiny values with precision".
4676
+ this.sig4Dig(slack, true));
4654
4677
  if(v[1] instanceof Product) {
4655
4678
  const ppc = v[1].productPositionClusters;
4656
4679
  for(let ci = 0; ci < ppc.length; ci++) {
4657
4680
  ppc[ci].usesSlack(b, v[1], v[0]);
4658
4681
  }
4659
4682
  }
4660
- } else if(CONFIGURATION.slight_slack_notices) {
4683
+ } else if(MODEL.show_notices) {
4661
4684
  this.logMessage(block, '---- Notice: (t=' + b + round + ') ' +
4662
4685
  v[1].displayName + ' ' + v[0] + ' slack = ' +
4663
4686
  slack.toPrecision(1));
@@ -5223,7 +5246,7 @@ class VirtualMachine {
5223
5246
  }
5224
5247
 
5225
5248
  addTableauSegment(start, abl) {
5226
- if(VM.halted) {
5249
+ if(this.halted) {
5227
5250
  this.hideSetUpOrWriteProgress();
5228
5251
  this.stopSolving();
5229
5252
  return;
@@ -5233,6 +5256,7 @@ class VirtualMachine {
5233
5256
  var l;
5234
5257
  const next_start = (start + this.tsl * 1.2 < abl ? start + this.tsl : abl);
5235
5258
  for(let i = start; i < next_start; i++) {
5259
+ this.executing_tableau_code = true;
5236
5260
  this.logTrace('EXECUTE for t=' + this.t);
5237
5261
  l = this.code.length;
5238
5262
  for(let j = 0; j < l; j++) {
@@ -5244,6 +5268,7 @@ class VirtualMachine {
5244
5268
  this.logTrace([(' ' + j).slice(-5), ': coeff = ',
5245
5269
  JSON.stringify(this.coefficients), '; rhs = ', this.rhs].join(''));
5246
5270
  }
5271
+ this.executing_tableau_code = false;
5247
5272
  this.logTrace('STOP executing block code');
5248
5273
  // Add constraints for paced process variables.
5249
5274
  // NOTE: This is effectuated by *executing* VM instructions.
@@ -6268,11 +6293,26 @@ function VMI_push_block_time(x) {
6268
6293
  const
6269
6294
  lt = x.step[x.step.length - 1] - 1,
6270
6295
  bnr = Math.floor(lt / MODEL.block_length),
6271
- t = lt - bnr * MODEL.block_length + 1;
6296
+ t = lt - bnr * MODEL.block_length + 1;
6272
6297
  if(DEBUGGING) console.log('push block time bt = ' + t);
6273
6298
  x.push(t);
6274
6299
  }
6275
6300
 
6301
+ function VMI_push_chunk_time(x) {
6302
+ // Push the time step for which the VM is preparing the tableau.
6303
+ // NOTE: Chunk time is meaningful only while the VM is solving a block.
6304
+ // If not, the block time is pushed.
6305
+ if(VM.executing_tableau_code) {
6306
+ const
6307
+ ct = VM.t - (VM.block_count - 1) * MODEL.block_length;
6308
+ if(DEBUGGING) console.log('push chunk time ct = ' + ct);
6309
+ x.push(ct);
6310
+ } else {
6311
+ if(DEBUGGING) console.log('push chunk time: NOT constructing tableau');
6312
+ VMI_push_block_time(x);
6313
+ }
6314
+ }
6315
+
6276
6316
  function VMI_push_block_number(x) {
6277
6317
  // Push the number of the block currently being optimized.
6278
6318
  // NOTE: Block numbering starts at 1.
@@ -6549,28 +6589,32 @@ function relativeTimeStep(t, anchor, offset, dtm, x) {
6549
6589
  // Offset relative to current time step, scaled to time unit of run.
6550
6590
  return Math.floor((t + offset) * dtm);
6551
6591
  }
6592
+ if(anchor === 'f') {
6593
+ // Last: offset relative to index 1 in the vector.
6594
+ return 1 + offset;
6595
+ }
6596
+ if(anchor === 'l') {
6597
+ // Last: offset relative to the last index in the vector.
6598
+ return VM.nr_of_time_steps + offset;
6599
+ }
6600
+ const cb = Math.trunc((t - 1) / MODEL.block_length);
6552
6601
  if(anchor === 'c') {
6553
6602
  // Relative to start of current optimization block.
6554
- return Math.trunc(t / MODEL.block_length) * MODEL.block_length + offset;
6603
+ return cb * MODEL.block_length + 1 + offset;
6555
6604
  }
6556
6605
  if(anchor === 'p') {
6557
6606
  // Relative to start of previous optimization block.
6558
- return (Math.trunc(t / MODEL.block_length) - 1) * MODEL.block_length + offset;
6607
+ return (cb - 1) * MODEL.block_length + 1 + offset;
6559
6608
  }
6560
6609
  if(anchor === 'n') {
6561
6610
  // Relative to start of next optimization block.
6562
- return (Math.trunc(t / MODEL.block_length) + 1) * MODEL.block_length + offset;
6563
- }
6564
- if(anchor === 'l') {
6565
- // Last: offset relative to the last index in the vector.
6566
- return VM.nr_of_time_steps + offset;
6611
+ return (cb + 1) * MODEL.block_length + 1 + offset;
6567
6612
  }
6568
6613
  if(anchor === 's') {
6569
6614
  // Scaled: offset is scaled to time unit of run.
6570
6615
  return Math.floor(offset * dtm);
6571
6616
  }
6572
6617
  // Fall-through: offset relative to the initial value index (0).
6573
- // NOTE: this also applies to anchor f (First).
6574
6618
  return offset;
6575
6619
  }
6576
6620
 
@@ -7088,7 +7132,8 @@ function VMI_push_statistic(x, args) {
7088
7132
  t2 = Math.max(0, Math.min(tmax, t2));
7089
7133
  // Trace only now that time step range has been computed
7090
7134
  if(DEBUGGING) {
7091
- const trc = ['push statistic: [', stat, ': N = ', list.length, ']', ao1, ao2];
7135
+ const trc = ['push statistic: [', stat, ': N = ', list.length, ']',
7136
+ ao1, ao2, ' (t = ', t1, '-', t2, ')'];
7092
7137
  console.log(trc.join(''));
7093
7138
  }
7094
7139
  // Establish whether statistic pertains to non-zero values only
@@ -7124,7 +7169,7 @@ function VMI_push_statistic(x, args) {
7124
7169
  const
7125
7170
  n = vlist.length,
7126
7171
  // NOTE: count is the number of values used in the statistic
7127
- count = (nz ? n : list.length);
7172
+ count = (nz ? n : list.length * (t2 - t1 + 1));
7128
7173
  if(stat === 'N') {
7129
7174
  x.push(count);
7130
7175
  return;
@@ -8683,11 +8728,12 @@ const
8683
8728
  SEPARATOR_CHARS = PARENTHESES + OPERATOR_CHARS + "[ '",
8684
8729
  COMPOUND_OPERATORS = ['!=', '<>', '>=', '<='],
8685
8730
  CONSTANT_SYMBOLS = [
8686
- 't', 'rt', 'bt', 'b', 'N', 'n', 'l', 'r', 'lr', 'nr', 'x', 'nx',
8731
+ 't', 'rt', 'bt', 'ct', 'b', 'N', 'n', 'l', 'r', 'lr', 'nr', 'x', 'nx',
8687
8732
  'random', 'dt', 'true', 'false', 'pi', 'infinity', '#',
8688
8733
  'i', 'j', 'k', 'yr', 'wk', 'd', 'h', 'm', 's'],
8689
8734
  CONSTANT_CODES = [
8690
8735
  VMI_push_time_step, VMI_push_relative_time, VMI_push_block_time,
8736
+ VMI_push_chunk_time,
8691
8737
  VMI_push_block_number, VMI_push_run_length, VMI_push_block_length,
8692
8738
  VMI_push_look_ahead, VMI_push_round, VMI_push_last_round,
8693
8739
  VMI_push_number_of_rounds, VMI_push_run_number, VMI_push_number_of_runs,
@@ -8696,7 +8742,7 @@ const
8696
8742
  VMI_push_i, VMI_push_j, VMI_push_k,
8697
8743
  VMI_push_year, VMI_push_week, VMI_push_day, VMI_push_hour,
8698
8744
  VMI_push_minute, VMI_push_second],
8699
- DYNAMIC_SYMBOLS = ['t', 'rt', 'bt', 'b', 'r', 'random', 'i', 'j', 'k'],
8745
+ DYNAMIC_SYMBOLS = ['t', 'rt', 'bt', 'ct', 'b', 'r', 'random', 'i', 'j', 'k'],
8700
8746
  MONADIC_OPERATORS = [
8701
8747
  '~', 'not', 'abs', 'sin', 'cos', 'atan', 'ln',
8702
8748
  'exp', 'sqrt', 'round', 'int', 'fract', 'min', 'max',