linny-r 2.0.1 → 2.0.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
@@ -1,39 +1,46 @@
1
- <img src="https://sysmod.tbm.tudelft.nl/linny-r/images/logo.png" height="55px" alt="Linny-R">
1
+ <img src="https://sysmod.tbm.tudelft.nl/linny-r/images/logo.png"
2
+ height="55px" alt="Linny-R">
2
3
 
3
4
  Linny-R is an executable graphical specification language for mixed integer
4
- <a href="https://en.wikipedia.org/wiki/Linear_programming" target="_blank">linear programming</a>
5
- (MILP) problems, especially
5
+ <a href="https://en.wikipedia.org/wiki/Linear_programming"
6
+ target="_blank">linear programming</a> (MILP) problems, especially
6
7
  <a href="https://en.wikipedia.org/wiki/Unit_commitment_problem_in_electrical_power_production"
7
8
  target="_blank">unit commitment problems</a>
8
9
  (UCP) and
9
10
  <a href="https://en.wikipedia.org/wiki/Generation_expansion_planning"
10
11
  target="_blank">generation expansion planning</a> (GEP).
11
12
 
12
- The graphical language and WYSIWYG model editor are developed by **Pieter Bots** at
13
- <a href="https://tudelft.nl" target="_blank">Delft University of Technology</a>.
13
+ The graphical language and WYSIWYG model editor are developed by **Pieter Bots**
14
+ at <a href="https://tudelft.nl" target="_blank">Delft University of Technology</a>.
14
15
 
15
- Originally implemented in Delphi Pascal, Linny-R is now developed in HTML+CSS+JavaScript
16
- so as to be platform-independent and 100% transparent open source (under the MIT license).
17
- The software comprises a server that runs on **Node.js**,
18
- and a graphical user interface (GUI) that runs in any modern browser.
19
-
20
- These <a href="https://sysmod.tbm.tudelft.nl/linny-r/docs/?68" target="_blank">instruction videos</a>
21
- published on YouTube give an idea of what Linny-R can do.
22
-
23
- User documentation for Linny-R is still scant, but it is growing. You can contribute yourself
24
- (in "wiki fashion") via the official user documentation site
16
+ Originally implemented in Delphi Pascal, Linny-R is now developed in
17
+ HTML+CSS+JavaScript so as to be platform-independent and 100% transparent
18
+ open source (under the MIT license). The software comprises a server that
19
+ runs on **Node.js**, and a graphical user interface (GUI) that runs in any
20
+ modern browser.
21
+
22
+ These <a href="https://sysmod.tbm.tudelft.nl/linny-r/docs/?68"
23
+ target="_blank">instruction videos</a> published on YouTube give
24
+ an idea of what Linny-R can do.
25
+
26
+ User documentation for Linny-R is still scant. A book "Modeling and
27
+ simulation with Linny-R" will be published by TU Delft OPEN in 2024.
28
+ Meanwhile, you can consult the official user documentation site
25
29
  <a href="https://linny-r.info" target="_blank">https://linny-r.info</a>.
26
- Technical documentation will be developed on GitHub: https://github.com/pwgbots/linny-r/wiki
30
+ Technical documentation will be developed in due time on GitHub:
31
+ https://github.com/pwgbots/linny-r/wiki
27
32
 
28
33
  ## Installing Node.js
29
34
 
30
- Linny-R is developed as a JavaScript package, and requires that **Node.js** is installed on your computer.
31
- This software can be downloaded from <a href="https://nodejs.org" target="_blank">https://nodejs.org</a>.
35
+ Linny-R is developed as a JavaScript package, and requires that **Node.js**
36
+ is installed on your computer. This software can be downloaded from
37
+ <a href="https://nodejs.org" target="_blank">https://nodejs.org</a>.
32
38
  Make sure that you choose the correct installer for your computer.
33
- Linny-R is developed using the _current_ release. Presently (April 2024) this is 21.7.3.
39
+ Linny-R is developed using the _current_ release. Presently (June 2024)
40
+ this is 22.2.0.
34
41
 
35
42
  Run the installer and accept the default settings.
36
- There is **no** need to install the optional _Tools for Native Modules_.
43
+ There is <u>**no**</u> need to install the optional _Tools for Native Modules_.
37
44
 
38
45
  Open the Command Line Interface (CLI) of your computer.
39
46
  On macOS, this will be `Terminal`, on Windows `Command Prompt`.
@@ -41,16 +48,19 @@ Verify the installation by typing:
41
48
 
42
49
  ``node --version``
43
50
 
44
- The response should be the version number of Node.js, for example: v21.7.3.
51
+ The response should be the version number of Node.js, for example: v22.2.0.
45
52
 
46
53
  ## Installing Linny-R
47
- It is advisable to install Linny-R in a directory on your computer, not in a cloud.
54
+ It is advisable to install Linny-R in a directory on your computer, **not**
55
+ in a cloud.
56
+
48
57
  In this installation guide, the path to this directory is denoted by `Linny-R`,
49
58
  so in all commands you should replace this with the actual directory path.
50
- On a Windows machine the suggested path is `C:\Users\(your user name)\Documents\Linny-R`,
59
+ On a Windows machine the suggested path is `C:\Users\(your user name)\Linny-R`,
51
60
  and on a macOS machine `/Users/(your user name)/Linny-R`.
52
61
 
53
- To install Linny-R in this directory, first change to the parent directory like so:
62
+ To install Linny-R in this directory, first change to the parent directory
63
+ like so:
54
64
 
55
65
  ``cd /Users/(your user name)``
56
66
 
@@ -69,7 +79,8 @@ and then type at the command line prompt:
69
79
  > [!IMPORTANT]
70
80
  > The spacing around the dot is essential. Type the command in lower case.
71
81
 
72
- After installation has completed, `Linny-R` should have this directory tree structure:
82
+ After installation has completed, `Linny-R` should have this directory tree
83
+ structure:
73
84
 
74
85
  <pre>
75
86
  Linny-R
@@ -92,52 +103,73 @@ Linny-R
92
103
  </pre>
93
104
 
94
105
  `Linny-R` should contain two JSON files `package.json` and `package-lock.json`
95
- that should **not** be removed, or you will have to re-install Linny-R. It should also contain
96
- a script file to facilitate (single click) launch: on a macOS machine the shell script `linny-r.command`,
97
- on a Windows machine the batch script `linny-r.bat`. By default, this script file contains
98
- two commands: first change to the Linny-R directory and then tell Node.js to launch the
99
- start the Linny-R server.
106
+ that should **not** be removed, or you will have to re-install Linny-R.
107
+ It should also contain the launch script. On a macOS machine, this will be
108
+ the shell script `linny-r.command`, on a Windows machine the batch script
109
+ `linny-r.bat`.
100
110
 
101
- > [!NOTE]
102
- > When configuring Linny-R for a network environment where individual users
103
- > each have their personal work space (e.g., a virtual drive U:), you must edit this script file,
104
- > adding the argument `workspace=path/to/workspace` to the `node` command.
105
- > This will instruct Linny-R to create the `user` directory in this workspace directory
106
- > instead of the Linny-R directory.
111
+ All other software is contained in the `node_modules` directory. It comprises
112
+ two Node.js packages: `@xlmdom` and `linny-r`.
107
113
 
108
- The `linny-r` directory should contain this file `README.md`,
109
- the files `server.js` and `console.js` that will be run by Node.js,
110
- and the sub-directory `static`. This `static` directory should contain three HTML files:
114
+ The `linny-r` package directory should contain this file `README.md`, the files
115
+ `server.js` and `console.js` that will be run by Node.js, and the sub-directory
116
+ `static`. This `static` directory should contain three HTML files:
111
117
 
112
118
  * `index.html` (the browser-based GUI)
113
119
  * `show-png.html` (to render SVG diagrams as PNG images)
114
- * `show-diff.html` (to display differences betwee two Linny-R models)
120
+ * `show-diff.html` (to display differences between two Linny-R models)
115
121
 
116
122
  It should also contain the style sheet `linny-r.css` required by the GUI.
117
123
 
