linny-r 1.1.0
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/LICENSE +21 -0
- package/README.md +312 -0
- package/console.js +973 -0
- package/package.json +32 -0
- package/server.js +1547 -0
- package/static/fonts/FantasqueSansMono-Bold.ttf +0 -0
- package/static/fonts/FantasqueSansMono-BoldItalic.ttf +0 -0
- package/static/fonts/FantasqueSansMono-Italic.ttf +0 -0
- package/static/fonts/FantasqueSansMono-Regular.ttf +0 -0
- package/static/fonts/Hack-Bold.ttf +0 -0
- package/static/fonts/Hack-BoldItalic.ttf +0 -0
- package/static/fonts/Hack-Italic.ttf +0 -0
- package/static/fonts/Hack-Regular.ttf +0 -0
- package/static/fonts/Lato-Bold.ttf +0 -0
- package/static/fonts/Lato-BoldItalic.ttf +0 -0
- package/static/fonts/Lato-Italic.ttf +0 -0
- package/static/fonts/Lato-Regular.ttf +0 -0
- package/static/fonts/mplus-1m-bold.ttf +0 -0
- package/static/fonts/mplus-1m-light.ttf +0 -0
- package/static/fonts/mplus-1m-medium.ttf +0 -0
- package/static/fonts/mplus-1m-regular.ttf +0 -0
- package/static/fonts/mplus-1m-thin.ttf +0 -0
- package/static/images/access.png +0 -0
- package/static/images/actor.png +0 -0
- package/static/images/actors.png +0 -0
- package/static/images/add-selector.png +0 -0
- package/static/images/add.png +0 -0
- package/static/images/back.png +0 -0
- package/static/images/black-box.png +0 -0
- package/static/images/by-sa.svg +74 -0
- package/static/images/cancel.png +0 -0
- package/static/images/chart.png +0 -0
- package/static/images/check-disab.png +0 -0
- package/static/images/check-off.png +0 -0
- package/static/images/check-on.png +0 -0
- package/static/images/check-x.png +0 -0
- package/static/images/clone.png +0 -0
- package/static/images/close.png +0 -0
- package/static/images/cluster.png +0 -0
- package/static/images/compare.png +0 -0
- package/static/images/compress.png +0 -0
- package/static/images/constraint.png +0 -0
- package/static/images/copy.png +0 -0
- package/static/images/data-to-clpbrd.png +0 -0
- package/static/images/dataset.png +0 -0
- package/static/images/delete.png +0 -0
- package/static/images/diagram.png +0 -0
- package/static/images/down.png +0 -0
- package/static/images/edit-chart.png +0 -0
- package/static/images/edit.png +0 -0
- package/static/images/eq.png +0 -0
- package/static/images/equation.png +0 -0
- package/static/images/experiment.png +0 -0
- package/static/images/favicon.ico +0 -0
- package/static/images/fewer-dec.png +0 -0
- package/static/images/filter.png +0 -0
- package/static/images/find.png +0 -0
- package/static/images/forward.png +0 -0
- package/static/images/host-logo.png +0 -0
- package/static/images/icon.png +0 -0
- package/static/images/icon.svg +23 -0
- package/static/images/ignore.png +0 -0
- package/static/images/include.png +0 -0
- package/static/images/info-to-clpbrd.png +0 -0
- package/static/images/info.png +0 -0
- package/static/images/is-black-box.png +0 -0
- package/static/images/lbl.png +0 -0
- package/static/images/lift.png +0 -0
- package/static/images/link.png +0 -0
- package/static/images/linny-r.icns +0 -0
- package/static/images/linny-r.ico +0 -0
- package/static/images/linny-r.png +0 -0
- package/static/images/linny-r.svg +21 -0
- package/static/images/logo.png +0 -0
- package/static/images/model-info.png +0 -0
- package/static/images/module.png +0 -0
- package/static/images/monitor.png +0 -0
- package/static/images/more-dec.png +0 -0
- package/static/images/ne.png +0 -0
- package/static/images/new.png +0 -0
- package/static/images/note.png +0 -0
- package/static/images/ok.png +0 -0
- package/static/images/open.png +0 -0
- package/static/images/outcome.png +0 -0
- package/static/images/parent.png +0 -0
- package/static/images/paste.png +0 -0
- package/static/images/pause.png +0 -0
- package/static/images/print-chart.png +0 -0
- package/static/images/print.png +0 -0
- package/static/images/process.png +0 -0
- package/static/images/product.png +0 -0
- package/static/images/pwlf.png +0 -0
- package/static/images/receiver.png +0 -0
- package/static/images/redo.png +0 -0
- package/static/images/remove.png +0 -0
- package/static/images/rename.png +0 -0
- package/static/images/repo-logo.png +0 -0
- package/static/images/repository.png +0 -0
- package/static/images/reset.png +0 -0
- package/static/images/resize.png +0 -0
- package/static/images/restore.png +0 -0
- package/static/images/save-chart.png +0 -0
- package/static/images/save-data.png +0 -0
- package/static/images/save-diagram.png +0 -0
- package/static/images/save.png +0 -0
- package/static/images/sensitivity.png +0 -0
- package/static/images/settings.png +0 -0
- package/static/images/solve.png +0 -0
- package/static/images/solver-logo.png +0 -0
- package/static/images/stats-to-clpbrd.png +0 -0
- package/static/images/stats.png +0 -0
- package/static/images/stop.png +0 -0
- package/static/images/store.png +0 -0
- package/static/images/stretch.png +0 -0
- package/static/images/table-to-clpbrd.png +0 -0
- package/static/images/table.png +0 -0
- package/static/images/tree.png +0 -0
- package/static/images/tudelft.png +0 -0
- package/static/images/ubl.png +0 -0
- package/static/images/undo.png +0 -0
- package/static/images/up.png +0 -0
- package/static/images/zoom-in.png +0 -0
- package/static/images/zoom-out.png +0 -0
- package/static/index.html +3088 -0
- package/static/linny-r.css +4722 -0
- package/static/scripts/iro.min.js +7 -0
- package/static/scripts/linny-r-config.js +105 -0
- package/static/scripts/linny-r-ctrl.js +1199 -0
- package/static/scripts/linny-r-gui.js +14814 -0
- package/static/scripts/linny-r-milp.js +286 -0
- package/static/scripts/linny-r-model.js +10405 -0
- package/static/scripts/linny-r-utils.js +687 -0
- package/static/scripts/linny-r-vm.js +7079 -0
- package/static/show-diff.html +84 -0
- package/static/show-png.html +113 -0
- package/static/sounds/error.wav +0 -0
- package/static/sounds/notification.wav +0 -0
- package/static/sounds/warning.wav +0 -0
@@ -0,0 +1,286 @@
|
|
1
|
+
/*
|
2
|
+
Linny-R is an executable graphical specification language for (mixed integer)
|
3
|
+
linear programming (MILP) problems, especially unit commitment problems (UCP).
|
4
|
+
The Linny-R language and tool have been developed by Pieter Bots at Delft
|
5
|
+
University of Technology, starting in 2009. The project to develop a browser-
|
6
|
+
based version started in 2017. See https://linny-r.org for more information.
|
7
|
+
|
8
|
+
This JavaScript file (linny-r-milp.js) implements the Node.js interface between
|
9
|
+
Linny-R and a MILP solver that has been installed on the computer where this
|
10
|
+
software is running.
|
11
|
+
|
12
|
+
NOTE: For browser-based Linny-R, this file should NOT be loaded, as it is
|
13
|
+
already included in the server.
|
14
|
+
*/
|
15
|
+
|
16
|
+
/*
|
17
|
+
Copyright (c) 2017-2022 Delft University of Technology
|
18
|
+
|
19
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
20
|
+
of this software and associated documentation files (the "Software"), to deal
|
21
|
+
in the Software without restriction, including without limitation the rights to
|
22
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
23
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
24
|
+
so, subject to the following conditions:
|
25
|
+
|
26
|
+
The above copyright notice and this permission notice shall be included in
|
27
|
+
all copies or substantial portions of the Software.
|
28
|
+
|
29
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
30
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
31
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
32
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
33
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
34
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
35
|
+
SOFTWARE.
|
36
|
+
*/
|
37
|
+
|
38
|
+
const
|
39
|
+
child_process = require('child_process'),
|
40
|
+
fs = require('fs'),
|
41
|
+
os = require('os'),
|
42
|
+
path = require('path');
|
43
|
+
|
44
|
+
// Class MILPSolver implements the connection with the solver
|
45
|
+
module.exports = class MILPSolver {
|
46
|
+
constructor(settings, workspace) {
|
47
|
+
this.name = settings.solver;
|
48
|
+
this.solver_path = settings.solver_path;
|
49
|
+
console.log(`Selected solver: "${this.name}"`);
|
50
|
+
this.id = this.name.toLowerCase();
|
51
|
+
// Each external MILP solver application has its own interface
|
52
|
+
// NOTE: the list may be extended to accommodate more MILP solvers
|
53
|
+
if(this.id === 'gurobi') {
|
54
|
+
this.ext = '.mps';
|
55
|
+
this.user_model = path.join(workspace.solver_output, 'usr_model.mps');
|
56
|
+
this.solver_model = path.join(workspace.solver_output, 'solver_model.lp');
|
57
|
+
this.solution = path.join(workspace.solver_output, 'model.json');
|
58
|
+
this.log = path.join(workspace.solver_output, 'model.log');
|
59
|
+
this.args = [
|
60
|
+
'timeLimit=30',
|
61
|
+
'intFeasTol=0.5e-6',
|
62
|
+
'JSONSolDetail=1',
|
63
|
+
`LogFile=${this.log}`,
|
64
|
+
`ResultFile=${this.solution}`,
|
65
|
+
`ResultFile=${this.solver_model}`,
|
66
|
+
`${this.user_model}`
|
67
|
+
];
|
68
|
+
this.errors = {
|
69
|
+
1: 'Model loaded -- no further information',
|
70
|
+
2: 'Optimal solution found',
|
71
|
+
3: 'The model is infeasible',
|
72
|
+
4: 'The model is either unbounded or infeasible',
|
73
|
+
5: 'The model is unbounded',
|
74
|
+
6: 'Aborted -- Optimal objective is worse than specified cut-off',
|
75
|
+
7: 'Halted -- Iteration limit exceeded',
|
76
|
+
8: 'Halted -- Node limit exceeded',
|
77
|
+
9: 'Halted -- Solver time limit exceeded',
|
78
|
+
10: 'Halted -- Solution count limit exceeded',
|
79
|
+
11: 'Halted -- Optimization terminated by user',
|
80
|
+
12: 'Halted -- Unrecoverable numerical difficulties',
|
81
|
+
13: 'The model is sub-obtimal',
|
82
|
+
14: 'Optimization still in progress',
|
83
|
+
15: 'User-specified objective limit has been reached'
|
84
|
+
};
|
85
|
+
} else if(this.id === 'lp_solve') {
|
86
|
+
// Execute file commands differ across platforms
|
87
|
+
if(os.platform().startsWith('win')) {
|
88
|
+
this.solve_cmd = 'lp_solve.exe ';
|
89
|
+
} else {
|
90
|
+
this.solve_cmd = './lp_solve ';
|
91
|
+
}
|
92
|
+
this.ext = '.lp';
|
93
|
+
this.user_model = path.join('user', 'solver', 'usr_model.lp');
|
94
|
+
this.solver_model = path.join('user', 'solver', 'solver_model.lp');
|
95
|
+
this.solution = path.join('.', 'user', 'solver', 'output.txt');
|
96
|
+
this.args = [
|
97
|
+
'-timeout 300',
|
98
|
+
'-v4',
|
99
|
+
'-g 1.0e-11',
|
100
|
+
'-epsel 1.0e-7',
|
101
|
+
`-wlp ${this.solver_model}`,
|
102
|
+
`>${this.solution}`,
|
103
|
+
this.user_model
|
104
|
+
];
|
105
|
+
this.errors = {
|
106
|
+
'-2': 'Out of memory',
|
107
|
+
1: 'The model is sub-optimal',
|
108
|
+
2: 'The model is infeasible',
|
109
|
+
3: 'The model is unbounded',
|
110
|
+
4: 'The model is degenerative',
|
111
|
+
5: 'Numerical failure encountered',
|
112
|
+
6: 'Solver was stopped by user',
|
113
|
+
7: 'Solver time limit exceeded',
|
114
|
+
9: 'The model could be solved by presolve',
|
115
|
+
25: 'Accuracy error encountered'
|
116
|
+
};
|
117
|
+
} else {
|
118
|
+
console.log(`WARNING: Unknown solver "${this.name}"`);
|
119
|
+
this.id = '';
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
solveBlock(sp) {
|
124
|
+
// Saves model file, executes solver, and returns results
|
125
|
+
const result = {
|
126
|
+
block: sp.get('block'),
|
127
|
+
round: sp.get('round'),
|
128
|
+
status: 0,
|
129
|
+
error: '',
|
130
|
+
messages: []
|
131
|
+
};
|
132
|
+
let timeout = parseInt(sp.get('timeout'));
|
133
|
+
// Default timeout per block is 30 seconds
|
134
|
+
if(isNaN(timeout)) timeout = 30;
|
135
|
+
if(!this.id) {
|
136
|
+
result.status = -999;
|
137
|
+
result.error = 'No MILP solver';
|
138
|
+
return result;
|
139
|
+
} else {
|
140
|
+
console.log('Solve block', result.block, result.round);
|
141
|
+
// Write the POSTed MILP model to a file
|
142
|
+
fs.writeFileSync(this.user_model, sp.get('data').trim());
|
143
|
+
// Delete previous log file (if any)
|
144
|
+
try {
|
145
|
+
if(this.log) fs.unlinkSync(this.log);
|
146
|
+
} catch(err) {
|
147
|
+
// Ignore error
|
148
|
+
}
|
149
|
+
// Delete previous solution file (if any)
|
150
|
+
try {
|
151
|
+
if(this.solution) fs.unlinkSync(this.solution);
|
152
|
+
} catch(err) {
|
153
|
+
// Ignore error
|
154
|
+
}
|
155
|
+
let spawn = null,
|
156
|
+
error = null,
|
157
|
+
status = 0;
|
158
|
+
try {
|
159
|
+
if(this.id === 'lp_solve') {
|
160
|
+
this.args[0] = '-timeout ' + timeout;
|
161
|
+
// NOTES:
|
162
|
+
// (1) LP_solve is picky about its command line, and will not work
|
163
|
+
// when the arguments are passed as an array; therefore execute
|
164
|
+
// it as a single command string that includes all arguments
|
165
|
+
// (2) the shell option must be set to TRUE (so the command is
|
166
|
+
// executed within an OS shell script) or LP_solve will interpret
|
167
|
+
// the first argument as the model file, and complain
|
168
|
+
// (3) output must be ignored, as LP_solve will output many warnings
|
169
|
+
// about 0-value coefficients, and these would otherwise also
|
170
|
+
// appear on the console
|
171
|
+
// (4) prevent Windows opening a visible sub-process shell window
|
172
|
+
const
|
173
|
+
cmd = this.solve_cmd + ' ' + this.args.join(' '),
|
174
|
+
options = {shell: true, stdio: 'ignore', windowsHide: true};
|
175
|
+
spawn = child_process.spawnSync(cmd, options);
|
176
|
+
} else {
|
177
|
+
this.args[0] = 'TimeLimit=' + timeout;
|
178
|
+
// When using Gurobi, the standard way works well
|
179
|
+
const options = {windowsHide: true};
|
180
|
+
spawn = child_process.spawnSync(this.solver_path, this.args, options);
|
181
|
+
}
|
182
|
+
status = spawn.status;
|
183
|
+
} catch(err) {
|
184
|
+
status = -13;
|
185
|
+
error = err;
|
186
|
+
}
|
187
|
+
if(status) console.log(`Process status: ${status}`);
|
188
|
+
if(status in this.errors) {
|
189
|
+
// If solver exited with known status code, report message
|
190
|
+
result.status = status;
|
191
|
+
result.error = this.errors[status];
|
192
|
+
} else if(status !== 0) {
|
193
|
+
result.status = -13;
|
194
|
+
const msg = (error ? error.message : 'Unknown error');
|
195
|
+
result.error += 'ERROR: ' + msg;
|
196
|
+
}
|
197
|
+
return this.processSolverOutput(result);
|
198
|
+
}
|
199
|
+
}
|
200
|
+
|
201
|
+
processSolverOutput(result) {
|
202
|
+
// Read solver output files and return solution (or error)
|
203
|
+
const x_values = [];
|
204
|
+
if(this.id === 'gurobi') {
|
205
|
+
// `messages` must be an array of strings
|
206
|
+
result.messages = fs.readFileSync(this.log, 'utf8').split(os.EOL);
|
207
|
+
if(result.status !== 0) {
|
208
|
+
// Non-zero solver exit code may indicate expired license
|
209
|
+
result.error = 'Your Gurobi license may have expired';
|
210
|
+
} else {
|
211
|
+
try {
|
212
|
+
// Read JSON string from solution file
|
213
|
+
const
|
214
|
+
json = fs.readFileSync(this.solution, 'utf8').trim(),
|
215
|
+
sol = JSON.parse(json);
|
216
|
+
result.seconds = sol.SolutionInfo.Runtime;
|
217
|
+
// NOTE: Status = 2 indicates success!
|
218
|
+
if(sol.SolutionInfo.Status !== 2) {
|
219
|
+
result.status = sol.SolutionInfo.Status;
|
220
|
+
result.error = this.errors[result.status];
|
221
|
+
if(!result.error) result.error = 'Unknown solver error';
|
222
|
+
console.log(`Solver status: ${result.status} - ${result.error}`);
|
223
|
+
}
|
224
|
+
// Objective value
|
225
|
+
result.obj = sol.SolutionInfo.ObjVal;
|
226
|
+
// Values of solution vector
|
227
|
+
if(sol.Vars) {
|
228
|
+
for(let i = 0; i < sol.Vars.length; i++) {
|
229
|
+
x_values.push(sol.Vars[i].X);
|
230
|
+
}
|
231
|
+
}
|
232
|
+
} catch(err) {
|
233
|
+
console.log('WARNING: Could not read solution file');
|
234
|
+
console.log(err.message);
|
235
|
+
result.status = -13;
|
236
|
+
result.error = 'No solution found';
|
237
|
+
}
|
238
|
+
}
|
239
|
+
} else if(this.id === 'lp_solve') {
|
240
|
+
// Read solver messages from file
|
241
|
+
// NOTE: Linny-R client expects a list of strings
|
242
|
+
const
|
243
|
+
output = fs.readFileSync(
|
244
|
+
this.solution, 'utf8').trim().split(os.EOL),
|
245
|
+
msgs = [];
|
246
|
+
result.seconds = 0;
|
247
|
+
let i = 0,
|
248
|
+
solved = false;
|
249
|
+
while(i< output.length && !solved) {
|
250
|
+
msgs.push(output[i]);
|
251
|
+
const m = output[i].match(/in total (\d+\.\d+) seconds/);
|
252
|
+
if(m && m.length > 1) result.seconds = parseFloat(m[1]);
|
253
|
+
solved = output[i].startsWith('Value of objective function:');
|
254
|
+
i++;
|
255
|
+
}
|
256
|
+
result.messages = msgs;
|
257
|
+
if(solved) {
|
258
|
+
while(i < output.length && !output[i].startsWith('C1')) i++;
|
259
|
+
while(i < output.length) {
|
260
|
+
let v = output[i].replace(/C\d+\s*/, '');
|
261
|
+
// Remove variable names from result output
|
262
|
+
v = parseFloat(v);
|
263
|
+
x_values.push(v);
|
264
|
+
i++;
|
265
|
+
}
|
266
|
+
} else {
|
267
|
+
console.log('No solution found');
|
268
|
+
}
|
269
|
+
}
|
270
|
+
// Add data and model to the results dict
|
271
|
+
result.data = {
|
272
|
+
block: result.block,
|
273
|
+
round: result.round,
|
274
|
+
seconds: result.seconds,
|
275
|
+
x: x_values
|
276
|
+
};
|
277
|
+
try {
|
278
|
+
result.model = fs.readFileSync(this.solver_model, 'utf8');
|
279
|
+
} catch(err) {
|
280
|
+
console.log(err);
|
281
|
+
result.model = 'ERROR reading solver model file: ' + err;
|
282
|
+
}
|
283
|
+
return result;
|
284
|
+
}
|
285
|
+
|
286
|
+
}; // END of class MILPSolver (semicolon needed because of export statement)
|