linny-r 2.0.6 → 2.0.8

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.
@@ -12,7 +12,7 @@ executed by the VM, construct the Simplex tableau that can be sent to the
12
12
  MILP solver.
13
13
  */
14
14
  /*
15
- Copyright (c) 2017-2024 Delft University of Technology
15
+ Copyright (c) 2017-2025 Delft University of Technology
16
16
 
17
17
  Permission is hereby granted, free of charge, to any person obtaining a copy
18
18
  of this software and associated documentation files (the "Software"), to deal
@@ -444,17 +444,27 @@ class Expression {
444
444
  // expression).
445
445
  if(t < 0 || this.isStatic) t = 0;
446
446
  if(t >= v.length) return VM.UNDEFINED;
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 ||
447
+ // Check for recursive calls.
448
+ if(v[t] === VM.COMPUTING) {
449
+ console.log('Already computing expression for', this.variableName);
450
+ console.log(this.text);
451
+ return VM.CYCLIC;
452
+ }
453
+ // NOTES:
454
+ // (1) When VM is setting up a tableau, values computed for the
455
+ // look-ahead period must be recomputed.
456
+ // (2) Always recompute value for sensitivity analysis parameter, as
457
+ // otherwise the vector value will be scaled cumulatively.
458
+ const sap = (this === MODEL.active_sensitivity_parameter);
459
+ if(sap || v[t] === VM.NOT_COMPUTED || v[t] === VM.COMPUTING ||
450
460
  (!this.isStatic && VM.inLookAhead(t))) {
451
461
  v[t] = VM.NOT_COMPUTED;
452
462
  this.compute(t, number);
453
463
  }
454
- // NOTE: when this expression is the "active" parameter for sensitivity
455
- // analysis, the result is multiplied by 1 + delta %
456
- if(this === MODEL.active_sensitivity_parameter) {
457
- // NOTE: do NOT scale exceptional values
464
+ // NOTE: When this expression is the "active" parameter for sensitivity
465
+ // analysis, the result is multiplied by 1 + delta %.
466
+ if(sap) {
467
+ // NOTE: Do NOT scale exceptional values.
458
468
  if(v[t] > VM.MINUS_INFINITY && v[t] < VM.PLUS_INFINITY) {
459
469
  v[t] *= (1 + MODEL.sensitivity_delta * 0.01);
460
470
  }
@@ -1849,14 +1859,14 @@ class ExpressionParser {
1849
1859
  if(this.then_stack.length < 1) {
1850
1860
  this.error = 'Unexpected :';
1851
1861
  } else {
1852
- // Similar to above: when a : operator is "coded", the ELSE part
1862
+ // Similar to above: When a : operator is "coded", the ELSE part
1853
1863
  // has been coded, so the end of the code array is the target for
1854
- // the most recently added JUMP
1864
+ // the most recently added JUMP.
1855
1865
  this.code[this.then_stack.pop()][1] = this.code.length;
1856
1866
  }
1857
1867
  } else {
1858
1868
  // All other operations require VM instructions that operate on the
1859
- // expression stack
1869
+ // expression stack.
1860
1870
  this.code.push([op, null]);
1861
1871
  if(op === VMI_concat) {
1862
1872
  this.concatenating = true;
@@ -1864,8 +1874,8 @@ class ExpressionParser {
1864
1874
  const randcode = RANDOM_CODES.indexOf(op) >= 0;
1865
1875
  if(REDUCING_CODES.indexOf(op) >= 0) {
1866
1876
  if(randcode && !this.concatenating) {
1867
- // NOTE: probability distributions MUST have a parameter list but
1868
- // MIN and MAX will also accept a single argument
1877
+ // NOTE: Probability distributions MUST have a parameter list but
1878
+ // MIN and MAX will also accept a single argument.
1869
1879
  console.log('OPERATOR:', op);
1870
1880
  this.error = 'Missing parameter list';
1871
1881
  }
@@ -2120,6 +2130,8 @@ class VirtualMachine {
2120
2130
  this.numeric_issue = '';
2121
2131
  // Warnings are stored in a list to permit browsing through them.
2122
2132
  this.issue_list = [];
2133
+ // Bound issues (UB < LB) are recorded to permit compact warnings.
2134
+ this.bound_issues = {};
2123
2135
  // The call stack tracks evaluation of "nested" expression variables.
2124
2136
  this.call_stack = [];
2125
2137
  this.block_count = 0;
@@ -2468,6 +2480,8 @@ class VirtualMachine {
2468
2480
  // block).
2469
2481
  this.error_count = 0;
2470
2482
  this.block_issues = 0;
2483
+ // Clear bound issue dictionary.
2484
+ this.bound_issues = {};
2471
2485
  // Clear issue list with warnings and hide issue panel.
2472
2486
  this.issue_list.length = 0;
2473
2487
  this.issue_index = -1;
@@ -2581,6 +2595,7 @@ class VirtualMachine {
2581
2595
  // Return number `n` formatted so as to show 2-3 significant digits
2582
2596
  // NOTE: as `n` should be a number, a warning sign will typically
2583
2597
  // indicate a bug in the software.
2598
+ if(typeof n === 'string') n = parseFloat(n);
2584
2599
  if(n === undefined || isNaN(n)) return '\u26A0'; // Warning sign
2585
2600
  const sv = this.specialValue(n);
2586
2601
  // If `n` has a special value, return its representation.
@@ -2608,6 +2623,7 @@ class VirtualMachine {
2608
2623
  // Return number `n` formatted so as to show 4-5 significant digits.
2609
2624
  // NOTE: As `n` should be a number, a warning sign will typically
2610
2625
  // indicate a bug in the software.
2626
+ if(typeof n === 'string') n = parseFloat(n);
2611
2627
  if(n === undefined || isNaN(n)) return '\u26A0';
2612
2628
  const sv = this.specialValue(n);
2613
2629
  // If `n` has a special value, return its representation.
@@ -3204,8 +3220,8 @@ class VirtualMachine {
3204
3220
 
3205
3221
  setBoundConstraints(p) {
3206
3222
  // Set LB and UB constraints for product `p`.
3207
- // NOTE: This method affects the VM coefficient vector, so save it
3208
- // (if needed) before calling this method.
3223
+ // NOTE: This method affects the VM coefficient vector, so this vector
3224
+ // should be saved (using a VM instruction) if it is needed later.
3209
3225
  const
3210
3226
  vi = p.level_var_index,
3211
3227
  lesvi = p.stock_LE_slack_var_index,
@@ -4338,7 +4354,7 @@ class VirtualMachine {
4338
4354
  ' will compromise computation of its binary variables';
4339
4355
  UI.warn(msg);
4340
4356
  this.logMessage(this.block_count,
4341
- 'WARNING: ' + msg.replace(/<\/?strong>/g, '"'));
4357
+ this.WARNING + msg.replace(/<\/?strong>/g, '"'));
4342
4358
  }
4343
4359
  }
4344
4360
  if(hub !== ub) {
@@ -4620,8 +4636,8 @@ class VirtualMachine {
4620
4636
  high_rate) + 1);
4621
4637
  if(this.slack_penalty > VM.MAX_SLACK_PENALTY) {
4622
4638
  this.slack_penalty = VM.MAX_SLACK_PENALTY;
4623
- this.logMessage(this.block_count,
4624
- 'WARNING: Max. slack penalty reached; try to scale down your model coefficients');
4639
+ this.logMessage(this.block_count, this.WARNING +
4640
+ 'Max. slack penalty reached; try to scale down your model coefficients');
4625
4641
  }
4626
4642
  const m = Math.max(
4627
4643
  Math.abs(this.low_coefficient), Math.abs(this.high_coefficient));
@@ -5696,7 +5712,6 @@ class VirtualMachine {
5696
5712
  return ` +${c} ${v}`; // Prefix coefficient with +
5697
5713
  // NOTE: This may return +0 X001.
5698
5714
  };
5699
-
5700
5715
  this.numeric_issue = '';
5701
5716
  // First add the objective (always MAXimize).
5702
5717
  if(cplex) {
@@ -6160,7 +6175,9 @@ class VirtualMachine {
6160
6175
  }
6161
6176
 
6162
6177
  stopSolving() {
6178
+ // Wrap-up after solving is completed or aborted.
6163
6179
  this.stopTimer();
6180
+ // Stop rotating the Linny-R icon, and update buttons.
6164
6181
  UI.stopSolving();
6165
6182
  }
6166
6183
 
@@ -6312,7 +6329,7 @@ Solver status = ${json.status}`);
6312
6329
  }
6313
6330
  // If negative delays require "fixating" variables for some number
6314
6331
  // of time steps, this must be logged in the monitor.
6315
- const keys = Object.keys(this.variables_to_fixate);
6332
+ let keys = Object.keys(this.variables_to_fixate);
6316
6333
  if(keys.length) {
6317
6334
  const msg = ['NOTE: Due to negative link delays, levels for ' +
6318
6335
  pluralS(keys.length, 'variable') + ' are pre-set:'];
@@ -6341,6 +6358,27 @@ Solver status = ${json.status}`);
6341
6358
  }
6342
6359
  this.logMessage(this.block_count, msg.join('\n'));
6343
6360
  }
6361
+ // Convert bound issues to warnings in the Monitor.
6362
+ keys = Object.keys(this.bound_issues).sort();
6363
+ const n = keys.length;
6364
+ if(n) {
6365
+ let vlist = '',
6366
+ first = 1e20;
6367
+ for(let i = 0; i < n; i++) {
6368
+ const
6369
+ k = keys[i],
6370
+ bit = this.bound_issues[k];
6371
+ vlist += `\n - ${k} (t=${listToRange(bit)})`;
6372
+ first = Math.min(first, bit[0]);
6373
+ }
6374
+ const msg = `Lower bound exceeds upper bound for ${n} processes`;
6375
+ this.logMessage(this.block_count,
6376
+ `${this.WARNING}(t=${first}) ${msg}:${vlist}`);
6377
+ UI.warn(msg + ' - check Monitor for details');
6378
+ // Clear bound issue dictionary, so next block starts anew.
6379
+ this.bound_issues = {};
6380
+ }
6381
+ // Create the input file for the solver.
6344
6382
  this.logMessage(this.block_count,
6345
6383
  'Creating model for block #' + this.blockWithRound);
6346
6384
  this.cbl = CONFIGURATION.progress_needle_interval * 200;
@@ -7581,6 +7619,108 @@ function VMI_ge(x) {
7581
7619
  }
7582
7620
  }
7583
7621
 
7622
+ function VMI_at(x) {
7623
+ // Pop the top number on the stack, and use its integer part as index i
7624
+ // to replace the new top element (which must be a dataset or a grouping)
7625
+ // by its i-th element.
7626
+ let d = x.pop();
7627
+ if(d !== false) {
7628
+ if(DEBUGGING) console.log('AT (' + d.join(', ') + ')');
7629
+ let a,
7630
+ from = false,
7631
+ to = false,
7632
+ step = 1,
7633
+ group = false,
7634
+ period = false,
7635
+ range = [],
7636
+ ok = true;
7637
+ // Check whether the first argument (d[0]) is indexable.
7638
+ if(d[0] instanceof Array) {
7639
+ a = d[0];
7640
+ group = true;
7641
+ } else if(d[0].entity instanceof Dataset) {
7642
+ a = d[0].entity.vector;
7643
+ period = d[0].periodic;
7644
+ } else {
7645
+ x.retop(VM.ARRAY_INDEX);
7646
+ return;
7647
+ }
7648
+ // Check whether the second argument (d[1]) is a number or a pair.
7649
+ if(d[1] instanceof Array) {
7650
+ if(d[1].length > 3 || typeof d[1][0] !== 'number') {
7651
+ ok = false;
7652
+ } else if(d[1].length === 3) {
7653
+ // Optional third index argument is range index increment.
7654
+ if(typeof d[1][2] === 'number') {
7655
+ step = Math.floor(d[1][2]);
7656
+ // Ignore increment if it truncates to zero.
7657
+ if(!step) step = 1;
7658
+ // Get the range end.
7659
+ if(typeof d[1][1] === 'number') {
7660
+ to = Math.floor(d[1][1]);
7661
+ } else {
7662
+ ok = false;
7663
+ }
7664
+ } else {
7665
+ ok = false;
7666
+ }
7667
+ } else if(d[1].length === 2) {
7668
+ // Optional second argument is range index end.
7669
+ if(typeof d[1][1] === 'number') {
7670
+ to = Math.floor(d[1][1]);
7671
+ } else {
7672
+ ok = false;
7673
+ }
7674
+ }
7675
+ if(ok) {
7676
+ from = Math.floor(d[1][0]);
7677
+ // Groupings are 0-based arrays but indexed as 1-based.
7678
+ if(group) {
7679
+ from--;
7680
+ to--;
7681
+ }
7682
+ // Check whether from, to and step are feasible.
7683
+ if(to !== false) {
7684
+ if(to <= from && step < 0) {
7685
+ for(let i = from; i >= to; i += step) range.push(i);
7686
+ } else if(to >= from && step > 0) {
7687
+ for(let i = from; i <= to; i += step) range.push(i);
7688
+ } else {
7689
+ ok = false;
7690
+ }
7691
+ }
7692
+ }
7693
+ }
7694
+ if(ok && !range.length && typeof d[1] === 'number') {
7695
+ range = [Math.floor(d[1]) - (group ? 1 : 0)];
7696
+ } else if(!range.length) {
7697
+ ok = false;
7698
+ }
7699
+ if(!ok) {
7700
+ x.retop(VM.ARRAY_INDEX);
7701
+ return;
7702
+ }
7703
+ const
7704
+ n = range.length,
7705
+ r = [];
7706
+ for(let i = 0; i < n; i++) {
7707
+ const index = range[i];
7708
+ if(index < 0) {
7709
+ r.push(VM.UNDEFINED);
7710
+ } else if(period) {
7711
+ r.push(a[index % a.length]);
7712
+ } else {
7713
+ r.push(a[index]);
7714
+ }
7715
+ }
7716
+ if(n === 1) {
7717
+ x.retop(r[0]);
7718
+ } else {
7719
+ x.retop(r);
7720
+ }
7721
+ }
7722
+ }
7723
+
7584
7724
  function VMI_add(x) {
7585
7725
  // Pop the top number on the stack, and add it to the new top number.
7586
7726
  const d = x.pop();
@@ -8217,6 +8357,13 @@ function VMI_set_bounds(args) {
8217
8357
  console.log(['set_bounds [', k, '] ', vbl.displayName, '[',
8218
8358
  VM.variables[vi - 1][0],'] t = ', VM.t, ' LB = ', VM.sig4Dig(l),
8219
8359
  ', UB = ', VM.sig4Dig(u), fixed].join(''), l, u, inf_val);
8360
+ } else if(u < l) {
8361
+ // Warn that "impossible" bounds would have been set...
8362
+ const vk = vbl.displayName;
8363
+ if(!VM.bound_issues[vk]) VM.bound_issues[vk] = [];
8364
+ VM.bound_issues[vk].push(VM.t);
8365
+ // ... and set LB to UB, so that lowest value is bounding.
8366
+ l = u;
8220
8367
  }
8221
8368
  // NOTE: Since the VM vectors for lower bounds and upper bounds are
8222
8369
  // initialized with default values (0 for LB, +INF for UB), the bounds
@@ -9270,7 +9417,7 @@ function VMI_add_available_capacity(link) {
9270
9417
  const
9271
9418
  // Valid symbols in expressions
9272
9419
  PARENTHESES = '()',
9273
- OPERATOR_CHARS = ';?:+-*/%=!<>^|',
9420
+ OPERATOR_CHARS = ';?:+-*/%=!<>^|@',
9274
9421
  // Opening bracket, space and single quote indicate a separation
