foamlib 0.5.0__tar.gz → 0.6.0__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.5.0 → foamlib-0.6.0}/PKG-INFO +20 -1
- {foamlib-0.5.0 → foamlib-0.6.0}/README.md +19 -0
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib/__init__.py +1 -1
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib/_cases/_async.py +59 -96
- foamlib-0.5.0/foamlib/_cases/_recipes.py → foamlib-0.6.0/foamlib/_cases/_run.py +164 -76
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib/_cases/_sync.py +39 -66
- foamlib-0.6.0/foamlib/_cases/_util.py +60 -0
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib/_files/_files.py +21 -43
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib/_files/_io.py +16 -8
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib/_files/_parsing.py +46 -46
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib.egg-info/PKG-INFO +20 -1
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib.egg-info/SOURCES.txt +1 -1
- {foamlib-0.5.0 → foamlib-0.6.0}/pyproject.toml +3 -0
- foamlib-0.5.0/foamlib/_cases/_util.py +0 -27
- {foamlib-0.5.0 → foamlib-0.6.0}/LICENSE.txt +0 -0
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib/_cases/__init__.py +0 -0
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib/_cases/_base.py +0 -0
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib/_cases/_subprocess.py +0 -0
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib/_files/__init__.py +0 -0
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib/_files/_base.py +0 -0
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib/_files/_serialization.py +0 -0
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib/_files/_util.py +0 -0
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib/py.typed +0 -0
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib.egg-info/dependency_links.txt +0 -0
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib.egg-info/requires.txt +0 -0
- {foamlib-0.5.0 → foamlib-0.6.0}/foamlib.egg-info/top_level.txt +0 -0
- {foamlib-0.5.0 → foamlib-0.6.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: foamlib
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.6.0
|
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
|
@@ -155,6 +155,25 @@ U = FoamFieldFile(Path(my_pitz) / "0/U")
|
|
155
155
|
print(U.internal_field)
|
156
156
|
```
|
157
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
|
+
|
158
177
|
## Documentation
|
159
178
|
|
160
179
|
For more information, check out the [documentation](https://foamlib.readthedocs.io/).
|
@@ -101,6 +101,25 @@ U = FoamFieldFile(Path(my_pitz) / "0/U")
|
|
101
101
|
print(U.internal_field)
|
102
102
|
```
|
103
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
|
+
|
104
123
|
## Documentation
|
105
124
|
|
106
125
|
For more information, check out the [documentation](https://foamlib.readthedocs.io/).
|
@@ -2,19 +2,19 @@ import asyncio
|
|
2
2
|
import multiprocessing
|
3
3
|
import os
|
4
4
|
import sys
|
5
|
-
import tempfile
|
6
5
|
from contextlib import asynccontextmanager
|
7
|
-
from
|
8
|
-
from typing import (
|
9
|
-
Callable,
|
10
|
-
Optional,
|
11
|
-
Union,
|
12
|
-
)
|
6
|
+
from typing import Any, Callable, Optional, TypeVar, Union
|
13
7
|
|
14
8
|
if sys.version_info >= (3, 9):
|
15
|
-
from collections.abc import
|
9
|
+
from collections.abc import (
|
10
|
+
AsyncGenerator,
|
11
|
+
Awaitable,
|
12
|
+
Collection,
|
13
|
+
Iterable,
|
14
|
+
Sequence,
|
15
|
+
)
|
16
16
|
else:
|
17
|
-
from typing import AsyncGenerator, Collection, Sequence
|
17
|
+
from typing import AsyncGenerator, Awaitable, Collection, Iterable, Sequence
|
18
18
|
|
19
19
|
if sys.version_info >= (3, 11):
|
20
20
|
from typing import Self
|
@@ -23,12 +23,15 @@ else:
|
|
23
23
|
|
24
24
|
import aioshutil
|
25
25
|
|
26
|
-
from .
|
26
|
+
from ._run import FoamCaseRunBase
|
27
27
|
from ._subprocess import run_async
|
28
|
-
from ._util import awaitableasynccontextmanager
|
28
|
+
from ._util import ValuedGenerator, awaitableasynccontextmanager
|
29
29
|
|
30
|
+
X = TypeVar("X")
|
31
|
+
Y = TypeVar("Y")
|
30
32
|
|
31
|
-
|
33
|
+
|
34
|
+
class AsyncFoamCase(FoamCaseRunBase):
|
32
35
|
"""
|
33
36
|
An OpenFOAM case with asynchronous support.
|
34
37
|
|
@@ -45,14 +48,11 @@ class AsyncFoamCase(_FoamCaseRecipes):
|
|
45
48
|
"""
|
46
49
|
|
47
50
|
_reserved_cpus = 0
|
48
|
-
_cpus_cond =
|
51
|
+
_cpus_cond = asyncio.Condition()
|
49
52
|
|
50
53
|
@staticmethod
|
51
54
|
@asynccontextmanager
|
52
55
|
async def _cpus(cpus: int) -> AsyncGenerator[None, None]:
|
53
|
-
if AsyncFoamCase._cpus_cond is None:
|
54
|
-
AsyncFoamCase._cpus_cond = asyncio.Condition()
|
55
|
-
|
56
56
|
cpus = min(cpus, AsyncFoamCase.max_cpus)
|
57
57
|
if cpus > 0:
|
58
58
|
async with AsyncFoamCase._cpus_cond:
|
@@ -69,6 +69,16 @@ class AsyncFoamCase(_FoamCaseRecipes):
|
|
69
69
|
AsyncFoamCase._reserved_cpus -= cpus
|
70
70
|
AsyncFoamCase._cpus_cond.notify(cpus)
|
71
71
|
|
72
|
+
@staticmethod
|
73
|
+
async def _run(
|
74
|
+
cmd: Union[Sequence[Union[str, "os.PathLike[str]"]], str],
|
75
|
+
*,
|
76
|
+
cpus: int,
|
77
|
+
**kwargs: Any,
|
78
|
+
) -> None:
|
79
|
+
async with AsyncFoamCase._cpus(cpus):
|
80
|
+
await run_async(cmd, **kwargs)
|
81
|
+
|
72
82
|
@staticmethod
|
73
83
|
async def _rmtree(
|
74
84
|
path: Union["os.PathLike[str]", str], ignore_errors: bool = False
|
@@ -87,75 +97,37 @@ class AsyncFoamCase(_FoamCaseRecipes):
|
|
87
97
|
) -> None:
|
88
98
|
await aioshutil.copytree(src, dest, symlinks=symlinks, ignore=ignore)
|
89
99
|
|
90
|
-
async def clean(
|
91
|
-
self,
|
92
|
-
*,
|
93
|
-
script: bool = True,
|
94
|
-
check: bool = False,
|
95
|
-
) -> None:
|
100
|
+
async def clean(self, *, check: bool = False) -> None:
|
96
101
|
"""
|
97
102
|
Clean this case.
|
98
103
|
|
99
|
-
:param script: If True, use an (All)clean script if it exists. If False, ignore any clean scripts.
|
100
104
|
:param check: If True, raise a CalledProcessError if the clean script returns a non-zero exit code.
|
101
105
|
"""
|
102
|
-
for
|
103
|
-
await
|
104
|
-
|
105
|
-
async def _run(
|
106
|
-
self,
|
107
|
-
cmd: Union[Sequence[Union[str, "os.PathLike[str]"]], str],
|
108
|
-
*,
|
109
|
-
parallel: bool = False,
|
110
|
-
cpus: int = 1,
|
111
|
-
check: bool = True,
|
112
|
-
log: bool = True,
|
113
|
-
) -> None:
|
114
|
-
with self._output(cmd, log=log) as (stdout, stderr):
|
115
|
-
if parallel:
|
116
|
-
if isinstance(cmd, str):
|
117
|
-
cmd = [
|
118
|
-
"mpiexec",
|
119
|
-
"-n",
|
120
|
-
str(cpus),
|
121
|
-
"/bin/sh",
|
122
|
-
"-c",
|
123
|
-
f"{cmd} -parallel",
|
124
|
-
]
|
125
|
-
else:
|
126
|
-
cmd = ["mpiexec", "-n", str(cpus), *cmd, "-parallel"]
|
127
|
-
|
128
|
-
async with self._cpus(cpus):
|
129
|
-
await run_async(
|
130
|
-
cmd,
|
131
|
-
check=check,
|
132
|
-
cwd=self.path,
|
133
|
-
env=self._env(shell=isinstance(cmd, str)),
|
134
|
-
stdout=stdout,
|
135
|
-
stderr=stderr,
|
136
|
-
)
|
106
|
+
for coro in self._clean_calls(check=check):
|
107
|
+
await coro
|
137
108
|
|
138
109
|
async def run(
|
139
110
|
self,
|
140
111
|
cmd: Optional[Union[Sequence[Union[str, "os.PathLike[str]"]], str]] = None,
|
141
112
|
*,
|
142
|
-
script: bool = True,
|
143
113
|
parallel: Optional[bool] = None,
|
144
114
|
cpus: Optional[int] = None,
|
145
115
|
check: bool = True,
|
116
|
+
log: bool = True,
|
146
117
|
) -> None:
|
147
118
|
"""
|
148
119
|
Run this case, or a specified command in the context of this case.
|
149
120
|
|
150
121
|
: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.
|
151
|
-
: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.
|
152
122
|
:param parallel: If True, run in parallel using MPI. If None, autodetect whether to run in parallel.
|
123
|
+
:param cpus: The number of CPUs to use. If None, autodetect according to the case.
|
153
124
|
:param check: If True, raise a CalledProcessError if any command returns a non-zero exit code.
|
125
|
+
:param log: If True, log the command output to a file.
|
154
126
|
"""
|
155
|
-
for
|
156
|
-
cmd=cmd,
|
127
|
+
for coro in self._run_calls(
|
128
|
+
cmd=cmd, parallel=parallel, cpus=cpus, check=check, log=log
|
157
129
|
):
|
158
|
-
await
|
130
|
+
await coro
|
159
131
|
|
160
132
|
async def block_mesh(self, *, check: bool = True) -> None:
|
161
133
|
"""Run blockMesh on this case."""
|
@@ -171,8 +143,8 @@ class AsyncFoamCase(_FoamCaseRecipes):
|
|
171
143
|
|
172
144
|
async def restore_0_dir(self) -> None:
|
173
145
|
"""Restore the 0 directory from the 0.orig directory."""
|
174
|
-
for
|
175
|
-
await
|
146
|
+
for coro in self._restore_0_dir_calls():
|
147
|
+
await coro
|
176
148
|
|
177
149
|
@awaitableasynccontextmanager
|
178
150
|
@asynccontextmanager
|
@@ -184,24 +156,16 @@ class AsyncFoamCase(_FoamCaseRecipes):
|
|
184
156
|
|
185
157
|
Use as an async context manager to automatically delete the copy when done.
|
186
158
|
|
187
|
-
:param dst: The destination path. If None,
|
159
|
+
:param dst: The destination path. If None, clone to `$FOAM_RUN/foamlib`.
|
188
160
|
"""
|
189
|
-
|
190
|
-
dst = Path(tempfile.mkdtemp(), self.name)
|
191
|
-
tmp = True
|
192
|
-
else:
|
193
|
-
tmp = False
|
161
|
+
calls = ValuedGenerator(self._copy_calls(dst))
|
194
162
|
|
195
|
-
for
|
196
|
-
await
|
163
|
+
for coro in calls:
|
164
|
+
await coro
|
197
165
|
|
198
|
-
yield
|
166
|
+
yield calls.value
|
199
167
|
|
200
|
-
|
201
|
-
assert isinstance(dst, Path)
|
202
|
-
await self._rmtree(dst.parent)
|
203
|
-
else:
|
204
|
-
await self._rmtree(dst)
|
168
|
+
await self._rmtree(calls.value.path)
|
205
169
|
|
206
170
|
@awaitableasynccontextmanager
|
207
171
|
@asynccontextmanager
|
@@ -213,21 +177,20 @@ class AsyncFoamCase(_FoamCaseRecipes):
|
|
213
177
|
|
214
178
|
Use as an async context manager to automatically delete the clone when done.
|
215
179
|
|
216
|
-
:param dst: The destination path. If None, clone to
|
180
|
+
:param dst: The destination path. If None, clone to `$FOAM_RUN/foamlib`.
|
217
181
|
"""
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
await self._rmtree(dst)
|
182
|
+
calls = ValuedGenerator(self._clone_calls(dst))
|
183
|
+
|
184
|
+
for coro in calls:
|
185
|
+
await coro
|
186
|
+
|
187
|
+
yield calls.value
|
188
|
+
|
189
|
+
await self._rmtree(calls.value.path)
|
190
|
+
|
191
|
+
@staticmethod
|
192
|
+
def map(coro: Callable[[X], Awaitable[Y]], iterable: Iterable[X]) -> Iterable[Y]:
|
193
|
+
"""Run an async function on each element of an iterable concurrently."""
|
194
|
+
return asyncio.get_event_loop().run_until_complete(
|
195
|
+
asyncio.gather(*(coro(arg) for arg in iterable))
|
196
|
+
)
|
@@ -2,6 +2,8 @@ import os
|
|
2
2
|
import shlex
|
3
3
|
import shutil
|
4
4
|
import sys
|
5
|
+
import tempfile
|
6
|
+
from abc import abstractmethod
|
5
7
|
from contextlib import contextmanager
|
6
8
|
from pathlib import Path
|
7
9
|
from typing import (
|
@@ -16,6 +18,7 @@ if sys.version_info >= (3, 9):
|
|
16
18
|
from collections.abc import (
|
17
19
|
Callable,
|
18
20
|
Collection,
|
21
|
+
Coroutine,
|
19
22
|
Generator,
|
20
23
|
Mapping,
|
21
24
|
Sequence,
|
@@ -26,17 +29,96 @@ else:
|
|
26
29
|
from typing import (
|
27
30
|
Callable,
|
28
31
|
Collection,
|
32
|
+
Coroutine,
|
29
33
|
Generator,
|
30
34
|
Mapping,
|
31
35
|
Sequence,
|
32
36
|
)
|
33
37
|
|
38
|
+
if sys.version_info >= (3, 11):
|
39
|
+
from typing import Self
|
40
|
+
else:
|
41
|
+
from typing_extensions import Self
|
42
|
+
|
34
43
|
from ._base import FoamCaseBase
|
35
44
|
from ._subprocess import DEVNULL, STDOUT
|
36
45
|
|
37
46
|
|
38
|
-
class
|
39
|
-
def
|
47
|
+
class FoamCaseRunBase(FoamCaseBase):
|
48
|
+
def __delitem__(self, key: Union[int, float, str]) -> None:
|
49
|
+
shutil.rmtree(self[key].path)
|
50
|
+
|
51
|
+
@staticmethod
|
52
|
+
@abstractmethod
|
53
|
+
def _run(
|
54
|
+
cmd: Union[Sequence[Union[str, "os.PathLike[str]"]], str],
|
55
|
+
*,
|
56
|
+
cpus: int,
|
57
|
+
**kwargs: Any,
|
58
|
+
) -> Union[None, Coroutine[None, None, None]]:
|
59
|
+
raise NotImplementedError
|
60
|
+
|
61
|
+
@staticmethod
|
62
|
+
@abstractmethod
|
63
|
+
def _rmtree(
|
64
|
+
path: Union["os.PathLike[str]", str], *, ignore_errors: bool = False
|
65
|
+
) -> Union[None, Coroutine[None, None, None]]:
|
66
|
+
raise NotImplementedError
|
67
|
+
|
68
|
+
@staticmethod
|
69
|
+
@abstractmethod
|
70
|
+
def _copytree(
|
71
|
+
src: Union["os.PathLike[str]", str],
|
72
|
+
dest: Union["os.PathLike[str]", str],
|
73
|
+
*,
|
74
|
+
symlinks: bool = False,
|
75
|
+
ignore: Optional[
|
76
|
+
Callable[[Union["os.PathLike[str]", str], Collection[str]], Collection[str]]
|
77
|
+
] = None,
|
78
|
+
) -> Union[None, Coroutine[None, None, None]]:
|
79
|
+
raise NotImplementedError
|
80
|
+
|
81
|
+
@abstractmethod
|
82
|
+
def clean(self, *, check: bool = False) -> Union[None, Coroutine[None, None, None]]:
|
83
|
+
raise NotImplementedError
|
84
|
+
|
85
|
+
@abstractmethod
|
86
|
+
def copy(self, dst: Optional[Union["os.PathLike[str]", str]] = None) -> Any:
|
87
|
+
raise NotImplementedError
|
88
|
+
|
89
|
+
@abstractmethod
|
90
|
+
def clone(self, dst: Optional[Union["os.PathLike[str]", str]] = None) -> Any:
|
91
|
+
raise NotImplementedError
|
92
|
+
|
93
|
+
@abstractmethod
|
94
|
+
def run(
|
95
|
+
self,
|
96
|
+
cmd: Optional[Union[Sequence[Union[str, "os.PathLike[str]"]], str]] = None,
|
97
|
+
*,
|
98
|
+
parallel: Optional[bool] = None,
|
99
|
+
cpus: Optional[int] = None,
|
100
|
+
check: bool = True,
|
101
|
+
log: bool = True,
|
102
|
+
) -> Union[None, Coroutine[None, None, None]]:
|
103
|
+
raise NotImplementedError
|
104
|
+
|
105
|
+
@abstractmethod
|
106
|
+
def block_mesh(
|
107
|
+
self, *, check: bool = True
|
108
|
+
) -> Union[None, Coroutine[None, None, None]]:
|
109
|
+
raise NotImplementedError
|
110
|
+
|
111
|
+
@abstractmethod
|
112
|
+
def decompose_par(
|
113
|
+
self, *, check: bool = True
|
114
|
+
) -> Union[None, Coroutine[None, None, None]]:
|
115
|
+
raise NotImplementedError
|
116
|
+
|
117
|
+
@abstractmethod
|
118
|
+
def restore_0_dir(self) -> Union[None, Coroutine[None, None, None]]:
|
119
|
+
raise NotImplementedError
|
120
|
+
|
121
|
+
def __clean_paths(self) -> Set[Path]:
|
40
122
|
has_decompose_par_dict = (self.path / "system" / "decomposeParDict").is_file()
|
41
123
|
has_block_mesh_dict = (self.path / "system" / "blockMeshDict").is_file()
|
42
124
|
|
@@ -61,18 +143,14 @@ class _FoamCaseRecipes(FoamCaseBase):
|
|
61
143
|
if has_block_mesh_dict and (self.path / "constant" / "polyMesh").exists():
|
62
144
|
paths.add(self.path / "constant" / "polyMesh")
|
63
145
|
|
64
|
-
|
65
|
-
paths.update(self.path.glob("log.*"))
|
146
|
+
paths.update(self.path.glob("log.*"))
|
66
147
|
|
67
148
|
return paths
|
68
149
|
|
69
|
-
def
|
70
|
-
shutil.rmtree(self[key].path)
|
71
|
-
|
72
|
-
def _clone_ignore(
|
150
|
+
def __clone_ignore(
|
73
151
|
self,
|
74
152
|
) -> Callable[[Union["os.PathLike[str]", str], Collection[str]], Collection[str]]:
|
75
|
-
clean_paths = self.
|
153
|
+
clean_paths = self.__clean_paths()
|
76
154
|
|
77
155
|
def ignore(
|
78
156
|
path: Union["os.PathLike[str]", str], names: Collection[str]
|
@@ -82,7 +160,7 @@ class _FoamCaseRecipes(FoamCaseBase):
|
|
82
160
|
|
83
161
|
return ignore
|
84
162
|
|
85
|
-
def
|
163
|
+
def __clean_script(self) -> Optional[Path]:
|
86
164
|
"""Return the path to the (All)clean script, or None if no clean script is found."""
|
87
165
|
clean = self.path / "clean"
|
88
166
|
all_clean = self.path / "Allclean"
|
@@ -97,9 +175,9 @@ class _FoamCaseRecipes(FoamCaseBase):
|
|
97
175
|
if sys.argv and Path(sys.argv[0]).absolute() == script.absolute():
|
98
176
|
return None
|
99
177
|
|
100
|
-
return script
|
178
|
+
return script
|
101
179
|
|
102
|
-
def
|
180
|
+
def __run_script(self, *, parallel: Optional[bool] = None) -> Optional[Path]:
|
103
181
|
"""Return the path to the (All)run script, or None if no run script is found."""
|
104
182
|
run = self.path / "run"
|
105
183
|
run_parallel = self.path / "run-parallel"
|
@@ -132,7 +210,7 @@ class _FoamCaseRecipes(FoamCaseBase):
|
|
132
210
|
|
133
211
|
return script
|
134
212
|
|
135
|
-
def
|
213
|
+
def __env(self, *, shell: bool) -> Optional[Mapping[str, str]]:
|
136
214
|
sip_workaround = os.environ.get(
|
137
215
|
"FOAM_LD_LIBRARY_PATH", ""
|
138
216
|
) and not os.environ.get("DYLD_LIBRARY_PATH", "")
|
@@ -151,7 +229,7 @@ class _FoamCaseRecipes(FoamCaseBase):
|
|
151
229
|
return None
|
152
230
|
|
153
231
|
@contextmanager
|
154
|
-
def
|
232
|
+
def __output(
|
155
233
|
self, cmd: Union[Sequence[Union[str, "os.PathLike[str]"]], str], *, log: bool
|
156
234
|
) -> Generator[Tuple[Union[int, IO[bytes]], Union[int, IO[bytes]]], None, None]:
|
157
235
|
if log:
|
@@ -168,70 +246,64 @@ class _FoamCaseRecipes(FoamCaseBase):
|
|
168
246
|
else:
|
169
247
|
yield DEVNULL, DEVNULL
|
170
248
|
|
171
|
-
def
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
self,
|
185
|
-
|
186
|
-
|
249
|
+
def __mkrundir(self) -> Path:
|
250
|
+
d = Path(os.environ["FOAM_RUN"], "foamlib")
|
251
|
+
d.mkdir(parents=True, exist_ok=True)
|
252
|
+
ret = Path(tempfile.mkdtemp(prefix=f"{self.name}-", dir=d))
|
253
|
+
ret.rmdir()
|
254
|
+
return ret
|
255
|
+
|
256
|
+
def _copy_calls(
|
257
|
+
self, dst: Optional[Union["os.PathLike[str]", str]]
|
258
|
+
) -> Generator[Any, None, Self]:
|
259
|
+
if dst is None:
|
260
|
+
dst = self.__mkrundir()
|
261
|
+
|
262
|
+
yield self._copytree(self.path, dst, symlinks=True)
|
263
|
+
|
264
|
+
return type(self)(dst)
|
265
|
+
|
266
|
+
def _clean_calls(self, *, check: bool = False) -> Generator[Any, None, None]:
|
267
|
+
script_path = self.__clean_script()
|
187
268
|
|
188
269
|
if script_path is not None:
|
189
|
-
yield (
|
270
|
+
yield self.run([script_path], cpus=0, check=check, log=False)
|
190
271
|
else:
|
191
|
-
for p in self.
|
272
|
+
for p in self.__clean_paths():
|
192
273
|
if p.is_dir():
|
193
|
-
yield
|
274
|
+
yield self._rmtree(p)
|
194
275
|
else:
|
195
276
|
p.unlink()
|
196
277
|
|
197
|
-
def
|
198
|
-
self,
|
199
|
-
) -> Generator[
|
200
|
-
if
|
201
|
-
|
202
|
-
|
278
|
+
def _clone_calls(
|
279
|
+
self, dst: Optional[Union["os.PathLike[str]", str]]
|
280
|
+
) -> Generator[Any, None, Self]:
|
281
|
+
if dst is None:
|
282
|
+
dst = self.__mkrundir()
|
283
|
+
|
284
|
+
if self.__clean_script() is not None:
|
285
|
+
yield self.copy(dst)
|
286
|
+
yield type(self)(dst).clean()
|
203
287
|
else:
|
204
|
-
yield (
|
205
|
-
|
206
|
-
(
|
207
|
-
self.path,
|
208
|
-
dest,
|
209
|
-
),
|
210
|
-
{"symlinks": True, "ignore": self._clone_ignore()},
|
288
|
+
yield self._copytree(
|
289
|
+
self.path, dst, symlinks=True, ignore=self.__clone_ignore()
|
211
290
|
)
|
212
291
|
|
213
|
-
|
214
|
-
|
215
|
-
) -> Generator[
|
216
|
-
yield
|
217
|
-
yield (
|
218
|
-
|
219
|
-
|
220
|
-
self.path / "0.orig",
|
221
|
-
self.path / "0",
|
222
|
-
),
|
223
|
-
{"symlinks": True},
|
224
|
-
)
|
225
|
-
|
226
|
-
def _run_cmds(
|
292
|
+
return type(self)(dst)
|
293
|
+
|
294
|
+
def _restore_0_dir_calls(self) -> Generator[Any, None, None]:
|
295
|
+
yield self._rmtree(self.path / "0", ignore_errors=True)
|
296
|
+
yield self._copytree(self.path / "0.orig", self.path / "0", symlinks=True)
|
297
|
+
|
298
|
+
def _run_calls(
|
227
299
|
self,
|
228
300
|
cmd: Optional[Union[Sequence[Union[str, "os.PathLike[str]"]], str]] = None,
|
229
301
|
*,
|
230
|
-
script: bool = True,
|
231
302
|
parallel: Optional[bool] = None,
|
232
303
|
cpus: Optional[int] = None,
|
233
304
|
check: bool = True,
|
234
|
-
|
305
|
+
log: bool = True,
|
306
|
+
) -> Generator[Any, None, None]:
|
235
307
|
if cmd is not None:
|
236
308
|
if parallel:
|
237
309
|
if cpus is None:
|
@@ -241,10 +313,32 @@ class _FoamCaseRecipes(FoamCaseBase):
|
|
241
313
|
if cpus is None:
|
242
314
|
cpus = 1
|
243
315
|
|
244
|
-
|
316
|
+
with self.__output(cmd, log=log) as (stdout, stderr):
|
317
|
+
if parallel:
|
318
|
+
if isinstance(cmd, str):
|
319
|
+
cmd = [
|
320
|
+
"mpiexec",
|
321
|
+
"-n",
|
322
|
+
str(cpus),
|
323
|
+
"/bin/sh",
|
324
|
+
"-c",
|
325
|
+
f"{cmd} -parallel",
|
326
|
+
]
|
327
|
+
else:
|
328
|
+
cmd = ["mpiexec", "-n", str(cpus), *cmd, "-parallel"]
|
329
|
+
|
330
|
+
yield self._run(
|
331
|
+
cmd,
|
332
|
+
cpus=cpus,
|
333
|
+
check=check,
|
334
|
+
cwd=self.path,
|
335
|
+
env=self.__env(shell=isinstance(cmd, str)),
|
336
|
+
stdout=stdout,
|
337
|
+
stderr=stderr,
|
338
|
+
)
|
245
339
|
|
246
340
|
else:
|
247
|
-
script_path = self.
|
341
|
+
script_path = self.__run_script(parallel=parallel)
|
248
342
|
|
249
343
|
if script_path is not None:
|
250
344
|
if parallel or parallel is None:
|
@@ -259,18 +353,14 @@ class _FoamCaseRecipes(FoamCaseBase):
|
|
259
353
|
if cpus is None:
|
260
354
|
cpus = 1
|
261
355
|
|
262
|
-
yield (
|
263
|
-
"_run",
|
264
|
-
([script_path],),
|
265
|
-
{"parallel": False, "cpus": cpus, "check": check},
|
266
|
-
)
|
356
|
+
yield self.run([script_path], parallel=False, cpus=cpus, check=check)
|
267
357
|
|
268
358
|
else:
|
269
359
|
if not self and (self.path / "0.orig").is_dir():
|
270
|
-
yield
|
360
|
+
yield self.restore_0_dir()
|
271
361
|
|
272
362
|
if (self.path / "system" / "blockMeshDict").is_file():
|
273
|
-
yield
|
363
|
+
yield self.block_mesh(check=check)
|
274
364
|
|
275
365
|
if parallel is None:
|
276
366
|
parallel = (
|
@@ -284,7 +374,7 @@ class _FoamCaseRecipes(FoamCaseBase):
|
|
284
374
|
self._nprocessors == 0
|
285
375
|
and (self.path / "system" / "decomposeParDict").is_file()
|
286
376
|
):
|
287
|
-
yield
|
377
|
+
yield self.decompose_par(check=check)
|
288
378
|
|
289
379
|
if cpus is None:
|
290
380
|
cpus = max(self._nprocessors, 1)
|
@@ -292,8 +382,6 @@ class _FoamCaseRecipes(FoamCaseBase):
|
|
292
382
|
if cpus is None:
|
293
383
|
cpus = 1
|
294
384
|
|
295
|
-
yield (
|
296
|
-
|
297
|
-
([self.application],),
|
298
|
-
{"parallel": parallel, "cpus": cpus, "check": check},
|
385
|
+
yield self.run(
|
386
|
+
[self.application], parallel=parallel, cpus=cpus, check=check
|
299
387
|
)
|
@@ -1,10 +1,10 @@
|
|
1
1
|
import os
|
2
2
|
import shutil
|
3
3
|
import sys
|
4
|
-
import tempfile
|
5
4
|
from pathlib import Path
|
6
5
|
from types import TracebackType
|
7
6
|
from typing import (
|
7
|
+
Any,
|
8
8
|
Callable,
|
9
9
|
Optional,
|
10
10
|
Type,
|
@@ -21,11 +21,12 @@ if sys.version_info >= (3, 11):
|
|
21
21
|
else:
|
22
22
|
from typing_extensions import Self
|
23
23
|
|
24
|
-
from .
|
24
|
+
from ._run import FoamCaseRunBase
|
25
25
|
from ._subprocess import run_sync
|
26
|
+
from ._util import ValuedGenerator
|
26
27
|
|
27
28
|
|
28
|
-
class FoamCase(
|
29
|
+
class FoamCase(FoamCaseRunBase):
|
29
30
|
"""
|
30
31
|
An OpenFOAM case.
|
31
32
|
|
@@ -36,6 +37,18 @@ class FoamCase(_FoamCaseRecipes):
|
|
36
37
|
:param path: The path to the case directory.
|
37
38
|
"""
|
38
39
|
|
40
|
+
def __init__(self, path: Union["os.PathLike[str]", str] = Path()):
|
41
|
+
super().__init__(path)
|
42
|
+
|
43
|
+
@staticmethod
|
44
|
+
def _run(
|
45
|
+
cmd: Union[Sequence[Union[str, "os.PathLike[str]"]], str],
|
46
|
+
*,
|
47
|
+
cpus: int,
|
48
|
+
**kwargs: Any,
|
49
|
+
) -> None:
|
50
|
+
run_sync(cmd, **kwargs)
|
51
|
+
|
39
52
|
@staticmethod
|
40
53
|
def _rmtree(
|
41
54
|
path: Union["os.PathLike[str]", str], *, ignore_errors: bool = False
|
@@ -65,73 +78,35 @@ class FoamCase(_FoamCaseRecipes):
|
|
65
78
|
) -> None:
|
66
79
|
self._rmtree(self.path)
|
67
80
|
|
68
|
-
def clean(
|
69
|
-
self,
|
70
|
-
*,
|
71
|
-
script: bool = True,
|
72
|
-
check: bool = False,
|
73
|
-
) -> None:
|
81
|
+
def clean(self, *, check: bool = False) -> None:
|
74
82
|
"""
|
75
83
|
Clean this case.
|
76
84
|
|
77
|
-
:param script: If True, use an (All)clean script if it exists. If False, ignore any clean scripts.
|
78
85
|
:param check: If True, raise a CalledProcessError if the clean script returns a non-zero exit code.
|
79
86
|
"""
|
80
|
-
for
|
81
|
-
|
82
|
-
|
83
|
-
def _run(
|
84
|
-
self,
|
85
|
-
cmd: Union[Sequence[Union[str, "os.PathLike[str]"]], str],
|
86
|
-
*,
|
87
|
-
parallel: bool = False,
|
88
|
-
cpus: int = 1,
|
89
|
-
check: bool = True,
|
90
|
-
log: bool = True,
|
91
|
-
) -> None:
|
92
|
-
with self._output(cmd, log=log) as (stdout, stderr):
|
93
|
-
if parallel:
|
94
|
-
if isinstance(cmd, str):
|
95
|
-
cmd = [
|
96
|
-
"mpiexec",
|
97
|
-
"-n",
|
98
|
-
str(cpus),
|
99
|
-
"/bin/sh",
|
100
|
-
"-c",
|
101
|
-
f"{cmd} -parallel",
|
102
|
-
]
|
103
|
-
else:
|
104
|
-
cmd = ["mpiexec", "-n", str(cpus), *cmd, "-parallel"]
|
105
|
-
|
106
|
-
run_sync(
|
107
|
-
cmd,
|
108
|
-
check=check,
|
109
|
-
cwd=self.path,
|
110
|
-
env=self._env(shell=isinstance(cmd, str)),
|
111
|
-
stdout=stdout,
|
112
|
-
stderr=stderr,
|
113
|
-
)
|
87
|
+
for _ in self._clean_calls(check=check):
|
88
|
+
pass
|
114
89
|
|
115
90
|
def run(
|
116
91
|
self,
|
117
92
|
cmd: Optional[Union[Sequence[Union[str, "os.PathLike[str]"]], str]] = None,
|
118
93
|
*,
|
119
|
-
script: bool = True,
|
120
94
|
parallel: Optional[bool] = None,
|
95
|
+
cpus: Optional[int] = None,
|
121
96
|
check: bool = True,
|
97
|
+
log: bool = True,
|
122
98
|
) -> None:
|
123
99
|
"""
|
124
100
|
Run this case, or a specified command in the context of this case.
|
125
101
|
|
126
102
|
: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.
|
127
|
-
: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.
|
128
103
|
:param parallel: If True, run in parallel using MPI. If None, autodetect whether to run in parallel.
|
104
|
+
:param cpus: The number of CPUs to use. If None, autodetect according to the case.
|
129
105
|
:param check: If True, raise a CalledProcessError if any command returns a non-zero exit code.
|
106
|
+
:param log: If True, log the command output to a file.
|
130
107
|
"""
|
131
|
-
for
|
132
|
-
|
133
|
-
):
|
134
|
-
getattr(self, name)(*args, **kwargs)
|
108
|
+
for _ in self._run_calls(cmd=cmd, parallel=parallel, check=check):
|
109
|
+
pass
|
135
110
|
|
136
111
|
def block_mesh(self, *, check: bool = True) -> None:
|
137
112
|
"""Run blockMesh on this case."""
|
@@ -147,37 +122,35 @@ class FoamCase(_FoamCaseRecipes):
|
|
147
122
|
|
148
123
|
def restore_0_dir(self) -> None:
|
149
124
|
"""Restore the 0 directory from the 0.orig directory."""
|
150
|
-
for
|
151
|
-
|
125
|
+
for _ in self._restore_0_dir_calls():
|
126
|
+
pass
|
152
127
|
|
153
|
-
def copy(self, dst: Optional[Union["os.PathLike[str]", str]] = None) ->
|
128
|
+
def copy(self, dst: Optional[Union["os.PathLike[str]", str]] = None) -> Self:
|
154
129
|
"""
|
155
130
|
Make a copy of this case.
|
156
131
|
|
157
132
|
Use as a context manager to automatically delete the copy when done.
|
158
133
|
|
159
|
-
:param dst: The destination path. If None,
|
134
|
+
:param dst: The destination path. If None, clone to `$FOAM_RUN/foamlib`.
|
160
135
|
"""
|
161
|
-
|
162
|
-
dst = Path(tempfile.mkdtemp(), self.name)
|
136
|
+
cmds = ValuedGenerator(self._copy_calls(dst))
|
163
137
|
|
164
|
-
for
|
165
|
-
|
138
|
+
for _ in cmds:
|
139
|
+
pass
|
166
140
|
|
167
|
-
return
|
141
|
+
return cmds.value
|
168
142
|
|
169
|
-
def clone(self, dst: Optional[Union["os.PathLike[str]", str]] = None) ->
|
143
|
+
def clone(self, dst: Optional[Union["os.PathLike[str]", str]] = None) -> Self:
|
170
144
|
"""
|
171
145
|
Clone this case (make a clean copy).
|
172
146
|
|
173
147
|
Use as a context manager to automatically delete the clone when done.
|
174
148
|
|
175
|
-
:param dst: The destination path. If None, clone to
|
149
|
+
:param dst: The destination path. If None, clone to `$FOAM_RUN/foamlib`.
|
176
150
|
"""
|
177
|
-
|
178
|
-
dst = Path(tempfile.mkdtemp(), self.name)
|
151
|
+
cmds = ValuedGenerator(self._clone_calls(dst))
|
179
152
|
|
180
|
-
for
|
181
|
-
|
153
|
+
for _ in cmds:
|
154
|
+
pass
|
182
155
|
|
183
|
-
return
|
156
|
+
return cmds.value
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import functools
|
2
|
+
import sys
|
3
|
+
from types import TracebackType
|
4
|
+
from typing import (
|
5
|
+
Any,
|
6
|
+
AsyncContextManager,
|
7
|
+
Callable,
|
8
|
+
Generic,
|
9
|
+
Optional,
|
10
|
+
Type,
|
11
|
+
TypeVar,
|
12
|
+
)
|
13
|
+
|
14
|
+
if sys.version_info >= (3, 9):
|
15
|
+
from collections.abc import Generator
|
16
|
+
else:
|
17
|
+
from typing import Generator
|
18
|
+
|
19
|
+
|
20
|
+
Y = TypeVar("Y")
|
21
|
+
S = TypeVar("S")
|
22
|
+
R = TypeVar("R")
|
23
|
+
|
24
|
+
|
25
|
+
class ValuedGenerator(Generic[Y, S, R]):
|
26
|
+
def __init__(self, generator: Generator[Y, S, R]):
|
27
|
+
self._generator = generator
|
28
|
+
|
29
|
+
def __iter__(self) -> Generator[Y, S, R]:
|
30
|
+
self.value = yield from self._generator
|
31
|
+
return self.value
|
32
|
+
|
33
|
+
|
34
|
+
class _AwaitableAsyncContextManager(Generic[R]):
|
35
|
+
def __init__(self, cm: "AsyncContextManager[R]"):
|
36
|
+
self._cm = cm
|
37
|
+
|
38
|
+
def __await__(self) -> Generator[Any, Any, R]:
|
39
|
+
return self._cm.__aenter__().__await__()
|
40
|
+
|
41
|
+
async def __aenter__(self) -> R:
|
42
|
+
return await self._cm.__aenter__()
|
43
|
+
|
44
|
+
async def __aexit__(
|
45
|
+
self,
|
46
|
+
exc_type: Optional[Type[BaseException]],
|
47
|
+
exc_val: Optional[BaseException],
|
48
|
+
exc_tb: Optional[TracebackType],
|
49
|
+
) -> Optional[bool]:
|
50
|
+
return await self._cm.__aexit__(exc_type, exc_val, exc_tb)
|
51
|
+
|
52
|
+
|
53
|
+
def awaitableasynccontextmanager(
|
54
|
+
cm: Callable[..., "AsyncContextManager[R]"],
|
55
|
+
) -> Callable[..., _AwaitableAsyncContextManager[R]]:
|
56
|
+
@functools.wraps(cm)
|
57
|
+
def f(*args: Any, **kwargs: Any) -> _AwaitableAsyncContextManager[R]:
|
58
|
+
return _AwaitableAsyncContextManager(cm(*args, **kwargs))
|
59
|
+
|
60
|
+
return f
|
@@ -11,13 +11,8 @@ if sys.version_info >= (3, 9):
|
|
11
11
|
else:
|
12
12
|
from typing import Iterator, Mapping, MutableMapping, Sequence
|
13
13
|
|
14
|
-
if sys.version_info >= (3, 11):
|
15
|
-
from typing import Self
|
16
|
-
else:
|
17
|
-
from typing_extensions import Self
|
18
|
-
|
19
14
|
from ._base import FoamFileBase
|
20
|
-
from ._io import
|
15
|
+
from ._io import FoamFileIO
|
21
16
|
from ._serialization import Kind, dumpb
|
22
17
|
from ._util import is_sequence
|
23
18
|
|
@@ -33,7 +28,7 @@ class FoamFile(
|
|
33
28
|
Optional[Union[str, Tuple[str, ...]]],
|
34
29
|
Union["FoamFile.Data", "FoamFile.SubDict"],
|
35
30
|
],
|
36
|
-
|
31
|
+
FoamFileIO,
|
37
32
|
):
|
38
33
|
"""
|
39
34
|
An OpenFOAM data file.
|
@@ -101,28 +96,6 @@ class FoamFile(
|
|
101
96
|
|
102
97
|
return cast(FoamFileBase._Dict, ret)
|
103
98
|
|
104
|
-
def create(self, *, exist_ok: bool = False, parents: bool = False) -> Self:
|
105
|
-
"""
|
106
|
-
Create the file.
|
107
|
-
|
108
|
-
:param exist_ok: If False, raise a FileExistsError if the file already exists.
|
109
|
-
|
110
|
-
:param parents: If True, also create parent directories as needed.
|
111
|
-
"""
|
112
|
-
if self.path.exists():
|
113
|
-
if not exist_ok:
|
114
|
-
raise FileExistsError(self.path)
|
115
|
-
else:
|
116
|
-
return self
|
117
|
-
|
118
|
-
if parents:
|
119
|
-
self.path.parent.mkdir(parents=True, exist_ok=True)
|
120
|
-
|
121
|
-
self.path.touch()
|
122
|
-
self._write_header()
|
123
|
-
|
124
|
-
return self
|
125
|
-
|
126
99
|
@property
|
127
100
|
def version(self) -> float:
|
128
101
|
"""Alias of `self["FoamFile", "version"]`."""
|
@@ -185,17 +158,6 @@ class FoamFile(
|
|
185
158
|
def object_(self, value: str) -> None:
|
186
159
|
self["FoamFile", "object"] = value
|
187
160
|
|
188
|
-
def _write_header(self) -> None:
|
189
|
-
assert "FoamFile" not in self
|
190
|
-
assert not self
|
191
|
-
|
192
|
-
self["FoamFile"] = {}
|
193
|
-
self.version = 2.0
|
194
|
-
self.format = "ascii"
|
195
|
-
self.class_ = "dictionary"
|
196
|
-
self.location = f'"{self.path.parent.name}"'
|
197
|
-
self.object_ = self.path.name
|
198
|
-
|
199
161
|
def __getitem__(
|
200
162
|
self, keywords: Optional[Union[str, Tuple[str, ...]]]
|
201
163
|
) -> Union["FoamFile.Data", "FoamFile.SubDict"]:
|
@@ -222,8 +184,24 @@ class FoamFile(
|
|
222
184
|
elif not isinstance(keywords, tuple):
|
223
185
|
keywords = (keywords,)
|
224
186
|
|
225
|
-
|
226
|
-
|
187
|
+
try:
|
188
|
+
write_header = (
|
189
|
+
not self
|
190
|
+
and "FoamFile" not in self
|
191
|
+
and (not keywords or keywords[0] != "FoamFile")
|
192
|
+
)
|
193
|
+
except FileNotFoundError:
|
194
|
+
write_header = not keywords or keywords[0] != "FoamFile"
|
195
|
+
|
196
|
+
if write_header:
|
197
|
+
self["FoamFile"] = {}
|
198
|
+
self.version = 2.0
|
199
|
+
self.format = "ascii"
|
200
|
+
self.class_ = "dictionary"
|
201
|
+
self.location = f'"{self.path.parent.name}"'
|
202
|
+
self.object_ = (
|
203
|
+
self.path.stem if self.path.suffix == ".gz" else self.path.name
|
204
|
+
)
|
227
205
|
|
228
206
|
kind = Kind.DEFAULT
|
229
207
|
if keywords == ("internalField",) or (
|
@@ -253,7 +231,7 @@ class FoamFile(
|
|
253
231
|
self[keywords] = data
|
254
232
|
|
255
233
|
else:
|
256
|
-
contents, parsed = self._read()
|
234
|
+
contents, parsed = self._read(missing_ok=True)
|
257
235
|
|
258
236
|
start, end = parsed.entry_location(keywords, missing_ok=True)
|
259
237
|
|
@@ -18,7 +18,7 @@ else:
|
|
18
18
|
from ._parsing import Parsed
|
19
19
|
|
20
20
|
|
21
|
-
class
|
21
|
+
class FoamFileIO:
|
22
22
|
def __init__(self, path: Union[str, Path]) -> None:
|
23
23
|
self.path = Path(path).absolute()
|
24
24
|
|
@@ -29,7 +29,7 @@ class _FoamFileIO:
|
|
29
29
|
|
30
30
|
def __enter__(self) -> Self:
|
31
31
|
if self.__defer_io == 0:
|
32
|
-
self._read()
|
32
|
+
self._read(missing_ok=True)
|
33
33
|
self.__defer_io += 1
|
34
34
|
return self
|
35
35
|
|
@@ -44,18 +44,26 @@ class _FoamFileIO:
|
|
44
44
|
assert self.__contents is not None
|
45
45
|
self._write(self.__contents)
|
46
46
|
|
47
|
-
def _read(self) -> Tuple[bytes, Parsed]:
|
47
|
+
def _read(self, *, missing_ok: bool = False) -> Tuple[bytes, Parsed]:
|
48
48
|
if not self.__defer_io:
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
contents =
|
49
|
+
try:
|
50
|
+
contents = self.path.read_bytes()
|
51
|
+
except FileNotFoundError:
|
52
|
+
contents = None
|
53
|
+
else:
|
54
|
+
assert isinstance(contents, bytes)
|
55
|
+
if self.path.suffix == ".gz":
|
56
|
+
contents = gzip.decompress(contents)
|
53
57
|
|
54
58
|
if contents != self.__contents:
|
55
59
|
self.__contents = contents
|
56
60
|
self.__parsed = None
|
57
61
|
|
58
|
-
|
62
|
+
if self.__contents is None:
|
63
|
+
if missing_ok:
|
64
|
+
return b"", Parsed(b"")
|
65
|
+
else:
|
66
|
+
raise FileNotFoundError(self.path)
|
59
67
|
|
60
68
|
if self.__parsed is None:
|
61
69
|
parsed = Parsed(self.__contents)
|
@@ -95,68 +95,68 @@ def _unpack_binary_field(
|
|
95
95
|
return [all]
|
96
96
|
|
97
97
|
|
98
|
-
_BINARY_FIELD = (
|
99
|
-
Keyword("nonuniform").suppress()
|
100
|
-
+ Literal("List").suppress()
|
101
|
-
+ Literal("<").suppress()
|
102
|
-
+ (
|
103
|
-
counted_array(
|
104
|
-
CharsNotIn(exact=8),
|
105
|
-
Literal("scalar").suppress()
|
106
|
-
+ Literal(">").suppress()
|
107
|
-
+ common.integer
|
108
|
-
+ Literal("(").suppress(),
|
109
|
-
)
|
110
|
-
| counted_array(
|
111
|
-
CharsNotIn(exact=8 * 3),
|
112
|
-
Literal("vector").suppress()
|
113
|
-
+ Literal(">").suppress()
|
114
|
-
+ common.integer
|
115
|
-
+ Literal("(").suppress(),
|
116
|
-
)
|
117
|
-
| counted_array(
|
118
|
-
CharsNotIn(exact=8 * 6),
|
119
|
-
Literal("symmTensor").suppress()
|
120
|
-
+ Literal(">").suppress()
|
121
|
-
+ common.integer
|
122
|
-
+ Literal("(").suppress(),
|
123
|
-
)
|
124
|
-
| counted_array(
|
125
|
-
CharsNotIn(exact=8 * 9),
|
126
|
-
Literal("tensor").suppress()
|
127
|
-
+ Literal(">").suppress()
|
128
|
-
+ common.integer
|
129
|
-
+ Literal("(").suppress(),
|
130
|
-
)
|
131
|
-
)
|
132
|
-
+ Literal(")").suppress()
|
133
|
-
).set_parse_action(_unpack_binary_field)
|
134
|
-
|
135
|
-
|
136
98
|
_SWITCH = (
|
137
99
|
Keyword("yes") | Keyword("true") | Keyword("on") | Keyword("y") | Keyword("t")
|
138
100
|
).set_parse_action(lambda: True) | (
|
139
101
|
Keyword("no") | Keyword("false") | Keyword("off") | Keyword("n") | Keyword("f")
|
140
102
|
).set_parse_action(lambda: False)
|
141
103
|
_DIMENSIONS = (
|
142
|
-
Literal("[").suppress() + common.number
|
104
|
+
Literal("[").suppress() + common.number[0, 7] + Literal("]").suppress()
|
143
105
|
).set_parse_action(lambda tks: FoamFileBase.DimensionSet(*tks))
|
144
106
|
_TENSOR = _list_of(common.number) | common.number
|
145
107
|
_IDENTIFIER = Combine(
|
146
|
-
Word(identchars + "$", printables, exclude_chars="{(;)}")
|
147
|
-
+ Opt(Literal("(") + Word(printables, exclude_chars="{(;)}") + Literal(")"))
|
108
|
+
Word(identchars + "$", printables, exclude_chars="[{(;)}]")
|
109
|
+
+ Opt(Literal("(") + Word(printables, exclude_chars="[{(;)}]") + Literal(")"))
|
148
110
|
)
|
149
111
|
_DIMENSIONED = (Opt(_IDENTIFIER) + _DIMENSIONS + _TENSOR).set_parse_action(
|
150
112
|
lambda tks: FoamFileBase.Dimensioned(*reversed(tks.as_list()))
|
151
113
|
)
|
152
|
-
_FIELD = (
|
153
|
-
|
154
|
-
|
155
|
-
|
114
|
+
_FIELD = (Keyword("uniform").suppress() + _TENSOR) | (
|
115
|
+
Keyword("nonuniform").suppress()
|
116
|
+
+ (
|
117
|
+
_list_of(_TENSOR)
|
118
|
+
| (
|
119
|
+
Literal("List").suppress()
|
120
|
+
+ Literal("<").suppress()
|
121
|
+
+ (
|
122
|
+
counted_array(
|
123
|
+
CharsNotIn(exact=8),
|
124
|
+
Literal("scalar").suppress()
|
125
|
+
+ Literal(">").suppress()
|
126
|
+
+ common.integer
|
127
|
+
+ Literal("(").suppress(),
|
128
|
+
)
|
129
|
+
| counted_array(
|
130
|
+
CharsNotIn(exact=8 * 3),
|
131
|
+
Literal("vector").suppress()
|
132
|
+
+ Literal(">").suppress()
|
133
|
+
+ common.integer
|
134
|
+
+ Literal("(").suppress(),
|
135
|
+
)
|
136
|
+
| counted_array(
|
137
|
+
CharsNotIn(exact=8 * 6),
|
138
|
+
Literal("symmTensor").suppress()
|
139
|
+
+ Literal(">").suppress()
|
140
|
+
+ common.integer
|
141
|
+
+ Literal("(").suppress(),
|
142
|
+
)
|
143
|
+
| counted_array(
|
144
|
+
CharsNotIn(exact=8 * 9),
|
145
|
+
Literal("tensor").suppress()
|
146
|
+
+ Literal(">").suppress()
|
147
|
+
+ common.integer
|
148
|
+
+ Literal("(").suppress(),
|
149
|
+
)
|
150
|
+
)
|
151
|
+
+ Literal(")").suppress()
|
152
|
+
).set_parse_action(_unpack_binary_field)
|
153
|
+
)
|
156
154
|
)
|
157
155
|
_TOKEN = QuotedString('"', unquote_results=False) | _IDENTIFIER
|
158
156
|
_DATA = Forward()
|
159
|
-
_KEYWORD =
|
157
|
+
_KEYWORD = _TOKEN | _list_of(_IDENTIFIER).set_parse_action(
|
158
|
+
lambda tks: "(" + " ".join(tks[0]) + ")"
|
159
|
+
)
|
160
160
|
_KEYWORD_ENTRY = Dict(Group(_keyword_entry_of(_KEYWORD, _DATA)), asdict=True)
|
161
161
|
_DATA_ENTRY = Forward()
|
162
162
|
_LIST_ENTRY = _KEYWORD_ENTRY | _DATA_ENTRY
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: foamlib
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.6.0
|
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
|
@@ -155,6 +155,25 @@ U = FoamFieldFile(Path(my_pitz) / "0/U")
|
|
155
155
|
print(U.internal_field)
|
156
156
|
```
|
157
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
|
+
|
158
177
|
## Documentation
|
159
178
|
|
160
179
|
For more information, check out the [documentation](https://foamlib.readthedocs.io/).
|
@@ -1,27 +0,0 @@
|
|
1
|
-
from types import TracebackType
|
2
|
-
from typing import Any, AsyncContextManager, Callable, Optional, Type
|
3
|
-
|
4
|
-
|
5
|
-
class _AwaitableAsyncContextManager:
|
6
|
-
def __init__(self, cm: "AsyncContextManager[Any]"):
|
7
|
-
self._cm = cm
|
8
|
-
|
9
|
-
def __await__(self) -> Any:
|
10
|
-
return self._cm.__aenter__().__await__()
|
11
|
-
|
12
|
-
async def __aenter__(self) -> Any:
|
13
|
-
return await self._cm.__aenter__()
|
14
|
-
|
15
|
-
async def __aexit__(
|
16
|
-
self,
|
17
|
-
exc_type: Optional[Type[BaseException]],
|
18
|
-
exc_val: Optional[BaseException],
|
19
|
-
exc_tb: Optional[TracebackType],
|
20
|
-
) -> Any:
|
21
|
-
return await self._cm.__aexit__(exc_type, exc_val, exc_tb)
|
22
|
-
|
23
|
-
|
24
|
-
def awaitableasynccontextmanager(
|
25
|
-
cm: Callable[..., "AsyncContextManager[Any]"],
|
26
|
-
) -> Callable[..., _AwaitableAsyncContextManager]:
|
27
|
-
return lambda *args, **kwargs: _AwaitableAsyncContextManager(cm(*args, **kwargs))
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|