runsim 0.1.0__py3-none-any.whl

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.
runsim/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """runsim -- Unified interface for running reservoir simulators."""
2
+
3
+ from runsim.base import Simulator
4
+ from runsim.dumux import DuMux
5
+ from runsim.opm import OPMFlow
6
+
7
+ __version__ = "0.1.0"
8
+ __all__ = ["Simulator", "OPMFlow", "DuMux"]
runsim/base.py ADDED
@@ -0,0 +1,76 @@
1
+ """Abstract base class for reservoir simulators."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+ import subprocess
7
+ from abc import ABC, abstractmethod
8
+ from pathlib import Path
9
+
10
+
11
+ class Simulator(ABC):
12
+ """Base class for all simulator wrappers.
13
+
14
+ Subclasses must implement `_find_executable` and `run`.
15
+ """
16
+
17
+ def __init__(self, name: str, executable: str | None = None):
18
+ self.name = name
19
+ if executable is not None:
20
+ self.executable = Path(executable)
21
+ else:
22
+ self.executable = self._find_executable()
23
+
24
+ @abstractmethod
25
+ def _find_executable(self) -> Path:
26
+ """Locate the simulator binary on this system."""
27
+ ...
28
+
29
+ @abstractmethod
30
+ def run(
31
+ self,
32
+ deck_file: str | Path,
33
+ output_dir: str | Path,
34
+ **kwargs,
35
+ ) -> subprocess.CompletedProcess:
36
+ """Run a simulation and return the completed process."""
37
+ ...
38
+
39
+ def version(self) -> str:
40
+ """Return the simulator version string."""
41
+ if not self.is_available():
42
+ return "not installed"
43
+ try:
44
+ result = subprocess.run(
45
+ [str(self.executable), "--version"],
46
+ capture_output=True,
47
+ text=True,
48
+ timeout=30,
49
+ )
50
+ output = result.stdout.strip() or result.stderr.strip()
51
+ return output.split("\n")[0] if output else "unknown"
52
+ except (subprocess.TimeoutExpired, OSError):
53
+ return "unknown"
54
+
55
+ def is_available(self) -> bool:
56
+ """Check if the simulator binary exists and is executable."""
57
+ return self.executable is not None and self.executable.is_file()
58
+
59
+ def __repr__(self) -> str:
60
+ return f"{type(self).__name__}(executable={self.executable!r})"
61
+
62
+
63
+ def find_in_paths(*candidates: str | Path) -> Path | None:
64
+ """Return the first existing executable from a list of candidate paths."""
65
+ for p in candidates:
66
+ path = Path(p)
67
+ if path.is_file():
68
+ return path
69
+ # Fall back to PATH lookup
70
+ return None
71
+
72
+
73
+ def find_on_path(name: str) -> Path | None:
74
+ """Find an executable on the system PATH."""
75
+ found = shutil.which(name)
76
+ return Path(found) if found else None
runsim/dumux.py ADDED
@@ -0,0 +1,71 @@
1
+ """DuMux porous media simulator wrapper (stub)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+ from runsim.base import Simulator
9
+
10
+
11
+ class DuMux(Simulator):
12
+ """Run DuMux simulations.
13
+
14
+ This is a stub -- DuMux executables are problem-specific (each example
15
+ compiles to its own binary). Full integration will be added when
16
+ standard DuMux workflows are established.
17
+
18
+ Usage:
19
+ sim = DuMux(executable="/path/to/dumux_example")
20
+ result = sim.run(input_file="params.input", output_dir="./output")
21
+ """
22
+
23
+ def __init__(self, executable: str | None = None):
24
+ super().__init__(name="dumux", executable=executable)
25
+
26
+ def _find_executable(self) -> Path:
27
+ """Search for a DuMux binary in common locations."""
28
+ pkg_dir = Path(__file__).resolve().parent # runsim/ (package)
29
+ project_root = pkg_dir.parent.parent # opm/
30
+ install_dir = project_root / "dumux-build" / "dumux-install"
31
+
32
+ # DuMux doesn't have a single binary -- check install dir exists
33
+ if install_dir.is_dir():
34
+ # Return the install bin directory as a marker
35
+ bin_dir = install_dir / "bin"
36
+ if bin_dir.is_dir():
37
+ # Return first executable found, if any
38
+ for f in sorted(bin_dir.iterdir()):
39
+ if f.is_file() and f.stat().st_mode & 0o111:
40
+ return f
41
+ return Path("dumux") # placeholder
42
+
43
+ def run(
44
+ self,
45
+ deck_file: str | Path = "",
46
+ output_dir: str | Path = ".",
47
+ **kwargs,
48
+ ) -> subprocess.CompletedProcess:
49
+ """Run a DuMux simulation.
50
+
51
+ Args:
52
+ deck_file: Path to input file (DuMux uses .input parameter files).
53
+ output_dir: Directory for simulation output.
54
+ **kwargs: Additional parameters.
55
+
56
+ Returns:
57
+ subprocess.CompletedProcess with stdout/stderr.
58
+ """
59
+ output_dir = Path(output_dir).resolve()
60
+ output_dir.mkdir(parents=True, exist_ok=True)
61
+
62
+ cmd = [str(self.executable)]
63
+
64
+ if deck_file:
65
+ cmd.append(str(Path(deck_file).resolve()))
66
+
67
+ for key, value in kwargs.items():
68
+ cmd.append(f"-{key}")
69
+ cmd.append(str(value))
70
+
71
+ return subprocess.run(cmd, capture_output=True, text=True, cwd=output_dir)
runsim/opm.py ADDED
@@ -0,0 +1,93 @@
1
+ """OPM Flow reservoir simulator wrapper."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+ from runsim.base import Simulator, find_in_paths, find_on_path
9
+
10
+
11
+ class OPMFlow(Simulator):
12
+ """Run OPM Flow simulations (native or Docker).
13
+
14
+ Usage:
15
+ sim = OPMFlow() # auto-detect executable
16
+ sim = OPMFlow(mode="docker") # use Docker wrapper
17
+ sim = OPMFlow(executable="/path/to/flow") # explicit path
18
+
19
+ result = sim.run("SPE1CASE1.DATA", output_dir="./output")
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ mode: str = "native",
25
+ executable: str | None = None,
26
+ ):
27
+ self.mode = mode # "native" or "docker"
28
+ super().__init__(name="opm-flow", executable=executable)
29
+
30
+ def _find_executable(self) -> Path:
31
+ """Search for the flow binary in common locations."""
32
+ # Determine project paths relative to this file
33
+ pkg_dir = Path(__file__).resolve().parent # runsim/ (package)
34
+ project_dir = pkg_dir.parent # runsim/ (project root)
35
+ project_root = project_dir.parent # opm/
36
+ venv_dir = project_dir / ".venv"
37
+
38
+ if self.mode == "docker":
39
+ candidates = [
40
+ venv_dir / "bin" / "flow-docker",
41
+ ]
42
+ else:
43
+ candidates = [
44
+ venv_dir / "bin" / "flow",
45
+ project_root / "opm-build" / "opm-install" / "bin" / "flow",
46
+ ]
47
+
48
+ found = find_in_paths(*candidates)
49
+ if found is not None:
50
+ return found
51
+
52
+ # Try system PATH
53
+ binary_name = "flow-docker" if self.mode == "docker" else "flow"
54
+ on_path = find_on_path(binary_name)
55
+ if on_path is not None:
56
+ return on_path
57
+
58
+ # Return the most likely path even if it doesn't exist yet
59
+ return candidates[0] if candidates else Path("flow")
60
+
61
+ def run(
62
+ self,
63
+ deck_file: str | Path,
64
+ output_dir: str | Path,
65
+ **kwargs,
66
+ ) -> subprocess.CompletedProcess:
67
+ """Run an OPM Flow simulation.
68
+
69
+ Args:
70
+ deck_file: Path to the .DATA deck file.
71
+ output_dir: Directory for simulation output.
72
+ **kwargs: Additional flags passed as --key=value.
73
+
74
+ Returns:
75
+ subprocess.CompletedProcess with stdout/stderr.
76
+ """
77
+ deck_file = Path(deck_file).resolve()
78
+ output_dir = Path(output_dir).resolve()
79
+ output_dir.mkdir(parents=True, exist_ok=True)
80
+
81
+ cmd = [
82
+ str(self.executable),
83
+ f"--output-dir={output_dir}",
84
+ ]
85
+
86
+ # Add extra keyword arguments as --key=value flags
87
+ for key, value in kwargs.items():
88
+ flag = key.replace("_", "-")
89
+ cmd.append(f"--{flag}={value}")
90
+
91
+ cmd.append(str(deck_file))
92
+
93
+ return subprocess.run(cmd, capture_output=True, text=True)
@@ -0,0 +1,259 @@
1
+ Metadata-Version: 2.4
2
+ Name: runsim
3
+ Version: 0.1.0
4
+ Summary: Unified interface for running reservoir simulators
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: numpy
8
+ Description-Content-Type: text/markdown
9
+
10
+ # runsim
11
+
12
+ > **Note:** This package is under active development. APIs and CLI interfaces may change.
13
+
14
+ Unified runner for reservoir simulators, data assimilation, and optimization on macOS.
15
+
16
+ ## Structure
17
+
18
+ ```
19
+ runsim/
20
+ ├── runsim.sh # Unified CLI entry point
21
+ ├── pyproject.toml # Python package config (uv + hatchling)
22
+ ├── runsim/ # Python package (flat layout)
23
+ │ ├── __init__.py
24
+ │ ├── base.py # Abstract Simulator class
25
+ │ ├── opm.py # OPM Flow wrapper
26
+ │ └── dumux.py # DuMux wrapper (stub)
27
+ ├── simulators/
28
+ │ ├── common.sh # Shared helpers + path resolution
29
+ │ ├── setup_env.sh # Create venv, install packages, Docker setup
30
+ │ ├── opm/ # OPM Flow simulator
31
+ │ │ ├── README.md
32
+ │ │ ├── install.sh # Build from source (macOS)
33
+ │ │ ├── install-docker.sh # Install via Docker
34
+ │ │ ├── run.sh # Run bundled test suite (native)
35
+ │ │ ├── run-docker.sh # Run bundled test suite (Docker)
36
+ │ │ └── Dockerfile
37
+ │ ├── pet/ # Python Ensemble Toolbox
38
+ │ │ ├── README.md
39
+ │ │ ├── install.sh # Download repos + pip install
40
+ │ │ └── run.sh # Run PET examples (DA/optimization)
41
+ │ ├── jutuldarcy/ # JutulDarcy simulator
42
+ │ │ ├── README.md
43
+ │ │ ├── install.sh # Install Python/Julia mode
44
+ │ │ └── run.sh # Run simulations
45
+ │ └── dumux/ # DuMux simulator
46
+ │ ├── README.md
47
+ │ ├── install.sh # Build from source (macOS)
48
+ │ ├── run.sh # Run simulations
49
+ │ └── Dockerfile
50
+ └── .venv/ # Python virtual environment (auto-created)
51
+ ```
52
+
53
+ ## Quick Start
54
+
55
+ ```bash
56
+ # 1. Set up environment (venv + packages + OPM Docker)
57
+ ./runsim.sh setup
58
+
59
+ # 2. List available PET examples
60
+ ./runsim.sh pet --list
61
+
62
+ # 3. Run an example
63
+ ./runsim.sh pet Quadratic # Analytic (no Docker needed)
64
+ ./runsim.sh pet 3dBox/tiny # OPM Flow (~2 min)
65
+ ```
66
+
67
+ ## CLI
68
+
69
+ ```
70
+ ./runsim.sh <command> [args...]
71
+
72
+ Commands:
73
+ flow [flow-args...] Run native OPM Flow (passthrough to binary)
74
+ flow-docker [flow-args...] Run OPM Flow via Docker (passthrough)
75
+ jutuldarcy [args...] Run JutulDarcy simulation
76
+ dumux [args...] Run DuMux simulation
77
+ dumux-docker [args...] Run DuMux via Docker
78
+ pet [options] <example> Run PET example (data assimilation / optimization)
79
+ setup Bootstrap environment (venv, PET, Docker)
80
+ help Show help
81
+ ```
82
+
83
+ ### Running Simulators
84
+
85
+ ```bash
86
+ # OPM Flow (native) -- passthrough to flow binary
87
+ ./runsim.sh flow --version
88
+ ./runsim.sh flow --output-dir=./out SPE1CASE1.DATA
89
+
90
+ # OPM Flow (Docker)
91
+ ./runsim.sh flow-docker --version
92
+ ./runsim.sh flow-docker --output-dir=./out SPE1CASE1.DATA
93
+
94
+ # JutulDarcy
95
+ ./runsim.sh jutuldarcy --test
96
+ ./runsim.sh jutuldarcy /path/to/DECK.DATA
97
+
98
+ # DuMux
99
+ ./runsim.sh dumux /path/to/executable
100
+ ```
101
+
102
+ ### Running PET Examples
103
+
104
+ ```bash
105
+ ./runsim.sh pet --list # List available examples
106
+ ./runsim.sh pet LinearModel # Analytic DA
107
+ ./runsim.sh pet 3dBox/tiny # 10x10x2 grid (~2 min)
108
+ ./runsim.sh pet --simulator flow 3dBox/tiny # Native OPM Flow (default)
109
+ ./runsim.sh pet --simulator flow-docker 3dBox/tiny # Docker
110
+ ./runsim.sh pet 3SpotRobust --variant bhp # Specific variant
111
+ ./runsim.sh pet --clean 3dBox/tiny # Clean outputs then run
112
+ ```
113
+
114
+ ### Bundled Test Suites
115
+
116
+ Per-simulator test suites are available via direct script access:
117
+
118
+ ```bash
119
+ ./simulators/opm/run.sh # Native OPM, all cases
120
+ ./simulators/opm/run-docker.sh # Docker, all cases
121
+ ./simulators/jutuldarcy/run.sh --test # JutulDarcy SPE1
122
+ ```
123
+
124
+ ## Simulators
125
+
126
+ ### OPM Flow (Docker) -- Recommended for quick start
127
+
128
+ Runs via Docker on any platform. Simplest setup.
129
+
130
+ ```bash
131
+ ./simulators/opm/install-docker.sh # Pull image + create flow wrapper
132
+ ./simulators/opm/install-docker.sh --test # + run SPE1 validation
133
+ ```
134
+
135
+ > Apple Silicon note: The Docker image is `linux/amd64` and runs under Rosetta/QEMU emulation.
136
+
137
+ ### OPM Flow (Native macOS)
138
+
139
+ Full ARM-native performance. Builds Dune 2.10 + OPM from source.
140
+
141
+ ```bash
142
+ ./simulators/opm/install.sh # Full build (~30-60 min, skips if installed)
143
+ ./simulators/opm/install.sh --deps-only # Homebrew + Dune only
144
+ ./simulators/opm/install.sh --test # Build + SPE1 test
145
+ ./simulators/opm/install.sh --rebuild # Force full rebuild
146
+ ```
147
+
148
+ Requires: Xcode CLI tools, Homebrew, cmake, boost, suite-sparse, cjson, fmt.
149
+
150
+ ### JutulDarcy
151
+
152
+ Differentiable reservoir simulator (Julia). Two modes available:
153
+
154
+ ```bash
155
+ ./simulators/jutuldarcy/install.sh # Python mode (pip install jutuldarcy)
156
+ ./simulators/jutuldarcy/install.sh --julia # Julia mode (JutulDarcy.jl + juliacall)
157
+ ./simulators/jutuldarcy/install.sh --both # Both modes
158
+ ./simulators/jutuldarcy/install.sh --test # Install + verify
159
+ ```
160
+
161
+ ### DuMux
162
+
163
+ DUNE-based porous media simulator. Builds from source.
164
+
165
+ ```bash
166
+ ./simulators/dumux/install.sh # Full build (shares Dune with OPM if available)
167
+ ./simulators/dumux/install.sh --deps-only # Homebrew + Dune only
168
+ ./simulators/dumux/install.sh --rebuild # Force full rebuild
169
+ ```
170
+
171
+ ### PET (Python Ensemble Toolbox)
172
+
173
+ Download PET repositories and install Python packages:
174
+
175
+ ```bash
176
+ ./simulators/pet/install.sh # Download repos + pip install PET/SimulatorWrap
177
+ ./simulators/pet/install.sh --status # Show which repos/packages are present
178
+ ```
179
+
180
+ ### Verifying Installations
181
+
182
+ ```bash
183
+ ./runsim.sh flow --version # e.g. "flow 2026.04-pre"
184
+ ./runsim.sh flow-docker --version # e.g. "flow 2025.10"
185
+
186
+ # JutulDarcy (Python mode)
187
+ .venv/bin/python -c "import jutuldarcy; print(jutuldarcy.__version__)"
188
+
189
+ # runsim Python package
190
+ .venv/bin/python -c "from runsim import OPMFlow; print(OPMFlow().version())"
191
+ ```
192
+
193
+ ## Python Package
194
+
195
+ The `runsim` package provides a programmatic interface to simulators:
196
+
197
+ ```python
198
+ from runsim import OPMFlow
199
+
200
+ sim = OPMFlow() # auto-detect native binary
201
+ sim = OPMFlow(mode="docker") # use Docker wrapper
202
+
203
+ # Check availability
204
+ print(sim.version()) # "flow 2026.04-pre"
205
+ print(sim.is_available()) # True
206
+
207
+ # Run a simulation
208
+ result = sim.run("SPE1CASE1.DATA", output_dir="./output")
209
+ print(result.returncode) # 0 on success
210
+ ```
211
+
212
+ ## PET Examples
213
+
214
+ ```
215
+ EXAMPLE TYPE SIMULATOR NOTES
216
+ ─────── ──── ───────── ─────
217
+ LinearModel DA none
218
+ 3dBox/tiny DA flow needs data generation
219
+ 3dBox/small DA flow needs data generation
220
+ 3dBox/medium DA flow needs data generation
221
+ 3dBox/large DA flow needs data generation
222
+ 3dBox/flowrock DA flow needs data generation
223
+ Spe11b DA flow needs data generation
224
+ 3Spot Opt flow
225
+ 3SpotRobust Opt flow variants: bhp, rate
226
+ 3SpotEcalc Opt flow
227
+ 5SpotInverted Opt flow
228
+ 5SpotLineSearch Opt flow
229
+ Rosenbrock Opt none
230
+ Quadratic Opt none
231
+ ```
232
+
233
+ Type: **DA** = Data Assimilation (ES-MDA), **Opt** = Optimization
234
+
235
+ ## Dependencies
236
+
237
+ - **Python 3.11+**
238
+ - **uv** (recommended) or pip
239
+ - **Docker Desktop** (for OPM Flow Docker mode)
240
+ - [PET](https://github.com/Python-Ensemble-Toolbox) -- data assimilation framework
241
+ - [SimulatorWrap](https://github.com/Python-Ensemble-Toolbox) -- simulator wrappers
242
+ - [OPM Flow](https://opm-project.org/) -- reservoir simulator
243
+ - [JutulDarcy.jl](https://github.com/sintefmath/JutulDarcy.jl) -- differentiable simulator (optional)
244
+ - [DuMux](https://dumux.org/) -- porous media simulator (optional)
245
+
246
+ ## Parent Repository Layout
247
+
248
+ ```
249
+ project/
250
+ ├── runsim/ # This project
251
+ ├── pet-repos/
252
+ │ ├── PET-main/ # Python Ensemble Toolbox source
253
+ │ ├── SimulatorWrap-main/ # Simulator wrapper source
254
+ │ └── Examples-main/ # PET example cases
255
+ ├── opm-repos/
256
+ │ └── opm-data-master/ # Standard OPM test data (SPE1-9, Norne)
257
+ ├── opm-build/ # Native OPM build artifacts (if built)
258
+ └── dumux-build/ # Native DuMux build artifacts (if built)
259
+ ```
@@ -0,0 +1,8 @@
1
+ runsim/__init__.py,sha256=MjHmKmESySDirrqpQSjs0CVN5IG1GmedPlIEqfSGFlM,232
2
+ runsim/base.py,sha256=bI3a_GXPpxKxWiysTggjev-TGMEfC45X2MZsrV07u3g,2254
3
+ runsim/dumux.py,sha256=W1-CsERb51FXFm9xZ1HCjSO2ApSHtcbLa35L1euXA5U,2362
4
+ runsim/opm.py,sha256=Diyc9pTb7beVDeeGHt-Md6aWfphkP6kx1Q6QWFZholw,2907
5
+ runsim-0.1.0.dist-info/METADATA,sha256=OTnHvDE6C_DYM96sa8ZXdSpCF6Q6zqc5XKjOpmJgkiQ,9119
6
+ runsim-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
7
+ runsim-0.1.0.dist-info/licenses/LICENSE,sha256=aGB5s5F-X6HF1RzssFMwzlq3gFgxTIx8xX8OIvrfaV8,1072
8
+ runsim-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Roman Manasipov
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.