linny-r 3.0.0 → 3.0.1

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
@@ -16,7 +16,7 @@ NOTE: For browser-based Linny-R, this file should NOT be loaded, as it
16
16
  */
17
17
 
18
18
  /*
19
- Copyright (c) 2017-2024 Delft University of Technology
19
+ Copyright (c) 2017-2026 Delft University of Technology
20
20
 
21
21
  Permission is hereby granted, free of charge, to any person obtaining a copy
22
22
  of this software and associated documentation files (the "Software"), to deal
@@ -117,19 +117,17 @@ Possible options are:
117
117
  (FUTURE OPTION)
118
118
  check will report whether current version is up-to-date
119
119
  data-dir=[path] will look for series data files in [path] instead of
120
- (main)/user/data
120
+ (Linny-R)/user/data
121
121
  model=[path] will load model file specified by [path]
122
- module=[name@repo] will load model [name] from repository [repo]
123
- (if @repo is blank, repository "local host" is used)
124
- (FUTURE OPTION)
122
+ (relative paths are searched for in (Linny-R)/user/models)
125
123
  report=[name] will write run results to [name]-series.txt and
126
124
  [name]-stats.txt in (workspace)/reports
127
125
  run will run the loaded model
128
126
  solver=[name] will select solver [name], or warn if not found
129
- (name choices: Gurobi, CPLEX, SCIP or LP_solve)
127
+ (name choices: Gurobi, CPLEX, MOSEK, SCIP or LP_solve)
130
128
  user=[identifier] user ID will be used to log onto remote servers
131
129
  verbose will output solver messages to the console
132
- workspace=[path] will create workspace in [path] instead of (main)/user
130
+ workspace=[path] will create workspace in [path] instead of (Linny-R)/user
133
131
  xrun=[title#list] will perform experiment runs in given range
134
132
  (list is comma-separated sequence of run numbers)
135
133
  `;
@@ -262,60 +260,6 @@ class ConsoleMonitor {
262
260
  } // END of class ConsoleMonitor
263
261
 
264
262
 
265
- // NOTE: This implementation is very incomplete, still!
266
- class ConsoleRepositoryBrowser {
267
- constructor() {
268
- this.repositories = [];
269
- this.repository_index = -1;
270
- this.module_index = -1;
271
- // Get the repository list from the modules.
272
- this.getRepositories();
273
- this.reset();
274
- }
275
-
276
- reset() {
277
- this.visible = false;
278
- }
279
-
280
- get isLocalHost() {
281
- // Return TRUE if first repository on the list is 'local host'.
282
- return this.repositories.length > 0 &&
283
- this.repositories[0].name === 'local host';
284
- }
285
-
286
- getRepositories() {
287
- // Gets the list of repository names from the server.
288
- this.repositories.length = 0;
289
- // @@TO DO!!
290
- }
291
-
292
- repositoryByName(n) {
293
- // Return the repository having name `n` if already known, otherwise NULL.
294
- for(let i = 0; i < this.repositories.length; i++) {
295
- if(this.repositories[i].name === n) {
296
- return this.repositories[i];
297
- }
298
- }
299
- return null;
300
- }
301
-
302
- asFileName(s) {
303
- // NOTE: asFileName is implemented as function (see below) to permit
304
- // its use prior to instantiation of the RepositoryBrowser.
305
- return stringToFileName(s);
306
- }
307
-
308
- }
309
-
310
- function stringToFileName(s) {
311
- // Return string `s` with whitespace converted to a single dash, and
312
- // special characters converted to underscores.
313
- return s.normalize('NFKD').trim()
314
- .replace(/[\s\-]+/g, '-')
315
- .replace(/[^A-Za-z0-9_\-]/g, '_')
316
- .replace(/^[\-\_]+|[\-\_]+$/g, '');
317
- }
318
-
319
263
  // CLASS ConsoleFileManager allows loading and saving models and diagrams, and
320
264
  // handles the interaction with the MILP solver via `exec` calls and files
321
265
  // stored on the modeler's computer
@@ -337,6 +281,31 @@ class ConsoleFileManager {
337
281
  return path.join(...parts);
338
282
  }
339
283
 
284
+ asFilePath(s, no_sep=false) {
285
+ // Return string `s` with whitespace converted to a single dash, and
286
+ // special characters (also periods!) converted to underscores.
287
+ // NOTE: Permit functional use of directory separator unless `no_sep`
288
+ // is FALSE.
289
+ const sanitize = (str) => str.trim()
290
+ // Consider dashes, commas and (semi)colons as whitespace, and
291
+ // condense all such "whitespace" into a single dash.
292
+ .replace(/[\s\-\,\;\:]+/g, '-')
293
+ // Condense special characters into a single underscore.
294
+ .replace(/[^A-Za-z0-9_\-]+/g, '_')
295
+ // No leading or trailing dashes or underscores.
296
+ .replace(/^[\-\_]+|[\-\_]+$/g, '');
297
+ s = s.trim().normalize('NFKD');
298
+ if(no_sep) return sanitize(s);
299
+ // When path is acceptable, split at both '/' and '\', because
300
+ // names may be constructed on different OS platforms.
301
+ return s.trim().split(/\/|\\/)
302
+ .map((p) => sanitize(p))
303
+ // Remove empty strings.
304
+ .filter((p) => p)
305
+ // Use the OS platform separator to reconstruct the path.
306
+ .join(path.sep);
307
+ }
308
+
340
309
  getRemoteData(dataset, url) {
341
310
  // Gets data from a URL, or from a file on the local host
342
311
  if(url === '') return;
@@ -849,7 +818,14 @@ function commandLineSettings() {
849
818
  if(mp.ext !== '.lnr') {
850
819
  console.log('WARNING: Model file should have extension .lnr');
851
820
  }
852
- fs.accessSync(av[1], fs.constants.R_OK);
821
+ try {
822
+ fs.accessSync(av[1], fs.constants.R_OK);
823
+ } catch(err) {
824
+ if(!mp.root && !mp.dir.startsWith('.')) {
825
+ av[1] = path.join(settings.user_dir, 'models', av[1]);
826
+ }
827
+ fs.accessSync(av[1], fs.constants.R_OK);
828
+ }
853
829
  settings.model_path = av[1];
854
830
  } catch(err) {
855
831
  console.log(`ERROR: File "${av[1]}" not found`);
@@ -873,18 +849,15 @@ function commandLineSettings() {
873
849
  console.log('ERROR: Failed to create data directory:', dp);
874
850
  }
875
851
  } else if(av[0] === 'report') {
876
- // Set report file name (if valid)
877
- const rfn = stringToFileName(av[1]);
852
+ // Set report file name (if valid).
853
+ // NOTE: No sub-directories for report files, so path separators
854
+ // are ignored.
855
+ const rfn = FILE_MANAGER.asFilePath(av[1], true);
878
856
  if(/^[A-Za-z0-9]+/.test(rfn)) {
879
857
  settings.report = path.join(settings.user_dir, 'reports', rfn);
880
858
  } else {
881
859
  console.log(`WARNING: Invalid report file name "{$rfn}"`);
882
860
  }
883
- } else if(av[0] === 'module') {
884
- // Add default repository is none specified
885
- if(av[1].indexOf('@') < 0) av[1] += '@local host';
886
- // Check is repository exists, etc.
887
- // @@@TO DO!
888
861
  } else if(av[0] === 'xrun') {
889
862
  if(!av[1].trim()) {
890
863
  // NOTE: `x_title` = TRUE indicates: list available experiments.
@@ -1037,10 +1010,10 @@ PROMPTER.questionPrompt = (str) => {
1037
1010
  //PROMPTER.stdoutMuted = true;
1038
1011
  */
