linny-r 2.0.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
59
  On a Windows machine the suggested path is `C:\Users\(your user name)\Documents\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,22 +103,12 @@ 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.
100
-
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.
106
+ that should **not** be removed, or you will have to re-install Linny-R.
107
107
 
108
- The `linny-r` directory should contain this file `README.md`,
108
+ The `linny-r` directory should also contain this file `README.md`,
109
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:
110
+ and the sub-directory `static`. This `static` directory should contain three
111
+ HTML files:
111
112
 
112
113
  * `index.html` (the browser-based GUI)
113
114
  * `show-png.html` (to render SVG diagrams as PNG images)
@@ -115,29 +116,30 @@ and the sub-directory `static`. This `static` directory should contain three HTM
115
116
 
116
117
  It should also contain the style sheet `linny-r.css` required by the GUI.
117
118
 
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.
119
+ The sub-directories of `static` contain files that are served to the browser
120
+ by the script `server.js` when it is running in Node.js.
120
121
 
121
122
  #### Installing and using an earlier version of Linny-R
122
123
 
123
124
  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:
125
+ As this software is developed as part of academic research, new features
126
+ are added without rigorous testing. Although much effort is dedicated to
127
+ maintaining upward and downward compatibility, you may find that the latest
128
+ version does not work as well for you as some earlier version. To re-install
129
+ an earlier release, for example version 1.9.3, open the CLI, change to your
130
+ `Linny-R` directory, and then type:
129
131
 
130
- ``npm install linny-r@1.4.0``
132
+ ``npm install linny-r@1.9.3``
131
133
 
132
134
  > [!NOTE]
133
135
  > This will overwrite the contents of the `node_modules` directory, but
134
136
  > it will not affect the files in your user space.
135
137
 
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
138
+ If you prefer to have different versions of Linny-R on your computer, you
139
+ can create a separate directory for a specific version, then change to this
138
140
  directory and type:
139
141
 
140
- ``npm install --prefix . linny-r@1.4.0``
142
+ ``npm install --prefix . linny-r@1.9.3``
141
143
 
142
144
  > [!NOTE]
143
145
  > To run a specific version in your browser, you must start the server from
@@ -147,10 +149,11 @@ directory and type:
147
149
 
148
150
  ## Configuring the MILP solver
149
151
 
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.
152
+ Linny-R presently supports five MILP solvers: Gurobi, MOSEK, CPLEX, SCIP
153
+ and LP_solve. Gurobi, MOSEK and CPLEX are _considerably_ more powerful than
154
+ the open source solvers SCIP and LP_solve, but they require a license.
155
+ Academic licenses can be obtained by students and staff of eligible
156
+ institutions.
154
157
 
155
158
  > [!IMPORTANT]
156
159
  > When installing a solver, it is advisable to accept the default file
@@ -166,8 +169,9 @@ Gurobi on your computer can be obtained via this URL:
166
169
  <a href="https://www.gurobi.com/academia/academic-program-and-licenses/"
167
170
  target="_blank">https://www.gurobi.com/academia/academic-program-and-licenses/</a>
168
171
 
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.
172
+ When running a model, Linny-R will try to execute the command line application
173
+ `gurobi_cl`. It will look for this application in the directory specified in
174
+ the environment variable PATH on your computer.
171
175
 
172
176
  #### Installing CPLEX
173
177
 
@@ -177,10 +181,11 @@ CPLEX on your computer can be obtained via this URL:
177
181
  <a href="https://www.ibm.com/products/ilog-cplex-optimization-studio"
178
182
  target="_blank">https://www.ibm.com/products/ilog-cplex-optimization-studio</a>
179
183
 
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.
184
+ When running a model, Linny-R will try to execute the command line application
185
+ `cplex`. It will look for this application in the directory specified in the
186
+ environment variable PATH or more specifically in the environment variable
187
+ CPLEX_STUDIO_BINARIES<em>nnnn</em> (where _nnnn_ denotes the CPLEX version
188
+ number) on your computer.
184
189
 
185
190
  #### Installing MOSEK
186
191
 
@@ -190,35 +195,43 @@ MOSEK on your computer can be obtained via this URL:
190
195
  <a href="https://www.mosek.com/resources/getting-started/"
191
196
  target="_blank">https://www.mosek.com/resources/getting-started/</a>
192
197
 
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.
198
+ When running a model, Linny-R will try to execute the command line application
199
+ `mosek`. It will look for this application in the directory specified in the
200
+ environment variable PATH on your computer.
195
201
 
196
202
  #### Installing SCIP
197
203
 
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>
204
+ The SCIP software is open source. Instructions for installation can be found
205
+ via this URL: <a href="https://scipopt.org/doc/html/INSTALL.php"
206
+ target="_blank">https://scipopt.org/doc/html/INSTALL.php</a>
200
207
 
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.
208
+ When running a model, Linny-R will try to execute the command line application
209
+ `scip`. It will look for this application in the directory specified in the
210
+ environment variable PATH on your computer.
203
211
 
204
212
  #### Installing LP_solve
205
213
 
206
214
  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:
215
+ <a href="https://sourceforge.net/projects/lpsolve"
216
+ target="_blank">https://sourceforge.net/projects/lpsolve</a>
217
+
218
+ To facilitate installation, the executable files for Windows and macOS can
219
+ be downloaded from the Linny-R website at Delft University of Technology:
220
+ <a href="https://sysmod.tbm.tudelft.nl/linny-r/lp_solve"
221
+ target="_blank">https://sysmod.tbm.tudelft.nl/linny-r/lp_solve</a>
222
+
223
+ There you will find links to download LP_solve applications that have been
224
+ compiled for different platforms. If you do not know which platform to choose,
225
+ run Linny-R as described below, and the platform will be listed in its output.
226
+ If no matching LP_solve version is listed, you can try to compile the software
227
+ from its source. How to do this is explained on the page "Installing LP_solve
228
+ on a Mac" on the Linny-R documentation site:
217
229
  <a href="https://linny-r.info" target="_blank">https://linny-r.info</a>
218
230
 
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.
231
+ When you have downloaded the file (just `lp_solve` for macOS, `lp_solve.exe`
232
+ for Windows), you must copy or move this file to your `Linny-R` directory,
233
+ as this is where Linny-R will look for it when it does not find one of the
234
+ other solvers.
222
235
 
223
236
  On a macOS machine, you must then make the file `lp_solve` executable.
224
237
  Open Terminal and change to your Linny-R directory, and then type:
@@ -230,30 +243,32 @@ When you then type:
230
243
  ``./lp_solve -h``
231
244
 
232
245
  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.
246
+ To allow running LP_solve, you must then go to Security & Privacy (via
247
+ System Preferences) and there click the Open Anyway button in the General
248
+ pane to confirm that you wish to use LP_solve. Then return to Terminal
249
+ and once more type `./lp_solve -h`. The response should then be a listing
250
+ of all the command line options of LP_solve. If you reach this stage,
251
+ Linny-R will be able to run LP_solve.
238
252
 
239
253
  ## Running Linny-R
240
254
 
241
- Open the Command Line Interface (CLI) of your computer, change to your Linny-R directory and type:
255
+ Open the Command Line Interface (CLI) of your computer, change to your
256
+ Linny-R directory and type:
242
257
 
243
258
  ``node node_modules/linny-r/server launch``
244
259
 
245
260
  This response should be something similar to:
246
261
 
247
262
  <pre>
248
- Node.js server for Linny-R version 1.9.3
249
- Node.js version: v21.7.3
263
+ Node.js server for Linny-R version 2.0.0
264
+ Node.js version: v22.2.0
250
265
  ... etc.
251
266
  </pre>
252
267
 
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:
268
+ Meanwhile, your default web browser should have opened a tab for the local
269
+ server URL, which by default will be http://127.0.0.1:5050.
270
+ The Linny-R GUI should show in your browser window, while in the CLI you
271
+ should see a long series of server log messages like:
257
272
 
258
273
  <pre>
259
274
  [2023-11-19 22:55:17] Static file: /index.html
@@ -269,13 +284,13 @@ while in the CLI you should see a long series of server log messages like:
269
284
  > Linny-R from a new CLI.
270
285
 
271
286
  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.
287
+ If successful, a notification (blue background) will appear on the status
288
+ bar at the bottom of the window, stating the name of the solver.
274
289
 
275
290
  You can then test the GUI by creating a simple model.
276
291
  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.
292
+ and this product must have a price or a set lower bound, otherwise the
293
+ model will have no objective function.
279
294
  Then click on the _Solve_ button at the bottom of the left-hand tool bar.
280
295
  The Linny-R icon in the upper left corner should start rotating, while the
281
296
  status bar at the bottom should display:
@@ -312,24 +327,47 @@ workspace=[path] to overrule the default path for the user directory
312
327
 
313
328
  ## Click-start for Linny-R
314
329
 
315
- To facilitate start-up, you can create a shortcut icon for Linny-R on your desktop.
330
+ The first time you start Linny-R after a fresh install or an update,
331
+ you will have to open the Command Line Interface (CLI) of your computer,
332
+ change to your Linny-R directory and type:
333
+
334
+ ``node node_modules/linny-r/server launch``
335
+
336
+ This will not only start Linny-R, but also create a script file in your
337
+ Linny-R directory that will allow you to start Linny-R by clicking its
338
+ icon on your machine. On a macOS machine, this fill will be the shell
339
+ script `linny-r.command`, on a Windows machine the batch script
340
+ `linny-r.bat`.
341
+
342
+ To facilitate start-up, you can create a shortcut icon for Linny-R on your
343
+ desktop.
316
344
 
317
345
  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.
346
+ right-click on the batch file `linny-r.bat`, and select the _Create shortcut_
347
+ option. Then right-click on the shortcut file to edit its properties, and
348
+ click the _Change Icon_ button. The dialog that then appears will allow
349
+ you to go to the sub-folder `node_modules\linny-r\static\images`, where
350
+ you should select the file `linny-r.ico`. Finally, rename the shortcut to
351
+ `Linny-R` and move or copy it to your desktop.
323
352
 
324
- On a macOS machine, open Terminal and change to your Linny-R directory, and then type:
353
+ On a macOS machine, open Terminal and change to your Linny-R directory,
354
+ and then type:
325
355
 
326
356
  ``chmod +x linny-r.command``
327
357
 
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_.
358
+ to make the script file executable. To set the icon, use Finder to open
359
+ the folder that contains the file `linny-r.command`, click on its icon
360
+ (which still is plain) and open the _Info dialog_ by pressing ``Cmd+I``.
361
+ Then open your Linny-R folder in Finder, change to the sub-folder
362
+ `node_modules/linny-r/static/images`, and from there drag/drop the file
363
+ `linny-r.icns` on the icon shown in the top left corner of the _Info dialog_.
364
+
365
+ > [!NOTE]
366
+ > When configuring Linny-R for a network environment where individual users
367
+ > each have their personal work space (e.g., a virtual drive U:), you must
368
+ > edit this script file, adding the argument `workspace=path/to/workspace`
369
+ > to the `node` command. This will instruct Linny-R to create the `user`
370
+ > directory in this workspace directory instead of the Linny-R directory.
333
371
 
334
372
  ## User workspace
335
373
 
@@ -338,20 +376,21 @@ The sub-directories of this directory `user` are used by Linny-R to store files.
338
376
 
339
377
  * `autosave` will contain models that have been _auto-saved_
340
378
  * `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
