foamlib 0.4.4__tar.gz → 0.5.1__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.
- {foamlib-0.4.4 → foamlib-0.5.1}/PKG-INFO +29 -1
- {foamlib-0.4.4 → foamlib-0.5.1}/README.md +27 -0
- {foamlib-0.4.4 → foamlib-0.5.1}/foamlib/__init__.py +1 -3
- {foamlib-0.4.4 → foamlib-0.5.1}/foamlib/_cases/__init__.py +1 -2
- foamlib-0.5.1/foamlib/_cases/_async.py +245 -0
- foamlib-0.5.1/foamlib/_cases/_base.py +213 -0
- foamlib-0.4.4/foamlib/_cases/_base.py → foamlib-0.5.1/foamlib/_cases/_recipes.py +32 -196
- foamlib-0.5.1/foamlib/_cases/_subprocess.py +86 -0
- {foamlib-0.4.4 → foamlib-0.5.1}/foamlib/_cases/_sync.py +100 -46
- foamlib-0.5.1/foamlib/_cases/_util.py +49 -0
- {foamlib-0.4.4 → foamlib-0.5.1}/foamlib/_files/_files.py +4 -8
- {foamlib-0.4.4 → foamlib-0.5.1}/foamlib/_files/_serialization.py +1 -1
- {foamlib-0.4.4 → foamlib-0.5.1}/foamlib.egg-info/PKG-INFO +29 -1
- {foamlib-0.4.4 → foamlib-0.5.1}/foamlib.egg-info/SOURCES.txt +4 -2
- {foamlib-0.4.4 → foamlib-0.5.1}/pyproject.toml +1 -0
- foamlib-0.4.4/foamlib/_cases/_async.py +0 -194
- foamlib-0.4.4/foamlib/_cases/_util.py +0 -35
- {foamlib-0.4.4 → foamlib-0.5.1}/LICENSE.txt +0 -0
- {foamlib-0.4.4 → foamlib-0.5.1}/foamlib/_files/__init__.py +0 -0
- {foamlib-0.4.4 → foamlib-0.5.1}/foamlib/_files/_base.py +0 -0
- {foamlib-0.4.4 → foamlib-0.5.1}/foamlib/_files/_io.py +0 -0
- {foamlib-0.4.4 → foamlib-0.5.1}/foamlib/_files/_parsing.py +0 -0
- {foamlib-0.4.4/foamlib → foamlib-0.5.1/foamlib/_files}/_util.py +0 -0
- {foamlib-0.4.4 → foamlib-0.5.1}/foamlib/py.typed +0 -0
- {foamlib-0.4.4 → foamlib-0.5.1}/foamlib.egg-info/dependency_links.txt +0 -0
- {foamlib-0.4.4 → foamlib-0.5.1}/foamlib.egg-info/requires.txt +0 -0
- {foamlib-0.4.4 → foamlib-0.5.1}/foamlib.egg-info/top_level.txt +0 -0
- {foamlib-0.4.4 → foamlib-0.5.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: foamlib
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.5.1
|
4
4
|
Summary: A Python interface for interacting with OpenFOAM
|
5
5
|
Author-email: "Gabriel S. Gerlero" <ggerlero@cimec.unl.edu.ar>
|
6
6
|
Project-URL: Homepage, https://github.com/gerlero/foamlib
|
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.10
|
20
20
|
Classifier: Programming Language :: Python :: 3.11
|
21
21
|
Classifier: Programming Language :: Python :: 3.12
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
22
23
|
Classifier: Topic :: Scientific/Engineering
|
23
24
|
Classifier: Topic :: Software Development
|
24
25
|
Classifier: Typing :: Typed
|
@@ -123,6 +124,14 @@ my_pitz.clean()
|
|
123
124
|
my_pitz.control_dict["writeInterval"] = 10
|
124
125
|
```
|
125
126
|
|
127
|
+
### Make multiple file reads and writes in a single go
|
128
|
+
|
129
|
+
```python
|
130
|
+
with my_pitz.fv_schemes as f:
|
131
|
+
f["gradSchemes"]["default"] = f["divSchemes"]["default"]
|
132
|
+
f["snGradSchemes"]["default"] = "uncorrected"
|
133
|
+
```
|
134
|
+
|
126
135
|
### Run a case asynchronously
|
127
136
|
|
128
137
|
```python
|
@@ -146,6 +155,25 @@ U = FoamFieldFile(Path(my_pitz) / "0/U")
|
|
146
155
|
print(U.internal_field)
|
147
156
|
```
|
148
157
|
|
158
|
+
### Run an optimization loop in parallel
|
159
|
+
|
160
|
+
```python
|
161
|
+
import os
|
162
|
+
from pathlib import Path
|
163
|
+
from foamlib import AsyncFoamCase
|
164
|
+
from scipy.optimize import differential_evolution
|
165
|
+
|
166
|
+
base = AsyncFoamCase(Path(os.environ["FOAM_TUTORIALS"]) / "incompressible/simpleFoam/pitzDaily")
|
167
|
+
|
168
|
+
async def cost(x):
|
169
|
+
async with base.clone() as clone:
|
170
|
+
clone[0]["U"].boundary_field["inlet"].value = [x[0], 0, 0]
|
171
|
+
await clone.run()
|
172
|
+
return abs(clone[-1]["U"].internal_field[0][0])
|
173
|
+
|
174
|
+
result = differential_evolution(cost, bounds=[(-1, 1)], workers=AsyncFoamCase.map, polish=False)
|
175
|
+
```
|
176
|
+
|
149
177
|
## Documentation
|
150
178
|
|
151
179
|
For more information, check out the [documentation](https://foamlib.readthedocs.io/).
|
@@ -70,6 +70,14 @@ my_pitz.clean()
|
|
70
70
|
my_pitz.control_dict["writeInterval"] = 10
|
71
71
|
```
|
72
72
|
|
73
|
+
### Make multiple file reads and writes in a single go
|
74
|
+
|
75
|
+
```python
|
76
|
+
with my_pitz.fv_schemes as f:
|
77
|
+
f["gradSchemes"]["default"] = f["divSchemes"]["default"]
|
78
|
+
f["snGradSchemes"]["default"] = "uncorrected"
|
79
|
+
```
|
80
|
+
|
73
81
|
### Run a case asynchronously
|
74
82
|
|
75
83
|
```python
|
@@ -93,6 +101,25 @@ U = FoamFieldFile(Path(my_pitz) / "0/U")
|
|
93
101
|
print(U.internal_field)
|
94
102
|
```
|
95
103
|
|
104
|
+
### Run an optimization loop in parallel
|
105
|
+
|
106
|
+
```python
|
107
|
+
import os
|
108
|
+
from pathlib import Path
|
109
|
+
from foamlib import AsyncFoamCase
|
110
|
+
from scipy.optimize import differential_evolution
|
111
|
+
|
112
|
+
base = AsyncFoamCase(Path(os.environ["FOAM_TUTORIALS"]) / "incompressible/simpleFoam/pitzDaily")
|
113
|
+
|
114
|
+
async def cost(x):
|
115
|
+
async with base.clone() as clone:
|
116
|
+
clone[0]["U"].boundary_field["inlet"].value = [x[0], 0, 0]
|
117
|
+
await clone.run()
|
118
|
+
return abs(clone[-1]["U"].internal_field[0][0])
|
119
|
+
|
120
|
+
result = differential_evolution(cost, bounds=[(-1, 1)], workers=AsyncFoamCase.map, polish=False)
|
121
|
+
```
|
122
|
+
|
96
123
|
## Documentation
|
97
124
|
|
98
125
|
For more information, check out the [documentation](https://foamlib.readthedocs.io/).
|
@@ -1,11 +1,10 @@
|
|
1
1
|
"""A Python interface for interacting with OpenFOAM."""
|
2
2
|
|
3
|
-
__version__ = "0.
|
3
|
+
__version__ = "0.5.1"
|
4
4
|
|
5
5
|
from ._cases import (
|
6
6
|
AsyncFoamCase,
|
7
7
|
CalledProcessError,
|
8
|
-
CalledProcessWarning,
|
9
8
|
FoamCase,
|
10
9
|
FoamCaseBase,
|
11
10
|
)
|
@@ -19,5 +18,4 @@ __all__ = [
|
|
19
18
|
"FoamFieldFile",
|
20
19
|
"FoamFileBase",
|
21
20
|
"CalledProcessError",
|
22
|
-
"CalledProcessWarning",
|
23
21
|
]
|
@@ -1,12 +1,11 @@
|
|
1
1
|
from ._async import AsyncFoamCase
|
2
2
|
from ._base import FoamCaseBase
|
3
|
+
from ._subprocess import CalledProcessError
|
3
4
|
from ._sync import FoamCase
|
4
|
-
from ._util import CalledProcessError, CalledProcessWarning
|
5
5
|
|
6
6
|
__all__ = [
|
7
7
|
"FoamCaseBase",
|
8
8
|
"FoamCase",
|
9
9
|
"AsyncFoamCase",
|
10
10
|
"CalledProcessError",
|
11
|
-
"CalledProcessWarning",
|
12
11
|
]
|
@@ -0,0 +1,245 @@
|
|
1
|
+
import asyncio
|
2
|
+
import multiprocessing
|
3
|
+
import os
|
4
|
+
import sys
|
5
|
+
import tempfile
|
6
|
+
from contextlib import asynccontextmanager
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import Callable, Optional, TypeVar, Union
|
9
|
+
|
10
|
+
if sys.version_info >= (3, 9):
|
11
|
+
from collections.abc import (
|
12
|
+
AsyncGenerator,
|
13
|
+
Awaitable,
|
14
|
+
Collection,
|
15
|
+
Iterable,
|
16
|
+
Sequence,
|
17
|
+
)
|
18
|
+
else:
|
19
|
+
from typing import AsyncGenerator, Awaitable, Collection, Iterable, Sequence
|
20
|
+
|
21
|
+
if sys.version_info >= (3, 11):
|
22
|
+
from typing import Self
|
23
|
+
else:
|
24
|
+
from typing_extensions import Self
|
25
|
+
|
26
|
+
import aioshutil
|
27
|
+
|
28
|
+
from ._recipes import _FoamCaseRecipes
|
29
|
+
from ._subprocess import run_async
|
30
|
+
from ._util import awaitableasynccontextmanager
|
31
|
+
|
32
|
+
X = TypeVar("X")
|
33
|
+
Y = TypeVar("Y")
|
34
|
+
|
35
|
+
|
36
|
+
class AsyncFoamCase(_FoamCaseRecipes):
|
37
|
+
"""
|
38
|
+
An OpenFOAM case with asynchronous support.
|
39
|
+
|
40
|
+
Provides methods for running and cleaning cases, as well as accessing files.
|
41
|
+
|
42
|
+
Access the time directories of the case as a sequence, e.g. `case[0]` or `case[-1]`.
|
43
|
+
|
44
|
+
:param path: The path to the case directory.
|
45
|
+
"""
|
46
|
+
|
47
|
+
max_cpus = multiprocessing.cpu_count()
|
48
|
+
"""
|
49
|
+
Maximum number of CPUs to use for running `AsyncFoamCase`s concurrently. Defaults to the number of CPUs on the system.
|
50
|
+
"""
|
51
|
+
|
52
|
+
_reserved_cpus = 0
|
53
|
+
_cpus_cond = None # Cannot be initialized here yet
|
54
|
+
|
55
|
+
@staticmethod
|
56
|
+
@asynccontextmanager
|
57
|
+
async def _cpus(cpus: int) -> AsyncGenerator[None, None]:
|
58
|
+
if AsyncFoamCase._cpus_cond is None:
|
59
|
+
AsyncFoamCase._cpus_cond = asyncio.Condition()
|
60
|
+
|
61
|
+
cpus = min(cpus, AsyncFoamCase.max_cpus)
|
62
|
+
if cpus > 0:
|
63
|
+
async with AsyncFoamCase._cpus_cond:
|
64
|
+
await AsyncFoamCase._cpus_cond.wait_for(
|
65
|
+
lambda: AsyncFoamCase.max_cpus - AsyncFoamCase._reserved_cpus
|
66
|
+
>= cpus
|
67
|
+
)
|
68
|
+
AsyncFoamCase._reserved_cpus += cpus
|
69
|
+
try:
|
70
|
+
yield
|
71
|
+
finally:
|
72
|
+
if cpus > 0:
|
73
|
+
async with AsyncFoamCase._cpus_cond:
|
74
|
+
AsyncFoamCase._reserved_cpus -= cpus
|
75
|
+
AsyncFoamCase._cpus_cond.notify(cpus)
|
76
|
+
|
77
|
+
@staticmethod
|
78
|
+
async def _rmtree(
|
79
|
+
path: Union["os.PathLike[str]", str], ignore_errors: bool = False
|
80
|
+
) -> None:
|
81
|
+
await aioshutil.rmtree(path, ignore_errors=ignore_errors) # type: ignore [call-arg]
|
82
|
+
|
83
|
+
@staticmethod
|
84
|
+
async def _copytree(
|
85
|
+
src: Union["os.PathLike[str]", str],
|
86
|
+
dest: Union["os.PathLike[str]", str],
|
87
|
+
*,
|
88
|
+
symlinks: bool = False,
|
89
|
+
ignore: Optional[
|
90
|
+
Callable[[Union["os.PathLike[str]", str], Collection[str]], Collection[str]]
|
91
|
+
] = None,
|
92
|
+
) -> None:
|
93
|
+
await aioshutil.copytree(src, dest, symlinks=symlinks, ignore=ignore)
|
94
|
+
|
95
|
+
async def clean(
|
96
|
+
self,
|
97
|
+
*,
|
98
|
+
script: bool = True,
|
99
|
+
check: bool = False,
|
100
|
+
) -> None:
|
101
|
+
"""
|
102
|
+
Clean this case.
|
103
|
+
|
104
|
+
:param script: If True, use an (All)clean script if it exists. If False, ignore any clean scripts.
|
105
|
+
:param check: If True, raise a CalledProcessError if the clean script returns a non-zero exit code.
|
106
|
+
"""
|
107
|
+
for name, args, kwargs in self._clean_cmds(script=script, check=check):
|
108
|
+
await getattr(self, name)(*args, **kwargs)
|
109
|
+
|
110
|
+
async def _run(
|
111
|
+
self,
|
112
|
+
cmd: Union[Sequence[Union[str, "os.PathLike[str]"]], str],
|
113
|
+
*,
|
114
|
+
parallel: bool = False,
|
115
|
+
cpus: int = 1,
|
116
|
+
check: bool = True,
|
117
|
+
log: bool = True,
|
118
|
+
) -> None:
|
119
|
+
with self._output(cmd, log=log) as (stdout, stderr):
|
120
|
+
if parallel:
|
121
|
+
if isinstance(cmd, str):
|
122
|
+
cmd = [
|
123
|
+
"mpiexec",
|
124
|
+
"-n",
|
125
|
+
str(cpus),
|
126
|
+
"/bin/sh",
|
127
|
+
"-c",
|
128
|
+
f"{cmd} -parallel",
|
129
|
+
]
|
130
|
+
else:
|
131
|
+
cmd = ["mpiexec", "-n", str(cpus), *cmd, "-parallel"]
|
132
|
+
|
133
|
+
async with self._cpus(cpus):
|
134
|
+
await run_async(
|
135
|
+
cmd,
|
136
|
+
check=check,
|
137
|
+
cwd=self.path,
|
138
|
+
env=self._env(shell=isinstance(cmd, str)),
|
139
|
+
stdout=stdout,
|
140
|
+
stderr=stderr,
|
141
|
+
)
|
142
|
+
|
143
|
+
async def run(
|
144
|
+
self,
|
145
|
+
cmd: Optional[Union[Sequence[Union[str, "os.PathLike[str]"]], str]] = None,
|
146
|
+
*,
|
147
|
+
script: bool = True,
|
148
|
+
parallel: Optional[bool] = None,
|
149
|
+
cpus: Optional[int] = None,
|
150
|
+
check: bool = True,
|
151
|
+
) -> None:
|
152
|
+
"""
|
153
|
+
Run this case, or a specified command in the context of this case.
|
154
|
+
|
155
|
+
:param cmd: The command to run. If None, run the case. If a sequence, the first element is the command and the rest are arguments. If a string, `cmd` is executed in a shell.
|
156
|
+
:param script: If True and `cmd` is None, use an (All)run(-parallel) script if it exists for running the case. If False or no run script is found, autodetermine the command(s) needed to run the case.
|
157
|
+
:param parallel: If True, run in parallel using MPI. If None, autodetect whether to run in parallel.
|
158
|
+
:param check: If True, raise a CalledProcessError if any command returns a non-zero exit code.
|
159
|
+
"""
|
160
|
+
for name, args, kwargs in self._run_cmds(
|
161
|
+
cmd=cmd, script=script, parallel=parallel, cpus=cpus, check=check
|
162
|
+
):
|
163
|
+
await getattr(self, name)(*args, **kwargs)
|
164
|
+
|
165
|
+
async def block_mesh(self, *, check: bool = True) -> None:
|
166
|
+
"""Run blockMesh on this case."""
|
167
|
+
await self.run(["blockMesh"], check=check)
|
168
|
+
|
169
|
+
async def decompose_par(self, *, check: bool = True) -> None:
|
170
|
+
"""Decompose this case for parallel running."""
|
171
|
+
await self.run(["decomposePar"], check=check)
|
172
|
+
|
173
|
+
async def reconstruct_par(self, *, check: bool = True) -> None:
|
174
|
+
"""Reconstruct this case after parallel running."""
|
175
|
+
await self.run(["reconstructPar"], check=check)
|
176
|
+
|
177
|
+
async def restore_0_dir(self) -> None:
|
178
|
+
"""Restore the 0 directory from the 0.orig directory."""
|
179
|
+
for name, args, kwargs in self._restore_0_dir_cmds():
|
180
|
+
await getattr(self, name)(*args, **kwargs)
|
181
|
+
|
182
|
+
@awaitableasynccontextmanager
|
183
|
+
@asynccontextmanager
|
184
|
+
async def copy(
|
185
|
+
self, dst: Optional[Union["os.PathLike[str]", str]] = None
|
186
|
+
) -> "AsyncGenerator[Self]":
|
187
|
+
"""
|
188
|
+
Make a copy of this case.
|
189
|
+
|
190
|
+
Use as an async context manager to automatically delete the copy when done.
|
191
|
+
|
192
|
+
:param dst: The destination path. If None, copy to a temporary directory.
|
193
|
+
"""
|
194
|
+
if dst is None:
|
195
|
+
dst = Path(tempfile.mkdtemp(), self.name)
|
196
|
+
tmp = True
|
197
|
+
else:
|
198
|
+
tmp = False
|
199
|
+
|
200
|
+
for name, args, kwargs in self._copy_cmds(dst):
|
201
|
+
await getattr(self, name)(*args, **kwargs)
|
202
|
+
|
203
|
+
yield type(self)(dst)
|
204
|
+
|
205
|
+
if tmp:
|
206
|
+
assert isinstance(dst, Path)
|
207
|
+
await self._rmtree(dst.parent)
|
208
|
+
else:
|
209
|
+
await self._rmtree(dst)
|
210
|
+
|
211
|
+
@awaitableasynccontextmanager
|
212
|
+
@asynccontextmanager
|
213
|
+
async def clone(
|
214
|
+
self, dst: Optional[Union["os.PathLike[str]", str]] = None
|
215
|
+
) -> "AsyncGenerator[Self]":
|
216
|
+
"""
|
217
|
+
Clone this case (make a clean copy).
|
218
|
+
|
219
|
+
Use as an async context manager to automatically delete the clone when done.
|
220
|
+
|
221
|
+
:param dst: The destination path. If None, clone to a temporary directory.
|
222
|
+
"""
|
223
|
+
if dst is None:
|
224
|
+
dst = Path(tempfile.mkdtemp(), self.name)
|
225
|
+
tmp = True
|
226
|
+
else:
|
227
|
+
tmp = False
|
228
|
+
|
229
|
+
for name, args, kwargs in self._clone_cmds(dst):
|
230
|
+
await getattr(self, name)(*args, **kwargs)
|
231
|
+
|
232
|
+
yield type(self)(dst)
|
233
|
+
|
234
|
+
if tmp:
|
235
|
+
assert isinstance(dst, Path)
|
236
|
+
await self._rmtree(dst.parent)
|
237
|
+
else:
|
238
|
+
await self._rmtree(dst)
|
239
|
+
|
240
|
+
@staticmethod
|
241
|
+
def map(coro: Callable[[X], Awaitable[Y]], iterable: Iterable[X]) -> Iterable[Y]:
|
242
|
+
"""Run an async function on each element of an iterable concurrently."""
|
243
|
+
return asyncio.get_event_loop().run_until_complete(
|
244
|
+
asyncio.gather(*(coro(arg) for arg in iterable))
|
245
|
+
)
|
@@ -0,0 +1,213 @@
|
|
1
|
+
import os
|
2
|
+
import shutil
|
3
|
+
import sys
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import (
|
6
|
+
Optional,
|
7
|
+
Union,
|
8
|
+
overload,
|
9
|
+
)
|
10
|
+
|
11
|
+
if sys.version_info >= (3, 9):
|
12
|
+
from collections.abc import (
|
13
|
+
Iterator,
|
14
|
+
Sequence,
|
15
|
+
Set,
|
16
|
+
)
|
17
|
+
else:
|
18
|
+
from typing import AbstractSet as Set
|
19
|
+
from typing import (
|
20
|
+
Iterator,
|
21
|
+
Sequence,
|
22
|
+
)
|
23
|
+
|
24
|
+
from .._files import FoamFieldFile, FoamFile
|
25
|
+
|
26
|
+
|
27
|
+
class FoamCaseBase(Sequence["FoamCaseBase.TimeDirectory"]):
|
28
|
+
def __init__(self, path: Union["os.PathLike[str]", str] = Path()):
|
29
|
+
self.path = Path(path).absolute()
|
30
|
+
|
31
|
+
class TimeDirectory(Set[FoamFieldFile]):
|
32
|
+
"""
|
33
|
+
An OpenFOAM time directory in a case.
|
34
|
+
|
35
|
+
Use to access field files in the directory, e.g. `time["U"]`.
|
36
|
+
|
37
|
+
:param path: The path to the time directory.
|
38
|
+
"""
|
39
|
+
|
40
|
+
def __init__(self, path: Union["os.PathLike[str]", str]):
|
41
|
+
self.path = Path(path).absolute()
|
42
|
+
|
43
|
+
@property
|
44
|
+
def time(self) -> float:
|
45
|
+
"""The time that corresponds to this directory."""
|
46
|
+
return float(self.path.name)
|
47
|
+
|
48
|
+
@property
|
49
|
+
def name(self) -> str:
|
50
|
+
"""The name of this time directory."""
|
51
|
+
return self.path.name
|
52
|
+
|
53
|
+
def __getitem__(self, key: str) -> FoamFieldFile:
|
54
|
+
if (self.path / f"{key}.gz").is_file() and not (self.path / key).is_file():
|
55
|
+
return FoamFieldFile(self.path / f"{key}.gz")
|
56
|
+
else:
|
57
|
+
return FoamFieldFile(self.path / key)
|
58
|
+
|
59
|
+
def __contains__(self, obj: object) -> bool:
|
60
|
+
if isinstance(obj, FoamFieldFile):
|
61
|
+
return obj.path.parent == self.path
|
62
|
+
elif isinstance(obj, str):
|
63
|
+
return (self.path / obj).is_file() or (
|
64
|
+
self.path / f"{obj}.gz"
|
65
|
+
).is_file()
|
66
|
+
else:
|
67
|
+
return False
|
68
|
+
|
69
|
+
def __iter__(self) -> Iterator[FoamFieldFile]:
|
70
|
+
for p in self.path.iterdir():
|
71
|
+
if p.is_file() and (
|
72
|
+
p.suffix != ".gz" or not p.with_suffix("").is_file()
|
73
|
+
):
|
74
|
+
yield FoamFieldFile(p)
|
75
|
+
|
76
|
+
def __len__(self) -> int:
|
77
|
+
return len(list(iter(self)))
|
78
|
+
|
79
|
+
def __delitem__(self, key: str) -> None:
|
80
|
+
if (self.path / f"{key}.gz").is_file() and not (self.path / key).is_file():
|
81
|
+
(self.path / f"{key}.gz").unlink()
|
82
|
+
else:
|
83
|
+
(self.path / key).unlink()
|
84
|
+
|
85
|
+
def __fspath__(self) -> str:
|
86
|
+
return str(self.path)
|
87
|
+
|
88
|
+
def __repr__(self) -> str:
|
89
|
+
return f"{type(self).__qualname__}('{self.path}')"
|
90
|
+
|
91
|
+
def __str__(self) -> str:
|
92
|
+
return str(self.path)
|
93
|
+
|
94
|
+
@property
|
95
|
+
def _times(self) -> Sequence["FoamCaseBase.TimeDirectory"]:
|
96
|
+
times = []
|
97
|
+
for p in self.path.iterdir():
|
98
|
+
if p.is_dir():
|
99
|
+
try:
|
100
|
+
float(p.name)
|
101
|
+
except ValueError:
|
102
|
+
pass
|
103
|
+
else:
|
104
|
+
times.append(FoamCaseBase.TimeDirectory(p))
|
105
|
+
|
106
|
+
times.sort(key=lambda t: t.time)
|
107
|
+
|
108
|
+
return times
|
109
|
+
|
110
|
+
@overload
|
111
|
+
def __getitem__(
|
112
|
+
self, index: Union[int, float, str]
|
113
|
+
) -> "FoamCaseBase.TimeDirectory": ...
|
114
|
+
|
115
|
+
@overload
|
116
|
+
def __getitem__(self, index: slice) -> Sequence["FoamCaseBase.TimeDirectory"]: ...
|
117
|
+
|
118
|
+
def __getitem__(
|
119
|
+
self, index: Union[int, slice, float, str]
|
120
|
+
) -> Union["FoamCaseBase.TimeDirectory", Sequence["FoamCaseBase.TimeDirectory"]]:
|
121
|
+
if isinstance(index, str):
|
122
|
+
return FoamCaseBase.TimeDirectory(self.path / index)
|
123
|
+
elif isinstance(index, float):
|
124
|
+
for time in self._times:
|
125
|
+
if time.time == index:
|
126
|
+
return time
|
127
|
+
raise IndexError(f"Time {index} not found")
|
128
|
+
return self._times[index]
|
129
|
+
|
130
|
+
def __len__(self) -> int:
|
131
|
+
return len(self._times)
|
132
|
+
|
133
|
+
def __delitem__(self, key: Union[int, float, str]) -> None:
|
134
|
+
shutil.rmtree(self[key].path)
|
135
|
+
|
136
|
+
@property
|
137
|
+
def name(self) -> str:
|
138
|
+
"""The name of the case."""
|
139
|
+
return self.path.name
|
140
|
+
|
141
|
+
def file(self, path: Union["os.PathLike[str]", str]) -> FoamFile:
|
142
|
+
"""Return a FoamFile object for the given path in the case."""
|
143
|
+
return FoamFile(self.path / path)
|
144
|
+
|
145
|
+
@property
|
146
|
+
def _nsubdomains(self) -> Optional[int]:
|
147
|
+
"""Return the number of subdomains as set in the decomposeParDict, or None if no decomposeParDict is found."""
|
148
|
+
try:
|
149
|
+
nsubdomains = self.decompose_par_dict["numberOfSubdomains"]
|
150
|
+
if not isinstance(nsubdomains, int):
|
151
|
+
raise TypeError(
|
152
|
+
f"numberOfSubdomains in {self.decompose_par_dict} is not an integer"
|
153
|
+
)
|
154
|
+
return nsubdomains
|
155
|
+
except FileNotFoundError:
|
156
|
+
return None
|
157
|
+
|
158
|
+
@property
|
159
|
+
def _nprocessors(self) -> int:
|
160
|
+
"""Return the number of processor directories in the case."""
|
161
|
+
return len(list(self.path.glob("processor*")))
|
162
|
+
|
163
|
+
@property
|
164
|
+
def application(self) -> str:
|
165
|
+
"""The application name as set in the controlDict."""
|
166
|
+
application = self.control_dict["application"]
|
167
|
+
if not isinstance(application, str):
|
168
|
+
raise TypeError(f"application in {self.control_dict} is not a string")
|
169
|
+
return application
|
170
|
+
|
171
|
+
@property
|
172
|
+
def control_dict(self) -> FoamFile:
|
173
|
+
"""The controlDict file."""
|
174
|
+
return self.file("system/controlDict")
|
175
|
+
|
176
|
+
@property
|
177
|
+
def fv_schemes(self) -> FoamFile:
|
178
|
+
"""The fvSchemes file."""
|
179
|
+
return self.file("system/fvSchemes")
|
180
|
+
|
181
|
+
@property
|
182
|
+
def fv_solution(self) -> FoamFile:
|
183
|
+
"""The fvSolution file."""
|
184
|
+
return self.file("system/fvSolution")
|
185
|
+
|
186
|
+
@property
|
187
|
+
def decompose_par_dict(self) -> FoamFile:
|
188
|
+
"""The decomposeParDict file."""
|
189
|
+
return self.file("system/decomposeParDict")
|
190
|
+
|
191
|
+
@property
|
192
|
+
def block_mesh_dict(self) -> FoamFile:
|
193
|
+
"""The blockMeshDict file."""
|
194
|
+
return self.file("system/blockMeshDict")
|
195
|
+
|
196
|
+
@property
|
197
|
+
def transport_properties(self) -> FoamFile:
|
198
|
+
"""The transportProperties file."""
|
199
|
+
return self.file("constant/transportProperties")
|
200
|
+
|
201
|
+
@property
|
202
|
+
def turbulence_properties(self) -> FoamFile:
|
203
|
+
"""The turbulenceProperties file."""
|
204
|
+
return self.file("constant/turbulenceProperties")
|
205
|
+
|
206
|
+
def __fspath__(self) -> str:
|
207
|
+
return str(self.path)
|
208
|
+
|
209
|
+
def __repr__(self) -> str:
|
210
|
+
return f"{type(self).__qualname__}('{self.path}')"
|
211
|
+
|
212
|
+
def __str__(self) -> str:
|
213
|
+
return str(self.path)
|