linny-r 1.3.1 → 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.
@@ -77,6 +77,24 @@ class Expression {
77
77
  return MODEL.timeStepDuration;
78
78
  }
79
79
 
80
+ get referencedEntities() {
81
+ // Returns a list of entities referenced in this expression
82
+ if(this.text.indexOf('[') < 0) return [];
83
+ const
84
+ el = [],
85
+ ml = [...this.text.matchAll(/\[(\{[^\}]+\}){0,1}([^\]]+)\]/g)];
86
+ for(let i = 0; i < ml.length; i++) {
87
+ const n = ml[i][2].trim();
88
+ let sep = n.lastIndexOf('|');
89
+ if(sep < 0) sep = n.lastIndexOf('@');
90
+ const
91
+ en = (sep < 0 ? n : n.substring(0, sep)),
92
+ e = MODEL.objectByName(en.trim());
93
+ if(e) addDistinct(e, el);
94
+ }
95
+ return el;
96
+ }
97
+
80
98
  update(parser) {
81
99
  // Must be called after successful compilation by the expression parser
82
100
  this.text = parser.expr;
@@ -216,7 +234,7 @@ class Expression {
216
234
  if(!this.compiled) this.compile();
217
235
  return this.is_static;
218
236
  }
219
-
237
+
220
238
  trace(action) {
221
239
  // Adds step stack (if any) and action to the trace.
222
240
  if(DEBUGGING) {
@@ -343,7 +361,7 @@ class Expression {
343
361
  }
344
362
  return v[t];
345
363
  }
346
-
364
+
347
365
  get asAttribute() {
348
366
  // Returns the result for the current time step if the model has been solved
349
367
  // (special values as human-readable string), or the expression as text
@@ -1604,8 +1622,9 @@ class VirtualMachine {
1604
1622
  this.attribute_names = {
1605
1623
  'LB': 'lower bound',
1606
1624
  'UB': 'upper bound',
1607
- 'L': 'level',
1608
1625
  'IL': 'initial level',
1626
+ 'LCF': 'level change frequency',
1627
+ 'L': 'level',
1609
1628
  'P': 'price',
1610
1629
  'CP': 'cost price',
1611
1630
  'HCP': 'highest cost price',
@@ -1622,21 +1641,21 @@ class VirtualMachine {
1622
1641
  // NOTE: defaults are level (L), link flow (F), cluster cash flow (CF),
1623
1642
  // actor cash flow (CF); dataset value (no attribute)
1624
1643
  // NOTE: exogenous properties first, then the computed properties
1625
- this.process_attr = ['LB', 'UB', 'IL', 'L', 'CI', 'CO', 'CF', 'CP'];
1644
+ this.process_attr = ['LB', 'UB', 'IL', 'LCF', 'L', 'CI', 'CO', 'CF', 'CP'];
1626
1645
  this.product_attr = ['LB', 'UB', 'IL', 'P', 'L', 'CP', 'HCP'];
1627
1646
  this.cluster_attr = ['CI', 'CO', 'CF'];
1628
1647
  this.link_attr = ['R', 'D', 'SOC', 'F'];
1629
1648
  this.constraint_attr = ['SOC', 'A'];
1630
1649
  this.actor_attr = ['W', 'CI', 'CO', 'CF'];
1631
1650
  // Only expression attributes can be used for sensitivity analysis
1632
- this.expression_attr = ['LB', 'UB', 'IL', 'P', 'R', 'W'];
1651
+ this.expression_attr = ['LB', 'UB', 'IL', 'LCF', 'P', 'R', 'D', 'W'];
1633
1652
  // Attributes per entity type letter
1634
1653
  this.attribute_codes = {
1635
1654
  A: this.actor_attr,
1636
1655
  B: this.constraint_attr,
1637
1656
  C: this.cluster_attr,
1638
- D: ['D'],
1639
- E: ['X'],
1657
+ D: ['DSM'], // ("dataset modifier" -- placeholder value, not used)
1658
+ E: ['X'], // ("expression" -- placeholder value, not used)
1640
1659
  L: this.link_attr,
1641
1660
  P: this.process_attr,
1642
1661
  Q: this.product_attr
@@ -2230,7 +2249,7 @@ class VirtualMachine {
2230
2249
  }
2231
2250
  // NOTES:
2232
2251
  // (1) Processes have NO slack variables, because sufficient slack is
2233
- // provided by addinng slack variables to products; these slack
2252
+ // provided by adding slack variables to products; these slack
2234
2253
  // variables will have high cost penalty values in the objective
2235
2254
  // function, to serve as "last resort" to still obtain a solution when
2236
2255
  // the "real" product levels are overconstrained
@@ -2282,8 +2301,7 @@ class VirtualMachine {
2282
2301
  let l = '';
2283
2302
  for(let i = 0; i < vcnt; i++) {
2284
2303
  const obj = this.variables[i][1];
2285
- let v = (this.solver_name === 'lp_solve' ?
2286
- 'C' + (i+1) : 'X' + (i+1).toString().padStart(z, '0'));
2304
+ let v = 'X' + (i+1).toString().padStart(z, '0');
2287
2305
  v += ' '.slice(v.length) + obj.displayName;
2288
2306
  const p = (obj instanceof Process && obj.pace > 1 ? ' 1/' + obj.pace : '');
2289
2307
  l += v + ' [' + this.variables[i][0] + p + ']\n';
@@ -2297,8 +2315,7 @@ class VirtualMachine {
2297
2315
  obj = this.chunk_variables[i][1],
2298
2316
  // NOTE: chunk offset takes into account that indices are 0-based
2299
2317
  cvi = chof + i;
2300
- let v = (this.solver_name === 'lp_solve' ?
2301
- 'C' + cvi : 'X' + cvi.toString().padStart(z, '0'));
2318
+ let v = 'X' + cvi.toString().padStart(z, '0');
2302
2319
  v += ' '.slice(v.length) + obj.displayName;
2303
2320
  l += v + ' [' + this.chunk_variables[i][0] + ']\n';
2304
2321
  }
@@ -2335,7 +2352,7 @@ class VirtualMachine {
2335
2352
  }
2336
2353
  }
2337
2354
  // Likewise get the upper bound
2338
- if(p.equal_bounds) {
2355
+ if(p.equal_bounds && p.lower_bound.defined) {
2339
2356
  u = l;
2340
2357
  } else if(p.upper_bound.defined) {
2341
2358
  if(p.upper_bound.isStatic) {
@@ -2558,9 +2575,9 @@ class VirtualMachine {
2558
2575
  // efficient, while cash flows are inferred properties and will not
2559
2576
  // result in an "unbounded problem" error message from the solver
2560
2577
  this.code.push(
2561
- [VMI_set_const_bounds, [a.cash_in_var_index,
2578
+ [VMI_set_bounds, [a.cash_in_var_index,
2562
2579
  VM.MINUS_INFINITY, VM.PLUS_INFINITY, true]],
2563
- [VMI_set_const_bounds, [a.cash_out_var_index,
2580
+ [VMI_set_bounds, [a.cash_out_var_index,
2564
2581
  VM.MINUS_INFINITY, VM.PLUS_INFINITY, true]]
2565
2582
  );
2566
2583
  }
@@ -2573,18 +2590,17 @@ class VirtualMachine {
2573
2590
  if(!MODEL.ignored_entities[k]) {
2574
2591
  p = MODEL.processes[k];
2575
2592
  lbx = p.lower_bound;
2576
- 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);
2577
2599
  // NOTE: pass TRUE as fourth parameter to indicate that +INF
2578
2600
  // and -INF can be coded as the infinity values used by the
2579
2601
  // solver, rather than the Linny-R values used to detect
2580
2602
  // unbounded problems
2581
- if(lbx.isStatic && ubx.isStatic) {
2582
- this.code.push([VMI_set_const_bounds,
2583
- [p.level_var_index, lbx.result(0), ubx.result(0), true]]);
2584
- } else {
2585
- this.code.push([VMI_set_var_bounds,
2586
- [p.level_var_index, lbx, ubx, true]]);
2587
- }
2603
+ this.code.push([VMI_set_bounds, [p.level_var_index, lbx, ubx, true]]);
2588
2604
  // Add level variable index to "fixed" list for specified rounds
2589
2605
  const rf = p.actor.round_flags;
2590
2606
  if(rf != 0) {
@@ -2612,17 +2628,17 @@ class VirtualMachine {
2612
2628
  // If no slack, the bound constraints can be set on the
2613
2629
  // variables themselves
2614
2630
  lbx = p.lower_bound;
2615
- ubx = (p.equal_bounds ? lbx : p.upper_bound);
2616
- if(lbx.isStatic && ubx.isStatic) {
2617
- this.code.push([VMI_set_const_bounds,
2618
- [vi, lbx.result(0), ubx.result(0)]]);
2619
- } else {
2620
- this.code.push([VMI_set_var_bounds, [vi, lbx, ubx]]);
2621
- }
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]]);
2622
2638
  } else {
2623
2639
  // Otherwise, set bounds of stock variable to -INF and +INF,
2624
2640
  // as product constraints will be added later on
2625
- this.code.push([VMI_set_const_bounds,
2641
+ this.code.push([VMI_set_bounds,
2626
2642
  [vi, VM.MINUS_INFINITY, VM.PLUS_INFINITY]]);
2627
2643
  }
2628
2644
  }
@@ -3207,7 +3223,7 @@ class VirtualMachine {
3207
3223
  // To deal with this, the default equations will NOT be set when UB <= 0,
3208
3224
  // while the "exceptional" equations (q.v.) will NOT be set when UB > 0.
3209
3225
  // This can be realized using a special VM instruction:
3210
- 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);
3211
3227
  this.code.push([VMI_set_add_constraints_flag, [ubx, '>', 0]]);
3212
3228
 
3213
3229
  // NOTE: if UB <= 0 the following constraints will be prepared but NOT added
@@ -3607,11 +3623,11 @@ class VirtualMachine {
3607
3623
  // simulation end time)
3608
3624
  const
3609
3625
  ncv = this.chunk_variables.length,
3626
+ ncv_msg = (ncv ? ' minus ' + pluralS(ncv, 'singular variable') : ''),
3610
3627
  xratio = (x.length - ncv) / this.cols,
3611
3628
  xbl = Math.floor(xratio);
3612
3629
  if(xbl < xratio) console.log('ANOMALY: solution vector length', x.length,
3613
- 'minus', ncv, 'singular variables is not a multiple of # columns',
3614
- this.cols);
3630
+ ncv_msg + ' is not a multiple of # columns', this.cols);
3615
3631
  if(xbl < abl) {
3616
3632
  console.log('Cropping actual block length', abl,
3617
3633
  'to solved block length', xbl);
@@ -4257,13 +4273,42 @@ class VirtualMachine {
4257
4273
  (this.block_count - 1) * MODEL.block_length;
4258
4274
  }
4259
4275
 
4260
- writeLpSolveFormat() {
4276
+ get columnsInBlock() {
4277
+ // Returns the actual block length plus the number of chunk variables
4278
+ return this.actualBlockLength * this.cols + this.chunk_variables.length;
4279
+ }
4280
+
4281
+ writeLpFormat(cplex=false) {
4261
4282
  // NOTE: actual block length `abl` of last block is likely to be
4262
4283
  // shorter than the standard, as it should not go beyond the end time
4263
- const abl = this.actualBlockLength;
4284
+
4285
+
4286
+ const
4287
+ abl = this.actualBlockLength,
4288
+ // Get the number digits for variable names
4289
+ z = this.columnsInBlock.toString().length,
4290
+ // LP_solve uses semicolon as separator between equations
4291
+ EOL = (cplex ? '\n' : ';\n'),
4292
+ // Local function that returns variable symbol (e.g. X001) with
4293
+ // its coefficient if specified (e.g., -0.123 X001) in the
4294
+ // most compact notation
4295
+ vbl = (index, c=false) => {
4296
+ const v = 'X' + index.toString().padStart(z, '0');
4297
+ if(c === false) return v; // Only the symbol
4298
+ if(c === -1) return ` -${v}`; // No coefficient needed
4299
+ if(c < 0) return ` ${c} ${v}`; // Number had minus sign
4300
+ if(c === 1) return ` +${v}`; // No coefficient needed
4301
+ return ` +${c} ${v}`; // Prefix coefficient with +
4302
+ // NOTE: this may return +0 X001
4303
+ };
4304
+
4264
4305
  this.numeric_issue = '';
4265
4306
  // First add the objective (always MAXimize)
4266
- this.lines = '/* Objective function */\nmax:\n';
4307
+ if(cplex) {
4308
+ this.lines = 'Maximize\n';
4309
+ } else {
4310
+ this.lines = '/* Objective function */\nmax:\n';
4311
+ }
4267
4312
  let c,
4268
4313
  p,
4269
4314
  line = '';
@@ -4277,25 +4322,7 @@ class VirtualMachine {
4277
4322
  this.setNumericIssue(c, p, 'objective function coefficient');
4278
4323
  break;
4279
4324
  }
4280
- if(c === -1) {
4281
- // No coefficient needed
4282
- line += ' -C' + p;
4283
- } else if(c < 0) {
4284
- // Minus sign already included in c
4285
- line += ' ' + c + ' C' + p;
4286
- } else if (c === 1) {
4287
- // No coefficient needed
4288
- line += ' +C' + p;
4289
- } else {
4290
- // Prefix coefficient with + sign
4291
- // NOTE: do NOT check for near-zero -- see note below!
4292
- line += ' +' + c + ' C' + p;
4293
- }
4294
- } else {
4295
- // Add variable with coefficient 0 to the objective
4296
- // NOTE: This may result in warnings by the solver; however, this is
4297
- // needed to maintain the variables in their order, so do not modify!
4298
- line += ' +0 C' + p;
4325
+ line += vbl(p, c);
4299
4326
  }
4300
4327
  // Keep lines under approx. 110 chars
4301
4328
  if(line.length >= 100) {
@@ -4303,10 +4330,14 @@ class VirtualMachine {
4303
4330
  line = '';
4304
4331
  }
4305
4332
  }
4306
- this.lines += line + ';\n';
4333
+ this.lines += line + EOL;
4307
4334
  line = '';
4308
4335
  // Add the row constraints
4309
- this.lines += '\n/* Constraints */\n';
4336
+ if(cplex) {
4337
+ this.lines += '\nSubject To\n';
4338
+ } else {
4339
+ this.lines += '\n/* Constraints */\n';
4340
+ }
4310
4341
  n = this.matrix.length;
4311
4342
  for(let r = 0; r < n; r++) {
4312
4343
  const row = this.matrix[r];
@@ -4316,16 +4347,8 @@ class VirtualMachine {
4316
4347
  this.setNumericIssue(c, p, 'constraint coefficient');
4317
4348
  break;
4318
4349
  }
4319
- if(c === -1) {
4320
- line += ' -C' + p;
4321
- } else if(c < 0) {
4322
- line += ' ' + c + ' C' + p;
4323
- } else if (c === 1) {
4324
- line += ' +C' + p;
4325
- } else {
4326
- line += ' +' + c + ' C' + p;
4327
- }
4328
- // Keep lines under approx. 80 chars
4350
+ line += vbl(p, c);
4351
+ // Keep lines under approx. 110 chars
4329
4352
  if(line.length >= 100) {
4330
4353
  this.lines += line + '\n';
4331
4354
  line = '';
@@ -4333,11 +4356,15 @@ class VirtualMachine {
4333
4356
  }
4334
4357
  c = this.right_hand_side[r];
4335
4358
  this.lines += line + ' ' +
4336
- this.constraint_symbols[this.constraint_types[r]] + ' ' + c + ';\n';
4359
+ this.constraint_symbols[this.constraint_types[r]] + ' ' + c + EOL;
4337
4360
  line = '';
4338
4361
  }
4339
4362
  // Add the variable bounds
4340
- this.lines += '\n/* Variable bounds */\n';
4363
+ if(cplex) {
4364
+ this.lines += '\nBounds\n';
4365
+ } else {
4366
+ this.lines += '\n/* Variable bounds */\n';
4367
+ }
4341
4368
  n = abl * this.cols;
4342
4369
  for(p = 1; p <= n; p++) {
4343
4370
  let lb = null,
@@ -4359,44 +4386,115 @@ class VirtualMachine {
4359
4386
  }
4360
4387
  line = '';
4361
4388
  if(lb === ub) {
4362
- if(lb !== null) line = 'C' + p + ' = ' + lb;
4389
+ if(lb !== null) line = ` ${vbl(p)} = ${lb}`;
4363
4390
  } else {
4364
- line = 'C' + p;
4365
4391
  // NOTE: by default, lower bound of variables is 0
4366
- if(lb !== null && lb !== 0) line = lb + ' <= ' + line;
4367
- if(ub !== null) line += ' <= ' + ub;
4392
+ line = ` ${vbl(p)}`;
4393
+ if(cplex) {
4394
+ // Explicitly denote free variables
4395
+ if(lb === null && ub === null && !this.is_binary[p]) {
4396
+ line += ' free';
4397
+ } else {
4398
+ // Separate lines for LB and UB if specified
4399
+ if(ub !== null) line += ' <= ' + ub;
4400
+ if(lb !== null && lb !== 0) line += `\n ${vbl(p)} >= ${lb}`;
4401
+ }
4402
+ } else {
4403
+ // Bounds can be specified on a single line: lb <= X001 <= ub
4404
+ if(lb !== null && lb !== 0) line = lb + ' <= ' + line;
4405
+ if(ub !== null) line += ' <= ' + ub;
4406
+ }
4368
4407
  }
4369
- if(line) this.lines += line + ';\n';
4408
+ if(line) this.lines += line + EOL;
4370
4409
  }
4371
4410
  // Add the special variable types
4372
- const v_set = [];
4373
- // NOTE: for binary variables, add the constraint <= 1
4374
- for(let i in this.is_binary) if(Number(i)) {
4375
- this.lines += 'C' + i + ' <= 1;\n';
4376
- v_set.push('C' + i);
4377
- }
4378
- for(let i in this.is_integer) if(Number(i)) v_set.push('C' + i);
4379
- if(v_set.length > 0) this.lines += 'int ' + v_set.join(', ') + ';\n';
4380
- // Clear the INT variable list
4381
- v_set.length = 0;
4382
- // Add the semi-continuous variables
4383
- for(let i in this.is_semi_continuous) if(Number(i)) v_set.push('C' + i);
4384
- if(v_set.length > 0) this.lines += 'sec ' + v_set.join(', ') + ';\n';
4385
- // Add the SOS section
4386
- if(this.sos_var_indices.length > 0) {
4387
- this.lines += 'sos\n';
4388
- let sos = 1;
4389
- for(let j = 0; j < abl; j++) {
4390
- for(let i = 0; i < this.sos_var_indices.length; i++) {
4391
- v_set.length = 0;
4392
- let vi = this.sos_var_indices[i][0] + j * this.cols;
4393
- const n = this.sos_var_indices[i][1];
4394
- for(let j = 1; j <= n; j++) {
4395
- v_set.push('C' + vi);
4396
- vi++;
4411
+ if(cplex) {
4412
+ line = '';
4413
+ let scv = 0;
4414
+ for(let i in this.is_binary) if(Number(i)) {
4415
+ line += ' ' + vbl(i);
4416
+ scv++;
4417
+ // Max. 10 variables per line
4418
+ if(scv >= 10) line += '\n';
4419
+ }
4420
+ if(scv) {
4421
+ this.lines += `Binary\n${line}\n`;
4422
+ line = '';
4423
+ scv = 0;
4424
+ }
4425
+ for(let i in this.is_integer) if(Number(i)) {
4426
+ line += ' ' + vbl(i);
4427
+ scv++;
4428
+ // Max. 10 variables per line
4429
+ if(scv >= 10) line += '\n';
4430
+ }
4431
+ if(scv) {
4432
+ this.lines += `General\n${line}\n`;
4433
+ line = '';
4434
+ scv = 0;
4435
+ }
4436
+ for(let i in this.is_semi_continuous) if(Number(i)) {
4437
+ line += ' '+ vbl(i);
4438
+ scv++;
4439
+ // Max. 10 variables per line
4440
+ if(scv >= 10) line += '\n';
4441
+ }
4442
+ if(scv) {
4443
+ this.lines += `Semi-continuous\n${line}\n`;
4444
+ line = '';
4445
+ scv = 0;
4446
+ }
4447
+ if(this.sos_var_indices.length > 0) {
4448
+ this.lines += 'SOS\n';
4449
+ let sos = 0;
4450
+ const v_set = [];
4451
+ for(let j = 0; j < abl; j++) {
4452
+ for(let i = 0; i < this.sos_var_indices.length; i++) {
4453
+ v_set.length = 0;
4454
+ let vi = this.sos_var_indices[i][0] + j * this.cols;
4455
+ const n = this.sos_var_indices[i][1];
4456
+ for(let j = 1; j <= n; j++) {
4457
+ v_set.push(`${vbl(vi)}:${j}`);
4458
+ vi++;
4459
+ }
4460
+ this.lines += ` s${sos}: S2:: ${v_set.join(' ')}\n`;
4461
+ sos++;
4462
+ }
4463
+ }
4464
+ }
4465
+ this.lines += 'End';
4466
+ } else {
4467
+ // NOTE: LP_solve does not differentiate between binary and integer,
4468
+ // so for binary variables, the constraint <= 1 must be added
4469
+ const v_set = [];
4470
+ for(let i in this.is_binary) if(Number(i)) {
4471
+ const v = vbl(i);
4472
+ this.lines += `${v} <= 1;\n`;
4473
+ v_set.push(v);
4474
+ }
4475
+ for(let i in this.is_integer) if(Number(i)) v_set.push(vbl(i));
4476
+ if(v_set.length > 0) this.lines += 'int ' + v_set.join(', ') + ';\n';
4477
+ // Clear the INT variable list
4478
+ v_set.length = 0;
4479
+ // Add the semi-continuous variables
4480
+ for(let i in this.is_semi_continuous) if(Number(i)) v_set.push(vbl(i));
4481
+ if(v_set.length > 0) this.lines += 'sec ' + v_set.join(', ') + ';\n';
4482
+ // Add the SOS section
4483
+ if(this.sos_var_indices.length > 0) {
4484
+ this.lines += 'sos\n';
4485
+ let sos = 1;
4486
+ for(let j = 0; j < abl; j++) {
4487
+ for(let i = 0; i < this.sos_var_indices.length; i++) {
4488
+ v_set.length = 0;
4489
+ let vi = this.sos_var_indices[i][0] + j * this.cols;
4490
+ const n = this.sos_var_indices[i][1];
4491
+ for(let j = 1; j <= n; j++) {
4492
+ v_set.push(vbl(vi));
4493
+ vi++;
4494
+ }
4495
+ this.lines += `SOS${sos}: ${v_set.join(',')} <= 2;\n`;
4496
+ sos++;
4397
4497
  }
4398
- this.lines += `SOS${sos}: ${v_set.join(',')} <= 2;\n`;
4399
- sos++;
4400
4498
  }
4401
4499
  }
4402
4500
  }
@@ -4485,7 +4583,16 @@ class VirtualMachine {
4485
4583
  this.lines += 'COLUMNS\n';
4486
4584
  for(c = 1; c <= ncol; c++) {
4487
4585
  const col_lbl = ' X' + c.toString().padStart(this.decimals, '0') + ' ';
4488
- this.lines += col_lbl + cols[c].join('\n' + col_lbl) + '\n';
4586
+ // NOTE: if processes have no in- or outgoing links their decision
4587
+ // variable does not occur in any constraint, and this may cause
4588
+ // problems for solvers that cannot handle columns having a blank
4589
+ // row name (e.g., CPLEX). To prevent errors, these columns are
4590
+ // given coefficient 0 in the OBJ row
4591
+ if(cols[c].length) {
4592
+ this.lines += col_lbl + cols[c].join('\n' + col_lbl) + '\n';
4593
+ } else {
4594
+ this.lines += col_lbl + ' OBJ 0\n';
4595
+ }
4489
4596
  }
4490
4597
  // Free up memory
4491
4598
  cols.length = 0;
@@ -4537,7 +4644,7 @@ class VirtualMachine {
4537
4644
  }
4538
4645
  }
4539
4646
  bnd = ' BND X' + p.toString().padStart(this.decimals, '0') + ' ';
4540
- /* MPS format bound types:
4647
+ /* Gurobi uses these MPS format bound types:
4541
4648
  LO lower bound
4542
4649
  UP upper bound
4543
4650
  FX variable is fixed at the specified value
@@ -4617,7 +4724,7 @@ class VirtualMachine {
4617
4724
  }
4618
4725
 
4619
4726
  get noSolutionStatus() {
4620
- // Returns set of status codes that sindicate that solver did not return
4727
+ // Returns set of status codes that indicate that solver did not return
4621
4728
  // a solution (so look-ahead should be conserved)
4622
4729
  if(this.solver_name === 'lp_solve') {
4623
4730
  return [-2, 2, 6];
@@ -4780,10 +4887,15 @@ Solver status = ${json.status}`);
4780
4887
  this.show_progress = false;
4781
4888
  }
4782
4889
  // Generate lines of code in format that should be accepted by solver
4783
- if(this.solver_name === 'lp_solve') {
4784
- this.writeLpSolveFormat();
4785
- } else if(this.solver_name === 'gurobi') {
4890
+ if(this.solver_name === 'gurobi') {
4786
4891
  this.writeMPSFormat();
4892
+ } else if(this.solver_name === 'scip' || this.solver_name === 'cplex') {
4893
+ // NOTE: the CPLEX LP format that is also used by SCIP differs from
4894
+ // the LP_solve format that was used by the first versions of Linny-R;
4895
+ // TRUE indicates "CPLEX format"
4896
+ this.writeLpFormat(true);
4897
+ } else if(this.solver_name === 'lp_solve') {
4898
+ this.writeLpFormat(false);
4787
4899
  } else {
4788
4900
  this.numeric_issue = 'solver name: ' + this.solver_name;
4789
4901
  }
@@ -6331,8 +6443,8 @@ from) the k'th coefficient.
6331
6443
 
6332
6444
  */
6333
6445
 
6334
- function VMI_set_const_bounds(args) {
6335
- // `args`: [var_index, number, number]
6446
+ function VMI_set_bounds(args) {
6447
+ // `args`: [var_index, number or expression, number or expression]
6336
6448
  const
6337
6449
  vi = args[0],
6338
6450
  vbl = VM.variables[vi - 1][1],
@@ -6350,7 +6462,8 @@ function VMI_set_const_bounds(args) {
6350
6462
  // if this is the first round
6351
6463
  if(VM.current_round) {
6352
6464
  l = vbl.actualLevel(VM.t);
6353
- //PATCH!!
6465
+ // QUICK PATCH! should resolve that small non-zero process levels
6466
+ // computed in prior round make problem infeasible
6354
6467
  if(l < 0.0005) l = 0;
6355
6468
  } else {
6356
6469
  l = 0;
@@ -6359,17 +6472,22 @@ function VMI_set_const_bounds(args) {
6359
6472
  fixed = ' (FIXED ' + vbl.displayName + ')';
6360
6473
  } else {
6361
6474
  // Set bounds as specified by the two arguments
6362
- l = (args[1] === VM.UNDEFINED ? 0 : args[1]),
6363
- 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);
6364
6481
  if(solver_inf) {
6365
6482
  if(l === VM.MINUS_INFINITY) l = -inf_val;
6366
6483
  if(u === VM.PLUS_INFINITY) u = inf_val;
6367
6484
  }
6368
6485
  fixed = '';
6369
6486
  }
6370
- // 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
6371
6489
  if(DEBUGGING) {
6372
- console.log(['set_const_bounds [', k, '] ', vbl.displayName, ' t = ', VM.t,
6490
+ console.log(['set_bounds [', k, '] ', vbl.displayName, ' t = ', VM.t,
6373
6491
  ' LB = ', VM.sig4Dig(l), ', UB = ', VM.sig4Dig(u), fixed].join(''));
6374
6492
  }
6375
6493
  // NOTE: since the VM vectors for lower bounds and upper bounds are
@@ -6396,61 +6514,6 @@ function VMI_set_const_bounds(args) {
6396
6514
  }
6397
6515
  }
6398
6516
 
6399
- function VMI_set_var_bounds(args) {
6400
- // `args`: [var_index, expression, expression]
6401
- const
6402
- vi = args[0],
6403
- vbl = VM.variables[vi - 1][1],
6404
- k = VM.offset + vi,
6405
- r = VM.round_letters.indexOf(VM.round_sequence[VM.current_round]),
6406
- // Optional fourth parameter indicates whether the solver's
6407
- // infinity values should be used
6408
- solver_inf = args.length > 3 && args[3],
6409
- inf_val = (solver_inf ? VM.SOLVER_PLUS_INFINITY : VM.PLUS_INFINITY);
6410
- let l,
6411
- u,
6412
- fixed = (vi in VM.fixed_var_indices[r - 1]);
6413
- if(fixed) {
6414
- // Set both bounds equal to the level set in the previous round, or to 0
6415
- // if this is the first round
6416
- if(VM.current_round) {
6417
- l = vbl.actualLevel(VM.t);
6418
- } else {
6419
- l = 0;
6420
- }
6421
- u = l;
6422
- fixed = ' (FIXED ' + vbl.displayName + ')';
6423
- } else {
6424
- l = args[1].result(VM.t);
6425
- if(l === VM.UNDEFINED) l = 0;
6426
- u = Math.min(args[2].result(VM.t), inf_val);
6427
- if(solver_inf) {
6428
- if(l === VM.MINUS_INFINITY) l = -inf_val;
6429
- if(u === VM.PLUS_INFINITY) u = inf_val;
6430
- }
6431
- fixed = '';
6432
- }
6433
- // Here, too, no need to set default values
6434
- if(Math.abs(l) > VM.NEAR_ZERO || u !== inf_val) {
6435
- VM.lower_bounds[k] = l;
6436
- VM.upper_bounds[k] = u;
6437
- // Check for peak increase -- see comments in VMI_set_const_bound
6438
- if(vbl.peak_inc_var_index >= 0) {
6439
- u = Math.max(0, u - vbl.b_peak[VM.block_count - 1]);
6440
- const
6441
- cvi = VM.chunk_offset + vbl.peak_inc_var_index,
6442
- piub = VM.upper_bounds[cvi];
6443
- if(piub) u = Math.max(piub, u);
6444
- VM.upper_bounds[cvi] = u;
6445
- VM.upper_bounds[cvi + 1] = u;
6446
- }
6447
- }
6448
- if(DEBUGGING) {
6449
- console.log(['set_var_bounds [', k, '] ', vbl.displayName, ' t = ', VM.t,
6450
- ' LB = ', l, ', UB = ', u, fixed].join(''));
6451
- }
6452
- }
6453
-
6454
6517
  function VMI_clear_coefficients(empty) {
6455
6518
  if(DEBUGGING) console.log('clear_coefficients');
6456
6519
  VM.coefficients = {};