linny-r 1.3.1 → 1.3.2
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 +25 -2
- package/package.json +1 -1
- package/server.js +26 -5
- 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 +144 -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 +202 -87
package/console.js
CHANGED
@@ -86,6 +86,9 @@ console.log('Platform:', PLATFORM, '(' + os.type() + ')');
|
|
86
86
|
console.log('Module directory:', MODULE_DIRECTORY);
|
87
87
|
console.log('Working directory:', WORKING_DIRECTORY);
|
88
88
|
|
89
|
+
// Currently, these external solvers are supported:
|
90
|
+
const SUPPORTED_SOLVERS = ['gurobi', 'scip', 'lp_solve'];
|
91
|
+
|
89
92
|
const
|
90
93
|
// Load the MILP solver (dependent on Node.js: `fs`, `os` and `path`)
|
91
94
|
MILPSolver = require('./static/scripts/linny-r-milp.js'),
|
@@ -123,7 +126,7 @@ Possible options are:
|
|
123
126
|
[name]-stats.txt in (workspace)/reports
|
124
127
|
run will run the loaded model
|
125
128
|
solver=[name] will select solver [name], or warn if not found
|
126
|
-
(name choices: Gurobi or LP_solve)
|
129
|
+
(name choices: Gurobi, SCIP or LP_solve)
|
127
130
|
user=[identifier] user ID will be used to log onto remote servers
|
128
131
|
verbose will output solver messages to the console
|
129
132
|
workspace=[path] will create workspace in [path] instead of (main)/user
|
@@ -817,7 +820,7 @@ function commandLineSettings() {
|
|
817
820
|
const av = lca.split('=');
|
818
821
|
if(av.length === 1) av.push('');
|
819
822
|
if(av[0] === 'solver') {
|
820
|
-
if(av[1]
|
823
|
+
if(SUPPORTED_SOLVERS.indexOf(av[1]) < 0) {
|
821
824
|
console.log(`WARNING: Unknown solver "${av[1]}"`);
|
822
825
|
} else {
|
823
826
|
settings.preferred_solver = av[1];
|
@@ -920,6 +923,7 @@ function commandLineSettings() {
|
|
920
923
|
// Check whether MILP solver(s) and Inkscape have been installed
|
921
924
|
const path_list = process.env.PATH.split(path.delimiter);
|
922
925
|
let gurobi_path = '',
|
926
|
+
scip_path = '',
|
923
927
|
match,
|
924
928
|
max_v = -1;
|
925
929
|
for(let i = 0; i < path_list.length; i++) {
|
@@ -928,6 +932,8 @@ function commandLineSettings() {
|
|
928
932
|
gurobi_path = path_list[i];
|
929
933
|
max_v = parseInt(match[1]);
|
930
934
|
}
|
935
|
+
match = path_list[i].match(/[\/\\]scip[^\/\\]+[\/\\]bin/i);
|
936
|
+
if(match) scip_path = path_list[i];
|
931
937
|
match = path_list[i].match(/inkscape/i);
|
932
938
|
if(match) settings.inkscape = path_list[i];
|
933
939
|
}
|
@@ -958,6 +964,23 @@ function commandLineSettings() {
|
|
958
964
|
'WARNING: Failed to access the Gurobi command line application');
|
959
965
|
}
|
960
966
|
}
|
967
|
+
// Check if scip(.exe) exists in its directory
|
968
|
+
let sp = path.join(scip_path, 'scip' + (PLATFORM.startsWith('win') ? '.exe' : ''));
|
969
|
+
const need_scip = !settings.solver || settings.preferred_solver === 'scip';
|
970
|
+
try {
|
971
|
+
fs.accessSync(sp, fs.constants.X_OK);
|
972
|
+
console.log('Path to SCIP:', sp);
|
973
|
+
if(need_scip) {
|
974
|
+
settings.solver = 'scip';
|
975
|
+
settings.solver_path = sp;
|
976
|
+
}
|
977
|
+
} catch(err) {
|
978
|
+
// Only report error if SCIP is needed
|
979
|
+
if(need_scip) {
|
980
|
+
console.log(err.message);
|
981
|
+
console.log('WARNING: SCIP application not found in', sp);
|
982
|
+
}
|
983
|
+
}
|
961
984
|
// Check if lp_solve(.exe) exists in main directory
|
962
985
|
const
|
963
986
|
sp = path.join(WORKING_DIRECTORY,
|
package/package.json
CHANGED
package/server.js
CHANGED
@@ -122,6 +122,8 @@ function checkNodeModule(name) {
|
|
122
122
|
}
|
123
123
|
}
|
124
124
|
|
125
|
+
// Currently, these external solvers are supported:
|
126
|
+
const SUPPORTED_SOLVERS = ['gurobi', 'scip', 'lp_solve'];
|
125
127
|
|
126
128
|
// Load class MILPSolver
|
127
129
|
const MILPSolver = require('./static/scripts/linny-r-milp.js');
|
@@ -1422,7 +1424,7 @@ function commandLineSettings() {
|
|
1422
1424
|
settings.port = n;
|
1423
1425
|
}
|
1424
1426
|
} else if(av[0] === 'solver') {
|
1425
|
-
if(av[1]
|
1427
|
+
if(SUPPORTED_SOLVERS.indexOf(av[1]) < 0) {
|
1426
1428
|
console.log(`WARNING: Unknown solver "${av[1]}"`);
|
1427
1429
|
} else {
|
1428
1430
|
settings.preferred_solver = av[1];
|
@@ -1455,6 +1457,7 @@ function commandLineSettings() {
|
|
1455
1457
|
// Check whether MILP solver(s) and Inkscape have been installed
|
1456
1458
|
const path_list = process.env.PATH.split(path.delimiter);
|
1457
1459
|
let gurobi_path = '',
|
1460
|
+
scip_path = '',
|
1458
1461
|
match,
|
1459
1462
|
max_v = -1;
|
1460
1463
|
for(let i = 0; i < path_list.length; i++) {
|
@@ -1463,6 +1466,8 @@ function commandLineSettings() {
|
|
1463
1466
|
gurobi_path = path_list[i];
|
1464
1467
|
max_v = parseInt(match[1]);
|
1465
1468
|
}
|
1469
|
+
match = path_list[i].match(/[\/\\]scip[^\/\\]+[\/\\]bin/i);
|
1470
|
+
if(match) scip_path = path_list[i];
|
1466
1471
|
match = path_list[i].match(/inkscape/i);
|
1467
1472
|
if(match) settings.inkscape = path_list[i];
|
1468
1473
|
}
|
@@ -1493,11 +1498,27 @@ function commandLineSettings() {
|
|
1493
1498
|
'WARNING: Failed to access the Gurobi command line application');
|
1494
1499
|
}
|
1495
1500
|
}
|
1501
|
+
// Check if scip(.exe) exists in its directory
|
1502
|
+
let sp = path.join(scip_path, 'scip' + (PLATFORM.startsWith('win') ? '.exe' : ''));
|
1503
|
+
const need_scip = !settings.solver || settings.preferred_solver === 'scip';
|
1504
|
+
try {
|
1505
|
+
fs.accessSync(sp, fs.constants.X_OK);
|
1506
|
+
console.log('Path to SCIP:', sp);
|
1507
|
+
if(need_scip) {
|
1508
|
+
settings.solver = 'scip';
|
1509
|
+
settings.solver_path = sp;
|
1510
|
+
}
|
1511
|
+
} catch(err) {
|
1512
|
+
// Only report error if SCIP is needed
|
1513
|
+
if(need_scip) {
|
1514
|
+
console.log(err.message);
|
1515
|
+
console.log('WARNING: SCIP application not found in', sp);
|
1516
|
+
}
|
1517
|
+
}
|
1496
1518
|
// Check if lp_solve(.exe) exists in working directory
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
1500
|
-
need_lps = !settings.solver || settings.preferred_solver === 'lp_solve';
|
1519
|
+
sp = path.join(WORKING_DIRECTORY,
|
1520
|
+
'lp_solve' + (PLATFORM.startsWith('win') ? '.exe' : ''));
|
1521
|
+
const need_lps = !settings.solver || settings.preferred_solver === 'lp_solve';
|
1501
1522
|
try {
|
1502
1523
|
fs.accessSync(sp, fs.constants.X_OK);
|
1503
1524
|
console.log('Path to LP_solve:', sp);
|
package/static/index.html
CHANGED
@@ -2804,7 +2804,65 @@ where X can be one or several of these letters: ABCDELPQ">
|
|
2804
2804
|
</div>
|
2805
2805
|
</div>
|
2806
2806
|
</div>
|
2807
|
-
|
2807
|
+
|
2808
|
+
<!-- the PASTE dialog prompts for a prefix and parameter bindings -->
|
2809
|
+
<div id="paste-modal" class="modal">
|
2810
|
+
<div id="paste-dlg" class="inp-dlg">
|
2811
|
+
<div class="dlg-title">
|
2812
|
+
Name conflict resolution strategy
|
2813
|
+
<img class="cancel-btn" src="images/cancel.png">
|
2814
|
+
<img class="ok-btn" src="images/ok.png">
|
2815
|
+
</div>
|
2816
|
+
<div id="paste-ftp" class="paste-option">
|
2817
|
+
<div id="paste-ftp-box" class="box checked"></div>
|
2818
|
+
<div class="paste-tactic">
|
2819
|
+
Change <span id="paste-from-prefix"></span>…
|
2820
|
+
to <span id="paste-to-prefix"></span>…
|
2821
|
+
</div>
|
2822
|
+
</div>
|
2823
|
+
<div id="paste-fta" class="paste-option">
|
2824
|
+
<div id="paste-fta-box" class="box checked"></div>
|
2825
|
+
<div class="paste-tactic">
|
2826
|
+
Change actor <span id="paste-from-actor"></span>
|
2827
|
+
to <span id="paste-to-actor"></span>
|
2828
|
+
</div>
|
2829
|
+
</div>
|
2830
|
+
<div class="paste-option">
|
2831
|
+
<div id="paste-a-box" class="box checked"></div>
|
2832
|
+
<div class="paste-tactic">
|
2833
|
+
<label>If no actor, add:</label>
|
2834
|
+
<input id="paste-actor" type="text"
|
2835
|
+
placeholder="(no actor)" autocomplete="off">
|
2836
|
+
</div>
|
2837
|
+
</div>
|
2838
|
+
<div class="paste-option">
|
2839
|
+
<div id="paste-p-box" class="box checked"></div>
|
2840
|
+
<div class="paste-tactic">
|
2841
|
+
<label>Add prefix:</label>
|
2842
|
+
<input id="paste-prefix" type="text"
|
2843
|
+
placeholder="(none)" autocomplete="off">
|
2844
|
+
</div>
|
2845
|
+
</div>
|
2846
|
+
<div class="paste-option">
|
2847
|
+
<div id="paste-inc-box" class="box checked"></div>
|
2848
|
+
<div class="paste-tactic">
|
2849
|
+
Auto-increment tail number
|
2850
|
+
</div>
|
2851
|
+
</div>
|
2852
|
+
<div class="paste-option">
|
2853
|
+
<div id="paste-near-box" class="box checked"></div>
|
2854
|
+
<div class="paste-tactic">
|
2855
|
+
Link to nearest eligible node
|
2856
|
+
</div>
|
2857
|
+
</div>
|
2858
|
+
<div style="font-weight: bold; margin:4px 2px 2px 2px">
|
2859
|
+
Mapping of nodes to link from/to:
|
2860
|
+
</div>
|
2861
|
+
<div id="paste-scroll-area">
|
2862
|
+
</div>
|
2863
|
+
</div>
|
2864
|
+
</div>
|
2865
|
+
|
2808
2866
|
<!--- rotating Linny-R logo icon -->
|
2809
2867
|
<div id="rotating-icon">
|
2810
2868
|
<div>
|
package/static/linny-r.css
CHANGED
@@ -4053,6 +4053,7 @@ span.sd-clear {
|
|
4053
4053
|
height: 111px;
|
4054
4054
|
}
|
4055
4055
|
|
4056
|
+
#paste-dlg,
|
4056
4057
|
#include-dlg {
|
4057
4058
|
width: 320px;
|
4058
4059
|
height: min-content;
|
@@ -4078,6 +4079,7 @@ span.sd-clear {
|
|
4078
4079
|
margin-left: 9px;
|
4079
4080
|
}
|
4080
4081
|
|
4082
|
+
#paste-scroll-area,
|
4081
4083
|
#include-scroll-area {
|
4082
4084
|
margin: 2px;
|
4083
4085
|
height: min-content;
|
@@ -4088,6 +4090,60 @@ span.sd-clear {
|
|
4088
4090
|
overflow-y: auto;
|
4089
4091
|
}
|
4090
4092
|
|
4093
|
+
div.paste-tactic {
|
4094
|
+
display: inline-block;
|
4095
|
+
vertical-align: top;
|
4096
|
+
padding-top: 3px;
|
4097
|
+
}
|
4098
|
+
|
4099
|
+
div.paste-select::before {
|
4100
|
+
content: ' \1F872';
|
4101
|
+
margin-right: 3px;
|
4102
|
+
}
|
4103
|
+
|
4104
|
+
div.paste-select {
|
4105
|
+
display: inline-block;
|
4106
|
+
font-size: 12px;
|
4107
|
+
max-width: calc(100% - 25px);
|
4108
|
+
margin: 2px;
|
4109
|
+
}
|
4110
|
+
|
4111
|
+
#paste-prefix,
|
4112
|
+
#paste-actor {
|
4113
|
+
width: 150px;
|
4114
|
+
font-size: 12px;
|
4115
|
+
height: 17px !important;
|
4116
|
+
margin-left: 1px;
|
4117
|
+
}
|
4118
|
+
|
4119
|
+
#paste-from-prefix,
|
4120
|
+
#paste-to-prefix,
|
4121
|
+
#paste-from-actor,
|
4122
|
+
#paste-to-actor {
|
4123
|
+
max-width: 220px;
|
4124
|
+
overflow: clip;
|
4125
|
+
white-space: nowrap;
|
4126
|
+
text-overflow: ellipsis;
|
4127
|
+
}
|
4128
|
+
|
4129
|
+
#paste-from-prefix {
|
4130
|
+
color: #6060c0;
|
4131
|
+
}
|
4132
|
+
|
4133
|
+
#paste-to-prefix {
|
4134
|
+
color: #0000c0;
|
4135
|
+
}
|
4136
|
+
|
4137
|
+
#paste-from-actor {
|
4138
|
+
font-style: italic;
|
4139
|
+
color: #8050a0;
|
4140
|
+
}
|
4141
|
+
|
4142
|
+
#paste-to-actor {
|
4143
|
+
font-style: italic;
|
4144
|
+
color: #600080;
|
4145
|
+
}
|
4146
|
+
|
4091
4147
|
tr.i-param {
|
4092
4148
|
font-style: italic;
|
4093
4149
|
border: 1px solid Silver;
|
@@ -136,7 +136,7 @@ class Controller {
|
|
136
136
|
ACTOR_PROPS: ['weight', 'comments'],
|
137
137
|
CLUSTER_PROPS: ['comments', 'collapsed', 'ignore'],
|
138
138
|
PROCESS_PROPS: ['comments', 'lower_bound', 'upper_bound', 'initial_level',
|
139
|
-
'
|
139
|
+
'pace_expression', 'equal_bounds', 'level_to_zero', 'integer_level', 'collapsed'],
|
140
140
|
PRODUCT_PROPS: ['comments', 'lower_bound', 'upper_bound', 'initial_level',
|
141
141
|
'scale_unit', 'equal_bounds', 'price', 'is_source', 'is_sink', 'is_buffer',
|
142
142
|
'is_data', 'integer_level', 'no_slack'],
|
@@ -316,6 +316,21 @@ class Controller {
|
|
316
316
|
return pan;
|
317
317
|
}
|
318
318
|
|
319
|
+
sharedPrefix(n1, n2) {
|
320
|
+
const
|
321
|
+
pan1 = this.prefixesAndName(n1),
|
322
|
+
pan2 = this.prefixesAndName(n2),
|
323
|
+
l = Math.min(pan1.length - 1, pan2.length - 1),
|
324
|
+
shared = [];
|
325
|
+
let i = 0;
|
326
|
+
while(i < l && ciCompare(pan1[i], pan2[i]) === 0) {
|
327
|
+
// NOTE: if identical except for case, prefer "Abc" over "aBc"
|
328
|
+
shared.push(pan1[i] < pan2[i] ? pan1[i] : pan2[i]);
|
329
|
+
i++;
|
330
|
+
}
|
331
|
+
return shared.join(this.PREFIXER);
|
332
|
+
}
|
333
|
+
|
319
334
|
nameToID(name) {
|
320
335
|
// Returns a name in lower case with link arrow replaced by three
|
321
336
|
// underscores, constraint link arrow by four underscores, and spaces
|