linny-r 1.7.3 → 1.8.0

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.
@@ -104,6 +104,7 @@ class LinnyRModel {
104
104
  this.preferred_solver = ''; // empty string denotes "use default"
105
105
  this.integer_tolerance = 5e-7; // integer feasibility tolerance
106
106
  this.MIP_gap = 1e-4; // relative MIP gap
107
+ this.always_diagnose = false;
107
108
 
108
109
  // Sensitivity-related properties
109
110
  this.base_case_selectors = '';
@@ -2666,6 +2667,7 @@ class LinnyRModel {
2666
2667
  this.infer_cost_prices = nodeParameterValue(node, 'cost-prices') === '1';
2667
2668
  this.report_results = nodeParameterValue(node, 'report-results') === '1';
2668
2669
  this.show_block_arrows = nodeParameterValue(node, 'block-arrows') === '1';
2670
+ this.always_diagnose = nodeParameterValue(node, 'diagnose') === '1';
2669
2671
  this.name = xmlDecoded(nodeContentByTag(node, 'name'));
2670
2672
  this.author = xmlDecoded(nodeContentByTag(node, 'author'));
2671
2673
  this.comments = xmlDecoded(nodeContentByTag(node, 'notes'));
@@ -3014,6 +3016,7 @@ class LinnyRModel {
3014
3016
  if(this.infer_cost_prices) p += ' cost-prices="1"';
3015
3017
  if(this.report_results) p += ' report-results="1"';
3016
3018
  if(this.show_block_arrows) p += ' block-arrows="1"';
3019
+ if(this.always_diagnose) p += ' diagnose="1"';
3017
3020
  let xml = this.xml_header + ['<model', p, '><name>', xmlEncoded(this.name),
3018
3021
  '</name><author>', xmlEncoded(this.author),
3019
3022
  '</author><notes>', xmlEncoded(this.comments),
@@ -6302,7 +6305,9 @@ class Cluster extends NodeBox {
6302
6305
  }
6303
6306
 
6304
6307
  usesSlack(t, p, slack_type) {
6305
- // Adds slack-using product `p` to slack info for this cluster
6308
+ // Adds slack-using product `p` to slack info for this cluster.
6309
+ // NOTE: When diagnosing an unbounded problem, `p` can also be a
6310
+ // process with an infinite level.
6306
6311
  let s;
6307
6312
  if(t in this.slack_info) {
6308
6313
  s = this.slack_info[t];
@@ -6311,6 +6316,7 @@ class Cluster extends NodeBox {
6311
6316
  this.slack_info[t] = s;
6312
6317
  }
6313
6318
  addDistinct(p, s[slack_type]);
6319
+ // NOTE: Recursive call to let the slack use info "bubble up".
6314
6320
  if(this.cluster) this.cluster.usesSlack(t, p, slack_type);
6315
6321
  }
6316
6322
 
@@ -8938,6 +8944,12 @@ class Dataset {
8938
8944
  return this.vector;
8939
8945
  }
8940
8946
  if(a === '.') return this.vector;
8947
+ // Still permit legacy use of [dataset|attr] as a way to "call" a
8948
+ // dataset modifier expression explicitly.
8949
+ if(a) {
8950
+ const x = this.attributeExpression(a);
8951
+ if(x) return x.result(MODEL.t);
8952
+ }
8941
8953
  // Fall-through: return the default value of this dataset.
8942
8954
  return this.defaultValue;
8943
8955
  }
@@ -9384,11 +9396,15 @@ class ChartVariable {
9384
9396
  t_end = tsteps;
9385
9397
  } else {
9386
9398
  // Get the variable's own value (number, vector or expression)
9387
- if(this.object instanceof Dataset && !this.attribute) {
9388
- // Special case: Variables that depict a dataset with no explicit
9389
- // modifier selector must recompute the vector using the current
9390
- // experiment run combination or the default selector.
9391
- av = this.object.activeModifierExpression;
9399
+ if(this.object instanceof Dataset) {
9400
+ if(this.attribute) {
9401
+ av = this.object.attributeExpression(this.attribute);
9402
+ } else {
9403
+ // Special case: Variables that depict a dataset with no explicit
9404
+ // modifier selector must recompute the vector using the current
9405
+ // experiment run combination or the default selector.
9406
+ av = this.object.activeModifierExpression;
9407
+ }
9392
9408
  } else if(this.object instanceof DatasetModifier) {
9393
9409
  av = this.object.expression;
9394
9410
  } else {
@@ -12024,7 +12040,7 @@ class Experiment {
12024
12040
  class BoundLine {
12025
12041
  constructor(c) {
12026
12042
  this.constraint = c;
12027
- // Default bound line imposes no constraint: Y >= 0 for all X
12043
+ // Default bound line imposes no constraint: Y >= 0 for all X.
12028
12044
  this.points = [[0, 0], [100, 0]];
12029
12045
  this.type = VM.GE;
12030
12046
  this.selectors = '';
@@ -12039,7 +12055,7 @@ class BoundLine {
12039
12055
  }
12040
12056
 
12041
12057
  get copy() {
12042
- // Returns a "clone" of this bound line
12058
+ // Return a "clone" of this bound line.
12043
12059
  let bl = new BoundLine(this.constraint);
12044
12060
  bl.points.length = 0;
12045
12061
  for(let i = 0; i < this.points.length; i++) {
@@ -12068,8 +12084,8 @@ class BoundLine {
12068
12084
  }
12069
12085
 
12070
12086
  get isActive() {
12071
- // Returns TRUE if this line has no selectors, or if its selectors match
12072
- // with the selectors of the current experiment run
12087
+ // Return TRUE if this line has no selectors, or if its selectors match
12088
+ // with the selectors of the current experiment run.
12073
12089
  if(!this.selectors) return true;
12074
12090
  const x = MODEL.running_experiment;
12075
12091
  if(!x) return false;
@@ -12077,12 +12093,49 @@ class BoundLine {
12077
12093
  return ss.length > 0;
12078
12094
  }
12079
12095
 
12096
+ get needsNoSOS() {
12097
+ // Return 1 if boundline is NOT of type <= and line segments have an
12098
+ // increasing slope, -1 if boundline is NOT of type >= and line segments
12099
+ // have a decreasing slope, and otherwise 0 (FALSE). If non-zero (TRUE),
12100
+ // the constraint can be implemented without the SOS2 constraint that
12101
+ // only two consecutive SOS variables may be non-zero.
12102
+ if(this.type !== VM.LE) {
12103
+ let slope = 0,
12104
+ pp = this.points[0];
12105
+ for(let i = 1; i < this.points.length; i++) {
12106
+ const
12107
+ p = this.points[i],
12108
+ dx = p[0] - pp[0],
12109
+ s = (dx <= VM.NEAR_ZERO ? VM.PLUS_INFINITY : (p[1] - pp[1]) / dx);
12110
+ // Decreasing slope => not convex.
12111
+ if(s < slope) return 0;
12112
+ slope = s;
12113
+ }
12114
+ return 1;
12115
+ } else if (this.type !== VM.GE) {
12116
+ let slope = VM.PLUS_INFINITY,
12117
+ pp = this.points[0];
12118
+ for(let i = 1; i < this.points.length; i++) {
12119
+ const
12120
+ p = this.points[i],
12121
+ dx = p[0] - pp[0],
12122
+ s = (dx <= VM.NEAR_ZERO ? VM.PLUS_INFINITY : (p[1] - pp[1]) / dx);
12123
+ // Increasing slope => not convex.
12124
+ if(s > slope) return 0;
12125
+ slope = s;
12126
+ }
12127
+ return -1;
12128
+ }
12129
+ // Fall-through (should not occur).
12130
+ return 0;
12131
+ }
12132
+
12080
12133
  get constrainsY() {
12081
- // Returns TRUE if this bound line constrains Y in some way
12134
+ // Return TRUE if this bound line constrains Y in some way.
12082
12135
  if(this.type === VM.EQ) return true;
12083
12136
  for(let j = 0; j < this.points.length; j++) {
12084
12137
  const p = this.points[j];
12085
- // LE bound line constrains when not at 100%, GE when not at 0%
12138
+ // LE bound line constrains when not at 100%, GE when not at 0%.
12086
12139
  if(this.type === VM.LE && p[1] < 100 || this.type === VM.GE && p[1] > 0) {
12087
12140
  return true;
12088
12141
  }
@@ -12091,8 +12144,8 @@ class BoundLine {
12091
12144
  }
12092
12145
 
12093
12146
  pointOnLine(x, y) {
12094
- // Returns TRUE iff (x, y) lies on this bound line (+/- 0.001%)
12095
- // or within radius < tolerance from a point
12147
+ // Return TRUE iff (x, y) lies on this bound line (+/- 0.001%)
12148
+ // or within radius < tolerance from a point.
12096
12149
  const
12097
12150
  tol = 0.001,
12098
12151
  tolsq = tol * tol;
@@ -12107,27 +12160,27 @@ class BoundLine {
12107
12160
  if(x > pp[0] - 1 && x < p[0] + 1 &&
12108
12161
  ((y > pp[1] - tol && y < p[1] + tol) ||
12109
12162
  (y < pp[1] + tol && y > p[1] + tol))) {
12110
- // Cursor lies within rectangle around line segment
12163
+ // Cursor lies within rectangle around line segment.
12111
12164
  const
12112
12165
  dx = p[0] - pp[0],
12113
12166
  dy = p[1] - pp[1];
12114
12167
  if(Math.abs(dx) < tol || Math.abs(dy) < tol) {
12115
- // Special case: (near) vertical or (near) horizontal line
12168
+ // Special case: (near) vertical or (near) horizontal line.
12116
12169
  return true;
12117
12170
  } else {
12118
- // Compute horizontal & vertical distance to line segment
12171
+ // Compute horizontal & vertical distance to line segment.
12119
12172
  const
12120
- // H & V distance from left-most point
12173
+ // H & V distance from left-most point.
12121
12174
  dpx = x - pp[0],
12122
12175
  dpy = y - pp[1],
12123
- // Projected X, given Y-distance
12176
+ // Projected X, given Y-distance.
12124
12177
  nx = pp[0] + dpy * dx / dy,
12125
- // Projected Y, given X-distance
12178
+ // Projected Y, given X-distance.
12126
12179
  ny = pp[1] + dpx * dy / dx,
12127
- // Take absolute differences
12180
+ // Take absolute differences.
12128
12181
  dxol = Math.abs(nx - x),
12129
12182
  dyol = Math.abs(ny - y);
12130
- // Only test the shortest distance
12183
+ // Only test the shortest distance.
12131
12184
  if (Math.min(dxol, dyol) < tol) {
12132
12185
  return true;
12133
12186
  }