slurming 0.2.3a1__tar.gz
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.
- slurming-0.2.3a1/LICENSE +21 -0
- slurming-0.2.3a1/PKG-INFO +257 -0
- slurming-0.2.3a1/README.md +235 -0
- slurming-0.2.3a1/pyproject.toml +23 -0
- slurming-0.2.3a1/slurming/DecentJob/__init__.py +0 -0
- slurming-0.2.3a1/slurming/DecentJob/metaJob.py +101 -0
- slurming-0.2.3a1/slurming/Slurm/__init__.py +0 -0
- slurming-0.2.3a1/slurming/Slurm/shellUtils.py +187 -0
- slurming-0.2.3a1/slurming/Slurm/slurm.py +61 -0
- slurming-0.2.3a1/slurming/SlurmJob/__init__.py +0 -0
- slurming-0.2.3a1/slurming/SlurmJob/job.py +163 -0
- slurming-0.2.3a1/slurming/__init__.py +0 -0
slurming-0.2.3a1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yuxiang Luo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: slurming
|
|
3
|
+
Version: 0.2.3a1
|
|
4
|
+
Summary:
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Yuxiang Luo
|
|
8
|
+
Author-email: yuxiang.lll@outlook.com
|
|
9
|
+
Requires-Python: >=3.8,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Requires-Dist: pandas (>=2.2.2,<3.0.0) ; python_version >= "3.9" and python_version < "4.0"
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# SlurmUtils
|
|
23
|
+
|
|
24
|
+
A Python utility library for generating and managing [Slurm](https://slurm.schedmd.com/) HPC job scripts.
|
|
25
|
+
|
|
26
|
+
**Version:** 0.2.3
|
|
27
|
+
**Author:** Yuxiang Luo ([luo.929@osu.edu](mailto:luo.929@osu.edu))
|
|
28
|
+
**License:** GPL-2.0
|
|
29
|
+
**Affiliation:** Center for Weldability, The Ohio State University
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install slurmutils
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or with Poetry:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
poetry add slurmutils
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Requirements:** Python ^3.8, pandas ^2.2.2 (Python ^3.9+)
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Package Structure
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
slurmutils/
|
|
53
|
+
├── Slurm/
|
|
54
|
+
│ ├── slurm.py # Query Slurm job queue
|
|
55
|
+
│ └── shellUtils.py # Shell script generation helpers
|
|
56
|
+
├── SlurmJob/
|
|
57
|
+
│ └── job.py # SlurmJob class (OOP job builder)
|
|
58
|
+
└── DecentJob/
|
|
59
|
+
└── metaJob.py # DataFrame-based job metadata utilities
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Modules
|
|
65
|
+
|
|
66
|
+
### `slurmutils.Slurm`
|
|
67
|
+
|
|
68
|
+
#### `get_job_dict(cluster_name)`
|
|
69
|
+
|
|
70
|
+
Query the Slurm queue and return job status dicts.
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from slurmutils.Slurm.slurm import get_job_dict
|
|
74
|
+
|
|
75
|
+
jobs, running_jobs, pending_jobs = get_job_dict("myCluster")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
- `jobs` — `{cluster: {job_name: {state, job_id}}}` for all visible jobs
|
|
80
|
+
- `running_jobs` — `{job_name: {state, job_id}}` filtered to `RUNNING`
|
|
81
|
+
- `pending_jobs` — `{job_name: {state, job_id}}` filtered to `PENDING` on the specified cluster
|
|
82
|
+
|
|
83
|
+
Tries `squeue --clusters=all` first; falls back to local cluster on error.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
#### `make_shell_script(...)`
|
|
88
|
+
|
|
89
|
+
Generate an sbatch shell script file.
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from slurmutils.Slurm.shellUtils import make_shell_script
|
|
93
|
+
from pathlib import Path
|
|
94
|
+
|
|
95
|
+
make_shell_script(
|
|
96
|
+
account="PAS2138",
|
|
97
|
+
script_path=Path("run_job.sh"),
|
|
98
|
+
content=["python train.py"],
|
|
99
|
+
hours=4,
|
|
100
|
+
cores=28,
|
|
101
|
+
modules=["python/3.10"],
|
|
102
|
+
memory=64, # GB
|
|
103
|
+
gpus=1,
|
|
104
|
+
jobname="my_train",
|
|
105
|
+
sbatch_log=Path("logs/job.log"),
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Key parameters:
|
|
110
|
+
|
|
111
|
+
| Parameter | Type | Default | Description |
|
|
112
|
+
|-----------|------|---------|-------------|
|
|
113
|
+
| `account` | `str` | — | Slurm account to charge |
|
|
114
|
+
| `script_path` | `Path` | — | Output `.sh` file path |
|
|
115
|
+
| `content` | `List[str]` | — | Shell commands to run |
|
|
116
|
+
| `hours` / `minutes` / `seconds` | `int` | `2` / `0` / `0` | Wall time |
|
|
117
|
+
| `cores` | `int` | `28` | CPUs per task |
|
|
118
|
+
| `gpus` | `int` | `0` | Number of GPUs |
|
|
119
|
+
| `memory` | `int` | `0` | Memory in GB (0 = unset) |
|
|
120
|
+
| `modules` | `List[str]` | `[]` | Modules to load |
|
|
121
|
+
| `module_profie` | `str\|None` | `None` | Custom module profile path |
|
|
122
|
+
| `python_env` | `str` | `""` | Path to Python virtualenv activate script |
|
|
123
|
+
| `env_vars` | `Dict[str,str]` | `{}` | Environment variables to export |
|
|
124
|
+
| `aliases` | `Dict[str,str]` | `{}` | Shell aliases to define |
|
|
125
|
+
| `paths` | `List[str]` | `[]` | Extra paths to append to `$PATH` |
|
|
126
|
+
| `notifies` | `List[str]` | `["FAIL"]` | Slurm mail-type events |
|
|
127
|
+
| `sbatch_args` | `Dict` | `{}` | Extra `#SBATCH` key-value directives |
|
|
128
|
+
| `license` | `Dict[str,int]` | `{}` | Software licenses (OSC format) |
|
|
129
|
+
| `set_flag` | `str\|None` | `"x"` | Bash `set -<flag>` option |
|
|
130
|
+
| `interactive` | `bool` | `False` | Use `#!/bin/bash -i` |
|
|
131
|
+
| `bashinit` | `List[str]` | `[]` | Lines inserted before env setup |
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
#### `make_command(head, params, params1_dict, params2_dict, stdout_redirect, connection)`
|
|
136
|
+
|
|
137
|
+
Build a shell command string from parts.
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from slurmutils.Slurm.shellUtils import make_command
|
|
141
|
+
|
|
142
|
+
cmd = make_command(
|
|
143
|
+
"mpirun",
|
|
144
|
+
params=["./solver"],
|
|
145
|
+
params1_dict={"n": 28},
|
|
146
|
+
params2_dict={"bind-to": "core"},
|
|
147
|
+
)
|
|
148
|
+
# => "mpirun -n=28 --bind-to=core ./solver"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
#### `make_if_statement(if_st, elif_sts, else_st)`
|
|
154
|
+
|
|
155
|
+
Build a bash `if/elif/else/fi` block as a list of strings.
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from slurmutils.Slurm.shellUtils import make_if_statement
|
|
159
|
+
|
|
160
|
+
lines = make_if_statement(
|
|
161
|
+
if_st=(["$ret -eq 0"], ["echo success"]),
|
|
162
|
+
else_st=["echo failure"],
|
|
163
|
+
)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
### `slurmutils.SlurmJob`
|
|
169
|
+
|
|
170
|
+
#### `SlurmJob`
|
|
171
|
+
|
|
172
|
+
OOP interface for constructing and writing sbatch scripts.
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from slurmutils.SlurmJob.job import SlurmJob
|
|
176
|
+
from pathlib import Path
|
|
177
|
+
|
|
178
|
+
job = SlurmJob(
|
|
179
|
+
account="PAS2138",
|
|
180
|
+
content=["python simulate.py"],
|
|
181
|
+
licenses={},
|
|
182
|
+
modules=["python/3.10"],
|
|
183
|
+
env_vars={},
|
|
184
|
+
notify_email=["FAIL"],
|
|
185
|
+
aliases={},
|
|
186
|
+
paths=[],
|
|
187
|
+
sbatch_args={},
|
|
188
|
+
output_storage=[],
|
|
189
|
+
hours=8,
|
|
190
|
+
cpus_per_task=28,
|
|
191
|
+
memory=128,
|
|
192
|
+
job_name="simulation",
|
|
193
|
+
partition="gpu",
|
|
194
|
+
gpus=1,
|
|
195
|
+
python_home=Path("/home/user/.venv"),
|
|
196
|
+
working_dir=Path("/scratch/user/project"),
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
job.append(["echo done"])
|
|
200
|
+
job.write(job_name="simulation", script_path=Path("sim.sh"))
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Key methods:
|
|
204
|
+
|
|
205
|
+
| Method | Description |
|
|
206
|
+
|--------|-------------|
|
|
207
|
+
| `append(content)` | Append lines to the job script |
|
|
208
|
+
| `prepend(content)` | Prepend lines to the job script |
|
|
209
|
+
| `write(job_name, script_path, log_file, delete_log_on_completion, delete_script_on_completion)` | Write the sbatch script to disk |
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
### `slurmutils.DecentJob`
|
|
214
|
+
|
|
215
|
+
Utilities for managing collections of jobs tracked in a pandas DataFrame.
|
|
216
|
+
|
|
217
|
+
#### `find_job_by_params(condition_dict, df, exception, is_exact)`
|
|
218
|
+
|
|
219
|
+
Query a DataFrame for jobs matching a parameter dict.
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from slurmutils.DecentJob.metaJob import find_job_by_params
|
|
223
|
+
|
|
224
|
+
result = find_job_by_params(
|
|
225
|
+
condition_dict={"solver": "myFoam", "cores": 28},
|
|
226
|
+
df=job_df,
|
|
227
|
+
is_exact=True,
|
|
228
|
+
)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
#### `query_dataframe(condition_dict, df)`
|
|
232
|
+
|
|
233
|
+
Low-level DataFrame query builder.
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
from slurmutils.DecentJob.metaJob import query_dataframe
|
|
237
|
+
|
|
238
|
+
result, query_str = query_dataframe({"cores": 28, "solver": "myFoam"}, df)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
#### `migrate_static(new_job_df, static_dir)`
|
|
242
|
+
|
|
243
|
+
Migrate a directory of static job folders to new parameter-based indices by matching `case.json` metadata against a new DataFrame layout.
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
from slurmutils.DecentJob.metaJob import migrate_static
|
|
247
|
+
from pathlib import Path
|
|
248
|
+
|
|
249
|
+
migrate_static(new_job_df=job_df, static_dir=Path("/scratch/cases"))
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## License
|
|
255
|
+
|
|
256
|
+
GPL-2.0. See [LICENSE](LICENSE) for details.
|
|
257
|
+
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# SlurmUtils
|
|
2
|
+
|
|
3
|
+
A Python utility library for generating and managing [Slurm](https://slurm.schedmd.com/) HPC job scripts.
|
|
4
|
+
|
|
5
|
+
**Version:** 0.2.3
|
|
6
|
+
**Author:** Yuxiang Luo ([luo.929@osu.edu](mailto:luo.929@osu.edu))
|
|
7
|
+
**License:** GPL-2.0
|
|
8
|
+
**Affiliation:** Center for Weldability, The Ohio State University
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install slurmutils
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or with Poetry:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
poetry add slurmutils
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Requirements:** Python ^3.8, pandas ^2.2.2 (Python ^3.9+)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Package Structure
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
slurmutils/
|
|
32
|
+
├── Slurm/
|
|
33
|
+
│ ├── slurm.py # Query Slurm job queue
|
|
34
|
+
│ └── shellUtils.py # Shell script generation helpers
|
|
35
|
+
├── SlurmJob/
|
|
36
|
+
│ └── job.py # SlurmJob class (OOP job builder)
|
|
37
|
+
└── DecentJob/
|
|
38
|
+
└── metaJob.py # DataFrame-based job metadata utilities
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Modules
|
|
44
|
+
|
|
45
|
+
### `slurmutils.Slurm`
|
|
46
|
+
|
|
47
|
+
#### `get_job_dict(cluster_name)`
|
|
48
|
+
|
|
49
|
+
Query the Slurm queue and return job status dicts.
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from slurmutils.Slurm.slurm import get_job_dict
|
|
53
|
+
|
|
54
|
+
jobs, running_jobs, pending_jobs = get_job_dict("myCluster")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
- `jobs` — `{cluster: {job_name: {state, job_id}}}` for all visible jobs
|
|
59
|
+
- `running_jobs` — `{job_name: {state, job_id}}` filtered to `RUNNING`
|
|
60
|
+
- `pending_jobs` — `{job_name: {state, job_id}}` filtered to `PENDING` on the specified cluster
|
|
61
|
+
|
|
62
|
+
Tries `squeue --clusters=all` first; falls back to local cluster on error.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
#### `make_shell_script(...)`
|
|
67
|
+
|
|
68
|
+
Generate an sbatch shell script file.
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from slurmutils.Slurm.shellUtils import make_shell_script
|
|
72
|
+
from pathlib import Path
|
|
73
|
+
|
|
74
|
+
make_shell_script(
|
|
75
|
+
account="PAS2138",
|
|
76
|
+
script_path=Path("run_job.sh"),
|
|
77
|
+
content=["python train.py"],
|
|
78
|
+
hours=4,
|
|
79
|
+
cores=28,
|
|
80
|
+
modules=["python/3.10"],
|
|
81
|
+
memory=64, # GB
|
|
82
|
+
gpus=1,
|
|
83
|
+
jobname="my_train",
|
|
84
|
+
sbatch_log=Path("logs/job.log"),
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Key parameters:
|
|
89
|
+
|
|
90
|
+
| Parameter | Type | Default | Description |
|
|
91
|
+
|-----------|------|---------|-------------|
|
|
92
|
+
| `account` | `str` | — | Slurm account to charge |
|
|
93
|
+
| `script_path` | `Path` | — | Output `.sh` file path |
|
|
94
|
+
| `content` | `List[str]` | — | Shell commands to run |
|
|
95
|
+
| `hours` / `minutes` / `seconds` | `int` | `2` / `0` / `0` | Wall time |
|
|
96
|
+
| `cores` | `int` | `28` | CPUs per task |
|
|
97
|
+
| `gpus` | `int` | `0` | Number of GPUs |
|
|
98
|
+
| `memory` | `int` | `0` | Memory in GB (0 = unset) |
|
|
99
|
+
| `modules` | `List[str]` | `[]` | Modules to load |
|
|
100
|
+
| `module_profie` | `str\|None` | `None` | Custom module profile path |
|
|
101
|
+
| `python_env` | `str` | `""` | Path to Python virtualenv activate script |
|
|
102
|
+
| `env_vars` | `Dict[str,str]` | `{}` | Environment variables to export |
|
|
103
|
+
| `aliases` | `Dict[str,str]` | `{}` | Shell aliases to define |
|
|
104
|
+
| `paths` | `List[str]` | `[]` | Extra paths to append to `$PATH` |
|
|
105
|
+
| `notifies` | `List[str]` | `["FAIL"]` | Slurm mail-type events |
|
|
106
|
+
| `sbatch_args` | `Dict` | `{}` | Extra `#SBATCH` key-value directives |
|
|
107
|
+
| `license` | `Dict[str,int]` | `{}` | Software licenses (OSC format) |
|
|
108
|
+
| `set_flag` | `str\|None` | `"x"` | Bash `set -<flag>` option |
|
|
109
|
+
| `interactive` | `bool` | `False` | Use `#!/bin/bash -i` |
|
|
110
|
+
| `bashinit` | `List[str]` | `[]` | Lines inserted before env setup |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
#### `make_command(head, params, params1_dict, params2_dict, stdout_redirect, connection)`
|
|
115
|
+
|
|
116
|
+
Build a shell command string from parts.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from slurmutils.Slurm.shellUtils import make_command
|
|
120
|
+
|
|
121
|
+
cmd = make_command(
|
|
122
|
+
"mpirun",
|
|
123
|
+
params=["./solver"],
|
|
124
|
+
params1_dict={"n": 28},
|
|
125
|
+
params2_dict={"bind-to": "core"},
|
|
126
|
+
)
|
|
127
|
+
# => "mpirun -n=28 --bind-to=core ./solver"
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
#### `make_if_statement(if_st, elif_sts, else_st)`
|
|
133
|
+
|
|
134
|
+
Build a bash `if/elif/else/fi` block as a list of strings.
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from slurmutils.Slurm.shellUtils import make_if_statement
|
|
138
|
+
|
|
139
|
+
lines = make_if_statement(
|
|
140
|
+
if_st=(["$ret -eq 0"], ["echo success"]),
|
|
141
|
+
else_st=["echo failure"],
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
### `slurmutils.SlurmJob`
|
|
148
|
+
|
|
149
|
+
#### `SlurmJob`
|
|
150
|
+
|
|
151
|
+
OOP interface for constructing and writing sbatch scripts.
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from slurmutils.SlurmJob.job import SlurmJob
|
|
155
|
+
from pathlib import Path
|
|
156
|
+
|
|
157
|
+
job = SlurmJob(
|
|
158
|
+
account="PAS2138",
|
|
159
|
+
content=["python simulate.py"],
|
|
160
|
+
licenses={},
|
|
161
|
+
modules=["python/3.10"],
|
|
162
|
+
env_vars={},
|
|
163
|
+
notify_email=["FAIL"],
|
|
164
|
+
aliases={},
|
|
165
|
+
paths=[],
|
|
166
|
+
sbatch_args={},
|
|
167
|
+
output_storage=[],
|
|
168
|
+
hours=8,
|
|
169
|
+
cpus_per_task=28,
|
|
170
|
+
memory=128,
|
|
171
|
+
job_name="simulation",
|
|
172
|
+
partition="gpu",
|
|
173
|
+
gpus=1,
|
|
174
|
+
python_home=Path("/home/user/.venv"),
|
|
175
|
+
working_dir=Path("/scratch/user/project"),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
job.append(["echo done"])
|
|
179
|
+
job.write(job_name="simulation", script_path=Path("sim.sh"))
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Key methods:
|
|
183
|
+
|
|
184
|
+
| Method | Description |
|
|
185
|
+
|--------|-------------|
|
|
186
|
+
| `append(content)` | Append lines to the job script |
|
|
187
|
+
| `prepend(content)` | Prepend lines to the job script |
|
|
188
|
+
| `write(job_name, script_path, log_file, delete_log_on_completion, delete_script_on_completion)` | Write the sbatch script to disk |
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
### `slurmutils.DecentJob`
|
|
193
|
+
|
|
194
|
+
Utilities for managing collections of jobs tracked in a pandas DataFrame.
|
|
195
|
+
|
|
196
|
+
#### `find_job_by_params(condition_dict, df, exception, is_exact)`
|
|
197
|
+
|
|
198
|
+
Query a DataFrame for jobs matching a parameter dict.
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from slurmutils.DecentJob.metaJob import find_job_by_params
|
|
202
|
+
|
|
203
|
+
result = find_job_by_params(
|
|
204
|
+
condition_dict={"solver": "myFoam", "cores": 28},
|
|
205
|
+
df=job_df,
|
|
206
|
+
is_exact=True,
|
|
207
|
+
)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### `query_dataframe(condition_dict, df)`
|
|
211
|
+
|
|
212
|
+
Low-level DataFrame query builder.
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
from slurmutils.DecentJob.metaJob import query_dataframe
|
|
216
|
+
|
|
217
|
+
result, query_str = query_dataframe({"cores": 28, "solver": "myFoam"}, df)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
#### `migrate_static(new_job_df, static_dir)`
|
|
221
|
+
|
|
222
|
+
Migrate a directory of static job folders to new parameter-based indices by matching `case.json` metadata against a new DataFrame layout.
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
from slurmutils.DecentJob.metaJob import migrate_static
|
|
226
|
+
from pathlib import Path
|
|
227
|
+
|
|
228
|
+
migrate_static(new_job_df=job_df, static_dir=Path("/scratch/cases"))
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
GPL-2.0. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "slurming"
|
|
3
|
+
version = "0.2.3a1"
|
|
4
|
+
description = ""
|
|
5
|
+
authors = ["Yuxiang Luo <yuxiang.lll@outlook.com>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
|
|
9
|
+
[tool.poetry.dependencies]
|
|
10
|
+
python = "^3.8"
|
|
11
|
+
pandas =[
|
|
12
|
+
{version="^2.2.2",python="^3.9"}
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
[tool.poetry.group.dev.dependencies]
|
|
17
|
+
build = "^1.2.1"
|
|
18
|
+
yapf = "^0.40.2"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["poetry-core"]
|
|
23
|
+
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Dict, List, Union
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
def find_job_by_params(
|
|
9
|
+
condition_dict: Dict,
|
|
10
|
+
df: pd.DataFrame,
|
|
11
|
+
exception: List[str] = [],
|
|
12
|
+
is_exact: bool = False,
|
|
13
|
+
):
|
|
14
|
+
|
|
15
|
+
result, quest = query_dataframe(
|
|
16
|
+
{
|
|
17
|
+
key: val
|
|
18
|
+
for key, val in condition_dict.items() if key not in exception
|
|
19
|
+
}, df)
|
|
20
|
+
if (len(result) == 0):
|
|
21
|
+
return {}
|
|
22
|
+
|
|
23
|
+
if (is_exact):
|
|
24
|
+
assert len(result) == 1, f"Query return {result} on quest {quest}"
|
|
25
|
+
|
|
26
|
+
result = result.to_dict(orient="index")
|
|
27
|
+
for key, val in result.items():
|
|
28
|
+
result[key]["Index"] = key
|
|
29
|
+
|
|
30
|
+
result_dict: Dict = result[list(result.keys())[0]]
|
|
31
|
+
else:
|
|
32
|
+
result_dict = result.to_dict(orient="index")
|
|
33
|
+
|
|
34
|
+
return result_dict
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def migrate_static(new_job_df: pd.DataFrame, static_dir: Path):
|
|
38
|
+
|
|
39
|
+
for dir_path in static_dir.iterdir():
|
|
40
|
+
if (dir_path.is_file()):
|
|
41
|
+
continue
|
|
42
|
+
with open(dir_path / "case.json", 'r') as fin:
|
|
43
|
+
dir_dict = json.load(fin)
|
|
44
|
+
|
|
45
|
+
new_keys = [key for key in new_job_df.columns if key not in dir_dict]
|
|
46
|
+
|
|
47
|
+
multi_val_keys = [
|
|
48
|
+
key for key in new_keys if len(new_job_df[key].unique()) != 1
|
|
49
|
+
]
|
|
50
|
+
assert len(
|
|
51
|
+
multi_val_keys
|
|
52
|
+
) == 0, f"{[[key , new_job_df[key].unique().tolist()] for key in multi_val_keys]}"
|
|
53
|
+
|
|
54
|
+
if (len(new_keys) > 0):
|
|
55
|
+
print(f"{dir_path/ 'case.json'} Found new keys {new_keys}")
|
|
56
|
+
for key in new_keys:
|
|
57
|
+
dir_dict[key] = new_job_df[key].unique().tolist()[0]
|
|
58
|
+
print(dir_dict)
|
|
59
|
+
input()
|
|
60
|
+
with open(dir_path / "case.json", 'w') as fout:
|
|
61
|
+
json.dump(dir_dict, fout)
|
|
62
|
+
|
|
63
|
+
new_job_dict = find_job_by_params(
|
|
64
|
+
dir_dict,
|
|
65
|
+
new_job_df,
|
|
66
|
+
is_exact=True,
|
|
67
|
+
)
|
|
68
|
+
if (len(new_job_dict) == 0):
|
|
69
|
+
dir_path.rename(
|
|
70
|
+
dir_path.parent /
|
|
71
|
+
f"{dir_path.name}_empty".replace("_empty_empty", "_empty"))
|
|
72
|
+
else:
|
|
73
|
+
dir_path.rename(dir_path.parent / f"{new_job_dict['Index']}_ren")
|
|
74
|
+
print(
|
|
75
|
+
f"Migrating {dir_path} -> {dir_path.parent/new_job_dict['Index']}"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
for dir_path in static_dir.glob("*_ren"):
|
|
79
|
+
|
|
80
|
+
dir_path.rename(dir_path.parent / dir_path.name.replace("_ren", ""))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def query_dataframe(condition_dict: Dict, df: pd.DataFrame):
|
|
84
|
+
conditions = []
|
|
85
|
+
|
|
86
|
+
for key, val in condition_dict.items():
|
|
87
|
+
# print(key, val, type(val))
|
|
88
|
+
if (type(val) in [int, float, bool]):
|
|
89
|
+
conditions.append(f"{key}=={val}")
|
|
90
|
+
else:
|
|
91
|
+
conditions.append(f"{key}==\"{val}\"")
|
|
92
|
+
|
|
93
|
+
# print(conditions)
|
|
94
|
+
# input()
|
|
95
|
+
quest = " and ".join(conditions)
|
|
96
|
+
# print(quest)
|
|
97
|
+
# input()
|
|
98
|
+
|
|
99
|
+
result = df.query(quest)
|
|
100
|
+
|
|
101
|
+
return result, quest
|
|
File without changes
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, List, Union, Tuple
|
|
6
|
+
import os
|
|
7
|
+
import stat
|
|
8
|
+
|
|
9
|
+
LICENSE_USAGE = {
|
|
10
|
+
28: 70,
|
|
11
|
+
14: 50,
|
|
12
|
+
8: 40,
|
|
13
|
+
7: 48,
|
|
14
|
+
1: 12,
|
|
15
|
+
}
|
|
16
|
+
CONCURRENCY_CORE_USAGE = {
|
|
17
|
+
28: 28,
|
|
18
|
+
14: 28,
|
|
19
|
+
8: 8,
|
|
20
|
+
7: 28,
|
|
21
|
+
1: 8,
|
|
22
|
+
}
|
|
23
|
+
TIME_CORE_USAGE = {28: 8, 14: 13, 8: 13, 7: 13, 1: 84}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def make_shell_script(
|
|
27
|
+
account: str,
|
|
28
|
+
script_path: Path,
|
|
29
|
+
content: List[str],
|
|
30
|
+
hours=2,
|
|
31
|
+
minutes=0,
|
|
32
|
+
seconds=0,
|
|
33
|
+
license: Dict[str, int] = {},
|
|
34
|
+
cores: int = 28,
|
|
35
|
+
module_profie: Union[str, None] = None,
|
|
36
|
+
modules: List[str] = [],
|
|
37
|
+
python_env: str = "",
|
|
38
|
+
gpus: int = 0,
|
|
39
|
+
env_vars: Dict = {},
|
|
40
|
+
set_flag: Union[str, None] = "x",
|
|
41
|
+
sbatch_log: Union[Path, None] = None,
|
|
42
|
+
pre_set_content: List[str] = [],
|
|
43
|
+
aliases: Dict[str, str] = {},
|
|
44
|
+
paths: List[str] = [],
|
|
45
|
+
chmod: bool = True,
|
|
46
|
+
notifies: List[str] = ["FAIL"],
|
|
47
|
+
memory: int = 0,
|
|
48
|
+
sbatch_args: Dict = {},
|
|
49
|
+
jobname: str = "",
|
|
50
|
+
interactive: bool = False,
|
|
51
|
+
bashinit: List[str] = [],
|
|
52
|
+
):
|
|
53
|
+
"""_summary_
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
script_path (Path): The path of new shell script to be generated
|
|
57
|
+
content (List[str]): The content of shell script, in a list of strings.
|
|
58
|
+
hours (int, optional): Allown hours to run for this job. Defaults to 2.
|
|
59
|
+
minutes (int, optional): Allown hours to run for this job. Defaults to 0.
|
|
60
|
+
license (Dict[str,int], optional): The software licences to be used for this job, keys and values are the software name and number of tokens repectively. Defaults to {}.
|
|
61
|
+
cores (int, optional): number of cores to be used for this job. Defaults to 28 (owens).
|
|
62
|
+
account (str, optional): account to charge from for this job. Defaults to 'PAS2138'.
|
|
63
|
+
module_profie (str,optional): Local module profile files to use for modules.
|
|
64
|
+
modules (List[str], optional): modules to be activated for this job. Defaults to [].
|
|
65
|
+
python_env (str, optional): python environment to be activated for this job. Defaults to "". Example for python -m venv environments: "source .pythonenv/bin/activate"
|
|
66
|
+
gpus (int, optional): number of GPUs for this job. Defaults to 0.
|
|
67
|
+
env_vars (Dict, optional): Diction of environment variable, name and content. Defaults to {}.
|
|
68
|
+
set_flag (Union[str, None], optional): flat for "set" command. Defaults to "x".
|
|
69
|
+
sbatch_log (Union[Path, None], optional): the log file for this job. Defaults to None.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
# cores = max(cores, 8)
|
|
73
|
+
env_var_decls = []
|
|
74
|
+
for vname, vval in env_vars.items():
|
|
75
|
+
env_var_decls.extend([f"{vname}={vval}", f"export {vname}"])
|
|
76
|
+
alias_var_decls = []
|
|
77
|
+
for vname, vval in aliases.items():
|
|
78
|
+
alias_var_decls.extend([f"alias {vname}={vval}"])
|
|
79
|
+
|
|
80
|
+
path_export = [f"PATH=$PATH:{':'.join(paths)}", f"export PATH"
|
|
81
|
+
] if len(paths) > 0 else []
|
|
82
|
+
|
|
83
|
+
sbatch_log_file = "output/%j.log" if (sbatch_log is None) else sbatch_log
|
|
84
|
+
|
|
85
|
+
shell_script_head = [
|
|
86
|
+
"#!/bin/bash" if not interactive else "#!/bin/bash -i",
|
|
87
|
+
f"#SBATCH --job-name={jobname}" if len(jobname) > 0 else "",
|
|
88
|
+
f"#SBATCH --time={hours:02d}:{minutes:02d}:{seconds:02d}",
|
|
89
|
+
f"#SBATCH --account={account}",
|
|
90
|
+
f"#SBATCH --output={sbatch_log_file}",
|
|
91
|
+
f"#SBATCH --mail-type={','.join(notifies)}" if
|
|
92
|
+
(len(notifies) > 0) else "",
|
|
93
|
+
f"#SBATCH --cpus-per-task={cores}",
|
|
94
|
+
f"#SBATCH --mem={memory}G" if memory > 0 else "",
|
|
95
|
+
*[f"#SBATCH -L {key}@osc:{val}" for key, val in license.items()],
|
|
96
|
+
f"#SBATCH --gres=gpu:{gpus}" if gpus > 0 else "",
|
|
97
|
+
*[
|
|
98
|
+
f"#SBATCH --{key}={val}"
|
|
99
|
+
if not isinstance(val, bool) else f"#SBATCH --{key}"
|
|
100
|
+
for key, val in sbatch_args.items() if len(str(val)) > 0
|
|
101
|
+
],
|
|
102
|
+
*bashinit,
|
|
103
|
+
# *["whoami", f"echo $SHELL", "w", "tty", "ps"],
|
|
104
|
+
# *init_bashrc,
|
|
105
|
+
*env_var_decls,
|
|
106
|
+
*alias_var_decls,
|
|
107
|
+
*path_export,
|
|
108
|
+
"" if module_profie is None else f"module use {module_profie}",
|
|
109
|
+
*[f"module load {key}" for key in modules],
|
|
110
|
+
*pre_set_content,
|
|
111
|
+
f"source \"{python_env}\"" if python_env != "" else "",
|
|
112
|
+
"" if set_flag is None else f"set -{set_flag}",
|
|
113
|
+
]
|
|
114
|
+
shell_script_tail = [
|
|
115
|
+
"mv \"$SLURM_SUBMIT_DIR/output/$SLURM_JOB_ID.log\" \"$OUTPUT_DIR/sbatch.log\""
|
|
116
|
+
if sbatch_log is None else "",
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
# print(content)
|
|
120
|
+
|
|
121
|
+
with open(script_path, 'w') as fout:
|
|
122
|
+
fout.write("\n".join(shell_script_head + content + shell_script_tail))
|
|
123
|
+
if (chmod):
|
|
124
|
+
# os.system(f"chmod {chmod} {script_path}")
|
|
125
|
+
os.chmod(script_path, stat.S_IRWXU)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def make_command(
|
|
129
|
+
head,
|
|
130
|
+
params: List[str] = [],
|
|
131
|
+
params1_dict: Dict = {},
|
|
132
|
+
params2_dict: Dict = {},
|
|
133
|
+
stdout_redirect: str = "",
|
|
134
|
+
connection="=",
|
|
135
|
+
):
|
|
136
|
+
for key, val in list(params1_dict.items()):
|
|
137
|
+
if (isinstance(val, bool)):
|
|
138
|
+
if (val):
|
|
139
|
+
params1_dict[key] = ""
|
|
140
|
+
else:
|
|
141
|
+
del params1_dict[key]
|
|
142
|
+
elif (isinstance(val, str)):
|
|
143
|
+
if (len(val) == 0):
|
|
144
|
+
del params1_dict[key]
|
|
145
|
+
else:
|
|
146
|
+
params1_dict[key] = f'"{val}"'
|
|
147
|
+
|
|
148
|
+
for key, val in list(params2_dict.items()):
|
|
149
|
+
if (isinstance(val, bool)):
|
|
150
|
+
if (val):
|
|
151
|
+
params2_dict[key] = ""
|
|
152
|
+
else:
|
|
153
|
+
del params2_dict[key]
|
|
154
|
+
elif (isinstance(val, str)):
|
|
155
|
+
if (len(val) == 0):
|
|
156
|
+
del params2_dict[key]
|
|
157
|
+
else:
|
|
158
|
+
params2_dict[key] = f'"{val}"'
|
|
159
|
+
command = [
|
|
160
|
+
head,
|
|
161
|
+
*[f"-{key}{connection}{val}" for key, val in params1_dict.items()],
|
|
162
|
+
*[f"--{key}{connection}{val}" for key, val in params2_dict.items()],
|
|
163
|
+
*params,
|
|
164
|
+
f">> {stdout_redirect}" if len(stdout_redirect) > 0 else "",
|
|
165
|
+
]
|
|
166
|
+
return " ".join(command).replace(" ", " ").strip()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def make_if_statement(
|
|
170
|
+
if_st: Tuple[(List[str], List[str])],
|
|
171
|
+
elif_sts: List[Tuple[(str, List[str])]] = [],
|
|
172
|
+
else_st: List[str] = [],
|
|
173
|
+
):
|
|
174
|
+
|
|
175
|
+
if_cond, if_act = if_st
|
|
176
|
+
if_cond_str = " && ".join([f"[ {c} ]" for c in if_cond])
|
|
177
|
+
terms = [f"if {if_cond_str}; then", *if_act]
|
|
178
|
+
|
|
179
|
+
for cond, act in elif_sts:
|
|
180
|
+
terms.extend([f"elif [ {cond} ]; then", *act])
|
|
181
|
+
|
|
182
|
+
if (len(else_st) > 0):
|
|
183
|
+
terms.extend(["else", *else_st])
|
|
184
|
+
|
|
185
|
+
terms.append("fi")
|
|
186
|
+
|
|
187
|
+
return terms
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from typing import Dict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_job_dict(cluster_name):
|
|
7
|
+
"""
|
|
8
|
+
Return {job_name: (state, cluster, job_id)} for *all* visible jobs.
|
|
9
|
+
Falls back gracefully if federation/cluster info is absent.
|
|
10
|
+
"""
|
|
11
|
+
# Ask every cluster we can see (requires Slurm ≥20.02 with Federation or dbd)
|
|
12
|
+
|
|
13
|
+
fields = ["Name", "StateCompact", "Cluster"]
|
|
14
|
+
cmd = ["squeue", "--noheader", f"-O{','.join(fields)}"]
|
|
15
|
+
|
|
16
|
+
fmt = "%.8j|%i|%T|%q" # id | state | cluster (M == cluster in squeue ≥22.05)
|
|
17
|
+
user_name = subprocess.getoutput("whoami")
|
|
18
|
+
try:
|
|
19
|
+
cmd = [
|
|
20
|
+
"squeue",
|
|
21
|
+
f"--user={user_name}",
|
|
22
|
+
"--noheader",
|
|
23
|
+
"--clusters=all",
|
|
24
|
+
"-o",
|
|
25
|
+
fmt,
|
|
26
|
+
]
|
|
27
|
+
out = subprocess.check_output(cmd, text=True).strip().splitlines()
|
|
28
|
+
except subprocess.CalledProcessError:
|
|
29
|
+
# Fallback: ask only the local cluster
|
|
30
|
+
cmd = ["squeue", f"--user={user_name}", "--noheader", "-o", fmt]
|
|
31
|
+
out = subprocess.check_output(cmd, text=True).strip().splitlines()
|
|
32
|
+
|
|
33
|
+
jobs: Dict[str, Dict[str, Dict]] = defaultdict(dict)
|
|
34
|
+
for line in out:
|
|
35
|
+
# If the user is on a single cluster, the third field will be empty;
|
|
36
|
+
# use $SLURM_CLUSTER_NAME or 'local' instead.
|
|
37
|
+
fields = line.split("|")
|
|
38
|
+
job_name, job_id, state, cluster = fields
|
|
39
|
+
cluster, *_ = cluster.split("-")
|
|
40
|
+
if (cluster not in jobs):
|
|
41
|
+
jobs[cluster]: Dict[str, Dict] = defaultdict(dict)
|
|
42
|
+
cluster_dict = jobs[cluster]
|
|
43
|
+
cluster_dict[job_name]["state"] = state
|
|
44
|
+
cluster_dict[job_name]["job_id"] = job_id
|
|
45
|
+
|
|
46
|
+
running_jobs = {
|
|
47
|
+
k: v
|
|
48
|
+
for cluster, job_dict in jobs.items()
|
|
49
|
+
for k, v in job_dict.items() if "RUNNING" == v['state']
|
|
50
|
+
}
|
|
51
|
+
pending_jobs = {
|
|
52
|
+
k: v
|
|
53
|
+
for k, v in jobs[cluster_name].items() if "PENDING" == v['state']
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
print(f"Detected {len(running_jobs)} running jobs and {len(pending_jobs)} pending jobs on cluster '{cluster_name}'.")
|
|
57
|
+
return jobs, running_jobs, pending_jobs
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
print(get_job_dict())
|
|
File without changes
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from typing import Dict, List, Union, Tuple
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from slurming.Slurm.shellUtils import make_shell_script
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SlurmJob:
|
|
7
|
+
|
|
8
|
+
account: str
|
|
9
|
+
content: List[str]
|
|
10
|
+
|
|
11
|
+
licenses: Dict[str, int]
|
|
12
|
+
|
|
13
|
+
modules: List[str]
|
|
14
|
+
module_profile: Union[str, None]
|
|
15
|
+
|
|
16
|
+
env_vars: Dict[str, str]
|
|
17
|
+
notify_email: List[str]
|
|
18
|
+
alias: Dict[str, str]
|
|
19
|
+
paths: List[str]
|
|
20
|
+
|
|
21
|
+
days: int
|
|
22
|
+
hours: int
|
|
23
|
+
minutes: int
|
|
24
|
+
seconds: int
|
|
25
|
+
|
|
26
|
+
cpus_per_task: int
|
|
27
|
+
gpus: int
|
|
28
|
+
memory: int
|
|
29
|
+
|
|
30
|
+
python_home: Union[Path, None]
|
|
31
|
+
set_flag: str
|
|
32
|
+
|
|
33
|
+
pre_content: List[str]
|
|
34
|
+
|
|
35
|
+
partition: Union[str, None]
|
|
36
|
+
job_name: Union[str, None]
|
|
37
|
+
|
|
38
|
+
interactive: bool
|
|
39
|
+
bashinit: List[str]
|
|
40
|
+
|
|
41
|
+
working_dir: Union[Path, None]
|
|
42
|
+
|
|
43
|
+
sbatch_args: Dict[str, Union[str, int, bool]]
|
|
44
|
+
|
|
45
|
+
output_storage: List[str]
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
account: str,
|
|
50
|
+
content: List[str],
|
|
51
|
+
licenses: Dict[str, int],
|
|
52
|
+
modules: List[str],
|
|
53
|
+
env_vars: Dict[str, str],
|
|
54
|
+
notify_email: List[str],
|
|
55
|
+
aliases: Dict[str, str],
|
|
56
|
+
paths: List[str],
|
|
57
|
+
sbatch_args: Dict[str, Union[str, int, bool]],
|
|
58
|
+
output_storage: List[str],
|
|
59
|
+
days: int = 0,
|
|
60
|
+
hours: int = 0,
|
|
61
|
+
minutes: int = 0,
|
|
62
|
+
seconds: int = 0,
|
|
63
|
+
cpus_per_task: int = 1,
|
|
64
|
+
gpus: int = 0,
|
|
65
|
+
memory: int = 0,
|
|
66
|
+
module_profile: Union[str, None] = None,
|
|
67
|
+
python_home: Union[Path, None] = None,
|
|
68
|
+
set_flag: str = "x",
|
|
69
|
+
job_name: str = "",
|
|
70
|
+
partition: str = "",
|
|
71
|
+
interactive: bool = False,
|
|
72
|
+
working_dir: Union[Path, None] = None,
|
|
73
|
+
bashinit: List[str] = [],
|
|
74
|
+
):
|
|
75
|
+
self.job_name = job_name
|
|
76
|
+
self.partition = partition
|
|
77
|
+
self.gpus = gpus
|
|
78
|
+
self.cpus_per_task = cpus_per_task
|
|
79
|
+
self.days = days
|
|
80
|
+
self.hours = hours
|
|
81
|
+
self.minutes = minutes
|
|
82
|
+
self.seconds = seconds
|
|
83
|
+
|
|
84
|
+
self.memory = memory
|
|
85
|
+
self.notify_email = notify_email
|
|
86
|
+
self.set_flag = set_flag
|
|
87
|
+
self.interactive = interactive
|
|
88
|
+
self.aliases = aliases
|
|
89
|
+
self.paths = paths
|
|
90
|
+
self.account = account
|
|
91
|
+
self.python_home = python_home
|
|
92
|
+
self.modules = modules
|
|
93
|
+
self.content = content
|
|
94
|
+
self.licenses = licenses
|
|
95
|
+
self.working_dir = working_dir
|
|
96
|
+
|
|
97
|
+
self.module_profile = module_profile
|
|
98
|
+
self.env_vars = env_vars
|
|
99
|
+
self.sbatch_args = sbatch_args
|
|
100
|
+
self.output_storage = output_storage
|
|
101
|
+
|
|
102
|
+
self.bashinit = bashinit
|
|
103
|
+
|
|
104
|
+
def prepend(self, content: List[str]):
|
|
105
|
+
"""Append content to the job script."""
|
|
106
|
+
self.content = [*content, *self.content]
|
|
107
|
+
|
|
108
|
+
def append(self, content: List[str]):
|
|
109
|
+
"""Append content to the job script."""
|
|
110
|
+
self.content = [*self.content, *content]
|
|
111
|
+
|
|
112
|
+
def write(
|
|
113
|
+
self,
|
|
114
|
+
job_name: str,
|
|
115
|
+
script_path: Path,
|
|
116
|
+
log_file: Union[Path, None] = None,
|
|
117
|
+
delete_log_on_completion: bool = False,
|
|
118
|
+
delete_script_on_completion: bool = False,
|
|
119
|
+
):
|
|
120
|
+
job_name = "" if (self.job_name is None) else self.job_name
|
|
121
|
+
python_env = str(self.python_home / "bin" /
|
|
122
|
+
"activate") if self.python_home else ""
|
|
123
|
+
log_file = log_file if log_file is not None else script_path.with_suffix(
|
|
124
|
+
'.log')
|
|
125
|
+
set_flags = set(list(self.set_flag))
|
|
126
|
+
|
|
127
|
+
if (delete_log_on_completion):
|
|
128
|
+
self.append([f"rm -f {log_file}"])
|
|
129
|
+
set_flags.add('e')
|
|
130
|
+
if (delete_script_on_completion):
|
|
131
|
+
self.append([f"rm -f {script_path}"])
|
|
132
|
+
set_flags.add('e')
|
|
133
|
+
|
|
134
|
+
preset_content = []
|
|
135
|
+
if (self.working_dir is not None):
|
|
136
|
+
preset_content.append(f"cd {self.working_dir}")
|
|
137
|
+
|
|
138
|
+
make_shell_script(
|
|
139
|
+
account=self.account,
|
|
140
|
+
script_path=script_path,
|
|
141
|
+
jobname=job_name,
|
|
142
|
+
gpus=self.gpus,
|
|
143
|
+
cores=self.cpus_per_task,
|
|
144
|
+
hours=self.hours + self.days * 24,
|
|
145
|
+
minutes=self.minutes,
|
|
146
|
+
seconds=self.seconds,
|
|
147
|
+
memory=self.memory,
|
|
148
|
+
notifies=self.notify_email,
|
|
149
|
+
sbatch_log=log_file,
|
|
150
|
+
set_flag=''.join(set_flags),
|
|
151
|
+
interactive=self.interactive,
|
|
152
|
+
aliases=self.aliases,
|
|
153
|
+
paths=self.paths,
|
|
154
|
+
python_env=python_env,
|
|
155
|
+
modules=self.modules,
|
|
156
|
+
content=self.content,
|
|
157
|
+
license=self.licenses,
|
|
158
|
+
pre_set_content=preset_content,
|
|
159
|
+
module_profie=self.module_profile,
|
|
160
|
+
env_vars=self.env_vars,
|
|
161
|
+
sbatch_args=self.sbatch_args,
|
|
162
|
+
bashinit=self.bashinit,
|
|
163
|
+
)
|
|
File without changes
|