linny-r 1.6.0 → 1.6.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 +1 -1
- package/server.js +27 -6
- package/static/scripts/linny-r-gui-controller.js +47 -35
- package/static/scripts/linny-r-gui-documentation-manager.js +36 -32
- package/static/scripts/linny-r-gui-equation-manager.js +10 -7
- package/static/scripts/linny-r-gui-experiment-manager.js +14 -3
- package/static/scripts/linny-r-gui-finder.js +1 -0
- package/static/scripts/linny-r-gui-paper.js +25 -23
- package/static/scripts/linny-r-milp.js +18 -15
- package/static/scripts/linny-r-model.js +143 -91
- package/static/scripts/linny-r-utils.js +33 -8
- package/static/scripts/linny-r-vm.js +250 -60
@@ -2312,17 +2312,18 @@ class Paper {
|
|
2312
2312
|
}
|
2313
2313
|
if(prod.hasBounds) {
|
2314
2314
|
font_color = 'black';
|
2315
|
-
// By default, "plain" factors having bounds are filled in silver
|
2315
|
+
// By default, "plain" factors having bounds are filled in silver.
|
2316
2316
|
fill_color = this.palette.has_bounds;
|
2317
2317
|
// Use relative distance to bounds so that 100000.1 is not shown
|
2318
|
-
// as overflow, but 100.1 is
|
2318
|
+
// as overflow, but 100.1 is.
|
2319
2319
|
let udif = this.relDif(l, ub),
|
2320
2320
|
ldif = this.relDif(lb, l);
|
2321
|
-
// Special case: for LB = 0, use the ON/OFF threshold
|
2321
|
+
// Special case: for LB = 0, use the ON/OFF threshold.
|
2322
2322
|
if(Math.abs(lb) <= VM.SIG_DIF_LIMIT &&
|
2323
2323
|
Math.abs(l) <= VM.ON_OFF_THRESHOLD) ldif = 0;
|
2324
2324
|
if(MODEL.solved) {
|
2325
|
-
// NOTE:
|
2325
|
+
// NOTE: Use bright red and blue colors in case of "stock level
|
2326
|
+
// out of bounds".
|
2326
2327
|
if(ub < VM.PLUS_INFINITY && l < VM.UNDEFINED && udif > VM.SIG_DIF_LIMIT) {
|
2327
2328
|
fill_color = this.palette.above_upper_bound;
|
2328
2329
|
font_color = 'blue';
|
@@ -2332,51 +2333,52 @@ class Paper {
|
|
2332
2333
|
} else if(l < VM.ERROR || l > VM.EXCEPTION) {
|
2333
2334
|
font_color = this.palette.VM_error;
|
2334
2335
|
} else if(l < VM.UNDEFINED) {
|
2335
|
-
// Shades of green reflect whether level within bounds, where
|
2336
|
+
// Shades of green reflect whether level is within bounds, where
|
2336
2337
|
// "sources" (negative level) and "sinks" (positive level) are
|
2337
|
-
// shown as more reddish / bluish shades of green
|
2338
|
+
// shown as more reddish / bluish shades of green.
|
2338
2339
|
if(l < -VM.ON_OFF_THRESHOLD) {
|
2339
2340
|
fill_color = this.palette.neg_within_bounds;
|
2340
2341
|
} else if(l > VM.ON_OFF_THRESHOLD) {
|
2341
2342
|
fill_color = this.palette.pos_within_bounds;
|
2342
2343
|
} else {
|
2343
|
-
|
2344
|
+
fill_color = this.palette.zero_within_bounds;
|
2344
2345
|
}
|
2345
2346
|
if(ub - lb < VM.NEAR_ZERO) {
|
2347
|
+
// When LB = UB, fill completely in the color, but ...
|
2346
2348
|
if(prod.isConstant && Math.abs(l) > VM.NEAR_ZERO) {
|
2347
|
-
//
|
2349
|
+
// ... non-zero constants have less saturated shades.
|
2348
2350
|
fill_color = (l < 0 ? this.palette.neg_constant :
|
2349
2351
|
this.palette.pos_constant);
|
2350
2352
|
}
|
2351
2353
|
} else if(ub - l < VM.SIG_DIF_LIMIT) {
|
2352
|
-
// Black font and darker fill color indicate "at upper bound"
|
2354
|
+
// Black font and darker fill color indicate "at upper bound".
|
2353
2355
|
font_color = 'black';
|
2354
2356
|
fill_color = (ub > 0 ? this.palette.at_pos_ub_fill :
|
2355
2357
|
(ub < 0 ? this.palette.at_neg_ub_fill :
|
2356
2358
|
this.palette.at_zero_ub_fill));
|
2357
2359
|
at_bound = true;
|
2358
2360
|
} else if (l - lb < VM.SIG_DIF_LIMIT) {
|
2359
|
-
// Font and rim color indicate "at
|
2361
|
+
// Font and rim color indicate "at lower bound".
|
2360
2362
|
font_color = 'black';
|
2361
2363
|
fill_color = (lb > 0 ? this.palette.at_pos_lb_fill :
|
2362
2364
|
(lb < 0 ? this.palette.at_neg_lb_fill :
|
2363
2365
|
this.palette.at_zero_lb_fill));
|
2364
2366
|
at_bound = true;
|
2365
2367
|
} else {
|
2366
|
-
//
|
2368
|
+
// Set "partial fill" flag if not at lower bound and UB < INF.
|
2367
2369
|
pf = ub < VM.PLUS_INFINITY;
|
2368
2370
|
font_color = this.palette.within_bounds_font;
|
2369
2371
|
}
|
2370
2372
|
}
|
2371
2373
|
} else if(ub - lb < VM.NEAR_ZERO) {
|
2372
|
-
// Not solved but equal bounds => probably constants
|
2374
|
+
// Not solved but equal bounds => probably constants.
|
2373
2375
|
if(prod.isConstant && Math.abs(ub) > VM.NEAR_ZERO) {
|
2374
|
-
// Non-zero constants have less saturated shades
|
2376
|
+
// Non-zero constants have less saturated shades.
|
2375
2377
|
fill_color = (ub < 0 ? this.palette.neg_constant :
|
2376
2378
|
this.palette.pos_constant);
|
2377
2379
|
}
|
2378
2380
|
} else if(l < VM.UNDEFINED) {
|
2379
|
-
// Different bounds and initial level set => partial fill
|
2381
|
+
// Different bounds and initial level set => partial fill.
|
2380
2382
|
fill_color = this.palette.src_snk;
|
2381
2383
|
pf = true;
|
2382
2384
|
if(ub - l < VM.SIG_DIF_LIMIT || l - lb < VM.SIG_DIF_LIMIT) {
|
@@ -2418,8 +2420,8 @@ class Paper {
|
|
2418
2420
|
let npfbg = 'white';
|
2419
2421
|
if(fill_color === this.palette.above_upper_bound ||
|
2420
2422
|
fill_color === this.palette.below_lower_bound ||
|
2421
|
-
// NOTE:
|
2422
|
-
(at_bound && l > VM.ON_OFF_THRESHOLD)) {
|
2423
|
+
// NOTE: Empty buffers should be entirely white.
|
2424
|
+
(at_bound && l > lb + VM.ON_OFF_THRESHOLD)) {
|
2423
2425
|
npfbg = fill_color;
|
2424
2426
|
pf = false;
|
2425
2427
|
}
|
@@ -2434,26 +2436,26 @@ class Paper {
|
|
2434
2436
|
{fill: fill_color, stroke: stroke_color, 'stroke-width': stroke_width,
|
2435
2437
|
'stroke-dasharray': sda, 'stroke-linecap': 'round',
|
2436
2438
|
'rx': hh, 'ry': hh});
|
2437
|
-
// NOTE:
|
2439
|
+
// NOTE: Set fill color to darker shade for partial fill.
|
2438
2440
|
fill_color = (!MODEL.solved ? this.palette.src_snk :
|
2439
2441
|
(l > VM.NEAR_ZERO ? this.palette.above_zero_fill :
|
2440
2442
|
(l < -VM.NEAR_ZERO ? this.palette.below_zero_fill :
|
2441
2443
|
this.palette.at_zero_fill)));
|
2442
2444
|
}
|
2443
|
-
// Add partial fill if appropriate
|
2445
|
+
// Add partial fill if appropriate.
|
2444
2446
|
if(pf && l > lb && l < VM.UNDEFINED) {
|
2445
2447
|
// Calculate used part of range (1 = 100%)
|
2446
2448
|
let part,
|
2447
2449
|
range = ub - lb;
|
2448
2450
|
if(l >= VM.PLUS_INFINITY) {
|
2449
|
-
// Show exceptions and +INF as "overflow"
|
2451
|
+
// Show exceptions and +INF as "overflow".
|
2450
2452
|
part = 1;
|
2451
2453
|
fill_color = this.palette.above_upper_bound;
|
2452
2454
|
} else {
|
2453
2455
|
part = (range > 0 ? (l - lb) / range : 1);
|
2454
2456
|
}
|
2455
2457
|
if(part > 0 && l >= lb) {
|
2456
|
-
// Only fill the portion of used range with the fill color
|
2458
|
+
// Only fill the portion of used range with the fill color.
|
2457
2459
|
const rad = Math.asin(1 - 2*part);
|
2458
2460
|
prod.shape.addPath(['m', x + hw - hh + (hh - 1.5) * Math.cos(rad),
|
2459
2461
|
',', y + (hh - 1.5) * Math.sin(rad),
|
@@ -2467,7 +2469,7 @@ class Paper {
|
|
2467
2469
|
stroke_color = 'none';
|
2468
2470
|
stroke_width = 0;
|
2469
2471
|
// Sources have a triangle pointing up from the bottom
|
2470
|
-
// (in outline if *implicit* source)
|
2472
|
+
// (in outline if *implicit* source).
|
2471
2473
|
if(prod.isSourceNode) {
|
2472
2474
|
if(!prod.is_source) {
|
2473
2475
|
fill_color = 'none';
|
@@ -2479,7 +2481,7 @@ class Paper {
|
|
2479
2481
|
'stroke-width': stroke_width});
|
2480
2482
|
}
|
2481
2483
|
// Sinks have a triangle pointing down from the top
|
2482
|
-
// (in outline if implicit sink)
|
2484
|
+
// (in outline if implicit sink).
|
2483
2485
|
if(prod.isSinkNode) {
|
2484
2486
|
if(!prod.is_sink) {
|
2485
2487
|
fill_color = 'none';
|
@@ -2491,7 +2493,7 @@ class Paper {
|
|
2491
2493
|
'stroke-width': stroke_width});
|
2492
2494
|
}
|
2493
2495
|
// Integer level is denoted by enclosing name in large [ and ]
|
2494
|
-
// to denote "floor" as well as "ceiling"
|
2496
|
+
// to denote "floor" as well as "ceiling".
|
2495
2497
|
if(prod.integer_level) {
|
2496
2498
|
const
|
2497
2499
|
brh = prod.name_lines.split('\n').length * this.font_heights[8] + 4,
|
@@ -51,8 +51,8 @@ module.exports = class MILPSolver {
|
|
51
51
|
// Each external MILP solver application has its own interface
|
52
52
|
// NOTE: the list may be extended to accommodate more MILP solvers
|
53
53
|
if(this.id === 'gurobi') {
|
54
|
-
this.ext = '.
|
55
|
-
this.user_model = path.join(workspace.solver_output, 'usr_model.
|
54
|
+
this.ext = '.lp';
|
55
|
+
this.user_model = path.join(workspace.solver_output, 'usr_model.lp');
|
56
56
|
this.solver_model = path.join(workspace.solver_output, 'solver_model.lp');
|
57
57
|
this.solution = path.join(workspace.solver_output, 'model.json');
|
58
58
|
this.log = path.join(workspace.solver_output, 'model.log');
|
@@ -283,17 +283,17 @@ module.exports = class MILPSolver {
|
|
283
283
|
x_dict = {},
|
284
284
|
getValuesFromDict = () => {
|
285
285
|
// Returns a result vector for as many real numbers (as strings!)
|
286
|
-
// as there are columns (0 if not reported by the solver)
|
287
|
-
//
|
286
|
+
// as there are columns (0 if not reported by the solver).
|
287
|
+
// First sort on variable name
|
288
288
|
const vlist = Object.keys(x_dict).sort();
|
289
|
-
// Start with column 1
|
289
|
+
// Start with column 1.
|
290
290
|
let col = 1;
|
291
291
|
for(let i = 0; i < vlist.length; i++) {
|
292
292
|
const
|
293
293
|
v = vlist[i],
|
294
|
-
// Variable names have zero-padded column numbers, e.g. "X001"
|
294
|
+
// Variable names have zero-padded column numbers, e.g. "X001".
|
295
295
|
vnr = parseInt(v.substring(1));
|
296
|
-
// Add zeros for unreported variables until column number matches
|
296
|
+
// Add zeros for unreported variables until column number matches.
|
297
297
|
while(col < vnr) {
|
298
298
|
x_values.push('0');
|
299
299
|
col++;
|
@@ -301,23 +301,23 @@ module.exports = class MILPSolver {
|
|
301
301
|
x_values.push(x_dict[v]);
|
302
302
|
col++;
|
303
303
|
}
|
304
|
-
// Add zeros to vector for remaining columns
|
304
|
+
// Add zeros to vector for remaining columns.
|
305
305
|
while(col <= result.columns) {
|
306
306
|
x_values.push('0');
|
307
307
|
col++;
|
308
308
|
}
|
309
|
-
// No return value; function operates on x_values
|
309
|
+
// No return value; function operates on x_values.
|
310
310
|
};
|
311
311
|
|
312
312
|
if(this.id === 'gurobi') {
|
313
|
-
// `messages` must be an array of strings
|
313
|
+
// `messages` must be an array of strings.
|
314
314
|
result.messages = fs.readFileSync(this.log, 'utf8').split(os.EOL);
|
315
315
|
if(result.status !== 0) {
|
316
|
-
// Non-zero solver exit code may indicate expired license
|
316
|
+
// Non-zero solver exit code may indicate expired license.
|
317
317
|
result.error = 'Your Gurobi license may have expired';
|
318
318
|
} else {
|
319
319
|
try {
|
320
|
-
// Read JSON string from solution file
|
320
|
+
// Read JSON string from solution file.
|
321
321
|
const
|
322
322
|
json = fs.readFileSync(this.solution, 'utf8').trim(),
|
323
323
|
sol = JSON.parse(json);
|
@@ -329,13 +329,16 @@ module.exports = class MILPSolver {
|
|
329
329
|
if(!result.error) result.error = 'Unknown solver error';
|
330
330
|
console.log(`Solver status: ${result.status} - ${result.error}`);
|
331
331
|
}
|
332
|
-
// Objective value
|
332
|
+
// Objective value.
|
333
333
|
result.obj = sol.SolutionInfo.ObjVal;
|
334
|
-
// Values of solution vector
|
334
|
+
// Values of solution vector.
|
335
335
|
if(sol.Vars) {
|
336
|
+
// Fill dictionary with variable name: value entries.
|
336
337
|
for(let i = 0; i < sol.Vars.length; i++) {
|
337
|
-
|
338
|
+
x_dict[sol.Vars[i].VarName] = sol.Vars[i].X;
|
338
339
|
}
|
340
|
+
// Fill the solution vector, adding 0 for missing columns.
|
341
|
+
getValuesFromDict();
|
339
342
|
}
|
340
343
|
} catch(err) {
|
341
344
|
console.log('WARNING: Could not read solution file');
|