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.
- package/README.md +33 -22
- package/console.js +2 -2
- package/package.json +1 -1
- package/server.js +2 -2
- package/static/index.html +13 -5
- package/static/linny-r.css +21 -2
- package/static/scripts/linny-r-ctrl.js +1 -1
- package/static/scripts/linny-r-gui-constraint-editor.js +15 -2
- package/static/scripts/linny-r-gui-controller.js +45 -13
- package/static/scripts/linny-r-gui-equation-manager.js +6 -6
- package/static/scripts/linny-r-gui-expression-editor.js +6 -3
- package/static/scripts/linny-r-gui-finder.js +1 -1
- package/static/scripts/linny-r-gui-monitor.js +5 -5
- package/static/scripts/linny-r-gui-paper.js +13 -6
- package/static/scripts/linny-r-milp.js +306 -86
- package/static/scripts/linny-r-model.js +75 -22
- package/static/scripts/linny-r-vm.js +354 -127
@@ -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
|
9388
|
-
|
9389
|
-
|
9390
|
-
|
9391
|
-
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
}
|