118
- The sub-directories of `static` contain files that are served to the browser by the script
119
- `server.js` when it is running in Node.js.
124
+ The sub-directories of `static` contain files that are served to the browser
125
+ by the script `server.js` when it is running in Node.js.
126
+
127
+ > [!IMPORTANT]
128
+ > Unless you _really_ know what you are doing, do **not** move or rename
129
+ > your Linny-R directory or change its contents. If for some reason Linny-R
130
+ > does not work, remove _all_ Linny-R software from your machine, and then
131
+ > install it anew, following the procedure described above.
132
+
133
+ #### Updating to the latest version of Linny-R
134
+
135
+ When a newer version has beeh released, Linny-R will prompt you to update
136
+ automatically. Click on the link in this prompt to see the release notes
137
+ on GitHub and find out about new features and bug fixes. When you click on
138
+ the _OK_ button, Linny-R will shut down its local server script, and then
139
+ the launch script should perform the `npm update` command and then restart
140
+ the server script. Your browser will prompt you to close its current tab,
141
+ and then Linny-R should reappear in a new browser tab or window.
142
+
143
+ > [!NOTE]
144
+ > The built-in updating function of Linny-R will _**not**_ automatically
145
+ > upgrade to a new _major_ version. To update from a version like 1.9.x to
146
+ > a version like 2.0.x, you have to open a command line interface
147
+ > (`Command Prompt` or `Terminal`), change to your Linny-R directory, and
148
+ > then type `npm install linny-r@2`. This should perform the upgrade.
149
+ > You can then launch Linny-R as usual by typing `linny-r` (Windows) or
150
+ > `./linny-r.command` (macOS).
120
151
 
121
152
  #### Installing and using an earlier version of Linny-R
122
153
 
123
154
  By default, **npm** will install the latest release of the Linny-R software.
124
- As this software is developed as part of academic research, new features are added
125
- without rigorous testing. Although much effort is dedicated to maintaining upward
126
- and downward compatibility, you may find that the latest version does not work as
127
- well for you as some earlier version. To re-install an earlier release, for example
128
- version 1.4.0, open the CLI, change to your `Linny-R` directory, and then type:
155
+ As this software is developed as part of academic research, new features
156
+ are added without rigorous testing. Although much effort is dedicated to
157
+ maintaining upward and downward compatibility, you may find that the latest
158
+ version does not work as well for you as some earlier version. To re-install
159
+ an earlier release, for example version 1.9.3, open the CLI, change to your
160
+ `Linny-R` directory, and then type:
129
161
 
130
- ``npm install linny-r@1.4.0``
162
+ ``npm install linny-r@1.9.3``
131
163
 
132
164
  > [!NOTE]
133
165
  > This will overwrite the contents of the `node_modules` directory, but
134
166
  > it will not affect the files in your user space.
135
167
 
136
- If you prefer to have different versions of Linny-R on your computer, you can
137
- create a separate directory for a specific version, then change to this
168
+ If you prefer to have different versions of Linny-R on your computer, you
169
+ can create a separate directory for a specific version, then change to this
138
170
  directory and type:
139
171
 
140
- ``npm install --prefix . linny-r@1.4.0``
172
+ ``npm install --prefix . linny-r@1.9.3``
141
173
 
142
174
  > [!NOTE]
143
175
  > To run a specific version in your browser, you must start the server from
@@ -147,10 +179,11 @@ directory and type:
147
179
 
148
180
  ## Configuring the MILP solver
149
181
 
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
- but they require a license.
153
- Academic licenses can be obtained by students and staff of eligible institutions.
182
+ Linny-R presently supports five MILP solvers: Gurobi, MOSEK, CPLEX, SCIP
183
+ and LP_solve. Gurobi, MOSEK and CPLEX are _considerably_ more powerful than
184
+ the open source solvers SCIP and LP_solve, but they require a license.
185
+ Academic licenses can be obtained by students and staff of eligible
186
+ institutions.
154
187
 
155
188
  > [!IMPORTANT]
156
189
  > When installing a solver, it is advisable to accept the default file
@@ -166,8 +199,9 @@ Gurobi on your computer can be obtained via this URL:
166
199
  <a href="https://www.gurobi.com/academia/academic-program-and-licenses/"
167
200
  target="_blank">https://www.gurobi.com/academia/academic-program-and-licenses/</a>
168
201
 
169
- When running a model, Linny-R will try to execute the command line application `gurobi_cl`.
170
- It will look for this application in the directory specified in the environment variable PATH on your computer.
202
+ When running a model, Linny-R will try to execute the command line application
203
+ `gurobi_cl`. It will look for this application in the directory specified in
204
+ the environment variable PATH on your computer.
171
205
 
172
206
  #### Installing CPLEX
173
207
 
@@ -177,10 +211,11 @@ CPLEX on your computer can be obtained via this URL:
177
211
  <a href="https://www.ibm.com/products/ilog-cplex-optimization-studio"
178
212
  target="_blank">https://www.ibm.com/products/ilog-cplex-optimization-studio</a>
179
213
 
180
- When running a model, Linny-R will try to execute the command line application `cplex`.
181
- It will look for this application in the directory specified in the environment variable PATH
182
- or more specifically in the environment variable CPLEX_STUDIO_BINARIES<em>nnnn</em>
183
- (where _nnnn_ denotes the CPLEX version number) on your computer.
214
+ When running a model, Linny-R will try to execute the command line application
215
+ `cplex`. It will look for this application in the directory specified in the
216
+ environment variable PATH or more specifically in the environment variable
217
+ CPLEX_STUDIO_BINARIES<em>nnnn</em> (where _nnnn_ denotes the CPLEX version
218
+ number) on your computer.
184
219
 
185
220
  #### Installing MOSEK
186
221
 
@@ -190,38 +225,46 @@ MOSEK on your computer can be obtained via this URL:
190
225
  <a href="https://www.mosek.com/resources/getting-started/"
191
226
  target="_blank">https://www.mosek.com/resources/getting-started/</a>
192
227
 
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.
228
+ When running a model, Linny-R will try to execute the command line application
229
+ `mosek`. It will look for this application in the directory specified in the
230
+ environment variable PATH on your computer.
195
231
 
196
232
  #### Installing SCIP
197
233
 
198
- The SCIP software is open source. Instructions for installation can be found via this URL:
199
- <a href="https://scipopt.org/doc/html/INSTALL.php" target="_blank">https://scipopt.org/doc/html/INSTALL.php</a>
234
+ The SCIP software is open source. Instructions for installation can be found
235
+ via this URL: <a href="https://scipopt.org/doc/html/INSTALL.php"
236
+ target="_blank">https://scipopt.org/doc/html/INSTALL.php</a>
200
237
 
201
- When running a model, Linny-R will try to execute the command line application `scip`.
202
- It will look for this application in the directory specified in the environment variable PATH on your computer.
238
+ When running a model, Linny-R will try to execute the command line application
239
+ `scip`. It will look for this application in the directory specified in the
240
+ environment variable PATH on your computer.
203
241
 
204
242
  #### Installing LP_solve
205
243
 
206
244
  The LP_solve software is open source and can be downloaded via this URL:
207
- <a href="https://sourceforge.net/projects/lpsolve" target="_blank">https://sourceforge.net/projects/lpsolve</a>
208
-
209
- To facilitate installation, the executable files for Windows and macOS can be downloaded from the Linny-R website
210
- at Delft University of Technology:
211
- <a href="https://sysmod.tbm.tudelft.nl/linny-r/lp_solve" target="_blank">https://sysmod.tbm.tudelft.nl/linny-r/lp_solve</a>
212
-
213
- There you will find links to download LP_solve applications that have been compiled for different platforms.
214
- If you do not know which platform to choose, run Linny-R as described below, and the platform will be listed in its output.
215
- If no matching LP_solve version is listed, you can try to compile the software from its source.
216
- How to do this is explained on the page "Installing LP_solve on a Mac" on the Linny-R documentation site:
245
+ <a href="https://sourceforge.net/projects/lpsolve"
246
+ target="_blank">https://sourceforge.net/projects/lpsolve</a>
247
+
248
+ To facilitate installation, the executable files for Windows and macOS can
249
+ be downloaded from the Linny-R website at Delft University of Technology:
250
+ <a href="https://sysmod.tbm.tudelft.nl/linny-r/lp_solve"
251
+ target="_blank">https://sysmod.tbm.tudelft.nl/linny-r/lp_solve</a>
252
+
253
+ There you will find links to download LP_solve applications that have been
254
+ compiled for different platforms. If you do not know which platform to choose,
255
+ run Linny-R as described below, and the platform will be listed in its output.
256
+ If no matching LP_solve version is listed, you can try to compile the software
257
+ from its source. How to do this is explained on the page "Installing LP_solve
258
+ on a Mac" on the Linny-R documentation site:
217
259
  <a href="https://linny-r.info" target="_blank">https://linny-r.info</a>
