linny-r 1.7.2 → 1.7.4
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/README.md +23 -12
- package/console.js +2 -2
- package/package.json +1 -1
- package/server.js +2 -2
- package/static/index.html +1 -0
- package/static/linny-r.css +10 -1
- package/static/scripts/linny-r-ctrl.js +1 -1
- package/static/scripts/linny-r-gui-constraint-editor.js +15 -2
- package/static/scripts/linny-r-gui-controller.js +6 -3
- package/static/scripts/linny-r-gui-equation-manager.js +6 -6
- package/static/scripts/linny-r-gui-expression-editor.js +6 -3
- package/static/scripts/linny-r-gui-finder.js +1 -1
- package/static/scripts/linny-r-gui-monitor.js +5 -5
- package/static/scripts/linny-r-milp.js +263 -81
- package/static/scripts/linny-r-model.js +68 -21
- package/static/scripts/linny-r-vm.js +263 -101
package/README.md
CHANGED
@@ -30,7 +30,7 @@ Technical documentation will be developed on GitHub: https://github.com/pwgbots/
|
|
30
30
|
Linny-R is developed as a JavaScript package, and requires that **Node.js** is installed on your computer.
|
31
31
|
This software can be downloaded from <a href="https://nodejs.org" target="_blank">https://nodejs.org</a>.
|
32
32
|
Make sure that you choose the correct installer for your computer.
|
33
|
-
Linny-R is developed using the _current_ release. Presently (
|
33
|
+
Linny-R is developed using the _current_ release. Presently (November 2023) this is 21.2.0.
|
34
34
|
|
35
35
|
Run the installer and accept the default settings.
|
36
36
|
There is **no** need to install the optional _Tools for Native Modules_.
|
@@ -41,7 +41,7 @@ Verify the installation by typing:
|
|
41
41
|
|
42
42
|
``node --version``
|
43
43
|
|
44
|
-
The response should be the version number of Node.js, for example: v21.
|
44
|
+
The response should be the version number of Node.js, for example: v21.2.0.
|
45
45
|
|
46
46
|
## Installing Linny-R
|
47
47
|
It is advisable to install Linny-R in a directory on your computer, not in a cloud.
|
@@ -147,10 +147,10 @@ directory and type:
|
|
147
147
|
|
148
148
|
## Configuring the MILP solver
|
149
149
|
|
150
|
-
Linny-R presently supports
|
151
|
-
Gurobi and CPLEX are _considerably_ more powerful than the open source solvers SCIP and LP_solve,
|
150
|
+
Linny-R presently supports five MILP solvers: Gurobi, MOSEK, CPLEX, SCIP and LP_solve.
|
151
|
+
Gurobi, MOSEK and CPLEX are _considerably_ more powerful than the open source solvers SCIP and LP_solve,
|
152
152
|
but they require a license.
|
153
|
-
Academic licenses can be obtained by students and staff of eligible institutions.
|
153
|
+
Academic licenses can be obtained by students and staff of eligible institutions.
|
154
154
|
|
155
155
|
> **Important**
|
156
156
|
> When installing a solver, it is advisable to accept the default file
|
@@ -182,6 +182,17 @@ It will look for this application in the directory specified in the environment
|
|
182
182
|
or more specifically in the environment variable CPLEX_STUDIO_BINARIES<em>nnnn</em>
|
183
183
|
(where _nnnn_ denotes the CPLEX version number) on your computer.
|
184
184
|
|
185
|
+
#### Installing MOSEK
|
186
|
+
|
187
|
+
The software you need to install is **MOSEK**.
|
188
|
+
More information on how to obtain a license, and instructions for installing
|
189
|
+
MOSEK on your computer can be obtained via this URL:
|
190
|
+
<a href="https://www.mosek.com/resources/getting-started/"
|
191
|
+
target="_blank">https://www.mosek.com/resources/getting-started/</a>
|
192
|
+
|
193
|
+
When running a model, Linny-R will try to execute the command line application `mosek`.
|
194
|
+
It will look for this application in the directory specified in the environment variable PATH on your computer.
|
195
|
+
|
185
196
|
#### Installing SCIP
|
186
197
|
|
187
198
|
The SCIP software is open source. Instructions for installation can be found via this URL:
|
@@ -234,8 +245,8 @@ Open the Command Line Interface (CLI) of your computer, change to your Linny-R d
|
|
234
245
|
This response should be something similar to:
|
235
246
|
|
236
247
|
<pre>
|
237
|
-
Node.js server for Linny-R version 1.7.
|
238
|
-
Node.js version: v21.
|
248
|
+
Node.js server for Linny-R version 1.7.4
|
249
|
+
Node.js version: v21.2.0
|
239
250
|
... etc.
|
240
251
|
</pre>
|
241
252
|
|
@@ -245,9 +256,9 @@ The Linny-R GUI should show in your browser window,
|
|
245
256
|
while in the CLI you should see a long series of server log messages like:
|
246
257
|
|
247
258
|
<pre>
|
248
|
-
[2023-
|
249
|
-
[2023-
|
250
|
-
[2023-
|
259
|
+
[2023-11-19 22:55:17] Static file: /index.html
|
260
|
+
[2023-11-19 22:55:17] Static file: /scripts/iro.min.js
|
261
|
+
[2023-11-19 22:55:17] Static file: /images/open.png
|
251
262
|
... etc.
|
252
263
|
</pre>
|
253
264
|
|
@@ -278,7 +289,7 @@ and then the diagram will be updated to reflect the obtained solution.
|
|
278
289
|
Meanwhile, in the CLI, you should see a server log message like:
|
279
290
|
|
280
291
|
<pre>
|
281
|
-
Solve block 1 a
|
292
|
+
Solve block 1 a with SCIP
|
282
293
|
</pre>
|
283
294
|
|
284
295
|
To end a modeling session, you can shut down the server by clicking on the
|
@@ -295,7 +306,7 @@ Optionally, you can add more arguments to the `node` command:
|
|
295
306
|
dpi=[number] to overrule the default resolution (300 dpi) for Inkscape
|
296
307
|
launch to automatically launch Linny-R in your default browser
|
297
308
|
port=[number] to overrule the default port number (5050)
|
298
|
-
solver=[name] to overrule the default sequence (Gurobi, CPLEX, SCIP, LP_solve)
|
309
|
+
solver=[name] to overrule the default sequence (Gurobi, MOSEK, CPLEX, SCIP, LP_solve)
|
299
310
|
workspace=[path] to overrule the default path for the user directory
|
300
311
|
</pre>
|
301
312
|
|
package/console.js
CHANGED
@@ -198,7 +198,7 @@ class ConsoleMonitor {
|
|
198
198
|
logOnToServer() {
|
199
199
|
VM.solver_user = '';
|
200
200
|
VM.solver_token = 'local host';
|
201
|
-
VM.
|
201
|
+
VM.solver_id = SOLVER.id;
|
202
202
|
}
|
203
203
|
|
204
204
|
connectToServer() {
|
@@ -1081,7 +1081,7 @@ if(SETTINGS.model_path) {
|
|
1081
1081
|
// NOTE: Solver preference in model overrides default solver.
|
1082
1082
|
const mps = MODEL.preferred_solver;
|
1083
1083
|
if(mps && SOLVER.solver_list.hasOwnProperty(mps)) {
|
1084
|
-
VM.
|
1084
|
+
VM.solver_id = mps;
|
1085
1085
|
SOLVER.id = mps;
|
1086
1086
|
console.log(`Using solver ${SOLVER.name} (model preference)`);
|
1087
1087
|
}
|
package/package.json
CHANGED
package/server.js
CHANGED
@@ -124,7 +124,7 @@ function checkNodeModule(name) {
|
|
124
124
|
}
|
125
125
|
|
126
126
|
// Currently, these external solvers are supported:
|
127
|
-
const SUPPORTED_SOLVERS = ['gurobi', 'cplex', 'scip', 'lp_solve'];
|
127
|
+
const SUPPORTED_SOLVERS = ['gurobi', 'mosek', 'cplex', 'scip', 'lp_solve'];
|
128
128
|
|
129
129
|
// Load class MILPSolver
|
130
130
|
const MILPSolver = require('./static/scripts/linny-r-milp.js');
|
@@ -1555,7 +1555,7 @@ Possible options are:
|
|
1555
1555
|
port=[number] will listen at the specified port number
|
1556
1556
|
(default is 5050; number must be unique for each server)
|
1557
1557
|
solver=[name] will select solver [name], or warn if not found
|
1558
|
-
(name choices: Gurobi, CPLEX, SCIP or LP_solve)
|
1558
|
+
(name choices: Gurobi, MOSEK, CPLEX, SCIP or LP_solve)
|
1559
1559
|
verbose will output solver messages to the console
|
1560
1560
|
workspace=[path] will create workspace in [path] instead of (Linny-R)/user
|
1561
1561
|
`;
|
package/static/index.html
CHANGED
@@ -1347,6 +1347,7 @@ NOTE: Unit symbols are case-sensitive, so BTU ≠ Btu">
|
|
1347
1347
|
<div id="constraint-no-slack" class="box clear"></div>
|
1348
1348
|
<div id="constraint-no-slack-lbl">No slack</div>
|
1349
1349
|
</div>
|
1350
|
+
<div id="constraint-convex"></div>
|
1350
1351
|
<div id="constraint-soc">
|
1351
1352
|
<label>Attributed share of cost:</label>
|
1352
1353
|
<select id="constraint-soc-direct">
|
package/static/linny-r.css
CHANGED
@@ -1985,6 +1985,7 @@ div.menu-item:hover {
|
|
1985
1985
|
#constraint-from-name,
|
1986
1986
|
#constraint-to-name {
|
1987
1987
|
display: inline-block;
|
1988
|
+
vertical-align: middle;
|
1988
1989
|
max-width: 190px;
|
1989
1990
|
white-space: nowrap;
|
1990
1991
|
overflow: hidden;
|
@@ -2025,7 +2026,15 @@ div.menu-item:hover {
|
|
2025
2026
|
position: absolute;
|
2026
2027
|
left: -2px;
|
2027
2028
|
bottom: -1px;
|
2028
|
-
width:
|
2029
|
+
width: 75px;
|
2030
|
+
}
|
2031
|
+
|
2032
|
+
#constraint-convex {
|
2033
|
+
position: absolute;
|
2034
|
+
left: 75px;
|
2035
|
+
bottom: -2px;
|
2036
|
+
color: #00c030;
|
2037
|
+
font-size: 20px;
|
2029
2038
|
}
|
2030
2039
|
|
2031
2040
|
#constraint-no-slack-lbl {
|
@@ -66,6 +66,7 @@ class ConstraintEditor {
|
|
66
66
|
this.pos_y_div = document.getElementById('constraint-pos-y');
|
67
67
|
this.point_div = document.getElementById('constraint-point');
|
68
68
|
this.equation_div = document.getElementById('constraint-equation');
|
69
|
+
this.convex_div = document.getElementById('constraint-convex');
|
69
70
|
this.add_point_btn = document.getElementById('add-point-btn');
|
70
71
|
this.add_point_btn.addEventListener('click',
|
71
72
|
() => CONSTRAINT_EDITOR.addPointToLine());
|
@@ -395,7 +396,7 @@ class ConstraintEditor {
|
|
395
396
|
|
396
397
|
checkLines() {
|
397
398
|
// Checks whether cursor is on a bound line and updates the constraint
|
398
|
-
// editor status accordingly
|
399
|
+
// editor status accordingly.
|
399
400
|
this.on_line = null;
|
400
401
|
this.on_point = -1;
|
401
402
|
this.seg_points = null;
|
@@ -446,7 +447,10 @@ class ConstraintEditor {
|
|
446
447
|
}
|
447
448
|
|
448
449
|
updateEquation() {
|
449
|
-
|
450
|
+
// Show the equation for the line segment under the cursor, and
|
451
|
+
// indicate whether the bound line is concave or convex.
|
452
|
+
var segeq = '',
|
453
|
+
convex = '';
|
450
454
|
if(this.on_line && this.seg_points) {
|
451
455
|
const
|
452
456
|
p1 = this.on_line.points[this.seg_points[0]],
|
@@ -466,7 +470,16 @@ class ConstraintEditor {
|
|
466
470
|
(y0 < 0 ? ' - ' : ' + ') + Math.abs(y0).toPrecision(3));
|
467
471
|
}
|
468
472
|
}
|
473
|
+
if(this.on_line) {
|
474
|
+
const c = this.on_line.needsNoSOS;
|
475
|
+
if(c > 0) {
|
476
|
+
convex = '\u2934'; // Curved arrow up
|
477
|
+
} else if(c < 0) {
|
478
|
+
convex = '\u2935'; // Curved arrow down
|
479
|
+
}
|
480
|
+
}
|
469
481
|
this.equation_div.innerHTML = segeq;
|
482
|
+
this.convex_div.innerHTML = convex;
|
470
483
|
}
|
471
484
|
|
472
485
|
positionPoint() {
|
@@ -895,13 +895,16 @@ class GUIController extends Controller {
|
|
895
895
|
// If not a valid Linny-R model, ensure that the current model is clean.
|
896
896
|
if(!loaded) MODEL = new LinnyRModel();
|
897
897
|
// If model specifies a preferred solver, immediately try to switch.
|
898
|
-
if(MODEL.preferred_solver !== VM.
|
898
|
+
if(MODEL.preferred_solver !== VM.solver_id) {
|
899
899
|
UI.changeSolver(MODEL.preferred_solver);
|
900
900
|
}
|
901
901
|
this.updateScaleUnitList();
|
902
902
|
this.drawDiagram(MODEL);
|
903
903
|
// Cursor may have been set to `waiting` when decrypting.
|
904
904
|
this.normalCursor();
|
905
|
+
// Reset the Virtual Machine.
|
906
|
+
VM.reset();
|
907
|
+
this.updateIssuePanel();
|
905
908
|
this.setMessage('');
|
906
909
|
this.updateButtons();
|
907
910
|
// Undoable operations no longer apply!
|
@@ -992,7 +995,7 @@ class GUIController extends Controller {
|
|
992
995
|
for(let i = 0; i < VM.solver_list.length; i++) {
|
993
996
|
const s = VM.solver_list[i];
|
994
997
|
html.push(['<option value="', s,
|
995
|
-
(s === VM.
|
998
|
+
(s === VM.solver_id ? '"selected="selected' : ''),
|
996
999
|
'">', VM.solver_names[s], '</option>'].join(''));
|
997
1000
|
}
|
998
1001
|
md.element('solver').innerHTML = html.join('');
|
@@ -1028,7 +1031,7 @@ class GUIController extends Controller {
|
|
1028
1031
|
})
|
1029
1032
|
.then((data) => {
|
1030
1033
|
if(UI.postResponseOK(data, true)) {
|
1031
|
-
VM.
|
1034
|
+
VM.selectSolver(sid);
|
1032
1035
|
UI.modals.server.hide();
|
1033
1036
|
}
|
1034
1037
|
})
|
@@ -234,7 +234,7 @@ class EquationManager {
|
|
234
234
|
this.new_modal.hide();
|
235
235
|
this.selected_modifier = m;
|
236
236
|
this.updateDialog();
|
237
|
-
// Open expression editor if expression is still undefined
|
237
|
+
// Open expression editor if expression is still undefined.
|
238
238
|
if(!m.expression.text) this.editEquation();
|
239
239
|
}
|
240
240
|
}
|
@@ -243,13 +243,13 @@ class EquationManager {
|
|
243
243
|
const m = this.selected_modifier;
|
244
244
|
if(m) {
|
245
245
|
this.edited_expression = m.expression;
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
246
|
+
X_EDIT.edited_input_id = '';
|
247
|
+
X_EDIT.property.innerHTML = this.selected_modifier.selector;
|
248
|
+
X_EDIT.text.value = m.expression.text;
|
249
|
+
X_EDIT.obj.value = 0;
|
250
250
|
X_EDIT.updateVariableBar();
|
251
251
|
X_EDIT.clearStatusBar();
|
252
|
-
|
252
|
+
UI.modals.expression.show('text');
|
253
253
|
}
|
254
254
|
}
|
255
255
|
|
@@ -38,16 +38,16 @@ class ExpressionEditor {
|
|
38
38
|
this.dataset_dot_option = '. (this dataset)';
|
39
39
|
this.edited_input_id = '';
|
40
40
|
this.edited_expression = null;
|
41
|
-
// Dialog DOM elements
|
41
|
+
// Dialog DOM elements.
|
42
42
|
this.property = document.getElementById('expression-property');
|
43
43
|
this.text = document.getElementById('expression-text');
|
44
44
|
this.status = document.getElementById('expression-status');
|
45
45
|
this.info = document.getElementById('expression-info');
|
46
|
-
// The DOM elements for the "insert variable" bar
|
46
|
+
// The DOM elements for the "insert variable" bar.
|
47
47
|
this.obj = document.getElementById('variable-obj');
|
48
48
|
this.name = document.getElementById('variable-name');
|
49
49
|
this.attr = document.getElementById('variable-attr');
|
50
|
-
// The quick guide to Linny-R expressions
|
50
|
+
// The quick guide to Linny-R expressions.
|
51
51
|
this.info.innerHTML = `
|
52
52
|
<h3>Linny-R expressions</h3>
|
53
53
|
<p><em>NOTE: Move cursor over a</em> <code>symbol</code>
|
@@ -229,6 +229,9 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
|
|
229
229
|
// CLear other properties that relate to the edited expression.
|
230
230
|
this.edited_input_id = '';
|
231
231
|
this.edited_expression = null;
|
232
|
+
// Clear edited expression attributes of other dialogs.
|
233
|
+
DATASET_MANAGER.edited_expression = null;
|
234
|
+
EQUATION_MANAGER.edited_expression = null;
|
232
235
|
}
|
233
236
|
|
234
237
|
parseExpression() {
|
@@ -294,7 +294,7 @@ class Finder {
|
|
294
294
|
for(let i = 0; i < n; i++) {
|
295
295
|
const e = this.entities[i];
|
296
296
|
// Exclude "no actor" and top cluster.
|
297
|
-
if(e.name !== '(no_actor)' && e.name !== '(top_cluster)' &&
|
297
|
+
if(e.name && e.name !== '(no_actor)' && e.name !== '(top_cluster)' &&
|
298
298
|
// Also exclude actor cash flow data products because
|
299
299
|
// many of their properties should not be changed.
|
300
300
|
!e.name.startsWith('$')) {
|
@@ -325,12 +325,12 @@ class GUIMonitor {
|
|
325
325
|
UI.alert(jsr.error);
|
326
326
|
} else if(jsr.server) {
|
327
327
|
VM.solver_token = jsr.token;
|
328
|
-
VM.
|
328
|
+
VM.selectSolver(jsr.solver);
|
329
329
|
// Remote solver may indicate user-specific solver time limit.
|
330
330
|
let utl = '';
|
331
331
|
if(jsr.time_limit) {
|
332
332
|
VM.max_solver_time = jsr.time_limit;
|
333
|
-
utl = ` -- ${VM.
|
333
|
+
utl = ` -- ${VM.solver_names[VM.solver_id]} solver: ` +
|
334
334
|
`max. ${VM.max_solver_time} seconds per block`;
|
335
335
|
// If user has a set time limit, no restrictions on tableau size.
|
336
336
|
VM.max_tableau_size = 0;
|
@@ -354,7 +354,7 @@ class GUIMonitor {
|
|
354
354
|
VM.solver_token = 'local host';
|
355
355
|
fetch('solver/', postData({
|
356
356
|
action: 'logon',
|
357
|
-
solver: MODEL.preferred_solver || VM.
|
357
|
+
solver: MODEL.preferred_solver || VM.solver_id}))
|
358
358
|
.then((response) => {
|
359
359
|
if(!response.ok) {
|
360
360
|
UI.alert(`ERROR ${response.status}: ${response.statusText}`);
|
@@ -367,10 +367,10 @@ class GUIMonitor {
|
|
367
367
|
jsr = JSON.parse(data),
|
368
368
|
sname = VM.solver_names[jsr.solver] || 'unknown',
|
369
369
|
svr = `Solver on ${jsr.server} is ${sname}`;
|
370
|
-
if(jsr.solver !== VM.
|
370
|
+
if(jsr.solver !== VM.solver_id) UI.notify(svr);
|
371
371
|
VM.server = jsr.server;
|
372
372
|
VM.working_directory = jsr.path;
|
373
|
-
VM.
|
373
|
+
VM.selectSolver(jsr.solver);
|
374
374
|
VM.solver_list = jsr.solver_list;
|
375
375
|
document.getElementById('host-logo').title = svr;
|
376
376
|
VM.connected = true;
|