1039
1012
 
1040
- // Initialize the Linny-R console components as global variables
1013
+ // Initialize the Linny-R console components as global variables.
1041
1014
  global.UI = new Controller();
1042
1015
  global.VM = new VirtualMachine();
1043
- global.REPOSITORY_BROWSER = new ConsoleRepositoryBrowser();
1016
+ global.POWER_GRID_MANAGER = new PowerGridManager();
1044
1017
  global.FILE_MANAGER = new ConsoleFileManager();
1045
1018
  global.DATASET_MANAGER = new DatasetManager();
1046
1019
  global.CHART_MANAGER = new ChartManager();
@@ -1050,10 +1023,8 @@ global.MONITOR = new ConsoleMonitor();
1050
1023
  global.RECEIVER = new ConsoleReceiver();
1051
1024
  global.IO_CONTEXT = null;
1052
1025
  global.MODEL = new LinnyRModel();
1053
-
1054
1026
  // Connect the virtual machine (may prompt for password).
1055
1027
  MONITOR.connectToServer();
1056
-
1057
1028
  // Load the model if specified.
1058
1029
  if(SETTINGS.model_path) {
1059
1030
  FILE_MANAGER.loadModel(SETTINGS.model_path, (model) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linny-r",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
package/static/index.html CHANGED
@@ -2878,8 +2878,8 @@ When modeling power grids, -c indicates &ldquo;disregard grid capacity&rdquo;,
2878
2878
  </div>
2879
2879
  </div>
2880
2880
 
2881
- <!-- The "clusters to ignore" dialog displays a scrollable list of clusters
2882
- set to be ignored for specified selectors.
2881
+ <!-- The "clusters to ignore" dialog displays a scrollable list of *name patterns*
2882
+ for clusters set to be ignored for specified selectors.
2883
2883
  NOTE: Changes to this list are discarded if CANCEL is clicked.
2884
2884
  -->
2885
2885
  <div id="xp-clusters-modal" class="modal">
@@ -2889,26 +2889,48 @@ When modeling power grids, -c indicates &ldquo;disregard grid capacity&rdquo;,
2889
2889
  <img class="cancel-btn" src="images/cancel.png">
2890
2890
  <img class="ok-btn" src="images/ok.png">
2891
2891
  </div>
2892
- <select id="xp-clusters-select"></select>
2893
- <img id="xp-clusters-add-btn" src="images/add.png" class="btn enab"
2894
- title="Add cluster to list">
2895
2892
  <div id="xp-clusters-no-list">
2896
2893
  No clusters set to be ignored
2897
2894
  </div>
2898
2895
  <div id="xp-clusters-list">
2899
2896
  <div id="xp-clusters-scroll-hdr">
2900
- <div id="xp-clusters-name-hdr">Cluster</div>
2897
+ <div id="xp-clusters-name-hdr">Cluster name pattern</div>
2901
2898
  <div id="xp-clusters-selectors-hdr">Selector(s)</div>
2902
2899
  </div>
2903
2900
  <div id="xp-clusters-scroll">
2904
2901
  <table id="xp-clusters-table" width="100%"></table>
2905
2902
  </div>
2906
- <div id="xp-clusters-selectors-div">
2907
- Selector(s):
2908
- <input id="xp-clusters-selectors" type="text" autocomplete="off">
2909
- <img id="xp-clusters-delete-btn" src="images/remove.png"
2910
- class="btn enab" title="Delete cluster from list">
2911
- </div>
2903
+ </div>
2904
+ <div id="xp-clusters-buttons">
2905
+ <img id="xp-clusters-new-btn" class="btn enab" src="images/new.png"
2906
+ title="New cluster selection pattern">
2907
+ <img id="xp-clusters-edit-btn" class="btn disab" src="images/edit.png"
2908
+ title="Edit selected pattern">
2909
+ <img id="xp-clusters-delete-btn" class="btn disab" src="images/delete.png"
2910
+ title="Delete selected pattern">
2911
+ </div>
2912
+ </div>
2913
+ </div>
2914
+
2915
+ <!-- Modal to appear when the New button or Edit button of the
2916
+ "clusters to ignore" dialog is clicked.
2917
+ -->
2918
+ <div id="xp-pattern-modal" class="modal">
2919
+ <div id="xp-pattern-dlg" class="inp-dlg">
2920
+ <div class="dlg-title">
2921
+ <span id="xp-pattern-action">Add</span> cluster selection
2922
+ <img class="cancel-btn" src="images/cancel.png">
2923
+ <img class="ok-btn" src="images/ok.png">
2924
+ </div>
2925
+ <div id="xp-pattern-filter">
2926
+ <label>Filter:</label>
2927
+ <input id="xp-pattern-pattern" type="text">
2928
+ <div id="xp-pattern-matches">(no matches)</div>
2929
+ </div>
2930
+ <div id="xp-pattern-selectors-div">
2931
+ <label>Selector(s):</label>
2932
+ <input id="xp-pattern-selectors" type="text" autocomplete="off">
2933
+ <div id="xp-pattern-dimensions">(no dimensions)</div>
2912
2934
  </div>
2913
2935
  </div>
2914
2936
  </div>
@@ -3136,7 +3158,7 @@ where X can be one or several of these letters: ABCDELPQ">
3136
3158
  </div>
3137
3159
  <div id="check-update-msg"></div>
3138
3160
  <div id="check-update-buttons">
3139
- <img class="ok-btn" src="images/ok.png">
3161
+ <img id="check-update-ok-btn" class="ok-btn" src="images/ok.png">
3140
3162
  Shut down server to install new version
3141
3163
  <img class="cancel-btn" src="images/cancel.png">
3142
3164
  Continue using current version
@@ -2160,6 +2160,7 @@ div.export {
2160
2160
  #replace-div {
2161
2161
  min-width: 200px;
2162
2162
  margin: 3px 0 3px 2px;
2163
+ white-space: nowrap;
2163
2164
  }
2164
2165
 
2165
2166
  #replace-by-name {
@@ -4243,7 +4244,8 @@ td.sa-not-run {
4243
4244
  width: 20px;
4244
4245
  }
4245
4246
 
4246
- #xp-delete-btn {
4247
+ #xp-delete-btn,
4248
+ #xp-clusters-delete-btn {
4247
4249
  position: absolute;
4248
4250
  right: 2px;
4249
4251
  }
@@ -4950,15 +4952,15 @@ span.sd-clear {
4950
4952
  font-family: monospace;
4951
4953
  }
4952
4954
 
4955
+ /* the CLUSTERS TO INGNORE dialog */
4953
4956
  #xp-clusters-dlg {
4954
4957
  width: 408px;
4955
4958
  height: min-content;
4956
4959
  }