218
260
 
219
- When you have downloaded the file (just `lp_solve` for macOS, `lp_solve.exe` for Windows),
220
- you must copy or move this file to your `Linny-R` directory,
221
- as this is where Linny-R will look for it when it does not find one of the other solvers.
261
+ When you have downloaded the file (just `lp_solve` for macOS, `lp_solve.exe`
262
+ for Windows), you must copy or move this file to your `Linny-R` directory,
263
+ as this is where Linny-R will look for it when it does not find one of the
264
+ other solvers.
222
265
 
223
266
  On a macOS machine, you must then make the file `lp_solve` executable.
224
- Open Terminal and change to your Linny-R directory, and then type:
267
+ Open `Terminal` and change to your Linny-R directory, and then type:
225
268
 
226
269
  ``chmod +x lp_solve``
227
270
 
@@ -230,35 +273,36 @@ When you then type:
230
273
  ``./lp_solve -h``
231
274
 
232
275
  a window may appear that warns you that the software may be malicious.
233
- To allow running LP_solve, you must then go to Security & Privacy (via System Preferences)
234
- and there click the Open Anyway button in the General pane to confirm that you wish to use LP_solve.
235
- Then return to Terminal and once more type `./lp_solve -h`.
236
- The response should then be a listing of all the command line options of LP_solve.
237
- If you reach this stage, Linny-R will be able to run LP_solve.
276
+ To allow running LP_solve, you must then go to _Security & Privacy_ (via
277
+ _System Preferences_) and there click the _Open Anyway_ button in the _General_
278
+ pane to confirm that you wish to use LP_solve. Then return to `Terminal`
279
+ and once more type `./lp_solve -h`. The response should then be a listing
280
+ of all the command line options of LP_solve. If you reach this stage,
281
+ Linny-R will be able to run LP_solve.
238
282
 
239
283
  ## Running Linny-R
240
284
 
241
- Open the Command Line Interface (CLI) of your computer, change to your Linny-R directory and type:
285
+ On a Windows machine, open `Command Prompt`, change to your Linny-R
286
+ directory and type:
242
287
 
243
- ``node node_modules/linny-r/server launch``
288
+ ``linny-r``
244
289
 
245
- This response should be something similar to:
290
+ On a macOS machine, open `Terminal`, change to your Linny-R directory
291
+ and type:
246
292
 
247
- <pre>
248
- Node.js server for Linny-R version 1.9.3
249
- Node.js version: v21.7.3
250
- ... etc.
251
- </pre>
293
+ ``./linny-r.command``
252
294
 
253
- Meanwhile, your default web browser should have opened a tab for the local server URL,
254
- which by default will be http://127.0.0.1:5050.
255
- The Linny-R GUI should show in your browser window,
256
- while in the CLI you should see a long series of server log messages like:
295
+ This should run the launch script for Linny-R, which will start the
296
+ local server script that connects your browser with the solver.
297
+ Meanwhile, your default web browser should have opened a tab for the local
298
+ server URL, which by default will be http://127.0.0.1:5050.
299
+ The Linny-R GUI should show in your browser window, while in the CLI you
300
+ should see a long series of server log messages like:
257
301
 
258
302
  <pre>
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
303
+ [2024-06-11 22:55:17] Static file: /index.html
304
+ [2024-06-11 22:55:17] Static file: /scripts/iro.min.js
305
+ [2024-06-11 22:55:17] Static file: /images/open.png
262
306
  ... etc.
263
307
  </pre>
264
308
 
@@ -266,16 +310,16 @@ while in the CLI you should see a long series of server log messages like:
266
310
  > Do **not** close the CLI. If you do, the Linny-R GUI may still be
267
311
  > visible in your browser, but you will be warned that it cannot connect
268
312
  > to the server (at 127.0.0.1:5050). This means that you have to restart
269
- > Linny-R from a new CLI.
313
+ > Linny-R as described above.
270
314
 
271
315
  After loading into the browser, Linny-R will try to connect to the solver.
272
- If successful, a notification (blue background) will appear on the status bar
273
- at the bottom of the window, stating the name of the solver.
316
+ If successful, a notification (blue background) will appear on the status
317
+ bar at the bottom of the window, stating the name of the solver.
274
318
 
275
319
  You can then test the GUI by creating a simple model.
276
320
  Make one that has at least one process that outputs a product,
277
- and this product must have a price or a set lower bound, otherwise the model
278
- will have no objective function.
321
+ and this product must have a price or a set lower bound, otherwise the
322
+ model will have no objective function.
279
323
  Then click on the _Solve_ button at the bottom of the left-hand tool bar.
280
324
  The Linny-R icon in the upper left corner should start rotating, while the
281
325
  status bar at the bottom should display:
@@ -298,9 +342,41 @@ confirming that you want to leave, and then closing your browser (tab).
298
342
  If you do not shut down the server from the browser, you can also stop the
299
343
  server by repeatedly pressing ``Ctrl+C`` in the CLI.
300
344
 
345
+ ## Click-start for Linny-R
346
+
347
+ When `npm` installs the Linny-R package, it creates a script file in your
348
+ Linny-R directory that launches Linny-R. On a macOS machine, this will be
349
+ the shell script `linny-r.command`, on a Windows machine the batch script
350
+ `linny-r.bat`. When you create a desktop shortcut to this script, this will
351
+ allow you to click-start Linny-R.
352
+
353
+ How you can create a shortcut icon for Linny-R on your desktop depends on
354
+ the type of computer you use.
355
+
356
+ On a Windows machine, open the `File Explorer`, select your Linny-R folder,
357
+ right-click on the batch file `linny-r.bat`, and select the _Create shortcut_
358
+ option. Then right-click on the shortcut file to edit its properties, and
359
+ click the _Change Icon_ button. The dialog that then appears will allow
360
+ you to go to the sub-folder `node_modules\linny-r\static\images`, where
361
+ you should select the file `linny-r.ico`. Finally, rename the shortcut to
362
+ `Linny-R` and move or copy it to your desktop.
363
+
364
+ On a macOS machine, open `Terminal` and change to your Linny-R directory,
365
+ and then type:
366
+
367
+ ``chmod +x linny-r.command``
368
+
369
+ to make the script file executable. To set the icon, use `Finder` to open
370
+ the folder that contains the file `linny-r.command`, click on its icon
371
+ (which still is plain) and open the _Info dialog_ by pressing ``Cmd+I``.
372
+ Then open your Linny-R folder in `Finder`, change to the sub-folder
373
+ `node_modules/linny-r/static/images`, and from there drag/drop the file
374
+ `linny-r.icns` on the icon shown in the top left corner of the _Info dialog_.
375
+
301
376
  ## Command line options
302
377
 
303
- Optionally, you can add more arguments to the `node` command:
378
+ You can customize Linny-R by adding more arguments to the `node` command
379
+ in the launch script:
304
380
 
305
381
  <pre>
306
382
  dpi=[number] to overrule the default resolution (300 dpi) for Inkscape
