linny-r 1.3.2 → 1.3.3

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/console.js CHANGED
@@ -87,7 +87,7 @@ console.log('Module directory:', MODULE_DIRECTORY);
87
87
  console.log('Working directory:', WORKING_DIRECTORY);
88
88
 
89
89
  // Currently, these external solvers are supported:
90
- const SUPPORTED_SOLVERS = ['gurobi', 'scip', 'lp_solve'];
90
+ const SUPPORTED_SOLVERS = ['gurobi', 'cplex', 'scip', 'lp_solve'];
91
91
 
92
92
  const
93
93
  // Load the MILP solver (dependent on Node.js: `fs`, `os` and `path`)
@@ -126,7 +126,7 @@ Possible options are:
126
126
  [name]-stats.txt in (workspace)/reports
127
127
  run will run the loaded model
128
128
  solver=[name] will select solver [name], or warn if not found
129
- (name choices: Gurobi, SCIP or LP_solve)
129
+ (name choices: Gurobi, CPLEX, SCIP or LP_solve)
130
130
  user=[identifier] user ID will be used to log onto remote servers
131
131
  verbose will output solver messages to the console
132
132
  workspace=[path] will create workspace in [path] instead of (main)/user
@@ -964,8 +964,25 @@ function commandLineSettings() {
964
964
  'WARNING: Failed to access the Gurobi command line application');
965
965
  }
966
966
  }
967
+ // Check if cplex(.exe) exists in its directory
968
+ let sp = path.join(cplex_path, 'cplex' + (PLATFORM.startsWith('win') ? '.exe' : ''));
969
+ const need_cplex = !settings.solver || settings.preferred_solver === 'cplex';
970
+ try {
971
+ fs.accessSync(sp, fs.constants.X_OK);
972
+ console.log('Path to CPLEX:', sp);
973
+ if(need_cplex) {
974
+ settings.solver = 'cplex';
975
+ settings.solver_path = sp;
976
+ }
977
+ } catch(err) {
978
+ // Only report error if CPLEX is needed
979
+ if(need_cplex) {
980
+ console.log(err.message);
981
+ console.log('WARNING: CPLEX application not found in', sp);
982
+ }
983
+ }
967
984
  // Check if scip(.exe) exists in its directory
968
- let sp = path.join(scip_path, 'scip' + (PLATFORM.startsWith('win') ? '.exe' : ''));
985
+ sp = path.join(scip_path, 'scip' + (PLATFORM.startsWith('win') ? '.exe' : ''));
969
986
  const need_scip = !settings.solver || settings.preferred_solver === 'scip';
970
987
  try {
971
988
  fs.accessSync(sp, fs.constants.X_OK);
@@ -982,10 +999,9 @@ function commandLineSettings() {
982
999
  }
983
1000
  }
984
1001
  // Check if lp_solve(.exe) exists in main directory
985
- const
986
- sp = path.join(WORKING_DIRECTORY,
987
- 'lp_solve' + (PLATFORM.startsWith('win') ? '.exe' : '')),
988
- need_lps = !settings.solver || settings.preferred_solver === 'lp_solve';
1002
+ sp = path.join(WORKING_DIRECTORY,
1003
+ 'lp_solve' + (PLATFORM.startsWith('win') ? '.exe' : ''));
1004
+ const need_lps = !settings.solver || settings.preferred_solver === 'lp_solve';
989
1005
  try {
990
1006
  fs.accessSync(sp, fs.constants.X_OK);
991
1007
  console.log('Path to LP_solve:', sp);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linny-r",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
package/server.js CHANGED
@@ -123,7 +123,7 @@ function checkNodeModule(name) {
123
123
  }
124
124
 
125
125
  // Currently, these external solvers are supported:
126
- const SUPPORTED_SOLVERS = ['gurobi', 'scip', 'lp_solve'];
126
+ const SUPPORTED_SOLVERS = ['gurobi', 'cplex', 'scip', 'lp_solve'];
127
127
 
128
128
  // Load class MILPSolver
129
129
  const MILPSolver = require('./static/scripts/linny-r-milp.js');
@@ -1458,6 +1458,7 @@ function commandLineSettings() {
1458
1458
  const path_list = process.env.PATH.split(path.delimiter);
1459
1459
  let gurobi_path = '',
1460
1460
  scip_path = '',
1461
+ cplex_path = '',
1461
1462
  match,
1462
1463
  max_v = -1;
1463
1464
  for(let i = 0; i < path_list.length; i++) {
@@ -1468,6 +1469,8 @@ function commandLineSettings() {
1468
1469
  }
1469
1470
  match = path_list[i].match(/[\/\\]scip[^\/\\]+[\/\\]bin/i);
1470
1471
  if(match) scip_path = path_list[i];
1472
+ match = path_list[i].match(/[\/\\]cplex[\/\\]bin/i);
1473
+ if(match) cplex_path = path_list[i];
1471
1474
  match = path_list[i].match(/inkscape/i);
1472
1475
  if(match) settings.inkscape = path_list[i];
1473
1476
  }
@@ -1498,8 +1501,25 @@ function commandLineSettings() {
1498
1501
  'WARNING: Failed to access the Gurobi command line application');
1499
1502
  }
1500
1503
  }