4957
4960
 
4958
- #xp-clusters-select {
4959
- width: 380px;
4960
- font-size: 12px;
4961
- margin: 2px;
4961
+ #xp-clusters-buttons > img.btn {
4962
+ height: 20px;
4963
+ width: 20px;
4962
4964
  }
4963
4965
 
4964
4966
  #xp-clusters-add-btn {
@@ -4986,12 +4988,12 @@ span.sd-clear {
4986
4988
  }
4987
4989
 
4988
4990
  #xp-clusters-name-hdr {
4989
- width: 300px;
4991
+ width: 250px;
4990
4992
  display: inline-block;
4991
4993
  }
4992
4994
 
4993
4995
  #xp-clusters-selectors-hdr {
4994
- width: 100px;
4996
+ width: 150px;
4995
4997
  display: inline-block;
4996
4998
  }
4997
4999
 
@@ -5003,29 +5005,61 @@ span.sd-clear {
5003
5005
  border-top: 1px solid Silver;
5004
5006
  }
5005
5007
 
5006
- #xp-clusters-table > tbody > tr > td:last-child {
5007
- width: 100px;
5008
- max-width: 100px;
5008
+ td.c-pat {
5009
+ border-right: none !important;
5010
+ }
5011
+
5012
+ td.c-match {
5013
+ width: 5%;
5014
+ color: Gray;
5015
+ border-left: none !important;
5016
+ font-style: italic;
5017
+ text-align: right;
5018
+ padding-right: 2px;
5019
+ }
5020
+
5021
+ td.c-sels {
5022
+ width: 150px;
5023
+ max-width: 150px;
5024
+ }
5025
+
5026
+ /* the CLUSTER PATTERN dialog */
5027
+ #xp-pattern-dlg {
5028
+ width: 370px;
5029
+ height: min-content;
5009
5030
  }
