flowboost 0.2.2__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.
- flowboost-0.2.2/PKG-INFO +122 -0
- flowboost-0.2.2/README.md +91 -0
- flowboost-0.2.2/flowboost/__init__.py +0 -0
- flowboost-0.2.2/flowboost/config/config.py +72 -0
- flowboost-0.2.2/flowboost/config/optifoam_config.toml +32 -0
- flowboost-0.2.2/flowboost/manager/Allrun_wrapper.sh +0 -0
- flowboost-0.2.2/flowboost/manager/interfaces/local.py +66 -0
- flowboost-0.2.2/flowboost/manager/interfaces/sge.py +79 -0
- flowboost-0.2.2/flowboost/manager/interfaces/slurm.py +84 -0
- flowboost-0.2.2/flowboost/manager/manager.py +441 -0
- flowboost-0.2.2/flowboost/manager/readme.md +14 -0
- flowboost-0.2.2/flowboost/openfoam/case.py +701 -0
- flowboost-0.2.2/flowboost/openfoam/data.py +322 -0
- flowboost-0.2.2/flowboost/openfoam/dictionary.py +651 -0
- flowboost-0.2.2/flowboost/openfoam/fields.py +11 -0
- flowboost-0.2.2/flowboost/openfoam/interface.py +83 -0
- flowboost-0.2.2/flowboost/openfoam/readme.md +68 -0
- flowboost-0.2.2/flowboost/openfoam/types.py +234 -0
- flowboost-0.2.2/flowboost/optimizer/acquisition_offload.py +85 -0
- flowboost-0.2.2/flowboost/optimizer/acquisition_offload.sh +15 -0
- flowboost-0.2.2/flowboost/optimizer/backend.py +319 -0
- flowboost-0.2.2/flowboost/optimizer/interfaces/Ax.py +524 -0
- flowboost-0.2.2/flowboost/optimizer/interfaces/readme.md +4 -0
- flowboost-0.2.2/flowboost/optimizer/objectives.py +420 -0
- flowboost-0.2.2/flowboost/optimizer/search_space.py +148 -0
- flowboost-0.2.2/flowboost/session/session.py +972 -0
- flowboost-0.2.2/flowboost/utilities/time.py +40 -0
- flowboost-0.2.2/pyproject.toml +67 -0
flowboost-0.2.2/PKG-INFO
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flowboost
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: Multi-objective Bayesian optimization library for OpenFOAM
|
|
5
|
+
Keywords: OpenFOAM,CFD,Bayesian optimization,Multi-objective optimization,HPC,Cluster computing
|
|
6
|
+
Author: Daniel Virokannas
|
|
7
|
+
Author-email: Daniel Virokannas <46869890+499602D2@users.noreply.github.com>
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
16
|
+
Requires-Dist: ax-platform>=1.0
|
|
17
|
+
Requires-Dist: coloredlogs>=15
|
|
18
|
+
Requires-Dist: pandas>=2.2
|
|
19
|
+
Requires-Dist: polars>=1.11
|
|
20
|
+
Requires-Dist: psutil>=5.9
|
|
21
|
+
Requires-Dist: pyarrow>=15
|
|
22
|
+
Requires-Dist: scikit-learn>=1.4
|
|
23
|
+
Requires-Dist: tomlkit>=0.12
|
|
24
|
+
Requires-Dist: polars-lts-cpu>=0.20 ; extra == 'lts-cpu'
|
|
25
|
+
Maintainer: Daniel Virokannas, Bulut Tekgul
|
|
26
|
+
Maintainer-email: Daniel Virokannas <46869890+499602D2@users.noreply.github.com>, Bulut Tekgul <bulut.tekgul@wartsila.com>
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Project-URL: Repository, https://github.com/499602D2/flowboost
|
|
29
|
+
Provides-Extra: lts-cpu
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# 🏄♂️ FlowBoost — Multi-objective Bayesian optimization for OpenFOAM
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
FlowBoost is a highly configurable and extensible library for handling and optimizing OpenFOAM CFD simulations. It provides ready bindings for state-of-the-art Bayesian optimization using Meta's Ax, powered by PyTorch, and simple interfaces for using any other optimization library.
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
- Easy API syntax (see `examples/`)
|
|
40
|
+
- Ready bindings for [Meta's Ax (Adaptive Experimentation Platform)](https://ax.dev/)
|
|
41
|
+
- Multi-objective, high-dimensional Bayesian optimization
|
|
42
|
+
- SAASBO, GPU acceleration
|
|
43
|
+
- Fully hands-off cluster-native job management
|
|
44
|
+
- Simple interfaces for OpenFOAM cases (`flowboost.Case`)
|
|
45
|
+
- Use any optimization backend by implementing a few interfaces
|
|
46
|
+
|
|
47
|
+
## Examples
|
|
48
|
+
The `examples/` directory contains code examples for simplified real-world scenarios:
|
|
49
|
+
|
|
50
|
+
1. `aerofoilNACA0012Steady`: parameter optimization for a NACA 0012 aerofoil steady-state simulation
|
|
51
|
+
|
|
52
|
+
By default, FlowBoost uses Ax's [Service API](https://ax.dev/) as its optimization backend. In practice, any optimizer can be used, as long as it conforms to the abstract `flowboost.optimizer.Backend` base class, which the backend interfaces in `flowboost.optimizer.interfaces` implement.
|
|
53
|
+
|
|
54
|
+
## OpenFOAM case abstraction
|
|
55
|
+
Working with OpenFOAM cases is performed through the `flowboost.Case` abstraction, which provides a high-level API for OpenFOAM case-data and configuration access. The `Case` abstraction can be used as-is outside of optimization workflows:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from flowboost import Case
|
|
59
|
+
|
|
60
|
+
# Clone tutorial to current working directory (or a specified dir)
|
|
61
|
+
tutorial_case = Case.from_tutorial("fluid/aerofoilNACA0012Steady")
|
|
62
|
+
|
|
63
|
+
# Dictionary read/write access
|
|
64
|
+
control_dict = tutorial_case.dictionary("system/controlDict")
|
|
65
|
+
control_dict.entry("writeInterval").set("5000")
|
|
66
|
+
|
|
67
|
+
# Access data in an evaluated case
|
|
68
|
+
case = Case("my/case/path")
|
|
69
|
+
df = case.data.simple_function_object_reader("forceCoeffsCompressible")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Installation
|
|
73
|
+
FlowBoost requires Python 3.10 or later.
|
|
74
|
+
|
|
75
|
+
### uv (recommended)
|
|
76
|
+
```shell
|
|
77
|
+
uv add flowboost
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### pip
|
|
81
|
+
```shell
|
|
82
|
+
pip install flowboost
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### CPU compatibility
|
|
86
|
+
In order to use the standard `polars` package, your CPU should support AVX2 instructions ([and other SIMD instructions](https://github.com/pola-rs/polars/blob/78dc62851a13b87dc751a627e1e96ba1bf1549ee/py-polars/polars/_cpu_check.py)). These are typically available in Intel Broadwell/4000-series and later, and all AMD Zen-based CPUs.
|
|
87
|
+
|
|
88
|
+
If your CPU is from 2012 or earlier, you will most likely receive an illegal instruction error. This can be solved by installing the `lts-cpu` extra:
|
|
89
|
+
|
|
90
|
+
```shell
|
|
91
|
+
uv add flowboost[lts-cpu]
|
|
92
|
+
# or: pip install flowboost[lts-cpu]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
This installs `polars-lts-cpu`, which is functionally identical but not as performant.
|
|
96
|
+
|
|
97
|
+
## Development
|
|
98
|
+
|
|
99
|
+
The project is packaged using [uv](https://docs.astral.sh/uv/). The code is linted and formatted using [Ruff](https://docs.astral.sh/ruff/).
|
|
100
|
+
|
|
101
|
+
```shell
|
|
102
|
+
uv sync
|
|
103
|
+
uv run pytest
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### GPU acceleration
|
|
107
|
+
If your environment has a CUDA-compatible NVIDIA GPU, you should verify you have a recent CUDA Toolkit release. Otherwise, GPU acceleration for PyTorch will not be available. This is especially critical if you are using SAASBO for high-dimensional optimization tasks (≥20 dimensions).
|
|
108
|
+
|
|
109
|
+
```shell
|
|
110
|
+
nvcc -V
|
|
111
|
+
|
|
112
|
+
# Verify CUDA availability
|
|
113
|
+
python3 -c "import torch; print(torch.cuda.is_available())"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Testing
|
|
117
|
+
Passing the full test suite requires OpenFOAM to be installed and sourced. FlowBoost has only been tested using the [openfoam.org](https://openfoam.org/) lineage (not the ESI/openfoam.com fork), specifically version 11.
|
|
118
|
+
|
|
119
|
+
If you wish to contribute code to the project, please ensure your contribution still passes the current test coverage.
|
|
120
|
+
|
|
121
|
+
## Acknowledgments
|
|
122
|
+
The base functionality for FlowBoost was created as part of a mechanical engineering master's thesis at Aalto University, funded by Wärtsilä. Wärtsilä designs and manufactures marine combustion engines and energy solutions in Vaasa, Finland.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# 🏄♂️ FlowBoost — Multi-objective Bayesian optimization for OpenFOAM
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
FlowBoost is a highly configurable and extensible library for handling and optimizing OpenFOAM CFD simulations. It provides ready bindings for state-of-the-art Bayesian optimization using Meta's Ax, powered by PyTorch, and simple interfaces for using any other optimization library.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
- Easy API syntax (see `examples/`)
|
|
9
|
+
- Ready bindings for [Meta's Ax (Adaptive Experimentation Platform)](https://ax.dev/)
|
|
10
|
+
- Multi-objective, high-dimensional Bayesian optimization
|
|
11
|
+
- SAASBO, GPU acceleration
|
|
12
|
+
- Fully hands-off cluster-native job management
|
|
13
|
+
- Simple interfaces for OpenFOAM cases (`flowboost.Case`)
|
|
14
|
+
- Use any optimization backend by implementing a few interfaces
|
|
15
|
+
|
|
16
|
+
## Examples
|
|
17
|
+
The `examples/` directory contains code examples for simplified real-world scenarios:
|
|
18
|
+
|
|
19
|
+
1. `aerofoilNACA0012Steady`: parameter optimization for a NACA 0012 aerofoil steady-state simulation
|
|
20
|
+
|
|
21
|
+
By default, FlowBoost uses Ax's [Service API](https://ax.dev/) as its optimization backend. In practice, any optimizer can be used, as long as it conforms to the abstract `flowboost.optimizer.Backend` base class, which the backend interfaces in `flowboost.optimizer.interfaces` implement.
|
|
22
|
+
|
|
23
|
+
## OpenFOAM case abstraction
|
|
24
|
+
Working with OpenFOAM cases is performed through the `flowboost.Case` abstraction, which provides a high-level API for OpenFOAM case-data and configuration access. The `Case` abstraction can be used as-is outside of optimization workflows:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from flowboost import Case
|
|
28
|
+
|
|
29
|
+
# Clone tutorial to current working directory (or a specified dir)
|
|
30
|
+
tutorial_case = Case.from_tutorial("fluid/aerofoilNACA0012Steady")
|
|
31
|
+
|
|
32
|
+
# Dictionary read/write access
|
|
33
|
+
control_dict = tutorial_case.dictionary("system/controlDict")
|
|
34
|
+
control_dict.entry("writeInterval").set("5000")
|
|
35
|
+
|
|
36
|
+
# Access data in an evaluated case
|
|
37
|
+
case = Case("my/case/path")
|
|
38
|
+
df = case.data.simple_function_object_reader("forceCoeffsCompressible")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
FlowBoost requires Python 3.10 or later.
|
|
43
|
+
|
|
44
|
+
### uv (recommended)
|
|
45
|
+
```shell
|
|
46
|
+
uv add flowboost
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### pip
|
|
50
|
+
```shell
|
|
51
|
+
pip install flowboost
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### CPU compatibility
|
|
55
|
+
In order to use the standard `polars` package, your CPU should support AVX2 instructions ([and other SIMD instructions](https://github.com/pola-rs/polars/blob/78dc62851a13b87dc751a627e1e96ba1bf1549ee/py-polars/polars/_cpu_check.py)). These are typically available in Intel Broadwell/4000-series and later, and all AMD Zen-based CPUs.
|
|
56
|
+
|
|
57
|
+
If your CPU is from 2012 or earlier, you will most likely receive an illegal instruction error. This can be solved by installing the `lts-cpu` extra:
|
|
58
|
+
|
|
59
|
+
```shell
|
|
60
|
+
uv add flowboost[lts-cpu]
|
|
61
|
+
# or: pip install flowboost[lts-cpu]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This installs `polars-lts-cpu`, which is functionally identical but not as performant.
|
|
65
|
+
|
|
66
|
+
## Development
|
|
67
|
+
|
|
68
|
+
The project is packaged using [uv](https://docs.astral.sh/uv/). The code is linted and formatted using [Ruff](https://docs.astral.sh/ruff/).
|
|
69
|
+
|
|
70
|
+
```shell
|
|
71
|
+
uv sync
|
|
72
|
+
uv run pytest
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### GPU acceleration
|
|
76
|
+
If your environment has a CUDA-compatible NVIDIA GPU, you should verify you have a recent CUDA Toolkit release. Otherwise, GPU acceleration for PyTorch will not be available. This is especially critical if you are using SAASBO for high-dimensional optimization tasks (≥20 dimensions).
|
|
77
|
+
|
|
78
|
+
```shell
|
|
79
|
+
nvcc -V
|
|
80
|
+
|
|
81
|
+
# Verify CUDA availability
|
|
82
|
+
python3 -c "import torch; print(torch.cuda.is_available())"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Testing
|
|
86
|
+
Passing the full test suite requires OpenFOAM to be installed and sourced. FlowBoost has only been tested using the [openfoam.org](https://openfoam.org/) lineage (not the ESI/openfoam.com fork), specifically version 11.
|
|
87
|
+
|
|
88
|
+
If you wish to contribute code to the project, please ensure your contribution still passes the current test coverage.
|
|
89
|
+
|
|
90
|
+
## Acknowledgments
|
|
91
|
+
The base functionality for FlowBoost was created as part of a mechanical engineering master's thesis at Aalto University, funded by Wärtsilä. Wärtsilä designs and manufactures marine combustion engines and energy solutions in Vaasa, Finland.
|
|
File without changes
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import importlib.resources as pkg_resources
|
|
2
|
+
import logging
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import tomlkit
|
|
7
|
+
|
|
8
|
+
DEFAULT_CONFIG_NAME: str = "flowboost_config.toml"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def validate(config: dict) -> bool:
|
|
12
|
+
"""Validate the TOML configuration file.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
config_path (Path): The path to the TOML configuration file.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
bool: True if the configuration is valid, False otherwise.
|
|
19
|
+
"""
|
|
20
|
+
# Check for offload_acquisition
|
|
21
|
+
offload_acquisition = config.get("scheduler", {}).get("offload_acquisition", False)
|
|
22
|
+
|
|
23
|
+
if offload_acquisition:
|
|
24
|
+
# Ensure acquisition configuration is present and valid
|
|
25
|
+
if "scheduler.acquisition" not in config:
|
|
26
|
+
logging.error(
|
|
27
|
+
"Offload acquisition is enabled, but `[scheduler.acquisition]` not configured"
|
|
28
|
+
)
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def create(dir: Path, filename: str = DEFAULT_CONFIG_NAME) -> dict:
|
|
35
|
+
with pkg_resources.path("flowboost.config", DEFAULT_CONFIG_NAME) as config_path:
|
|
36
|
+
if not config_path.exists():
|
|
37
|
+
raise FileNotFoundError(f"Configuration template not found [{config_path}]")
|
|
38
|
+
|
|
39
|
+
dest = Path(dir, filename)
|
|
40
|
+
if dest.exists():
|
|
41
|
+
raise FileExistsError("Configuration already exists, but create() called")
|
|
42
|
+
|
|
43
|
+
shutil.copy(config_path, dest)
|
|
44
|
+
with open(dest, "r") as config_file:
|
|
45
|
+
config = tomlkit.load(config_file)
|
|
46
|
+
|
|
47
|
+
if not validate(config):
|
|
48
|
+
raise ValueError("Default configuration is invalid")
|
|
49
|
+
|
|
50
|
+
return config
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def load(dir: Path, filename: str = DEFAULT_CONFIG_NAME) -> dict:
|
|
54
|
+
fpath = Path(dir, filename)
|
|
55
|
+
|
|
56
|
+
if not fpath.exists():
|
|
57
|
+
return create(dir=dir, filename=filename)
|
|
58
|
+
|
|
59
|
+
with open(fpath, "r") as toml_f:
|
|
60
|
+
config = tomlkit.load(toml_f)
|
|
61
|
+
|
|
62
|
+
if not validate(config):
|
|
63
|
+
raise ValueError("Configuration is invalid")
|
|
64
|
+
|
|
65
|
+
return config
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def save(config: dict, dir: Path, filename: str = DEFAULT_CONFIG_NAME):
|
|
69
|
+
fpath = Path(dir, filename)
|
|
70
|
+
|
|
71
|
+
with open(fpath, "w") as toml_f:
|
|
72
|
+
tomlkit.dump(config, toml_f)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[session]
|
|
2
|
+
name = ""
|
|
3
|
+
data_dir = ""
|
|
4
|
+
archival_dir = ""
|
|
5
|
+
dataframe_format = "polars"
|
|
6
|
+
created_at = ""
|
|
7
|
+
|
|
8
|
+
[template]
|
|
9
|
+
path = ""
|
|
10
|
+
additional_files = []
|
|
11
|
+
|
|
12
|
+
[optimizer]
|
|
13
|
+
type = "Ax"
|
|
14
|
+
offload_acquisition = false # Acquisition offloading in cluster env
|
|
15
|
+
|
|
16
|
+
[scheduler]
|
|
17
|
+
type = "" # Manager-implementing class name
|
|
18
|
+
job_limit = 1 # Also node reservation limit (TODO not always)
|
|
19
|
+
|
|
20
|
+
[scheduler.OpenFOAM]
|
|
21
|
+
# Can also be provided in your template case's Allrun script
|
|
22
|
+
# args = { pe = "orte 36", M = "user@example.com", m = "base" }
|
|
23
|
+
# setup = [
|
|
24
|
+
# "module load gnu openmpi",
|
|
25
|
+
# "source /nfs/prg/OpenFOAM/OpenFOAM-dev/etc/bashrc",
|
|
26
|
+
# ]
|
|
27
|
+
|
|
28
|
+
[scheduler.acquisition]
|
|
29
|
+
# This is a required configuration if you desire offloaded acquisition
|
|
30
|
+
# args = { q = "gpgpu", M = "user@example.com", m = "base" }
|
|
31
|
+
# setup = ["source /nfs/prg/anaconda3/bin/activate", "conda activate py3.10"]
|
|
32
|
+
# torch_device = ""
|
|
File without changes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import signal
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
import psutil
|
|
9
|
+
|
|
10
|
+
from flowboost.manager.manager import Manager
|
|
11
|
+
from flowboost.openfoam.interface import FOAM
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Local(Manager):
|
|
15
|
+
def __init__(self, wdir: Path | str, job_limit: int = 1) -> None:
|
|
16
|
+
super().__init__(wdir, job_limit)
|
|
17
|
+
self.shell: str = "bash" # os.getenv("SHELL", "bash")
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def _is_available() -> bool:
|
|
21
|
+
available = FOAM.in_env()
|
|
22
|
+
if not available:
|
|
23
|
+
logging.error("OpenFOAM not found in PATH")
|
|
24
|
+
|
|
25
|
+
return available
|
|
26
|
+
|
|
27
|
+
def _submit_job(
|
|
28
|
+
self,
|
|
29
|
+
job_name: str,
|
|
30
|
+
submission_cwd: Path,
|
|
31
|
+
script: Path,
|
|
32
|
+
script_args: dict[str, Any] = {},
|
|
33
|
+
) -> Optional[str]:
|
|
34
|
+
# Base command
|
|
35
|
+
cmd = [self.shell, script]
|
|
36
|
+
|
|
37
|
+
if script_args:
|
|
38
|
+
script_kv = Manager._construct_scipt_args(script_args, " ")
|
|
39
|
+
cmd.extend(script_kv)
|
|
40
|
+
|
|
41
|
+
# Execute the script and get the PID
|
|
42
|
+
process = subprocess.Popen(cmd, cwd=submission_cwd, start_new_session=True)
|
|
43
|
+
pid = process.pid
|
|
44
|
+
|
|
45
|
+
# Create and track the job
|
|
46
|
+
return str(pid)
|
|
47
|
+
|
|
48
|
+
def _cancel_job(self, job_id: str) -> bool:
|
|
49
|
+
try:
|
|
50
|
+
os.kill(int(job_id), signal.SIGTERM)
|
|
51
|
+
except OSError:
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
def _job_has_finished(self, job_id: str) -> bool:
|
|
57
|
+
try:
|
|
58
|
+
# Check if the process is still running
|
|
59
|
+
process = psutil.Process(int(job_id))
|
|
60
|
+
# If the process is running or sleeping, it's not finished
|
|
61
|
+
if process.status() in [psutil.STATUS_RUNNING, psutil.STATUS_SLEEPING]:
|
|
62
|
+
return False
|
|
63
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
return True
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from flowboost.manager.manager import Manager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SGE(Manager):
|
|
11
|
+
def __init__(self, wdir: Path | str, job_limit: int) -> None:
|
|
12
|
+
super().__init__(wdir, job_limit)
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def _is_available() -> bool:
|
|
16
|
+
if shutil.which("qsub") and shutil.which("qstat"):
|
|
17
|
+
return True
|
|
18
|
+
|
|
19
|
+
logging.error("qsub or qstat commands not found in PATH")
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
def _submit_job(
|
|
23
|
+
self,
|
|
24
|
+
job_name: str,
|
|
25
|
+
submission_cwd: Path,
|
|
26
|
+
script: Path,
|
|
27
|
+
script_args: dict[str, Any] = {},
|
|
28
|
+
) -> Optional[str]:
|
|
29
|
+
# Base command with name
|
|
30
|
+
cmd = ["qsub", "-N", job_name]
|
|
31
|
+
|
|
32
|
+
if script_args:
|
|
33
|
+
# If Allrun accepts args, pass them
|
|
34
|
+
script_kv = Manager._construct_scipt_args(script_args)
|
|
35
|
+
cmd.extend(["-v", script_kv])
|
|
36
|
+
|
|
37
|
+
cmd.append(str(script))
|
|
38
|
+
|
|
39
|
+
# Run in case working directory
|
|
40
|
+
result = subprocess.run(cmd, capture_output=True, text=True, cwd=submission_cwd)
|
|
41
|
+
|
|
42
|
+
if result.returncode != 0:
|
|
43
|
+
logging.error(f"Error submitting job: out='{result.stderr.strip()}'")
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
job_id = result.stdout.split()[2].split(".")[0]
|
|
47
|
+
return job_id
|
|
48
|
+
|
|
49
|
+
def _cancel_job(self, job_id: str) -> bool:
|
|
50
|
+
cmd = ["qdel", job_id]
|
|
51
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
52
|
+
|
|
53
|
+
if result.returncode != 0:
|
|
54
|
+
logging.error(f"Error cancelling jobs: {result.stderr.strip()}")
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
def _job_has_finished(self, job_id: str) -> bool:
|
|
60
|
+
cmd = ["qstat", "-j", job_id]
|
|
61
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
62
|
+
|
|
63
|
+
if result.returncode == 1:
|
|
64
|
+
if "Following jobs do not exist" in result.stderr:
|
|
65
|
+
# Job does not exist, meaning it has ended: OK
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
logging.warning(
|
|
69
|
+
f"Querying job finish status failed: {result.stderr.strip()}"
|
|
70
|
+
)
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
def _get_job_info(self, job_id: str) -> str:
|
|
76
|
+
"""Fetch job details from Sun Grid Engine using the given job_id."""
|
|
77
|
+
cmd = ["qstat", "-j", job_id]
|
|
78
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
79
|
+
return result.stdout
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from flowboost.manager.manager import Manager
|
|
8
|
+
|
|
9
|
+
class Slurm(Manager):
|
|
10
|
+
def __init__(self, wdir: Path | str, job_limit: int) -> None:
|
|
11
|
+
super().__init__(wdir, job_limit)
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def _is_available() -> bool:
|
|
15
|
+
if shutil.which("sbatch") and shutil.which("squeue"):
|
|
16
|
+
return True
|
|
17
|
+
|
|
18
|
+
logging.error("sbatch or squeue commands not found in PATH")
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
def _submit_job(
|
|
22
|
+
self,
|
|
23
|
+
job_name: str,
|
|
24
|
+
submission_cwd: Path,
|
|
25
|
+
script: Path,
|
|
26
|
+
script_args: dict[str, Any] = {},
|
|
27
|
+
) -> Optional[str]:
|
|
28
|
+
# Base command with name
|
|
29
|
+
cmd = ["sbatch", "--job-name", job_name]
|
|
30
|
+
|
|
31
|
+
if script_args:
|
|
32
|
+
# If Allrun accepts args, pass them
|
|
33
|
+
script_kv = Manager._construct_scipt_args(script_args)
|
|
34
|
+
cmd.extend(["-v", script_kv])
|
|
35
|
+
|
|
36
|
+
cmd.append(str(script))
|
|
37
|
+
# Run in case working directory
|
|
38
|
+
result = subprocess.run(cmd, capture_output=True, text=True, cwd=submission_cwd)
|
|
39
|
+
|
|
40
|
+
if result.returncode != 0:
|
|
41
|
+
logging.error(f"Error submitting job: out='{result.stderr.strip()}'")
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
job_id = result.stdout.split()[-1]
|
|
45
|
+
return job_id
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _cancel_job(self, job_id: str) -> bool:
|
|
49
|
+
cmd = ["scancel", job_id]
|
|
50
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
51
|
+
|
|
52
|
+
if result.returncode != 0:
|
|
53
|
+
logging.error(f"Error cancelling jobs: {result.stderr.strip()}")
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
return True
|
|
57
|
+
|
|
58
|
+
def _job_has_finished(self, job_id: str) -> bool:
|
|
59
|
+
"""Check if a Slurm job has finished."""
|
|
60
|
+
cmd = ["squeue", "-j", job_id, "-h"] # -h suppresses header
|
|
61
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
62
|
+
|
|
63
|
+
if result.returncode != 0:
|
|
64
|
+
# Job not in queue - likely finished or never existed
|
|
65
|
+
if "Invalid job id specified" in result.stderr:
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
logging.warning(
|
|
69
|
+
f"Querying job finish status failed: {result.stderr.strip()}"
|
|
70
|
+
)
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
# If squeue returns successfully with output, job is still running/pending
|
|
74
|
+
if result.stdout.strip():
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
# Empty output means job finished
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
def _get_job_info(self, job_id: str) -> str:
|
|
81
|
+
"""Fetch job details from Slurm using the given job_id."""
|
|
82
|
+
cmd = ["scontrol", "show", "job", job_id]
|
|
83
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
84
|
+
return result.stdout
|