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 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 (October 2023) this is 21.1.0.
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.1.0.
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 four MILP solvers: Gurobi, CPLEX, SCIP and LP_solve.
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.0
238
- Node.js version: v21.1.0
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-10-29 22:55:17] Static file: /index.html
249
- [2023-10-29 22:55:17] Static file: /scripts/iro.min.js
250
- [2023-10-29 22:55:17] Static file: /images/open.png
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.solver_name = SOLVER.id;
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.solver_name = mps;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linny-r",
3
- "version": "1.7.2",
3
+ "version": "1.7.4",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
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 &ne; 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">
@@ -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: 100px;
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 {
@@ -1412,4 +1412,4 @@ if(NODE) module.exports = {
1412
1412
  ChartManager: ChartManager,
1413
1413
  SensitivityAnalysis: SensitivityAnalysis,
1414
1414
  ExperimentManager: ExperimentManager
1415
- }
1415
+ };
@@ -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
- var segeq = '';
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.solver_name) {
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.solver_name ? '"selected="selected' : ''),
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.solver_name = sid;
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
- const md = UI.modals.expression;
247
- md.element('property').innerHTML = this.selected_modifier.selector;
248
- md.element('text').value = m.expression.text;
249
- document.getElementById('variable-obj').value = 0;
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
- md.show('text');
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.solver_name = jsr.solver;
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.solver_name} solver: ` +
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.solver_name}))
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.solver_name) UI.notify(svr);
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.solver_name = jsr.solver;
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;