@@ -310,26 +386,12 @@ solver=[name] to overrule the default sequence (Gurobi, MOSEK, CPLEX, SCIP,
310
386
  workspace=[path] to overrule the default path for the user directory
311
387
  </pre>
312
388
 
313
- ## Click-start for Linny-R
314
-
315
- To facilitate start-up, you can create a shortcut icon for Linny-R on your desktop.
316
-
317
- On a Windows machine, open the _File Explorer_, select your Linny-R folder,
318
- right-click on the batch file `linny-r.bat`, and select the _Create shortcut_ option.
319
- Then right-click on the shortcut file to edit its properties, and click the _Change Icon_ button.
320
- The dialog that then appears will allow you to go to the sub-folder
321
- `node_modules\linny-r\static\images`, where you should select the file `linny-r.ico`.
322
- Finally, rename the shortcut to `Linny-R` and move or copy it to your desktop.
323
-
324
- On a macOS machine, open Terminal and change to your Linny-R directory, and then type:
325
-
326
- ``chmod +x linny-r.command``
327
-
328
- to make the script file executable.
329
- To set the icon, use Finder to open the folder that contains the file `linny-r.command`,
330
- click on its icon (which still is plain) and open the _Info dialog_ by pressing ``Cmd+I``.
331
- Then open your Linny-R folder in Finder, change to the sub-folder `node_modules/linny-r/static/images`,
332
- and from there drag/drop the file `linny-r.icns` on the icon shown in the top left corner of the _Info dialog_.
389
+ > [!NOTE]
390
+ > When configuring Linny-R for a network environment where individual users
391
+ > each have their personal work space (e.g., a virtual drive U:), you **must**
392
+ > edit the launch script file, adding the argument `workspace=path/to/workspace`
393
+ > to the `node` command. This will instruct Linny-R to create the `user`
394
+ > directory in this workspace directory instead of the Linny-R directory.
333
395
 
334
396
  ## User workspace
335
397
 
@@ -338,20 +400,23 @@ The sub-directories of this directory `user` are used by Linny-R to store files.
338
400
 
339
401
  * `autosave` will contain models that have been _auto-saved_
340
402
  * `channel` and `callback` will be used to interact with Linny-R via its _Receiver_
341
- * `data` will be used by the _Dataset Manager_ to locate datasets for which a path
342
- has been specified
403
+ * `data` will be used by the _Dataset Manager_ to locate datasets for which
404
+ a path has been specified
343
405
  * `diagrams` will be used to render Scalable Vector Graphics (SVG) files as
344
406
  Portable Network Graphics (PNG) using Inkscape (if installed)
407
+ * `models` will contain models that you saved by Shift-clicking on the
408
+ _Save_ button, or using the keyboard shortcut Ctrl-Shift-S
345
409
  * `modules` will contain models stored in the `local host` _repository_
346
- * `reports` will contain text files with time series data and statistics in tab-separated
347
- format that can be imported or copy/pasted into Excel
348
- * `solver` will contain the files that are exchanged with the Mixed Integer Linear Programming (MILP) solver
349
- (the names of the files that will appear in this directory may vary, depending on the MILP-solver you use)
410
+ * `reports` will contain text files with time series data and statistics in
411
+ tab-separated format that can be imported or copy/pasted into Excel
412
+ * `solver` will contain the files that are exchanged with the Mixed Integer
413
+ Linear Programming (MILP) solver (the names of the files that will appear
414
+ in this directory may vary, depending on the MILP-solver you use)
350
415
 
351
416
  > [!NOTE]
352
417
  > By default, the `user` directory is created in your `Linny-R` directory.
353
- > You can overrule this by starting the server with the `workspace=[path]` option.
354
- > This will create a new, empty workspace (the directories listed above) in the specified path.
418
+ > You can overrule this by starting the server with the `workspace=[path]`
419
+ > option. This will create a new, empty workspace in the specified path.
355
420
  > It will **not** affect or duplicate information from existing workspaces.
356
421
 
357
422
  ## Installing Inkscape
@@ -362,8 +427,8 @@ These files can be viewed and edited using Inkscape, an open source
362
427
  vector graphics editor.
363
428
 
364
429
  As it may be tedious to first save a diagram as SVG and then render it
365
- manually as a bitmap image, Linny-R features a *Render diagram as bitmap* button
366
- on the top toolbar, and on the bottom toolbar of the _Chart manager_.
430
+ manually as a bitmap image, Linny-R features a *Render diagram as bitmap*
431
+ button on the top toolbar, and on the bottom toolbar of the _Chart manager_.
367
432
  When you click it, Linny-R will send the image as SVG to the server.
368
433
  The server script will save the SVG in the `user/diagrams` sub-directory,
369
434
  and then try to execute an Inkscape command that will convert this SVG to
@@ -375,7 +440,8 @@ If rendering was successful, the image will appear in this browser tab;
375
440
  if rendering failed, the original SVG image will be shown.
376
441
 
377
442
  To install Inkscape, please look here:
378
- <a href="https://inkscape.org/release" target="_blank">https://inkscape.org/release</a>
443
+ <a href="https://inkscape.org/release"
444
+ target="_blank">https://inkscape.org/release</a>
379
445
 
380
446
  Linny-R will automatically detect whether Inkscape is installed by searching
381
447
  for it in the environment variable PATH on your computer. On a macOS computer,
@@ -388,21 +454,25 @@ Linny-R will look for Inkscape in `/Applications/Inkscape.app/Contents/MacOS`.
388
454
 
389
455
  ## Using Linny-R console
390
456
 
391
- The console-only version of Linny-R allows you to run a Linny-R model without a web browser.
392
- This may be useful when you want run models from a script (shell script, Python, ...).
393
- If you open a CLI box, change to your `Linny-R` directory, and then type:
457
+ The console-only version of Linny-R allows you to run a Linny-R model without
458
+ a web browser. This may be useful when you want run models from a script
459
+ (shell script, Python, ...). If you open a CLI box, change to your `Linny-R`
460
+ directory, and then type:
394
461
 
395
462
  ``node node_modules/linny-r/console`` _(on Windows, use backslashes)_
396
463
 
397
- you will see the command line options that allow you to run models in various ways.
464
+ you will see the command line options that allow you to run models in various
465
+ ways.
398
466
 
399
467
  > [!NOTE]
400
- > The console-only version is still in development, and does not provide all functions yet.
468
+ > The console-only version is still in development, and does not provide
469
+ > all functions yet.
401
470
 
402
471
  ## Troubleshooting problems
403
472
 
404
- If during any of the steps above you encounter problems, please try to diagnose them and resolve them yourself.
405
- You can find a lot of useful information on the Linny-R user documentation website:
473
+ If during any of the steps above you encounter problems, please try to
474
+ diagnose them and resolve them yourself. You can find a lot of useful
475
+ information on the Linny-R user documentation website:
406
476
  <a href="https://linny-r.info" target="_blank">https://linny-r.info</a>.
407
477
 
408
478
  > [!IMPORTANT]
@@ -411,6 +481,8 @@ You can find a lot of useful information on the Linny-R user documentation websi
411
481
 
412
482
  Then also look at the console window of your browser.
413
483
  Most browsers offer a _Web Developer Tools_ option via their application menu.
414
- This will allow you to view the browser console, which will display JavaScript errors in red font.
484
+ This will allow you to view the browser console, which will display JavaScript
485
+ errors in red font.
415
486
 
416
- If you've tried hard, but failed, you can try to contact Pieter Bots at ``p.w.g.bots@tudelft.nl``
487
+ If you've tried hard, but failed, you can try to contact Pieter Bots at
488
+ ``p.w.g.bots@tudelft.nl``
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-2022 Delft University of Technology
19
+ Copyright (c) 2017-2024 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
@@ -953,6 +953,7 @@ function createWorkspace() {
953
953
  callback: path.join(SETTINGS.user_dir, 'callback'),
954
954
  data: path.join(SETTINGS.user_dir, 'data'),
955
955
  diagrams: path.join(SETTINGS.user_dir, 'diagrams'),
956
+ models: path.join(SETTINGS.user_dir, 'models'),
956
957
  modules: path.join(SETTINGS.user_dir, 'modules'),
957
958
  reports: path.join(SETTINGS.user_dir, 'reports'),
958
959
  solver_output: path.join(SETTINGS.user_dir, 'solver'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linny-r",
3
- "version": "2.0.1",
3
+ "version": "2.0.4",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
package/post-install.js CHANGED
@@ -5,28 +5,30 @@ The Linny-R language and tool have been developed by Pieter Bots at Delft
5
5
  University of Technology, starting in 2009. The project to develop a browser-
6
6
  based version started in 2017. See https://linny-r.org for more information.
7
7
 
8
- This NodeJS script (post-install.js) creates a launch script for Linny-R
9
- that facilitates start-up: the user can then type `linny-r` at the command
10
- line prompt, and also create a clickable icon as desktop shortcut.
8
+ This NodeJS script (post-install.js) checks whether the Linny-R directory
9
+ contains a launch script for Linny-R. For macOS, the script file is
10
+ `linny-r.command`, for Windows `linny-r.bat`.
11
+ If such a file already exists, this script will try to rename it, adding
12
+ the prefix `OLD-` to the name. Doing this will make that the the Linny-R
13
+ server script will create the newest version of the launch script the
14
+ first time it is run.
11
15
 
12
- For macOS, the script file is `linny-r.command`, for Windows `linny-r.bat`.
13
- The script comprises two commands:
16
+ The launch script has two intended functions:
17
+ (1) to facilitate start-up: the user can type `linny-r` at the command
18
+ line prompt,and (more importantly) create a clickable icon as desktop
19
+ shortcut.
20
+ (2) to facilitate automatic software updates: when (after lauch in a browser)
21
+ Linny-R detects a newer version, it will prompt the user whether this
22
+ update should be installed. When the user confirms, the server script
23
+ is terminated, and then launch script executes the commands to update
24
+ and then restart the Linny-R server.
14
25
 
15
- cd path/to/linny-r/directory
16
- node node_modules/linny-r/server launch
17
-
18
- since Windows also supports the slash as path separator. The "launch"
19
- command tells the script `server.js` to start Linny-R in the default
20
- web browser.
21
-
22
- Comments are added to the script file to facilitate customization of the
23
- scripts by the user. The README.md file explains how the script file can be
24
- used for single-click launch of Linny-R, and how the "workspace" parameter
25
- can be used in a multi-user network environment to provide individual
26
- workspaces for users.
26
+ The README.md file explains how the script file can be used for single-click
27
+ launch of Linny-R, and how the "workspace" parameter can be used in a
28
+ multi-user network environment to provide individual workspaces for users.
27
29
  */
28
30
  /*
29
- Copyright (c) 2020-2022 Delft University of Technology
31
+ Copyright (c) 2020-2024 Delft University of Technology
30
32
 
31
33
  Permission is hereby granted, free of charge, to any person obtaining a copy
32
34
  of this software and associated documentation files (the "Software"), to deal
@@ -56,32 +58,86 @@ const
56
58
  // NOTE: working directory for this script is the *module* directory,
57
59
  // which is two levels deeper than the actual working directory
58
60
  WORKING_DIRECTORY = process.cwd().replace(path.sep + mod_dir, ''),
59
- lines = [
61
+ ext = (PLATFORM.startsWith('win') ? 'bat' : 'command'),
62
+ sp = path.join(WORKING_DIRECTORY, 'linny-r.' + ext);
63
+
64
+ // NOTE: Function `createLaunchScript` is a copy of this function as
65
+ // defined in script file `server.js`.
66
+
67
+ function createLaunchScript() {
68
+ // Creates platform-specific script with Linny-R start-up command
69
+ const
70
+ lines = [
60
71
  '# The first line (without the comment symbol #) should be like this:',
61
- '# cd ',
62
72
  '',
73
+ 'cd ' + WORKING_DIRECTORY,
63
74
  '# Then this command to launch the Linny-R server should work:',
64
- `node ${mod_dir}${path.sep}server launch`
65
- ];
66
- let sp;
67
- if(PLATFORM.startsWith('win')) {
68
- sp = path.join(WORKING_DIRECTORY, 'linny-r.bat');
69
- lines[1] += 'C:\\path\\to\\\\your\\Linny-R\\directory';
70
- } else {
71
- sp = path.join(WORKING_DIRECTORY, 'linny-r.command');
72
- lines[1] += '/path/to/your/Linny-R/directory';
75
+ '',
76
+ '# After shut-down, check whether new version should be installed:'
77
+ ],
78
+ windows = PLATFORM.startsWith('win'),
79
+ sp = path.join(WORKING_DIRECTORY, 'linny-r.' + (windows ? 'bat' : 'command'));
80
+ if(windows) {
81
+ lines.push(
82
+ ':loop',
83
+ 'if exist newer_version (',
84
+ ' del newer_version',
85
+ ' npm update linny-r',
86
+ ' node node_modules\\linny-r\\server',
87
+ ' goto loop',
88
+ ')');
89
+ lines[1] = '# cd C:\\path\\to\\main\\Linny-R\\directory';
90
+ lines[4] = 'node node_modules\\linny-r\\server launch';
91
+ } else {
92
+ lines.push(
93
+ 'while test -f newer_version; do',
94
+ ' unlink newer_version',
95
+ ' npm update linny-r',
96
+ ' node node_modules/linny-r/server',
97
+ 'done');
98
+ lines[1] = '# cd /path/to/main/Linny-R/directory';
99
+ lines[4] = 'node node_modules/linny-r/server launch';
100
+ }
101
+ try {
102
+ let code = lines.join(os.EOL);
103
+ if(windows) code = code.replaceAll('#', '::');
104
+ try {
105
+ fs.accessSync(sp);
106
+ // Do not overwrite existing script, as it may have been customized
107
+ // by the user. When istalling/updating Linny-R, the post-install
108
+ // script should have renamed it, so typically it is created the
109
+ // first time Linny-R is run after install/update.
110
+ } catch(err) {
111
+ console.log('Creating launch script:', sp);
112
+ fs.writeFileSync(sp, code, 'utf8');
113
+ // On macOS machines, try to make the script executable.
114
+ if(!windows) try {
115
+ fs.chmodSync(sp, '+x');
116
+ } catch(err) {
117
+ console.log('WARNING: Failed to make script executable -- please check');
118
+ }
119
+ }
120
+ } catch(err) {
121
+ console.log('WARNING: Failed to create launch script');
122
+ }
73
123
  }
74
- lines[2] = 'cd ' + WORKING_DIRECTORY;
124
+
125
+ // First rename the existing script (if any) as this may have been changed
126
+ // by the user, and the idea of the changes would be lost by overwriting it.
75
127
  try {
128
+ fs.accessSync(sp);
76
129
  try {
77
- fs.accessSync(sp);
130
+ // Only rename the script content if the file it does not yet exist
131
+ console.log('Renaming existing launch script:', sp);
132
+ fs.renameSync(sp, path.join(WORKING_DIRECTORY, 'OLD-linny-r.' + ext));
78
133
  } catch(err) {
79
- // Only write the script content if the file it does not yet exist
80
- console.log('Creating launch script:', sp);
81
- let code = lines.join(os.EOL);
82
- if(PLATFORM.startsWith('win')) code = code.replaceAll('#', '::');
83
- fs.writeFileSync(sp, code, 'utf8');
134
+ console.log('WARNING: Failed to rename existing launch script');
84
135
  }
85
136
  } catch(err) {
86
- console.log('WARNING: Failed to create launch script');
137
+ // No existing script => action needed.
87
138
  }
139
+
140
+ // Then create new launch script. This way, after update or clean install,
141
+ // the user can
142
+ createLaunchScript();
143
+
package/server.js CHANGED
@@ -255,22 +255,24 @@ function clearNewerVersion() {
255
255
  }
256
256
 
257
257
  // HTML page to show when the server is shut down by the user.
258
- // NOTE: on a macOS machine, this is slightly more work
259
- const OS_TEXT = {close: '', reopen: ''};
260
- if(PLATFORM === 'darwin') {
261
- OS_TEXT.close =
262
- `<p>You can close the <em>Terminal</em> window that shows
263
- <tt>[Process Terminated]</tt> at the bottom.
264
- </p>`;
265
- OS_TEXT.reopen =
266
- `open <em>Terminal</em> again, change to your Linny-R directory by typing:
258
+ // Parts of the text are platform-specific.
259
+ const
260
+ macOS = (PLATFORM === 'darwin'),
261
+ close = (macOS ?
262
+ `<p>You can now close the <em>Terminal</em> window that shows
263
+ <tt>[Process Terminated]</tt> at the bottom.</p>` :
264
+ `<p>The <em>Command Prompt</em> window where the server was
265
+ running will be closed automatically.</p>`),
266
+ cli = (macOS ? 'Terminal' : 'Command Prompt'),
267
+ ext = (macOS ? '.command' : ''),
268
+ chmod = (!macOS ? '' : `
269
+ <p>If launch fails, you may still need to make the script executable.</p>
270
+ <p>
271
+ You can do this by typing <code>chmod +x linny-r.command</code>
272
+ at the command prompt.
267
273
  </p>
268
- <p><code>cd ${WORKING_DIRECTORY}</code></p>
269
- <p>`;
270
- } else {
271
- OS_TEXT.reopen = 'switch to your <em>Command Prompt</em> window ';
272
- }
273
- const SHUTDOWN_MESSAGE = `<!DOCTYPE html>
274
+ <p>Then retype <code>linny-r.command</code> to launch Linny-R.</p>`),
275
+ SHUTDOWN_MESSAGE = `<!DOCTYPE html>
274
276
  <html lang="en-US">
275
277
  <head>
276
278
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
@@ -290,16 +292,18 @@ const SHUTDOWN_MESSAGE = `<!DOCTYPE html>
290
292
  </style>
291
293
  </head>
292
294
  <body>
293
- <h3>Linny-R server (127.0.0.1) is shutting down</h3>${OS_TEXT.close}
294
- <p>To restart Linny-R, ${OS_TEXT.reopen} and then at the prompt type:</p>
295
- <p><code>node node_modules${path.sep}linny-r${path.sep}server</code></p>
295
+ <h3>Linny-R server (127.0.0.1) is shutting down</h3>${close}
296
296
  <p>
297
- Then switch back to this window, and click this
298
- <button type="button"
299
- onclick="window.location.href = 'http://127.0.0.1:${SETTINGS.port}';">
300
- Restart
301
- </button> button.
297
+ To restart Linny-R, open <em>${cli}</em> again, change to
298
+ your Linny-R directory by typing:
302
299
  </p>
300
+ <p><code>cd ${WORKING_DIRECTORY}</code></p>
301
+ <p>and then type:</p>
302
+ <p><code>linny-r${ext}</code></p>
303
+ <p>
304
+ This should launch Linny-R in a new browser window or tab, so you
305
+ can close this one.
306
+ </p>${chmod}
303
307
  </body>
304
308
  </html>`;
305
309
 
@@ -416,9 +420,21 @@ function autoSaveLoad(res, sp) {
416
420
  function autoSaveStore(res, sp) {
417
421
  // Stores XML data under specified file name in the auto-save directory
418
422
  let data = 'OK';
419
- const fn = sp.get('file');
423
+ const
424
+ fn = sp.get('file'),
425
+ wsd = sp.get('wsd'),
426
+ ws = (wsd ? WORKSPACE.models : WORKSPACE.autosave),
427
+ msg = (wsd === 'models' ? 'save to user workspace' : 'auto-save'),
428
+ exists = (path) => {
429
+ try {
430
+ fs.accessSync(path);
431
+ return true;
432
+ } catch(err) {
433
+ return false;
434
+ }
435
+ };
420
436
  if(!fn) {
421
- data = 'WARNING: No name for file to auto-save';
437
+ data = 'WARNING: No name for file to ' + msg;
422
438
  } else {
423
439
  const xml = sp.get('xml');
424
440
  // Validate XML as a Linny-R model
@@ -429,10 +445,41 @@ function autoSaveStore(res, sp) {
429
445
  root = doc.documentElement;
430
446
  // Linny-R models have a model element as root
431
447
  if(root.nodeName !== 'model') throw 'XML document has no model element';
432
- fs.writeFileSync(path.join(WORKSPACE.autosave, fn + '.lnr'), xml);
448
+ let fp = path.join(ws, fn + '.lnr');
449
+ if(wsd) {
450
+ // Append a version number to the file name if named file exists.
451
+ const re = /\(\d+\).lnr$/;
452
+ if(exists(fp)) {
453
+ const m = fp.match(re);
454
+ let n = 1;
455
+ if(m) {
456
+ // Replace version number (n) by (n+1).
457
+ n = parseInt(m[0].substring(1, m[0].length - 1)) + 1;
458
+ fp = fp.replace(re, `(${n}).lnr`);
459
+ } else {
460
+ // Add (1) as version number.
461
+ fp = fp.substring(0, fp.length - 4) + ' (1).lnr';
462
+ }
463
+ while(exists(fp)) {
464
+ // Iterate to find the first available version number.
465
+ n++;
466
+ fp = fp.replace(re, `(${n}).lnr`);
467
+ }
468
+ }
469
+ }
470
+ try {
471
+ fs.writeFileSync(fp, xml);
472
+ const d = `Model ${ws ? '' : 'auto-'}saved as ${fp}`;
473
+ console.log(d);
474
+ // No message (other than OK) when auto-saving.
475
+ if(ws) data = d;
476
+ } catch(err) {
477
+ console.log(err);
478
+ data = `ERROR: Failed to ${msg} to ${fp}`;
479
+ }
433
480
  } catch(err) {
434
481
  console.log(err);
435
- data = 'ERROR: Not a Linny-R model to auto-save';
482
+ data = 'ERROR: Not a Linny-R model to ' + msg;
436
483
  }
437
484
  }
438
485
  servePlainText(res, data);
@@ -1687,6 +1734,7 @@ function createWorkspace() {
1687
1734
  callback: path.join(SETTINGS.user_dir, 'callback'),
1688
1735
  data: path.join(SETTINGS.user_dir, 'data'),
1689
1736
  diagrams: path.join(SETTINGS.user_dir, 'diagrams'),
1737
+ models: path.join(SETTINGS.user_dir, 'models'),
1690
1738
  modules: path.join(SETTINGS.user_dir, 'modules'),
1691
1739
  reports: path.join(SETTINGS.user_dir, 'reports'),
1692
1740
  solver_output: path.join(SETTINGS.user_dir, 'solver')
@@ -1715,18 +1763,18 @@ function createWorkspace() {
1715
1763
 
1716
1764
  function createLaunchScript() {
1717
1765
  // Creates platform-specific script with Linny-R start-up command
1718
- const lines = [
1719
- '# The first line (without the comment symbol #) should be like this:',
1720
- '',
1721
- '',
1722
- '# Then this command to launch the Linny-R server should work:',
1723
- '',
1724
- '# After shut-down, check whether new version should be installed:'
1725
- ];
1726
- lines[2] = 'cd ' + WORKING_DIRECTORY;
1727
- let sp;
1728
- if(PLATFORM.startsWith('win')) {
1729
- sp = path.join(WORKING_DIRECTORY, 'linny-r.bat');
1766
+ const
1767
+ lines = [
1768
+ '# The first line (without the comment symbol #) should be like this:',
1769
+ '',
1770
+ 'cd ' + WORKING_DIRECTORY,
1771
+ '# Then this command to launch the Linny-R server should work:',
1772
+ '',
1773
+ '# After shut-down, check whether new version should be installed:'
1774
+ ],
1775
+ windows = PLATFORM.startsWith('win'),
1776
+ sp = path.join(WORKING_DIRECTORY, 'linny-r.' + (windows ? 'bat' : 'command'));
1777
+ if(windows) {
1730
1778
  lines.push(
1731
1779
  ':loop',
1732
1780
  'if exist newer_version (',
@@ -1738,7 +1786,6 @@ function createLaunchScript() {
1738
1786
  lines[1] = '# cd C:\\path\\to\\main\\Linny-R\\directory';
1739
1787
  lines[4] = 'node node_modules\\linny-r\\server launch';
1740
1788
  } else {
1741
- sp = path.join(WORKING_DIRECTORY, 'linny-r.command');
1742
1789
  lines.push(
1743
1790
  'while test -f newer_version; do',
1744
1791
  ' unlink newer_version',
@@ -1749,22 +1796,23 @@ function createLaunchScript() {
1749
1796
  lines[4] = 'node node_modules/linny-r/server launch';
1750
1797
  }
1751
1798
  try {
1752
- let make_script = false,
1753
- code = lines.join(os.EOL);
1754
- if(PLATFORM.startsWith('win')) code = code.replaceAll('#', '::');
1799
+ let code = lines.join(os.EOL);
1800
+ if(windows) code = code.replaceAll('#', '::');
1755
1801
  try {
1756
1802
  fs.accessSync(sp);
1757
- // Only write the script content if the file has not been customized
1758
- // by the user...
1759
- const data = fs.readFileSync(sp, 'utf-8');
1760
- make_script = code.indexOf(data) >= 0;
1803
+ // Do not overwrite existing script, as it may have been customized
1804
+ // by the user. When istalling/updating Linny-R, the post-install
1805
+ // script should have renamed it, so typically it is created the
1806
+ // first time Linny-R is run after install/update.
1761
1807
  } catch(err) {
1762
- // ... or if it does not exist yet.
1763
- make_script = true;
1764
- }
1765
- if(make_script) {
1766
1808
  console.log('Creating launch script:', sp);
1767
1809
  fs.writeFileSync(sp, code, 'utf8');
1810
+ // On macOS machines, try to make the script executable.
1811
+ if(!windows) try {
1812
+ fs.chmodSync(sp, '+x');
1813
+ } catch(err) {
1814
+ console.log('WARNING: Failed to make script executable -- please check');
1815
+ }
1768
1816
  }
1769
1817
  } catch(err) {
1770
1818
  console.log('WARNING: Failed to create launch script');
package/static/index.html CHANGED
@@ -262,7 +262,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
262
262
  <img id="settings-btn" class="btn enab" src="images/settings.png"
263
263
  title="Change model settings (Alt-M)">
264
264
  <img id="save-btn" class="btn enab" src="images/save.png"
265
- title="Save model (Ctrl-S)">
265
+ title="Save model (Ctrl-S) &ndash; Shift-click to save to user workspace (Ctrl-Shift-S)">
266
266
  <img id="repository-btn" class="btn enab" src="images/repository.png"
267
267
  title="Browse repositories (Ctrl-B)">
268
268
  <img id="actors-btn" class="btn enab" src="images/actors.png"
@@ -549,7 +549,7 @@ class GUIController extends Controller {
549
549
  this.buttons.settings.addEventListener('click',
550
550
  () => UI.showSettingsDialog(MODEL));
551
551
  this.buttons.save.addEventListener('click',
552
- () => FILE_MANAGER.saveModel());
552
+ () => FILE_MANAGER.saveModel(event.shiftKey));
553
553
  this.buttons.actors.addEventListener('click',
554
554
  () => ACTOR_MANAGER.showDialog());
555
555
  this.buttons.diagram.addEventListener('click',
@@ -1099,10 +1099,10 @@ class GUIController extends Controller {
1099
1099
  // Only then try to restart.
1100
1100
  if(SOLVER.user_id) return;
1101
1101
  UI.updating_modal.show();
1102
- setTimeout(() => UI.tryToRestart(0), 5000);
1102
+ setTimeout(() => UI.tryToRestart(), 5000);
1103
1103
  }
1104
1104
 
1105
- tryToRestart(trials) {
1105
+ tryToRestart() {
1106
1106
  // Fetch the current version number from the server. This may take
1107
1107
  // a wile, as the server was shut down and restarts only after npm
1108
1108
  // has updated the Linny-R software. Typically, this takes only a few
@@ -1132,6 +1132,7 @@ class GUIController extends Controller {
1132
1132
  'confirm when prompted by your browser.');
1133
1133
  // Hide "update" button in server dialog.
1134
1134
  UI.modals.server.element('update').style.display = 'none';
1135
+ return;
1135
1136
  } else {
1136
1137
  // Inform user that install appears to have failed.
1137
1138
  msg.push(
@@ -1145,11 +1146,7 @@ class GUIController extends Controller {
1145
1146
  }
1146
1147
  })
1147
1148
  .catch((err) => {
1148
- if(trials < 10) {
1149
- setTimeout(() => UI.tryToRestart(trials + 1), 5000);
1150
- } else {
1151
- UI.warn(UI.WARNING.NO_CONNECTION, err);
1152
- }
1149
+ UI.warn(UI.WARNING.NO_CONNECTION, err);
1153
1150
  });
1154
1151
  }
1155
1152
 
@@ -2310,9 +2307,14 @@ class GUIController extends Controller {
2310
2307
  } else if(code === 'ArrowRight') {
2311
2308
  e.preventDefault();
2312
2309
  this.stepForward(e);
2310
+ } else if(e.ctrlKey && code === 'KeyS') {
2311
+ // Ctrl-S means: save model. Treat separately because Shift-key
2312
+ // alters the way in which the model file is saved.
2313
+ e.preventDefault();
2314
+ FILE_MANAGER.saveModel(e.shiftKey);
2313
2315
  } else if(alt && code === 'KeyR') {
2314
2316
  // Alt-R means: run to diagnose infeasible/unbounded problem.
2315
- VM.solveModel(true);
2317
+ VM.solveModel(true);
2316
2318
  } else if(alt && ['KeyC', 'KeyM'].indexOf(code) >= 0) {
2317
2319
  // Special shortcut keys for "clone selection" and "model settings".
2318
2320
  const be = new Event('click');
@@ -253,7 +253,12 @@ class GUIFileManager {
253
253
  code.focus();
254
254
  }
255
255
 
256
- saveModel() {
256
+ saveModel(ws=false) {
257
+ // Save the current model either as a download (directly from the browser),
258
+ // or in the user workspace (via the server) when the Save button is
259
+ // Shift-clicked.
260
+ // NOTE: The File manager keeps track of which option to use.
261
+ this.save_to_workspace = ws;
257
262
  MODEL.clearSelection();
258
263
  if(MODEL.encrypt) {
259
264
  const md = UI.modals.password;
@@ -270,7 +275,17 @@ class GUIFileManager {
270
275
  }
271
276
 
272
277
  pushModelToBrowser(xml) {
278
+ // Save model as .lnr file.
273
279
  UI.setMessage('Model file size: ' + UI.sizeInBytes(xml.length));
280
+ // NOTE: Since version 2.0.2, Shift-click on the Save button means
281
+ // that the model should be saved in the user workspace.
282
+ if(this.save_to_workspace) {
283
+ // Immediately reset the flag...
284
+ this.save_to_workspace = false;
285
+ // ... but pass is on to the auto-save routine.
286
+ this.storeAutoSavedModel(true);
287
+ return;
288
+ }
274
289
  const el = document.getElementById('xml-saver');
275
290
  el.href = 'data:attachment/text,' + encodeURI(xml);
276
291
  console.log('Encoded file size:', el.href.length);
@@ -281,6 +296,9 @@ class GUIFileManager {
281
296
  'If it does not download, store it in a repository');
282
297
  }
283
298
  el.click();
299
+ // Clear the HREF after 3 seconds or it may use a lot of memory.
300
+ setTimeout(
301
+ () => { document.getElementById('xml-saver').href = ''; }, 3000);
284
302
  UI.normalCursor();
285
303
  }
286
304
 
@@ -289,7 +307,7 @@ class GUIFileManager {
289
307
  md = UI.modals.password,
290
308
  code = md.element('code'),
291
309
  pwd = code.value;
292
- // NOTE: immediately clear password field
310
+ // NOTE: Immediately clear password field.
293
311
  code.value = '';
294
312
  md.hide();
295
313
  if(pwd !== md.encryption_code) {
@@ -298,7 +316,7 @@ class GUIFileManager {
298
316
  }
299
317
  UI.setMessage('Encrypting...');
300
318
  UI.waitingCursor();
301
- // Wait for key (NOTE: asynchronous functions defined in linny-r.js)
319
+ // Wait for key (NOTE: asynchronous functions defined in linny-r.js).
302
320
  encryptionKey(pwd)
303
321
  .then((key) => encryptMessage(MODEL.asXML.replace(/#/g, '%23'), key)
304
322
  .then((enc) => this.pushModelToBrowser(MODEL.asEncryptedXML(enc)))
@@ -329,8 +347,12 @@ class GUIFileManager {
329
347
  .catch((err) => UI.warn(UI.WARNING.NO_CONNECTION, err));
330
348
  }
331
349
 
332
- storeAutoSavedModel() {
333
- // Stores the current model in the local auto-save directory
350
+ storeAutoSavedModel(workspace=false) {
351
+ // Store the current model in the local auto-save directory, or in
352
+ // the local models directory when `workspace` = TRUE.
353
+ // NOTE: Always reset the "save to workspace" flag, because it may
354
+ // have not been cleared when the user canceled encryption.
355
+ this.save_to_workspace = false;
334
356
  const bcl = document.getElementById('autosave-btn').classList;
335
357
  if(MODEL.running_experiment) {
336
358
  console.log('No autosaving while running an experiment');
@@ -342,7 +364,8 @@ class GUIFileManager {
342
364
  file: REPOSITORY_BROWSER.asFileName(
343
365
  (MODEL.name || 'no-name') + '_by_' +
344
366
  (MODEL.author || 'no-author')),
345
- xml: MODEL.asXML
367
+ xml: MODEL.asXML,
368
+ wsd: workspace
346
369
  }))
347
370
  .then((response) => {
348
371
  if(!response.ok) {
@@ -356,6 +379,9 @@ class GUIFileManager {
356
379
  AUTO_SAVE.interval = 0;
357
380
  AUTO_SAVE.not_implemented = true;
358
381
  console.log('Auto-save disabled');
382
+ } else if(workspace) {
383
+ // Notify user where the model file has been stored.
384
+ UI.notify(data);
359
385
  }
360
386
  bcl.remove('stay-activ');
361
387
  })
@@ -5710,6 +5710,12 @@ class NodeBox extends ObjectWithXYWH {
5710
5710
  UI.warningInvalidName(name);
5711
5711
  return false;
5712
5712
  }
5713
+ // Check whether a non-node entity has this name.
5714
+ const nne = MODEL.namedObjectByID(UI.nameToID(name));
5715
+ if(nne && nne !== this) {
5716
+ UI.warningEntityExists(nne);
5717
+ return false;
5718
+ }
5713
5719
  // Compose the full name.
5714
5720
  if(actor_name === '') actor_name = UI.NO_ACTOR;
5715
5721
  let fn = name;
@@ -8747,8 +8753,8 @@ class Product extends Node {
8747
8753
  NL = '\\\\\n',
8748
8754
  tex = [NL],
8749
8755
  x = (this.level_to_zero ? '\\hat{x}' : 'x'),
8750
- sub = (MODEL.start_period !== MODEL.end_period ?
8751
- '_{' + this.TEX_id + ',t}' : '_' + this.TEX_id),
8756
+ dyn = (MODEL.start_period !== MODEL.end_period),
8757
+ sub = (dyn ? '_{' + this.TEX_id + ',t}' : '_' + this.TEX_id),
8752
8758
  param = (x, p) => {
8753
8759
  if(!x.defined) return '';
8754
8760
  const v = safeStrToFloat(x.text, p + sub);
@@ -8780,8 +8786,7 @@ class Product extends Node {
8780
8786
  tex.push(x + sub, '=');
8781
8787
  if(this.is_buffer) {
8782
8788
  // Insert X[t-1]
8783
- tex.push(x + '_{' + this.code +
8784
- (MODEL.start_period !== MODEL.end_period ? ',t-1}' : ',0}'));
8789
+ tex.push(x + '_{' + this.code + (dyn ? ',t-1}' : ',0}'));
8785
8790
  }
8786
8791
  let first = true;
8787
8792
  for(let i = 0; i < this.inputs.length; i++) {
@@ -8793,11 +8798,13 @@ class Product extends Node {
8793
8798
  for(let i = 0; i < this.outputs.length; i++) {
8794
8799
  let ltex = this.outputs[i].TEXcode.trim();
8795
8800
  if(ltex.trim().startsWith('-')) {
8796
- ltex = ltex.substring(1);
8797
8801
  if(!first) {
8798
- ltex = '+ ' + ltex;
8802
+ ltex = ltex.substring(1);
8803
+ ltex = '- ' + ltex;
8799
8804
  first = false;
8800
8805
  }
8806
+ } else {
8807
+ ltex = '- ' + ltex;
8801
8808
  }
8802
8809
  tex.push(ltex);
8803
8810
  }
@@ -9059,15 +9066,13 @@ class Link {
9059
9066
  // products will take care of the sign of this term.
9060
9067
  const
9061
9068
  dyn = MODEL.start_period !== MODEL.end_period,
9062
- x = (this.from_node.level_to_zero ? '\\hat(x)' : 'x'),
9063
- fsub = (dyn ?
9064
- '_{' + this.from_node.TEX_id + ',t}' :
9065
- '_' + this.from_node.TEX_id),
9069
+ n1 = (this.to_node instanceof Process ? this.to_node : this.from_node),
9070
+ n2 = (n1 === this.to_node ? this.from_node: this.to_node),
9071
+ x = (n1.level_to_zero ? '\\hat(x)' : 'x'),
9072
+ fsub = (dyn ? '_{' + n1.TEX_id + ',t}' : '_' + n1.TEX_id),
9066
9073
  fsub_i = fsub.replace(',t}', ',i}'),
9067
- rsub = (dyn ?
9068
- '_{' + this.from_node.TEX_id +
9069
- ' \\rightarrow ' + this.to_node.TEX_id + ',t}' :
9070
- '_' + this.from_node.TEX_id),
9074
+ rs = n1.TEX_id + ' \\rightarrow ' + n2.TEX_id,
9075
+ rsub = (dyn ? '_{' + rs + ',t}' : '_' + rs),
9071
9076
  param = (x, p, sub) => {
9072
9077
  if(!x.defined) return '';
9073
9078
  const v = safeStrToFloat(x.text, p + sub);
@@ -10052,7 +10057,8 @@ class ChartVariable {
10052
10057
  }
10053
10058
 
10054
10059
  tallyVector() {
10055
- // Use local constants to save some time within the FOR loop
10060
+ // Compute the histogram bin tallies for this chart variable.
10061
+ // Use local constants to save some time within the FOR loop.
10056
10062
  const
10057
10063
  bins = this.chart.bins,
10058
10064
  bin1 = this.chart.first_bin,
@@ -10061,7 +10067,7 @@ class ChartVariable {
10061
10067
  this.bin_tallies = Array(bins).fill(0);
10062
10068
  for(let i = 1; i < l; i++) {
10063
10069
  let v = this.vector[i];
10064
- // NOTE: ignore exceptional values in histogram
10070
+ // NOTE: Ignore exceptional values in histogram.
10065
10071
  if(v >= VM.MINUS_INFINITY && v <= VM.PLUS_INFINITY) {
10066
10072
  const bi = Math.min(bins,
10067
10073
  Math.floor((v - bin1 - VM.NEAR_ZERO) / binsize + 1));
@@ -11922,7 +11928,7 @@ class Experiment {
11922
11928
  }
11923
11929
 
11924
11930
  matchingCombinationIndex(sl) {
11925
- // Returns index of combination with most selectors in common wilt `sl`
11931
+ // Returns index of combination with most selectors in common with `sl`
11926
11932
  let high = 0,
11927
11933
  index = false;
11928
11934
  // NOTE: results of current run are not available yet, hence length-1
@@ -663,10 +663,23 @@ class ExpressionParser {
663
663
  this.context_number = owner.numberContext;
664
664
  // NOTE: The owner prefix includes the trailing colon+space.
665
665
  if(owner instanceof Link || owner instanceof Constraint) {
666
- // For links and constraints, use the longest prefix that
667
- // their nodes have in common.
668
- this.owner_prefix = UI.sharedPrefix(owner.from_node.displayName,
669
- owner.to_node.displayName) + UI.PREFIXER;
666
+ // For links and constraints, it depends:
667
+ const
668
+ fn = owner.from_node.displayName,
669
+ tn = owner.to_node.displayName;
670
+ if(fn.indexOf(UI.PREFIXER) >= 0) {
671
+ if(tn.indexOf(UI.PREFIXER) >= 0) {
672
+ // If both nodes are prefixed, use the longest prefix that these
673
+ // nodes have in common.
674
+ this.owner_prefix = UI.sharedPrefix(fn, tn) + UI.PREFIXER;
675
+ } else {
676
+ // Use the FROM node prefix.
677
+ this.owner_prefix = UI.completePrefix(fn);
678
+ }
679
+ } else if(tn.indexOf(UI.PREFIXER) >= 0) {
680
+ // Use the TO node prefix.
681
+ this.owner_prefix = UI.completePrefix(tn);
682
+ }
670
683
  } else if(owner === MODEL.equations_dataset) {
671
684
  this.owner_prefix = UI.completePrefix(attribute);
672
685
  } else {
@@ -8993,13 +9006,15 @@ function VMI_add_bound_line_constraint(args) {
8993
9006
  vy = VM.variables[viy - 1],
8994
9007
  objy= vy[1],
8995
9008
  uby = args[5].result(VM.t),
8996
- bl = args[6],
9009
+ bl = args[6];
9010
+ // Set bound line point coordinates for current run and time step.
9011
+ bl.setDynamicPoints(VM.t);
9012
+ // Then use the actualized points.
9013
+ const
8997
9014
  n = bl.points.length,
8998
9015
  x = new Array(n),
8999
9016
  y = new Array(n),
9000
9017
  w = new Array(n);
9001
- // Set bound line point coordinates for current run and time step.
9002
- bl.setDynamicPoints(VM.t);
9003
9018
  if(DEBUGGING) {
9004
9019
  console.log('add_bound_line_constraint:', bl.displayName);
9005
9020
  }