9275
9422
  SEPARATOR_CHARS = PARENTHESES + OPERATOR_CHARS + "[ '",
9276
9423
  COMPOUND_OPERATORS = ['!=', '<>', '>=', '<='],
@@ -9302,13 +9449,13 @@ const
9302
9449
  VMI_weibull, VMI_npv],
9303
9450
  DYADIC_OPERATORS = [
9304
9451
  ';', '?', ':', 'or', 'and',
9305
- '=', '<>', '!=',
9306
- '>', '<', '>=', '<=', '+', '-', '*', '/',
9452
+ '=', '<>', '!=', '>', '<', '>=', '<=',
9453
+ '@', '+', '-', '*', '/',
9307
9454
  '%', '^', 'log', '|'],
9308
9455
  DYADIC_CODES = [
9309
9456
  VMI_concat, VMI_if_then, VMI_if_else, VMI_or, VMI_and,
9310
9457
  VMI_eq, VMI_ne, VMI_ne, VMI_gt, VMI_lt, VMI_ge, VMI_le,
9311
- VMI_add, VMI_sub, VMI_mul, VMI_div, VMI_mod,
9458
+ VMI_at, VMI_add, VMI_sub, VMI_mul, VMI_div, VMI_mod,
9312
9459
  VMI_power, VMI_log, VMI_replace_undefined],
