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 +172 -126
- package/console.js +2 -1
- package/package.json +1 -1
- package/post-install.js +93 -37
- package/server.js +72 -28
- package/static/index.html +1 -1
- package/static/scripts/linny-r-gui-controller.js +11 -9
- package/static/scripts/linny-r-gui-file-manager.js +32 -6
- package/static/scripts/linny-r-model.js +23 -17
- package/static/scripts/linny-r-vm.js +17 -4
package/README.md
CHANGED
@@ -1,39 +1,46 @@
|
|
1
|
-
<img src="https://sysmod.tbm.tudelft.nl/linny-r/images/logo.png"
|
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"
|
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**
|
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
|
16
|
-
so as to be platform-independent and 100% transparent
|
17
|
-
The software comprises a server that
|
18
|
-
and a graphical user interface (GUI) that runs in any
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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:
|
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**
|
31
|
-
This software can be downloaded from
|
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 (
|
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
|
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:
|
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
|
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
|
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
|
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.
|
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
|
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
|
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
|
125
|
-
without rigorous testing. Although much effort is dedicated to
|
126
|
-
and downward compatibility, you may find that the latest
|
127
|
-
well for you as some earlier version. To re-install
|
128
|
-
version 1.
|
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.
|
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
|
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.
|
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
|
151
|
-
Gurobi, MOSEK and CPLEX are _considerably_ more powerful than
|
152
|
-
but they require a license.
|
153
|
-
Academic licenses can be obtained by students and staff of eligible
|
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
|
170
|
-
It will look for this application in the directory specified in
|
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
|
181
|
-
It will look for this application in the directory specified in the
|
182
|
-
or more specifically in the environment variable
|
183
|
-
(where _nnnn_ denotes the CPLEX version
|
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
|
194
|
-
It will look for this application in the directory specified in the
|
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
|
199
|
-
<a href="https://scipopt.org/doc/html/INSTALL.php"
|
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
|
202
|
-
It will look for this application in the directory specified in the
|
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"
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
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`
|
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
|
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
|
234
|
-
and there click the Open Anyway button in the General
|
235
|
-
|
236
|
-
The response should then be a listing
|
237
|
-
|
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
|
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
|
249
|
-
Node.js version:
|
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
|
254
|
-
which by default will be http://127.0.0.1:5050.
|
255
|
-
The Linny-R GUI should show in your browser window,
|
256
|
-
|
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
|
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
|
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
|
-
|
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_
|
319
|
-
Then right-click on the shortcut file to edit its properties, and
|
320
|
-
The dialog that then appears will allow
|
321
|
-
`node_modules\linny-r\static\images`, where
|
322
|
-
|
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,
|
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
|
-
|
330
|
-
|
331
|
-
Then open your Linny-R folder in Finder, change to the sub-folder
|
332
|
-
and from there drag/drop the file
|
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
|
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
|
347
|
-
format that can be imported or copy/pasted into Excel
|
348
|
-
* `solver` will contain the files that are exchanged with the Mixed Integer
|
349
|
-
(the names of the files that will appear
|
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]`
|
354
|
-
> This will create a new, empty workspace
|
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*
|
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"
|
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
|
392
|
-
This may be useful when you want run models from a script
|
393
|
-
If you open a CLI box, change to your `Linny-R`
|
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
|
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
|
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
|
405
|
-
You can find a lot of useful
|
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
|
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
|
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-
|
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
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)
|
9
|
-
|
10
|
-
|
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
|
-
|
13
|
-
|
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
|
-
|
16
|
-
|
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-
|
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
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
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
|
1719
|
-
|
1720
|
-
|
1721
|
-
|
1722
|
-
|
1723
|
-
|
1724
|
-
|
1725
|
-
|
1726
|
-
|
1727
|
-
|
1728
|
-
|
1729
|
-
|
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
|
1753
|
-
|
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
|
-
//
|
1758
|
-
// by the user
|
1759
|
-
|
1760
|
-
|
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) – 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(
|
1102
|
+
setTimeout(() => UI.tryToRestart(), 5000);
|
1103
1103
|
}
|
1104
1104
|
|
1105
|
-
tryToRestart(
|
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
|
-
|
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
|
-
|
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:
|
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
|
-
//
|
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
|
-
|
8751
|
-
|
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 =
|
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
|
-
|
9063
|
-
|
9064
|
-
|
9065
|
-
|
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
|
-
|
9068
|
-
|
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
|
-
//
|
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:
|
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
|
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,
|
667
|
-
|
668
|
-
|
669
|
-
owner.to_node.displayName
|
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 {
|