5010
5031
 
5011
- #xp-clusters-selectors-div {
5032
+ #xp-pattern-filter {
5033
+ margin: 1px 0 0 2px;
5034
+ }
5035
+
5036
+ #xp-pattern-pattern {
5037
+ width: 220px;
5038
+ font-size: 12px;
5039
+ margin: 2px;
5040
+ }
5041
+
5042
+ #xp-pattern-matches,
5043
+ #xp-pattern-dimensions {
5044
+ display: inline-block;
5045
+ font-style: italic;
5046
+ color: Gray;
5047
+ width: 90px;
5048
+ margin: 2px;
5049
+ }
5050
+
5051
+ #xp-pattern-selectors-div {
5012
5052
  margin: 2px;
5053
+ margin-bottom: 3px;
5013
5054
  width: calc(100% - 4px);
5014
5055
  }
5015
5056
 
5016
- #xp-clusters-selectors {
5017
- width: 318px;
5018
- height: 16px !important;
5057
+ #xp-pattern-selectors {
5058
+ width: 193px;
5019
5059
  font-size: 12px;
5060
+ margin: 0 2px;
5020
5061
  }
5021
5062
 
5022
- #xp-clusters-delete-btn {
5023
- position: absolute;
5024
- bottom: 2px;
5025
- right: 2px;
5026
- height: 18px;
5027
- width: 18px;
5028
- }
5029
5063
 
