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.
- package/console.js +45 -6
- package/package.json +1 -1
- package/server.js +47 -6
- package/static/index.html +59 -1
- package/static/linny-r.css +56 -0
- package/static/scripts/linny-r-ctrl.js +16 -1
- package/static/scripts/linny-r-gui.js +335 -7
- package/static/scripts/linny-r-milp.js +237 -13
- package/static/scripts/linny-r-model.js +109 -21
- package/static/scripts/linny-r-utils.js +10 -2
- package/static/scripts/linny-r-vm.js +233 -170
@@ -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: ['
|
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
|
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 = (
|
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 = (
|
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
|
-
[
|
2578
|
+
[VMI_set_bounds, [a.cash_in_var_index,
|
2562
2579
|
VM.MINUS_INFINITY, VM.PLUS_INFINITY, true]],
|
2563
|
-
[
|
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
|
-
|
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
|
-
|
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
|
-
|
2616
|
-
|
2617
|
-
|
2618
|
-
|
2619
|
-
|
2620
|
-
|
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([
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 +
|
4333
|
+
this.lines += line + EOL;
|
4307
4334
|
line = '';
|
4308
4335
|
// Add the row constraints
|
4309
|
-
|
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
|
-
|
4320
|
-
|
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 +
|
4359
|
+
this.constraint_symbols[this.constraint_types[r]] + ' ' + c + EOL;
|
4337
4360
|
line = '';
|
4338
4361
|
}
|
4339
4362
|
// Add the variable bounds
|
4340
|
-
|
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 =
|
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
|
-
|
4367
|
-
if(
|
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 +
|
4408
|
+
if(line) this.lines += line + EOL;
|
4370
4409
|
}
|
4371
4410
|
// Add the special variable types
|
4372
|
-
|
4373
|
-
|
4374
|
-
|
4375
|
-
|
4376
|
-
|
4377
|
-
|
4378
|
-
|
4379
|
-
|
4380
|
-
|
4381
|
-
|
4382
|
-
|
4383
|
-
|
4384
|
-
|
4385
|
-
|
4386
|
-
|
4387
|
-
|
4388
|
-
|
4389
|
-
|
4390
|
-
|
4391
|
-
|
4392
|
-
|
4393
|
-
|
4394
|
-
|
4395
|
-
|
4396
|
-
|
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
|
-
|
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
|
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 === '
|
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
|
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 =
|
6363
|
-
|
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
|
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(['
|
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 = {};
|