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.
@@ -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: use bright red and blue colors in case of "stock level out of bounds"
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
- fill_color = this.palette.zero_within_bounds;
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
- // Non-zero constants have less saturated shades
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 upper bound"
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
- // set "partial fill" flag if not at lower bound and UB < INF
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: empty buffers (at level 0) should be entirely white
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: set fill color to darker shade for partial fill
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 = '.mps';
55
- this.user_model = path.join(workspace.solver_output, 'usr_model.mps');
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
- // (1) Sort on variable name
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
- x_values.push(sol.Vars[i].X);
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');