379
+ * `data` will be used by the _Dataset Manager_ to locate datasets for which
380
+ a path has been specified
343
381
  * `diagrams` will be used to render Scalable Vector Graphics (SVG) files as
344
382
  Portable Network Graphics (PNG) using Inkscape (if installed)
345
383
  * `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)
384
+ * `reports` will contain text files with time series data and statistics in
385
+ tab-separated format that can be imported or copy/pasted into Excel
386
+ * `solver` will contain the files that are exchanged with the Mixed Integer
387
+ Linear Programming (MILP) solver (the names of the files that will appear
388
+ in this directory may vary, depending on the MILP-solver you use)
350
389
 
351
390
  > [!NOTE]
352
391
  > 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.
392
+ > You can overrule this by starting the server with the `workspace=[path]`
393
+ > option. This will create a new, empty workspace in the specified path.
355
394
  > It will **not** affect or duplicate information from existing workspaces.
356
395
 
357
396
  ## Installing Inkscape
@@ -362,8 +401,8 @@ These files can be viewed and edited using Inkscape, an open source
362
401
  vector graphics editor.
363
402
 
364
403
  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_.
404
+ manually as a bitmap image, Linny-R features a *Render diagram as bitmap*
405
+ button on the top toolbar, and on the bottom toolbar of the _Chart manager_.
367
406
  When you click it, Linny-R will send the image as SVG to the server.
368
407
  The server script will save the SVG in the `user/diagrams` sub-directory,
369
408
  and then try to execute an Inkscape command that will convert this SVG to
@@ -375,7 +414,8 @@ If rendering was successful, the image will appear in this browser tab;
375
414
  if rendering failed, the original SVG image will be shown.
376
415
 
377
416
  To install Inkscape, please look here:
378
- <a href="https://inkscape.org/release" target="_blank">https://inkscape.org/release</a>
417
+ <a href="https://inkscape.org/release"
418
+ target="_blank">https://inkscape.org/release</a>
379
419
 
380
420
  Linny-R will automatically detect whether Inkscape is installed by searching
381
421
  for it in the environment variable PATH on your computer. On a macOS computer,
@@ -388,21 +428,25 @@ Linny-R will look for Inkscape in `/Applications/Inkscape.app/Contents/MacOS`.
388
428
 
389
429
  ## Using Linny-R console
390
430
 
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:
431
+ The console-only version of Linny-R allows you to run a Linny-R model without
432
+ a web browser. This may be useful when you want run models from a script
433
+ (shell script, Python, ...). If you open a CLI box, change to your `Linny-R`
434
+ directory, and then type:
394
435
 
395
436
  ``node node_modules/linny-r/console`` _(on Windows, use backslashes)_
396
437
 
397
- you will see the command line options that allow you to run models in various ways.
438
+ you will see the command line options that allow you to run models in various
439
+ ways.
398
440
 
399
441
  > [!NOTE]
400
- > The console-only version is still in development, and does not provide all functions yet.
442
+ > The console-only version is still in development, and does not provide
443
+ > all functions yet.
401
444
 
402
445
  ## Troubleshooting problems
403
446
 
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:
447
+ If during any of the steps above you encounter problems, please try to
448
+ diagnose them and resolve them yourself. You can find a lot of useful
449
+ information on the Linny-R user documentation website:
406
450
  <a href="https://linny-r.info" target="_blank">https://linny-r.info</a>.
407
451
 
408
452
  > [!IMPORTANT]
@@ -411,6 +455,8 @@ You can find a lot of useful information on the Linny-R user documentation websi
411
455
 
412
456
  Then also look at the console window of your browser.
413
457
  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.
458
+ This will allow you to view the browser console, which will display JavaScript
459
+ errors in red font.
415
460
 
416
- If you've tried hard, but failed, you can try to contact Pieter Bots at ``p.w.g.bots@tudelft.nl``
461
+ If you've tried hard, but failed, you can try to contact Pieter Bots at
462
+ ``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.2",
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
@@ -416,9 +416,21 @@ function autoSaveLoad(res, sp) {
416
416
  function autoSaveStore(res, sp) {
417
417
  // Stores XML data under specified file name in the auto-save directory
418
418
  let data = 'OK';
419
- const fn = sp.get('file');
419
+ const
420
+ fn = sp.get('file'),
421
+ wsd = sp.get('wsd'),
422
+ ws = (wsd ? WORKSPACE.models : WORKSPACE.autosave),
423
+ msg = (wsd === 'models' ? 'save to user workspace' : 'auto-save'),
424
+ exists = (path) => {
425
+ try {
426
+ fs.accessSync(path);
427
+ return true;
428
+ } catch(err) {
429
+ return false;
430
+ }
431
+ };
420
432
  if(!fn) {
421
- data = 'WARNING: No name for file to auto-save';
433
+ data = 'WARNING: No name for file to ' + msg;
422
434
  } else {
423
435
  const xml = sp.get('xml');
424
436
  // Validate XML as a Linny-R model
@@ -429,10 +441,41 @@ function autoSaveStore(res, sp) {
429
441
  root = doc.documentElement;
430
442
  // Linny-R models have a model element as root
431
443
  if(root.nodeName !== 'model') throw 'XML document has no model element';
432
- fs.writeFileSync(path.join(WORKSPACE.autosave, fn + '.lnr'), xml);
444
+ let fp = path.join(ws, fn + '.lnr');
445
+ if(wsd) {
446
+ // Append a version number to the file name if named file exists.
447
+ const re = /\(\d+\).lnr$/;
448
+ if(exists(fp)) {
449
+ const m = fp.match(re);
450
+ let n = 1;
451
+ if(m) {
452
+ // Replace version number (n) by (n+1).
453
+ n = parseInt(m[0].substring(1, m[0].length - 1)) + 1;
454
+ fp = fp.replace(re, `(${n}).lnr`);
455
+ } else {
456
+ // Add (1) as version number.
457
+ fp = fp.substring(0, fp.length - 4) + ' (1).lnr';
458
+ }
459
+ while(exists(fp)) {
460
+ // Iterate to find the first available version number.
461
+ n++;
462
+ fp = fp.replace(re, `(${n}).lnr`);
463
+ }
464
+ }
465
+ }
466
+ try {
467
+ fs.writeFileSync(fp, xml);
468
+ const d = `Model ${ws ? '' : 'auto-'}saved as ${fp}`;
469
+ console.log(d);
470
+ // No message (other than OK) when auto-saving.
471
+ if(ws) data = d;
472
+ } catch(err) {
473
+ console.log(err);
474
+ data = `ERROR: Failed to ${msg} to ${fp}`;
475
+ }
433
476
  } catch(err) {
434
477
  console.log(err);
435
- data = 'ERROR: Not a Linny-R model to auto-save';
478
+ data = 'ERROR: Not a Linny-R model to ' + msg;
436
479
  }
437
480
  }
438
481
  servePlainText(res, data);
@@ -1687,6 +1730,7 @@ function createWorkspace() {
1687
1730
  callback: path.join(SETTINGS.user_dir, 'callback'),
1688
1731
  data: path.join(SETTINGS.user_dir, 'data'),
1689
1732
  diagrams: path.join(SETTINGS.user_dir, 'diagrams'),
1733
+ models: path.join(SETTINGS.user_dir, 'models'),
1690
1734
  modules: path.join(SETTINGS.user_dir, 'modules'),
1691
1735
  reports: path.join(SETTINGS.user_dir, 'reports'),
1692
1736
  solver_output: path.join(SETTINGS.user_dir, 'solver')
@@ -1715,18 +1759,18 @@ function createWorkspace() {
1715
1759
 
1716
1760
  function createLaunchScript() {
1717
1761
  // 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');
1762
+ const
1763
+ lines = [
1764
+ '# The first line (without the comment symbol #) should be like this:',
1765
+ '',
1766
+ 'cd ' + WORKING_DIRECTORY,
1767
+ '# Then this command to launch the Linny-R server should work:',
1768
+ '',
1769
+ '# After shut-down, check whether new version should be installed:'
1770
+ ],
1771
+ windows = PLATFORM.startsWith('win'),
1772
+ sp = path.join(WORKING_DIRECTORY, 'linny-r.' + (windows ? 'bat' : 'command'));
1773
+ if(windows) {
1730
1774
  lines.push(
1731
1775
  ':loop',
1732
1776
  'if exist newer_version (',
@@ -1738,7 +1782,6 @@ function createLaunchScript() {
1738
1782
  lines[1] = '# cd C:\\path\\to\\main\\Linny-R\\directory';
1739
1783
  lines[4] = 'node node_modules\\linny-r\\server launch';
1740
1784
  } else {
1741
- sp = path.join(WORKING_DIRECTORY, 'linny-r.command');
1742
1785
  lines.push(
1743
1786
  'while test -f newer_version; do',
1744
1787
  ' unlink newer_version',
@@ -1749,22 +1792,23 @@ function createLaunchScript() {
1749
1792
  lines[4] = 'node node_modules/linny-r/server launch';
1750
1793
  }
1751
1794
  try {
1752
- let make_script = false,
1753
- code = lines.join(os.EOL);
1754
- if(PLATFORM.startsWith('win')) code = code.replaceAll('#', '::');
1795
+ let code = lines.join(os.EOL);
1796
+ if(windows) code = code.replaceAll('#', '::');
1755
1797
  try {
1756
1798
  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;
1799
+ // Do not overwrite existing script, as it may have been customized
1800
+ // by the user. When istalling/updating Linny-R, the post-install
1801
+ // script should have renamed it, so typically it is created the
1802
+ // first time Linny-R is run after install/update.
1761
1803
  } catch(err) {
1762
- // ... or if it does not exist yet.
1763
- make_script = true;
1764
- }
1765
- if(make_script) {
1766
1804
  console.log('Creating launch script:', sp);
1767
1805
  fs.writeFileSync(sp, code, 'utf8');
1806
+ // On macOS machines, try to make the script executable.
1807
+ if(!windows) try {
1808
+ fs.chmodSync(sp, '+x');
1809
+ } catch(err) {
1810
+ console.log('WARNING: Failed to make script executable -- please check');
1811
+ }
1768
1812
  }
1769
1813
  } catch(err) {
1770
1814
  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 {