9313
9460
 
9314
9461
  // Compiler checks for random codes as they make an expression dynamic
@@ -9316,7 +9463,7 @@ const
9316
9463
  VMI_triangular, VMI_weibull],
9317
9464
 
9318
9465
  // Compiler checks for reducing codes to unset its "concatenating" flag
9319
- REDUCING_CODES = [VMI_min, VMI_max, VMI_binomial, VMI_normal,
9466
+ REDUCING_CODES = [VMI_at, VMI_min, VMI_max, VMI_binomial, VMI_normal,
9320
9467
  VMI_triangular, VMI_weibull, VMI_npv],
9321
9468
 
9322
9469
  // Custom operators may make an expression level-based
@@ -9324,7 +9471,7 @@ const
9324
9471
 
9325
9472
  OPERATORS = DYADIC_OPERATORS.concat(MONADIC_OPERATORS),
9326
9473
  OPERATOR_CODES = DYADIC_CODES.concat(MONADIC_CODES),
9327
- PRIORITIES = [1, 2, 2, 3, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 10,
9474
+ PRIORITIES = [1, 2, 2, 3, 4, 5, 5, 5, 5, 5, 5, 5, 5.5, 6, 6, 7, 7, 7, 8, 8, 10,
9328
9475
  9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
9329
9476
  ACTUAL_SYMBOLS = CONSTANT_SYMBOLS.concat(OPERATORS),
9330
9477
  SYMBOL_CODES = CONSTANT_CODES.concat(OPERATOR_CODES);