1504
+ // Check if cplex(.exe) exists in its directory
1505
+ let sp = path.join(cplex_path, 'cplex' + (PLATFORM.startsWith('win') ? '.exe' : ''));
1506
+ const need_cplex = !settings.solver || settings.preferred_solver === 'cplex';
1507
+ try {
1508
+ fs.accessSync(sp, fs.constants.X_OK);
1509
+ console.log('Path to CPLEX:', sp);
1510
+ if(need_cplex) {
1511
+ settings.solver = 'cplex';
1512
+ settings.solver_path = sp;
1513
+ }
1514
+ } catch(err) {
1515
+ // Only report error if CPLEX is needed
1516
+ if(need_cplex) {
1517
+ console.log(err.message);
1518
+ console.log('WARNING: CPLEX application not found in', sp);
1519
+ }
1520
+ }
1501
1521
  // Check if scip(.exe) exists in its directory
1502
- let sp = path.join(scip_path, 'scip' + (PLATFORM.startsWith('win') ? '.exe' : ''));
1522
+ sp = path.join(scip_path, 'scip' + (PLATFORM.startsWith('win') ? '.exe' : ''));
1503
1523
  const need_scip = !settings.solver || settings.preferred_solver === 'scip';
1504
1524
  try {
1505
1525
  fs.accessSync(sp, fs.constants.X_OK);
@@ -1595,7 +1615,7 @@ function createWorkspace() {
1595
1615
  data: path.join(SETTINGS.user_dir, 'data'),
1596
1616
  diagrams: path.join(SETTINGS.user_dir, 'diagrams'),
1597
1617
  modules: path.join(SETTINGS.user_dir, 'modules'),
1598
- solver_output: path.join(SETTINGS.user_dir, 'solver'),
1618
+ solver_output: path.join(SETTINGS.user_dir, 'solver')
1599
1619
  };
1600
1620
  // Create these sub-directories if not aready there
1601
1621
  try {
@@ -82,6 +82,27 @@ module.exports = class MILPSolver {
82
82
  14: 'Optimization still in progress',
83
83
  15: 'User-specified objective limit has been reached'
84
84
  };
85
+ } else if(this.id === 'cplex') {
86
+ this.ext = '.lp';
87
+ this.user_model = path.join(workspace.solver_output, 'usr_model.lp');
88
+ this.solver_model = path.join(workspace.solver_output, 'solver_model.lp');
89
+ this.solution = path.join(workspace.solver_output, 'model.sol');
90
+ // NOTE: CPLEX log file is located in the Linny-R working directory
91
+ this.log = path.join(workspace.solver_output, 'cplex.log');
92
+ // NOTE: CPLEX command line accepts space separated commands ...
93
+ this.args = [
94
+ 'read ' + this.user_model,
95
+ 'write ' + this.solver_model + ' lp',
96
+ 'set timelimit 300',
97
+ 'optimize',
98
+ 'write ' + this.solution + ' 0',
99
+ 'quit'
100
+ ];
101
+ // ... when CPLEX is called with -c option; then each command must be
102
+ // enclosed in double quotes; SCIP outputs its messages to a log file
103
+ // terminal, so these must be caputured in a log file
104
+ this.solve_cmd = 'cplex -c "' + this.args.join('" "') + '"';
105
+ this.errors = {};
85
106
  } else if(this.id === 'scip') {
86
107
  this.ext = '.lp';
87
108
  this.user_model = path.join(workspace.solver_output, 'usr_model.lp');
@@ -217,6 +238,18 @@ module.exports = class MILPSolver {
217
238
  this.args[0] = 'TimeLimit=' + timeout;
218
239
  const options = {windowsHide: true};
219
240
  spawn = child_process.spawnSync(this.solver_path, this.args, options);
241
+ } else if(this.id === 'cplex') {
242
+ // Delete previous solver model file (if any)
243
+ try {
244
+ if(this.solver_model) fs.unlinkSync(this.solver_model);
245
+ } catch(err) {
246
+ // Ignore error
247
+ }
248
+ // Spawn using the LP_solve approach
249
+ const
250
+ cmd = this.solve_cmd.replace(/timelimit \d+/, `timelimit ${timeout}`),
251
+ options = {shell: true, cwd: 'user/solver', stdio: 'ignore', windowsHide: true};
252
+ spawn = child_process.spawnSync(cmd, options);
220
253
  } else if(this.id === 'scip') {
221
254
  // When using SCIP, take the LP_solve approach
222
255
  const
@@ -311,6 +344,64 @@ module.exports = class MILPSolver {
311
344
  result.error = 'No solution found';
312
345
  }
313
346
  }
347
+ } else if(this.id === 'cplex') {
348
+ result.seconds = 0;
349
+ const
350
+ msg = fs.readFileSync(this.log, 'utf8'),
351
+ no_license = (msg.indexOf('No license found') >= 0);
352
+ // `messages` must be an array of strings
353
+ result.messages = msg.split(os.EOL);
354
+ let solved = false,
355
+ output = [];
356
+ if(no_license) {
357
+ result.error = 'Too many variables for unlicensed CPLEX solver';
358
+ result.status = -13;
359
+ } else if(result.status !== 0) {
360
+ // Non-zero solver exit code indicates serious trouble
361
+ result.error = 'CPLEX solver terminated with error';
362
+ result.status = -13;
363
+ } else {
364
+ try {
365
+ output = fs.readFileSync(this.solution, 'utf8').trim();
366
+ if(output.indexOf('CPLEXSolution') >= 0) {
367
+ solved = true;
368
+ output = output.split(os.EOL);
369
+ }
370
+ } catch(err) {
371
+ console.log('No CPLEX solution file');
372
+ }
373
+ }
374
+ if(solved) {
375
+ // CPLEX saves solution as XML, but for now just extract the
376
+ // status and then the variables
377
+ let i = 0;
378
+ while(i < output.length) {
379
+ const s = output[i].split('"');
380
+ if(s[0].indexOf('objectiveValue') >= 0) {
381
+ result.obj = s[1];
382
+ } else if(s[0].indexOf('solutionStatusValue') >= 0) {
383
+ result.status = s[1];
384
+ } else if(s[0].indexOf('solutionStatusString') >= 0) {
385
+ result.error = s[1];
386
+ break;
387
+ }
388
+ i++;
389
+ }
390
+ if(['1', '101', '102'].indexOf(result.status) >= 0) {
391
+ result.status = 0;
392
+ result.error = '';
393
+ }
394
+ // Fill dictionary with variable name: value entries
395
+ while(i < output.length) {
396
+ const m = output[i].match(/^.*name="(X[^"]+)".*value="([^"]+)"/);
397
+ if(m !== null) x_dict[m[1]] = m[2];
398
+ i++;
399
+ }
400
+ // Fill the solution vector, adding 0 for missing columns
401
+ getValuesFromDict();
402
+ } else {
403
+ console.log('No solution found');
404
+ }
314
405
  } else if(this.id === 'scip') {
315
406
  result.seconds = 0;
316
407
  // `messages` must be an array of strings
@@ -332,19 +423,21 @@ module.exports = class MILPSolver {
332
423
  const m = result.messages[i];
333
424
  if(m.startsWith('SCIP Status')) {
334
425
  if(m.indexOf('problem is solved') >= 0) {
335
- solved = true;
426
+ if(m.indexOf('infeasible') >= 0) {
427
+ result.status = (m.indexOf('unbounded') >= 0 ? 14 : 12);
428
+ } else if(m.indexOf('unbounded') >= 0) {
429
+ result.status = 13;
430
+ } else {
431
+ solved = true;
432
+ }
336
433
  } else if(m.indexOf('interrupted') >= 0) {
337
434
  if(m.indexOf('time limit') >= 0) {
338
435
  result.status = 5;
339
436
  } else if(m.indexOf('memory limit') >= 0) {
340
437
  result.status = 6;
341
- } else if(m.indexOf('infeasible') >= 0) {
342
- result.status = (m.indexOf('unbounded') >= 0 ? 14 : 12);
343
- } else if(m.indexOf('unbounded') >= 0) {
344
- result.status = 13;
345
438
  }
346
- result.error = this.errors[result.status];
347
439
  }
440
+ if(result.status) result.error = this.errors[result.status];
348
441
  } else if (m.startsWith('Solving Time')) {
349
442
  result.seconds = parseFloat(m.split(':')[1]);
350
443
  }
@@ -2249,7 +2249,7 @@ class VirtualMachine {
2249
2249
  }
2250
2250
  // NOTES:
2251
2251
  // (1) Processes have NO slack variables, because sufficient slack is
2252
- // provided by addinng slack variables to products; these slack
2252
+ // provided by adding slack variables to products; these slack
2253
2253
  // variables will have high cost penalty values in the objective
2254
2254
  // function, to serve as "last resort" to still obtain a solution when
2255
2255
  // the "real" product levels are overconstrained
@@ -2352,7 +2352,7 @@ class VirtualMachine {
2352
2352
  }
2353
2353
  }
2354
2354
  // Likewise get the upper bound
2355
- if(p.equal_bounds) {
2355
+ if(p.equal_bounds && p.lower_bound.defined) {
2356
2356
  u = l;
2357
2357
  } else if(p.upper_bound.defined) {
2358
2358
  if(p.upper_bound.isStatic) {
@@ -2575,9 +2575,9 @@ class VirtualMachine {
2575
2575
  // efficient, while cash flows are inferred properties and will not
2576
2576
  // result in an "unbounded problem" error message from the solver
2577
2577
  this.code.push(
2578
- [VMI_set_const_bounds, [a.cash_in_var_index,
2578
+ [VMI_set_bounds, [a.cash_in_var_index,
2579
2579
  VM.MINUS_INFINITY, VM.PLUS_INFINITY, true]],
2580
- [VMI_set_const_bounds, [a.cash_out_var_index,
2580
+ [VMI_set_bounds, [a.cash_out_var_index,
2581
2581
  VM.MINUS_INFINITY, VM.PLUS_INFINITY, true]]
2582
2582
  );
2583
2583
  }
@@ -2590,18 +2590,17 @@ class VirtualMachine {
2590
2590
  if(!MODEL.ignored_entities[k]) {
2591
2591
  p = MODEL.processes[k];
2592
2592
  lbx = p.lower_bound;
2593
- ubx = (p.equal_bounds ? lbx : p.upper_bound);
2593
+ // NOTE: if UB = LB, set UB to LB only if LB is defined,
2594
+ // because LB expressions default to -INF while UB expressions
2595
+ // default to + INF
2596
+ ubx = (p.equal_bounds && lbx.defined ? lbx : p.upper_bound);
2597
+ if(lbx.isStatic) lbx = lbx.result(0);
2598
+ if(ubx.isStatic) ubx = ubx.result(0);
2594
2599
  // NOTE: pass TRUE as fourth parameter to indicate that +INF
2595
2600
  // and -INF can be coded as the infinity values used by the
2596
2601
  // solver, rather than the Linny-R values used to detect
2597
2602
  // unbounded problems
2598
- if(lbx.isStatic && ubx.isStatic) {
2599
- this.code.push([VMI_set_const_bounds,
2600
- [p.level_var_index, lbx.result(0), ubx.result(0), true]]);
2601
- } else {
2602
- this.code.push([VMI_set_var_bounds,
2603
- [p.level_var_index, lbx, ubx, true]]);
2604
- }
2603
+ this.code.push([VMI_set_bounds, [p.level_var_index, lbx, ubx, true]]);
2605
2604
  // Add level variable index to "fixed" list for specified rounds
2606
2605
  const rf = p.actor.round_flags;
2607
2606
  if(rf != 0) {
@@ -2629,17 +2628,17 @@ class VirtualMachine {
2629
2628
  // If no slack, the bound constraints can be set on the
2630
2629
  // variables themselves
2631
2630
  lbx = p.lower_bound;
2632
- ubx = (p.equal_bounds ? lbx : p.upper_bound);
2633
- if(lbx.isStatic && ubx.isStatic) {
2634
- this.code.push([VMI_set_const_bounds,
2635
- [vi, lbx.result(0), ubx.result(0)]]);
2636
- } else {
2637
- this.code.push([VMI_set_var_bounds, [vi, lbx, ubx]]);
2638
- }
2631
+ // NOTE: if UB = LB, set UB to LB only if LB is defined,
2632
+ // because LB expressions default to -INF while UB expressions
2633
+ // default to + INF
2634
+ ubx = (p.equal_bounds && lbx.defined ? lbx : p.upper_bound);
2635
+ if(lbx.isStatic) lbx = lbx.result(0);
2636
+ if(ubx.isStatic) ubx = ubx.result(0);
2637
+ this.code.push([VMI_set_bounds, [vi, lbx, ubx]]);
2639
2638
  } else {
2640
2639
  // Otherwise, set bounds of stock variable to -INF and +INF,
2641
2640
  // as product constraints will be added later on
2642
- this.code.push([VMI_set_const_bounds,
2641
+ this.code.push([VMI_set_bounds,
2643
2642
  [vi, VM.MINUS_INFINITY, VM.PLUS_INFINITY]]);
2644
2643
  }
2645
2644
  }
@@ -3224,7 +3223,7 @@ class VirtualMachine {
3224
3223
  // To deal with this, the default equations will NOT be set when UB <= 0,
3225
3224
  // while the "exceptional" equations (q.v.) will NOT be set when UB > 0.
3226
3225
  // This can be realized using a special VM instruction:
3227
- ubx = (p.equal_bounds && p.lower_bound.text ? p.lower_bound : p.upper_bound);
3226
+ ubx = (p.equal_bounds && p.lower_bound.defined ? p.lower_bound : p.upper_bound);
3228
3227
  this.code.push([VMI_set_add_constraints_flag, [ubx, '>', 0]]);
3229
3228
 
3230
3229
  // NOTE: if UB <= 0 the following constraints will be prepared but NOT added
@@ -4731,8 +4730,6 @@ class VirtualMachine {
4731
4730
  return [-2, 2, 6];
4732
4731
  } else if(this.solver_name === 'gurobi') {
4733
4732
  return [1, 3, 4, 6, 11, 12, 14];
4734
- } else if(this.solver_name === 'scip') {
4735
- return [];
4736
4733
  } else {
4737
4734
  return [];
4738
4735
  }
@@ -6446,8 +6443,8 @@ from) the k'th coefficient.
6446
6443
 
6447
6444
  */
6448
6445
 
6449
- function VMI_set_const_bounds(args) {
6450
- // `args`: [var_index, number, number]
6446
+ function VMI_set_bounds(args) {
6447
+ // `args`: [var_index, number or expression, number or expression]
6451
6448
  const
6452
6449
  vi = args[0],
6453
6450
  vbl = VM.variables[vi - 1][1],
@@ -6465,7 +6462,8 @@ function VMI_set_const_bounds(args) {
6465
6462
  // if this is the first round
6466
6463
  if(VM.current_round) {
6467
6464
  l = vbl.actualLevel(VM.t);
6468
- //PATCH!!
6465
+ // QUICK PATCH! should resolve that small non-zero process levels
6466
+ // computed in prior round make problem infeasible
6469
6467
  if(l < 0.0005) l = 0;
6470
6468
  } else {
6471
6469
  l = 0;
@@ -6474,17 +6472,22 @@ function VMI_set_const_bounds(args) {
6474
6472
  fixed = ' (FIXED ' + vbl.displayName + ')';
6475
6473
  } else {
6476
6474
  // Set bounds as specified by the two arguments
6477
- l = (args[1] === VM.UNDEFINED ? 0 : args[1]),
6478
- u = Math.min(args[2], inf_val);
6475
+ l = args[1];
6476
+ if(l instanceof Expression) l = l.result(VM.t);
6477
+ if(l === VM.UNDEFINED) l = 0;
6478
+ u = args[2];
6479
+ if(u instanceof Expression) u = u.result(VM.t);
6480
+ u = Math.min(u, VM.PLUS_INFINITY);
6479
6481
  if(solver_inf) {
6480
6482
  if(l === VM.MINUS_INFINITY) l = -inf_val;
6481
6483
  if(u === VM.PLUS_INFINITY) u = inf_val;
6482
6484
  }
6483
6485
  fixed = '';
6484
6486
  }
6485
- // NOTE: to check, add this to the condition below: fixed !== ''
6487
+ // NOTE: to see in the console whether fixing across rounds works, insert
6488
+ // "fixed !== '' || " before DEBUGGING below
6486
6489
  if(DEBUGGING) {
6487
- console.log(['set_const_bounds [', k, '] ', vbl.displayName, ' t = ', VM.t,
6490
+ console.log(['set_bounds [', k, '] ', vbl.displayName, ' t = ', VM.t,
6488
6491
  ' LB = ', VM.sig4Dig(l), ', UB = ', VM.sig4Dig(u), fixed].join(''));
6489
6492
  }
6490
6493
  // NOTE: since the VM vectors for lower bounds and upper bounds are
@@ -6511,61 +6514,6 @@ function VMI_set_const_bounds(args) {
6511
6514
  }
6512
6515
  }
6513
6516
 
6514
- function VMI_set_var_bounds(args) {
6515
- // `args`: [var_index, expression, expression]
6516
- const
6517
- vi = args[0],
6518
- vbl = VM.variables[vi - 1][1],
6519
- k = VM.offset + vi,
6520
- r = VM.round_letters.indexOf(VM.round_sequence[VM.current_round]),
6521
- // Optional fourth parameter indicates whether the solver's
6522
- // infinity values should be used
6523
- solver_inf = args.length > 3 && args[3],
6524
- inf_val = (solver_inf ? VM.SOLVER_PLUS_INFINITY : VM.PLUS_INFINITY);
6525
- let l,
6526
- u,
6527
- fixed = (vi in VM.fixed_var_indices[r - 1]);
6528
- if(fixed) {
6529
- // Set both bounds equal to the level set in the previous round, or to 0
6530
- // if this is the first round
6531
- if(VM.current_round) {
6532
- l = vbl.actualLevel(VM.t);
6533
- } else {
6534
- l = 0;
6535
- }
6536
- u = l;
6537
- fixed = ' (FIXED ' + vbl.displayName + ')';
6538
- } else {
6539
- l = args[1].result(VM.t);
6540
- if(l === VM.UNDEFINED) l = 0;
6541
- u = Math.min(args[2].result(VM.t), inf_val);
6542
- if(solver_inf) {
6543
- if(l === VM.MINUS_INFINITY) l = -inf_val;
6544
- if(u === VM.PLUS_INFINITY) u = inf_val;
6545
- }
6546
- fixed = '';
6547
- }
6548
- // Here, too, no need to set default values
6549
- if(Math.abs(l) > VM.NEAR_ZERO || u !== inf_val) {
6550
- VM.lower_bounds[k] = l;
6551
- VM.upper_bounds[k] = u;
6552
- // Check for peak increase -- see comments in VMI_set_const_bound
6553
- if(vbl.peak_inc_var_index >= 0) {
6554
- u = Math.max(0, u - vbl.b_peak[VM.block_count - 1]);
6555
- const
6556
- cvi = VM.chunk_offset + vbl.peak_inc_var_index,
6557
- piub = VM.upper_bounds[cvi];
6558
- if(piub) u = Math.max(piub, u);
6559
- VM.upper_bounds[cvi] = u;
6560
- VM.upper_bounds[cvi + 1] = u;
6561
- }
6562
- }
6563
- if(DEBUGGING) {
6564
- console.log(['set_var_bounds [', k, '] ', vbl.displayName, ' t = ', VM.t,
6565
- ' LB = ', l, ', UB = ', u, fixed].join(''));
6566
- }
6567
- }
6568
-
6569
6517
  function VMI_clear_coefficients(empty) {
6570
6518
  if(DEBUGGING) console.log('clear_coefficients');
6571
6519
  VM.coefficients = {};