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 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] !== 'gurobi' && av[1] !== 'lp_solve') {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linny-r",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
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] !== 'gurobi' && av[1] !== 'lp_solve') {
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
- const
1498
- sp = path.join(WORKING_DIRECTORY,
1499
- 'lp_solve' + (PLATFORM.startsWith('win') ? '.exe' : '')),
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>&hellip;
2820
+ to <span id="paste-to-prefix"></span>&hellip;
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>
@@ -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
- 'pace', 'equal_bounds', 'level_to_zero', 'integer_level', 'collapsed'],
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