linny-r 2.0.1 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +219 -147
- package/console.js +2 -1
- package/package.json +1 -1
- package/post-install.js +93 -37
- package/server.js +99 -51
- 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 +22 -7
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
|
-
On a Windows machine the suggested path is `C:\Users\(your user name)\
|
59
|
+
On a Windows machine the suggested path is `C:\Users\(your user name)\Linny-R`,
|
51
60
|
and on a macOS machine `/Users/(your user name)/Linny-R`.
|
52
61
|
|
53
|
-
To install Linny-R in this directory, first change to the parent directory
|
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,52 +103,73 @@ Linny-R
|
|
92
103
|
</pre>
|
93
104
|
|
94
105
|
`Linny-R` should contain two JSON files `package.json` and `package-lock.json`
|
95
|
-
that should **not** be removed, or you will have to re-install Linny-R.
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
start the Linny-R server.
|
106
|
+
that should **not** be removed, or you will have to re-install Linny-R.
|
107
|
+
It should also contain the launch script. On a macOS machine, this will be
|
108
|
+
the shell script `linny-r.command`, on a Windows machine the batch script
|
109
|
+
`linny-r.bat`.
|
100
110
|
|
101
|
-
|
102
|
-
|
103
|
-
> each have their personal work space (e.g., a virtual drive U:), you must edit this script file,
|
104
|
-
> adding the argument `workspace=path/to/workspace` to the `node` command.
|
105
|
-
> This will instruct Linny-R to create the `user` directory in this workspace directory
|
106
|
-
> instead of the Linny-R directory.
|
111
|
+
All other software is contained in the `node_modules` directory. It comprises
|
112
|
+
two Node.js packages: `@xlmdom` and `linny-r`.
|
107
113
|
|
108
|
-
The `linny-r` directory should contain this file `README.md`,
|
109
|
-
|
110
|
-
|
114
|
+
The `linny-r` package directory should contain this file `README.md`, the files
|
115
|
+
`server.js` and `console.js` that will be run by Node.js, and the sub-directory
|
116
|
+
`static`. This `static` directory should contain three HTML files:
|
111
117
|
|
112
118
|
* `index.html` (the browser-based GUI)
|
113
119
|
* `show-png.html` (to render SVG diagrams as PNG images)
|
114
|
-
* `show-diff.html` (to display differences
|
120
|
+
* `show-diff.html` (to display differences between two Linny-R models)
|
115
121
|
|
116
122
|
It should also contain the style sheet `linny-r.css` required by the GUI.
|
117
123
|
|
118
|
-
The sub-directories of `static` contain files that are served to the browser
|
119
|
-
`server.js` when it is running in Node.js.
|
124
|
+
The sub-directories of `static` contain files that are served to the browser
|
125
|
+
by the script `server.js` when it is running in Node.js.
|
126
|
+
|
127
|
+
> [!IMPORTANT]
|
128
|
+
> Unless you _really_ know what you are doing, do **not** move or rename
|
129
|
+
> your Linny-R directory or change its contents. If for some reason Linny-R
|
130
|
+
> does not work, remove _all_ Linny-R software from your machine, and then
|
131
|
+
> install it anew, following the procedure described above.
|
132
|
+
|
133
|
+
#### Updating to the latest version of Linny-R
|
134
|
+
|
135
|
+
When a newer version has beeh released, Linny-R will prompt you to update
|
136
|
+
automatically. Click on the link in this prompt to see the release notes
|
137
|
+
on GitHub and find out about new features and bug fixes. When you click on
|
138
|
+
the _OK_ button, Linny-R will shut down its local server script, and then
|
139
|
+
the launch script should perform the `npm update` command and then restart
|
140
|
+
the server script. Your browser will prompt you to close its current tab,
|
141
|
+
and then Linny-R should reappear in a new browser tab or window.
|
142
|
+
|
143
|
+
> [!NOTE]
|
144
|
+
> The built-in updating function of Linny-R will _**not**_ automatically
|
145
|
+
> upgrade to a new _major_ version. To update from a version like 1.9.x to
|
146
|
+
> a version like 2.0.x, you have to open a command line interface
|
147
|
+
> (`Command Prompt` or `Terminal`), change to your Linny-R directory, and
|
148
|
+
> then type `npm install linny-r@2`. This should perform the upgrade.
|
149
|
+
> You can then launch Linny-R as usual by typing `linny-r` (Windows) or
|
150
|
+
> `./linny-r.command` (macOS).
|
120
151
|
|
121
152
|
#### Installing and using an earlier version of Linny-R
|
122
153
|
|
123
154
|
By default, **npm** will install the latest release of the Linny-R software.
|
124
|
-
As this software is developed as part of academic research, new features
|
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.
|
155
|
+
As this software is developed as part of academic research, new features
|
156
|
+
are added without rigorous testing. Although much effort is dedicated to
|
157
|
+
maintaining upward and downward compatibility, you may find that the latest
|
158
|
+
version does not work as well for you as some earlier version. To re-install
|
159
|
+
an earlier release, for example version 1.9.3, open the CLI, change to your
|
160
|
+
`Linny-R` directory, and then type:
|
129
161
|
|
130
|
-
``npm install linny-r@1.
|
162
|
+
``npm install linny-r@1.9.3``
|
131
163
|
|
132
164
|
> [!NOTE]
|
133
165
|
> This will overwrite the contents of the `node_modules` directory, but
|
134
166
|
> it will not affect the files in your user space.
|
135
167
|
|
136
|
-
If you prefer to have different versions of Linny-R on your computer, you
|
137
|
-
create a separate directory for a specific version, then change to this
|
168
|
+
If you prefer to have different versions of Linny-R on your computer, you
|
169
|
+
can create a separate directory for a specific version, then change to this
|
138
170
|
directory and type:
|
139
171
|
|
140
|
-
``npm install --prefix . linny-r@1.
|
172
|
+
``npm install --prefix . linny-r@1.9.3``
|
141
173
|
|
142
174
|
> [!NOTE]
|
143
175
|
> To run a specific version in your browser, you must start the server from
|
@@ -147,10 +179,11 @@ directory and type:
|
|
147
179
|
|
148
180
|
## Configuring the MILP solver
|
149
181
|
|
150
|
-
Linny-R presently supports five MILP solvers: Gurobi, MOSEK, CPLEX, SCIP
|
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
|
182
|
+
Linny-R presently supports five MILP solvers: Gurobi, MOSEK, CPLEX, SCIP
|
183
|
+
and LP_solve. Gurobi, MOSEK and CPLEX are _considerably_ more powerful than
|
184
|
+
the open source solvers SCIP and LP_solve, but they require a license.
|
185
|
+
Academic licenses can be obtained by students and staff of eligible
|
186
|
+
institutions.
|
154
187
|
|
155
188
|
> [!IMPORTANT]
|
156
189
|
> When installing a solver, it is advisable to accept the default file
|
@@ -166,8 +199,9 @@ Gurobi on your computer can be obtained via this URL:
|
|
166
199
|
<a href="https://www.gurobi.com/academia/academic-program-and-licenses/"
|
167
200
|
target="_blank">https://www.gurobi.com/academia/academic-program-and-licenses/</a>
|
168
201
|
|
169
|
-
When running a model, Linny-R will try to execute the command line application
|
170
|
-
It will look for this application in the directory specified in
|
202
|
+
When running a model, Linny-R will try to execute the command line application
|
203
|
+
`gurobi_cl`. It will look for this application in the directory specified in
|
204
|
+
the environment variable PATH on your computer.
|
171
205
|
|
172
206
|
#### Installing CPLEX
|
173
207
|
|
@@ -177,10 +211,11 @@ CPLEX on your computer can be obtained via this URL:
|
|
177
211
|
<a href="https://www.ibm.com/products/ilog-cplex-optimization-studio"
|
178
212
|
target="_blank">https://www.ibm.com/products/ilog-cplex-optimization-studio</a>
|
179
213
|
|
180
|
-
When running a model, Linny-R will try to execute the command line application
|
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
|
214
|
+
When running a model, Linny-R will try to execute the command line application
|
215
|
+
`cplex`. It will look for this application in the directory specified in the
|
216
|
+
environment variable PATH or more specifically in the environment variable
|
217
|
+
CPLEX_STUDIO_BINARIES<em>nnnn</em> (where _nnnn_ denotes the CPLEX version
|
218
|
+
number) on your computer.
|
184
219
|
|
185
220
|
#### Installing MOSEK
|
186
221
|
|
@@ -190,38 +225,46 @@ MOSEK on your computer can be obtained via this URL:
|
|
190
225
|
<a href="https://www.mosek.com/resources/getting-started/"
|
191
226
|
target="_blank">https://www.mosek.com/resources/getting-started/</a>
|
192
227
|
|
193
|
-
When running a model, Linny-R will try to execute the command line application
|
194
|
-
It will look for this application in the directory specified in the
|
228
|
+
When running a model, Linny-R will try to execute the command line application
|
229
|
+
`mosek`. It will look for this application in the directory specified in the
|
230
|
+
environment variable PATH on your computer.
|
195
231
|
|
196
232
|
#### Installing SCIP
|
197
233
|
|
198
|
-
The SCIP software is open source. Instructions for installation can be found
|
199
|
-
<a href="https://scipopt.org/doc/html/INSTALL.php"
|
234
|
+
The SCIP software is open source. Instructions for installation can be found
|
235
|
+
via this URL: <a href="https://scipopt.org/doc/html/INSTALL.php"
|
236
|
+
target="_blank">https://scipopt.org/doc/html/INSTALL.php</a>
|
200
237
|
|
201
|
-
When running a model, Linny-R will try to execute the command line application
|
202
|
-
It will look for this application in the directory specified in the
|
238
|
+
When running a model, Linny-R will try to execute the command line application
|
239
|
+
`scip`. It will look for this application in the directory specified in the
|
240
|
+
environment variable PATH on your computer.
|
203
241
|
|
204
242
|
#### Installing LP_solve
|
205
243
|
|
206
244
|
The LP_solve software is open source and can be downloaded via this URL:
|
207
|
-
<a href="https://sourceforge.net/projects/lpsolve"
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
245
|
+
<a href="https://sourceforge.net/projects/lpsolve"
|
246
|
+
target="_blank">https://sourceforge.net/projects/lpsolve</a>
|
247
|
+
|
248
|
+
To facilitate installation, the executable files for Windows and macOS can
|
249
|
+
be downloaded from the Linny-R website at Delft University of Technology:
|
250
|
+
<a href="https://sysmod.tbm.tudelft.nl/linny-r/lp_solve"
|
251
|
+
target="_blank">https://sysmod.tbm.tudelft.nl/linny-r/lp_solve</a>
|
252
|
+
|
253
|
+
There you will find links to download LP_solve applications that have been
|
254
|
+
compiled for different platforms. If you do not know which platform to choose,
|
255
|
+
run Linny-R as described below, and the platform will be listed in its output.
|
256
|
+
If no matching LP_solve version is listed, you can try to compile the software
|
257
|
+
from its source. How to do this is explained on the page "Installing LP_solve
|
258
|
+
on a Mac" on the Linny-R documentation site:
|
217
259
|
<a href="https://linny-r.info" target="_blank">https://linny-r.info</a>
|
218
260
|
|
219
|
-
When you have downloaded the file (just `lp_solve` for macOS, `lp_solve.exe`
|
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
|
261
|
+
When you have downloaded the file (just `lp_solve` for macOS, `lp_solve.exe`
|
262
|
+
for Windows), you must copy or move this file to your `Linny-R` directory,
|
263
|
+
as this is where Linny-R will look for it when it does not find one of the
|
264
|
+
other solvers.
|
222
265
|
|
223
266
|
On a macOS machine, you must then make the file `lp_solve` executable.
|
224
|
-
Open Terminal and change to your Linny-R directory, and then type:
|
267
|
+
Open `Terminal` and change to your Linny-R directory, and then type:
|
225
268
|
|
226
269
|
``chmod +x lp_solve``
|
227
270
|
|
@@ -230,35 +273,36 @@ When you then type:
|
|
230
273
|
``./lp_solve -h``
|
231
274
|
|
232
275
|
a window may appear that warns you that the software may be malicious.
|
233
|
-
To allow running LP_solve, you must then go to
|
234
|
-
and there click the
|
235
|
-
|
236
|
-
The response should then be a listing
|
237
|
-
|
276
|
+
To allow running LP_solve, you must then go to _Security & Privacy_ (via
|
277
|
+
_System Preferences_) and there click the _Open Anyway_ button in the _General_
|
278
|
+
pane to confirm that you wish to use LP_solve. Then return to `Terminal`
|
279
|
+
and once more type `./lp_solve -h`. The response should then be a listing
|
280
|
+
of all the command line options of LP_solve. If you reach this stage,
|
281
|
+
Linny-R will be able to run LP_solve.
|
238
282
|
|
239
283
|
## Running Linny-R
|
240
284
|
|
241
|
-
|
285
|
+
On a Windows machine, open `Command Prompt`, change to your Linny-R
|
286
|
+
directory and type:
|
242
287
|
|
243
|
-
``
|
288
|
+
``linny-r``
|
244
289
|
|
245
|
-
|
290
|
+
On a macOS machine, open `Terminal`, change to your Linny-R directory
|
291
|
+
and type:
|
246
292
|
|
247
|
-
|
248
|
-
Node.js server for Linny-R version 1.9.3
|
249
|
-
Node.js version: v21.7.3
|
250
|
-
... etc.
|
251
|
-
</pre>
|
293
|
+
``./linny-r.command``
|
252
294
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
295
|
+
This should run the launch script for Linny-R, which will start the
|
296
|
+
local server script that connects your browser with the solver.
|
297
|
+
Meanwhile, your default web browser should have opened a tab for the local
|
298
|
+
server URL, which by default will be http://127.0.0.1:5050.
|
299
|
+
The Linny-R GUI should show in your browser window, while in the CLI you
|
300
|
+
should see a long series of server log messages like:
|
257
301
|
|
258
302
|
<pre>
|
259
|
-
[
|
260
|
-
[
|
261
|
-
[
|
303
|
+
[2024-06-11 22:55:17] Static file: /index.html
|
304
|
+
[2024-06-11 22:55:17] Static file: /scripts/iro.min.js
|
305
|
+
[2024-06-11 22:55:17] Static file: /images/open.png
|
262
306
|
... etc.
|
263
307
|
</pre>
|
264
308
|
|
@@ -266,16 +310,16 @@ while in the CLI you should see a long series of server log messages like:
|
|
266
310
|
> Do **not** close the CLI. If you do, the Linny-R GUI may still be
|
267
311
|
> visible in your browser, but you will be warned that it cannot connect
|
268
312
|
> to the server (at 127.0.0.1:5050). This means that you have to restart
|
269
|
-
> Linny-R
|
313
|
+
> Linny-R as described above.
|
270
314
|
|
271
315
|
After loading into the browser, Linny-R will try to connect to the solver.
|
272
|
-
If successful, a notification (blue background) will appear on the status
|
273
|
-
at the bottom of the window, stating the name of the solver.
|
316
|
+
If successful, a notification (blue background) will appear on the status
|
317
|
+
bar at the bottom of the window, stating the name of the solver.
|
274
318
|
|
275
319
|
You can then test the GUI by creating a simple model.
|
276
320
|
Make one that has at least one process that outputs a product,
|
277
|
-
and this product must have a price or a set lower bound, otherwise the
|
278
|
-
will have no objective function.
|
321
|
+
and this product must have a price or a set lower bound, otherwise the
|
322
|
+
model will have no objective function.
|
279
323
|
Then click on the _Solve_ button at the bottom of the left-hand tool bar.
|
280
324
|
The Linny-R icon in the upper left corner should start rotating, while the
|
281
325
|
status bar at the bottom should display:
|
@@ -298,9 +342,41 @@ confirming that you want to leave, and then closing your browser (tab).
|
|
298
342
|
If you do not shut down the server from the browser, you can also stop the
|
299
343
|
server by repeatedly pressing ``Ctrl+C`` in the CLI.
|
300
344
|
|
345
|
+
## Click-start for Linny-R
|
346
|
+
|
347
|
+
When `npm` installs the Linny-R package, it creates a script file in your
|
348
|
+
Linny-R directory that launches Linny-R. On a macOS machine, this will be
|
349
|
+
the shell script `linny-r.command`, on a Windows machine the batch script
|
350
|
+
`linny-r.bat`. When you create a desktop shortcut to this script, this will
|
351
|
+
allow you to click-start Linny-R.
|
352
|
+
|
353
|
+
How you can create a shortcut icon for Linny-R on your desktop depends on
|
354
|
+
the type of computer you use.
|
355
|
+
|
356
|
+
On a Windows machine, open the `File Explorer`, select your Linny-R folder,
|
357
|
+
right-click on the batch file `linny-r.bat`, and select the _Create shortcut_
|
358
|
+
option. Then right-click on the shortcut file to edit its properties, and
|
359
|
+
click the _Change Icon_ button. The dialog that then appears will allow
|
360
|
+
you to go to the sub-folder `node_modules\linny-r\static\images`, where
|
361
|
+
you should select the file `linny-r.ico`. Finally, rename the shortcut to
|
362
|
+
`Linny-R` and move or copy it to your desktop.
|
363
|
+
|
364
|
+
On a macOS machine, open `Terminal` and change to your Linny-R directory,
|
365
|
+
and then type:
|
366
|
+
|
367
|
+
``chmod +x linny-r.command``
|
368
|
+
|
369
|
+
to make the script file executable. To set the icon, use `Finder` to open
|
370
|
+
the folder that contains the file `linny-r.command`, click on its icon
|
371
|
+
(which still is plain) and open the _Info dialog_ by pressing ``Cmd+I``.
|
372
|
+
Then open your Linny-R folder in `Finder`, change to the sub-folder
|
373
|
+
`node_modules/linny-r/static/images`, and from there drag/drop the file
|
374
|
+
`linny-r.icns` on the icon shown in the top left corner of the _Info dialog_.
|
375
|
+
|
301
376
|
## Command line options
|
302
377
|
|
303
|
-
|
378
|
+
You can customize Linny-R by adding more arguments to the `node` command
|
379
|
+
in the launch script:
|
304
380
|
|
305
381
|
<pre>
|
306
382
|
dpi=[number] to overrule the default resolution (300 dpi) for Inkscape
|
@@ -310,26 +386,12 @@ solver=[name] to overrule the default sequence (Gurobi, MOSEK, CPLEX, SCIP,
|
|
310
386
|
workspace=[path] to overrule the default path for the user directory
|
311
387
|
</pre>
|
312
388
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
Then right-click on the shortcut file to edit its properties, and click the _Change Icon_ button.
|
320
|
-
The dialog that then appears will allow you to go to the sub-folder
|
321
|
-
`node_modules\linny-r\static\images`, where you should select the file `linny-r.ico`.
|
322
|
-
Finally, rename the shortcut to `Linny-R` and move or copy it to your desktop.
|
323
|
-
|
324
|
-
On a macOS machine, open Terminal and change to your Linny-R directory, and then type:
|
325
|
-
|
326
|
-
``chmod +x linny-r.command``
|
327
|
-
|
328
|
-
to make the script file executable.
|
329
|
-
To set the icon, use Finder to open the folder that contains the file `linny-r.command`,
|
330
|
-
click on its icon (which still is plain) and open the _Info dialog_ by pressing ``Cmd+I``.
|
331
|
-
Then open your Linny-R folder in Finder, change to the sub-folder `node_modules/linny-r/static/images`,
|
332
|
-
and from there drag/drop the file `linny-r.icns` on the icon shown in the top left corner of the _Info dialog_.
|
389
|
+
> [!NOTE]
|
390
|
+
> When configuring Linny-R for a network environment where individual users
|
391
|
+
> each have their personal work space (e.g., a virtual drive U:), you **must**
|
392
|
+
> edit the launch script file, adding the argument `workspace=path/to/workspace`
|
393
|
+
> to the `node` command. This will instruct Linny-R to create the `user`
|
394
|
+
> directory in this workspace directory instead of the Linny-R directory.
|
333
395
|
|
334
396
|
## User workspace
|
335
397
|
|
@@ -338,20 +400,23 @@ The sub-directories of this directory `user` are used by Linny-R to store files.
|
|
338
400
|
|
339
401
|
* `autosave` will contain models that have been _auto-saved_
|
340
402
|
* `channel` and `callback` will be used to interact with Linny-R via its _Receiver_
|
341
|
-
* `data` will be used by the _Dataset Manager_ to locate datasets for which
|
342
|
-
has been specified
|
403
|
+
* `data` will be used by the _Dataset Manager_ to locate datasets for which
|
404
|
+
a path has been specified
|
343
405
|
* `diagrams` will be used to render Scalable Vector Graphics (SVG) files as
|
344
406
|
Portable Network Graphics (PNG) using Inkscape (if installed)
|
407
|
+
* `models` will contain models that you saved by Shift-clicking on the
|
408
|
+
_Save_ button, or using the keyboard shortcut Ctrl-Shift-S
|
345
409
|
* `modules` will contain models stored in the `local host` _repository_
|
346
|
-
* `reports` will contain text files with time series data and statistics in
|
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
|
410
|
+
* `reports` will contain text files with time series data and statistics in
|
411
|
+
tab-separated format that can be imported or copy/pasted into Excel
|
412
|
+
* `solver` will contain the files that are exchanged with the Mixed Integer
|
413
|
+
Linear Programming (MILP) solver (the names of the files that will appear
|
414
|
+
in this directory may vary, depending on the MILP-solver you use)
|
350
415
|
|
351
416
|
> [!NOTE]
|
352
417
|
> By default, the `user` directory is created in your `Linny-R` directory.
|
353
|
-
> You can overrule this by starting the server with the `workspace=[path]`
|
354
|
-
> This will create a new, empty workspace
|
418
|
+
> You can overrule this by starting the server with the `workspace=[path]`
|
419
|
+
> option. This will create a new, empty workspace in the specified path.
|
355
420
|
> It will **not** affect or duplicate information from existing workspaces.
|
356
421
|
|
357
422
|
## Installing Inkscape
|
@@ -362,8 +427,8 @@ These files can be viewed and edited using Inkscape, an open source
|
|
362
427
|
vector graphics editor.
|
363
428
|
|
364
429
|
As it may be tedious to first save a diagram as SVG and then render it
|
365
|
-
manually as a bitmap image, Linny-R features a *Render diagram as bitmap*
|
366
|
-
on the top toolbar, and on the bottom toolbar of the _Chart manager_.
|
430
|
+
manually as a bitmap image, Linny-R features a *Render diagram as bitmap*
|
431
|
+
button on the top toolbar, and on the bottom toolbar of the _Chart manager_.
|
367
432
|
When you click it, Linny-R will send the image as SVG to the server.
|
368
433
|
The server script will save the SVG in the `user/diagrams` sub-directory,
|
369
434
|
and then try to execute an Inkscape command that will convert this SVG to
|
@@ -375,7 +440,8 @@ If rendering was successful, the image will appear in this browser tab;
|
|
375
440
|
if rendering failed, the original SVG image will be shown.
|
376
441
|
|
377
442
|
To install Inkscape, please look here:
|
378
|
-
<a href="https://inkscape.org/release"
|
443
|
+
<a href="https://inkscape.org/release"
|
444
|
+
target="_blank">https://inkscape.org/release</a>
|
379
445
|
|
380
446
|
Linny-R will automatically detect whether Inkscape is installed by searching
|
381
447
|
for it in the environment variable PATH on your computer. On a macOS computer,
|
@@ -388,21 +454,25 @@ Linny-R will look for Inkscape in `/Applications/Inkscape.app/Contents/MacOS`.
|
|
388
454
|
|
389
455
|
## Using Linny-R console
|
390
456
|
|
391
|
-
The console-only version of Linny-R allows you to run a Linny-R model without
|
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`
|
457
|
+
The console-only version of Linny-R allows you to run a Linny-R model without
|
458
|
+
a web browser. This may be useful when you want run models from a script
|
459
|
+
(shell script, Python, ...). If you open a CLI box, change to your `Linny-R`
|
460
|
+
directory, and then type:
|
394
461
|
|
395
462
|
``node node_modules/linny-r/console`` _(on Windows, use backslashes)_
|
396
463
|
|
397
|
-
you will see the command line options that allow you to run models in various
|
464
|
+
you will see the command line options that allow you to run models in various
|
465
|
+
ways.
|
398
466
|
|
399
467
|
> [!NOTE]
|
400
|
-
> The console-only version is still in development, and does not provide
|
468
|
+
> The console-only version is still in development, and does not provide
|
469
|
+
> all functions yet.
|
401
470
|
|
402
471
|
## Troubleshooting problems
|
403
472
|
|
404
|
-
If during any of the steps above you encounter problems, please try to
|
405
|
-
You can find a lot of useful
|
473
|
+
If during any of the steps above you encounter problems, please try to
|
474
|
+
diagnose them and resolve them yourself. You can find a lot of useful
|
475
|
+
information on the Linny-R user documentation website:
|
406
476
|
<a href="https://linny-r.info" target="_blank">https://linny-r.info</a>.
|
407
477
|
|
408
478
|
> [!IMPORTANT]
|
@@ -411,6 +481,8 @@ You can find a lot of useful information on the Linny-R user documentation websi
|
|
411
481
|
|
412
482
|
Then also look at the console window of your browser.
|
413
483
|
Most browsers offer a _Web Developer Tools_ option via their application menu.
|
414
|
-
This will allow you to view the browser console, which will display JavaScript
|
484
|
+
This will allow you to view the browser console, which will display JavaScript
|
485
|
+
errors in red font.
|
415
486
|
|
416
|
-
If you've tried hard, but failed, you can try to contact Pieter Bots at
|
487
|
+
If you've tried hard, but failed, you can try to contact Pieter Bots at
|
488
|
+
``p.w.g.bots@tudelft.nl``
|
package/console.js
CHANGED
@@ -16,7 +16,7 @@ NOTE: For browser-based Linny-R, this file should NOT be loaded, as it
|
|
16
16
|
*/
|
17
17
|
|
18
18
|
/*
|
19
|
-
Copyright (c) 2017-
|
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
@@ -255,22 +255,24 @@ function clearNewerVersion() {
|
|
255
255
|
}
|
256
256
|
|
257
257
|
// HTML page to show when the server is shut down by the user.
|
258
|
-
//
|
259
|
-
const
|
260
|
-
|
261
|
-
|
262
|
-
`<p>You can close the <em>Terminal</em> window that shows
|
263
|
-
|
264
|
-
</
|
265
|
-
|
266
|
-
|
258
|
+
// Parts of the text are platform-specific.
|
259
|
+
const
|
260
|
+
macOS = (PLATFORM === 'darwin'),
|
261
|
+
close = (macOS ?
|
262
|
+
`<p>You can now close the <em>Terminal</em> window that shows
|
263
|
+
<tt>[Process Terminated]</tt> at the bottom.</p>` :
|
264
|
+
`<p>The <em>Command Prompt</em> window where the server was
|
265
|
+
running will be closed automatically.</p>`),
|
266
|
+
cli = (macOS ? 'Terminal' : 'Command Prompt'),
|
267
|
+
ext = (macOS ? '.command' : ''),
|
268
|
+
chmod = (!macOS ? '' : `
|
269
|
+
<p>If launch fails, you may still need to make the script executable.</p>
|
270
|
+
<p>
|
271
|
+
You can do this by typing <code>chmod +x linny-r.command</code>
|
272
|
+
at the command prompt.
|
267
273
|
</p>
|
268
|
-
<p
|
269
|
-
|
270
|
-
} else {
|
271
|
-
OS_TEXT.reopen = 'switch to your <em>Command Prompt</em> window ';
|
272
|
-
}
|
273
|
-
const SHUTDOWN_MESSAGE = `<!DOCTYPE html>
|
274
|
+
<p>Then retype <code>linny-r.command</code> to launch Linny-R.</p>`),
|
275
|
+
SHUTDOWN_MESSAGE = `<!DOCTYPE html>
|
274
276
|
<html lang="en-US">
|
275
277
|
<head>
|
276
278
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
@@ -290,16 +292,18 @@ const SHUTDOWN_MESSAGE = `<!DOCTYPE html>
|
|
290
292
|
</style>
|
291
293
|
</head>
|
292
294
|
<body>
|
293
|
-
<h3>Linny-R server (127.0.0.1) is shutting down</h3>${
|
294
|
-
<p>To restart Linny-R, ${OS_TEXT.reopen} and then at the prompt type:</p>
|
295
|
-
<p><code>node node_modules${path.sep}linny-r${path.sep}server</code></p>
|
295
|
+
<h3>Linny-R server (127.0.0.1) is shutting down</h3>${close}
|
296
296
|
<p>
|
297
|
-
|
298
|
-
|
299
|
-
onclick="window.location.href = 'http://127.0.0.1:${SETTINGS.port}';">
|
300
|
-
Restart
|
301
|
-
</button> button.
|
297
|
+
To restart Linny-R, open <em>${cli}</em> again, change to
|
298
|
+
your Linny-R directory by typing:
|
302
299
|
</p>
|
300
|
+
<p><code>cd ${WORKING_DIRECTORY}</code></p>
|
301
|
+
<p>and then type:</p>
|
302
|
+
<p><code>linny-r${ext}</code></p>
|
303
|
+
<p>
|
304
|
+
This should launch Linny-R in a new browser window or tab, so you
|
305
|
+
can close this one.
|
306
|
+
</p>${chmod}
|
303
307
|
</body>
|
304
308
|
</html>`;
|
305
309
|
|
@@ -416,9 +420,21 @@ function autoSaveLoad(res, sp) {
|
|
416
420
|
function autoSaveStore(res, sp) {
|
417
421
|
// Stores XML data under specified file name in the auto-save directory
|
418
422
|
let data = 'OK';
|
419
|
-
const
|
423
|
+
const
|
424
|
+
fn = sp.get('file'),
|
425
|
+
wsd = sp.get('wsd'),
|
426
|
+
ws = (wsd ? WORKSPACE.models : WORKSPACE.autosave),
|
427
|
+
msg = (wsd === 'models' ? 'save to user workspace' : 'auto-save'),
|
428
|
+
exists = (path) => {
|
429
|
+
try {
|
430
|
+
fs.accessSync(path);
|
431
|
+
return true;
|
432
|
+
} catch(err) {
|
433
|
+
return false;
|
434
|
+
}
|
435
|
+
};
|
420
436
|
if(!fn) {
|
421
|
-
data = 'WARNING: No name for file to
|
437
|
+
data = 'WARNING: No name for file to ' + msg;
|
422
438
|
} else {
|
423
439
|
const xml = sp.get('xml');
|
424
440
|
// Validate XML as a Linny-R model
|
@@ -429,10 +445,41 @@ function autoSaveStore(res, sp) {
|
|
429
445
|
root = doc.documentElement;
|
430
446
|
// Linny-R models have a model element as root
|
431
447
|
if(root.nodeName !== 'model') throw 'XML document has no model element';
|
432
|
-
|
448
|
+
let fp = path.join(ws, fn + '.lnr');
|
449
|
+
if(wsd) {
|
450
|
+
// Append a version number to the file name if named file exists.
|
451
|
+
const re = /\(\d+\).lnr$/;
|
452
|
+
if(exists(fp)) {
|
453
|
+
const m = fp.match(re);
|
454
|
+
let n = 1;
|
455
|
+
if(m) {
|
456
|
+
// Replace version number (n) by (n+1).
|
457
|
+
n = parseInt(m[0].substring(1, m[0].length - 1)) + 1;
|
458
|
+
fp = fp.replace(re, `(${n}).lnr`);
|
459
|
+
} else {
|
460
|
+
// Add (1) as version number.
|
461
|
+
fp = fp.substring(0, fp.length - 4) + ' (1).lnr';
|
462
|
+
}
|
463
|
+
while(exists(fp)) {
|
464
|
+
// Iterate to find the first available version number.
|
465
|
+
n++;
|
466
|
+
fp = fp.replace(re, `(${n}).lnr`);
|
467
|
+
}
|
468
|
+
}
|
469
|
+
}
|
470
|
+
try {
|
471
|
+
fs.writeFileSync(fp, xml);
|
472
|
+
const d = `Model ${ws ? '' : 'auto-'}saved as ${fp}`;
|
473
|
+
console.log(d);
|
474
|
+
// No message (other than OK) when auto-saving.
|
475
|
+
if(ws) data = d;
|
476
|
+
} catch(err) {
|
477
|
+
console.log(err);
|
478
|
+
data = `ERROR: Failed to ${msg} to ${fp}`;
|
479
|
+
}
|
433
480
|
} catch(err) {
|
434
481
|
console.log(err);
|
435
|
-
data = 'ERROR: Not a Linny-R model to
|
482
|
+
data = 'ERROR: Not a Linny-R model to ' + msg;
|
436
483
|
}
|
437
484
|
}
|
438
485
|
servePlainText(res, data);
|
@@ -1687,6 +1734,7 @@ function createWorkspace() {
|
|
1687
1734
|
callback: path.join(SETTINGS.user_dir, 'callback'),
|
1688
1735
|
data: path.join(SETTINGS.user_dir, 'data'),
|
1689
1736
|
diagrams: path.join(SETTINGS.user_dir, 'diagrams'),
|
1737
|
+
models: path.join(SETTINGS.user_dir, 'models'),
|
1690
1738
|
modules: path.join(SETTINGS.user_dir, 'modules'),
|
1691
1739
|
reports: path.join(SETTINGS.user_dir, 'reports'),
|
1692
1740
|
solver_output: path.join(SETTINGS.user_dir, 'solver')
|
@@ -1715,18 +1763,18 @@ function createWorkspace() {
|
|
1715
1763
|
|
1716
1764
|
function createLaunchScript() {
|
1717
1765
|
// Creates platform-specific script with Linny-R start-up command
|
1718
|
-
const
|
1719
|
-
|
1720
|
-
|
1721
|
-
|
1722
|
-
|
1723
|
-
|
1724
|
-
|
1725
|
-
|
1726
|
-
|
1727
|
-
|
1728
|
-
|
1729
|
-
|
1766
|
+
const
|
1767
|
+
lines = [
|
1768
|
+
'# The first line (without the comment symbol #) should be like this:',
|
1769
|
+
'',
|
1770
|
+
'cd ' + WORKING_DIRECTORY,
|
1771
|
+
'# Then this command to launch the Linny-R server should work:',
|
1772
|
+
'',
|
1773
|
+
'# After shut-down, check whether new version should be installed:'
|
1774
|
+
],
|
1775
|
+
windows = PLATFORM.startsWith('win'),
|
1776
|
+
sp = path.join(WORKING_DIRECTORY, 'linny-r.' + (windows ? 'bat' : 'command'));
|
1777
|
+
if(windows) {
|
1730
1778
|
lines.push(
|
1731
1779
|
':loop',
|
1732
1780
|
'if exist newer_version (',
|
@@ -1738,7 +1786,6 @@ function createLaunchScript() {
|
|
1738
1786
|
lines[1] = '# cd C:\\path\\to\\main\\Linny-R\\directory';
|
1739
1787
|
lines[4] = 'node node_modules\\linny-r\\server launch';
|
1740
1788
|
} else {
|
1741
|
-
sp = path.join(WORKING_DIRECTORY, 'linny-r.command');
|
1742
1789
|
lines.push(
|
1743
1790
|
'while test -f newer_version; do',
|
1744
1791
|
' unlink newer_version',
|
@@ -1749,22 +1796,23 @@ function createLaunchScript() {
|
|
1749
1796
|
lines[4] = 'node node_modules/linny-r/server launch';
|
1750
1797
|
}
|
1751
1798
|
try {
|
1752
|
-
let
|
1753
|
-
|
1754
|
-
if(PLATFORM.startsWith('win')) code = code.replaceAll('#', '::');
|
1799
|
+
let code = lines.join(os.EOL);
|
1800
|
+
if(windows) code = code.replaceAll('#', '::');
|
1755
1801
|
try {
|
1756
1802
|
fs.accessSync(sp);
|
1757
|
-
//
|
1758
|
-
// by the user
|
1759
|
-
|
1760
|
-
|
1803
|
+
// Do not overwrite existing script, as it may have been customized
|
1804
|
+
// by the user. When istalling/updating Linny-R, the post-install
|
1805
|
+
// script should have renamed it, so typically it is created the
|
1806
|
+
// first time Linny-R is run after install/update.
|
1761
1807
|
} catch(err) {
|
1762
|
-
// ... or if it does not exist yet.
|
1763
|
-
make_script = true;
|
1764
|
-
}
|
1765
|
-
if(make_script) {
|
1766
1808
|
console.log('Creating launch script:', sp);
|
1767
1809
|
fs.writeFileSync(sp, code, 'utf8');
|
1810
|
+
// On macOS machines, try to make the script executable.
|
1811
|
+
if(!windows) try {
|
1812
|
+
fs.chmodSync(sp, '+x');
|
1813
|
+
} catch(err) {
|
1814
|
+
console.log('WARNING: Failed to make script executable -- please check');
|
1815
|
+
}
|
1768
1816
|
}
|
1769
1817
|
} catch(err) {
|
1770
1818
|
console.log('WARNING: Failed to create launch script');
|
package/static/index.html
CHANGED
@@ -262,7 +262,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
262
262
|
<img id="settings-btn" class="btn enab" src="images/settings.png"
|
263
263
|
title="Change model settings (Alt-M)">
|
264
264
|
<img id="save-btn" class="btn enab" src="images/save.png"
|
265
|
-
title="Save model (Ctrl-S)">
|
265
|
+
title="Save model (Ctrl-S) – 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 {
|
@@ -8993,13 +9006,15 @@ function VMI_add_bound_line_constraint(args) {
|
|
8993
9006
|
vy = VM.variables[viy - 1],
|
8994
9007
|
objy= vy[1],
|
8995
9008
|
uby = args[5].result(VM.t),
|
8996
|
-
bl = args[6]
|
9009
|
+
bl = args[6];
|
9010
|
+
// Set bound line point coordinates for current run and time step.
|
9011
|
+
bl.setDynamicPoints(VM.t);
|
9012
|
+
// Then use the actualized points.
|
9013
|
+
const
|
8997
9014
|
n = bl.points.length,
|
8998
9015
|
x = new Array(n),
|
8999
9016
|
y = new Array(n),
|
9000
9017
|
w = new Array(n);
|
9001
|
-
// Set bound line point coordinates for current run and time step.
|
9002
|
-
bl.setDynamicPoints(VM.t);
|
9003
9018
|
if(DEBUGGING) {
|
9004
9019
|
console.log('add_bound_line_constraint:', bl.displayName);
|
9005
9020
|
}
|