5030
5064
  /* The BROWSER DIALOG is a resizable modal dialog for the File manager */
5031
5065
  #browser-dlg {
@@ -13,7 +13,7 @@ the Linny-R software is available.
13
13
  */
14
14
 
15
15
  /*
16
- Copyright (c) 2017-2025 Delft University of Technology
16
+ Copyright (c) 2017-2026 Delft University of Technology
17
17
 
18
18
  Permission is hereby granted, free of charge, to any person obtaining a copy
19
19
  of this software and associated documentation files (the "Software"), to deal
@@ -176,11 +176,11 @@ function checkForUpdates() {
176
176
  'This is a <em>major</em> version change, so automatic ',
177
177
  'updating is <strong>not</strong> possible.<br>',
178
178
  'Please read <a href="', GITHUB_REPOSITORY,
179
- '/linny-r#updating-to-the-latest-version-of-linny-r" ',
179
+ '#updating-to-the-latest-version-of-linny-r" ',
180
180
  'target="_blank"> this information on GitHub</a> ',
181
181
  ' on how to manually upgrade Linny-R.'].join('');
182
182
  // ... so shutdown instead of update...
183
- UI.removeListeners(UI.check_update_modal.ok)
183
+ UI.removeListeners('check-update-ok-btn')
184
184
  .addEventListener('click', () => UI.shutDownServer());
185
185
  // ... and show blinking notification in dialog header.
186
186
  blinker.style.display = 'inline-block';
@@ -217,7 +217,7 @@ function initializeLinnyR() {
217
217
  X_EDIT = new ExpressionEditor();
218
218
  ACTOR_MANAGER = new ActorManager();
219
219
  SCALE_UNIT_MANAGER = new ScaleUnitManager();
220
- POWER_GRID_MANAGER = new PowerGridManager();
220
+ POWER_GRID_MANAGER = new GUIPowerGridManager();
221
221
  EQUATION_MANAGER = new EquationManager();
222
222
  FINDER = new Finder();
223
223
  CONSTRAINT_EDITOR = new ConstraintEditor();