foamlib 0.3.3__tar.gz → 0.3.5__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.3.3 → foamlib-0.3.5}/PKG-INFO +13 -3
- {foamlib-0.3.3 → foamlib-0.3.5}/README.md +12 -2
- {foamlib-0.3.3 → foamlib-0.3.5}/foamlib/__init__.py +1 -1
- {foamlib-0.3.3 → foamlib-0.3.5}/foamlib/_cases.py +3 -13
- {foamlib-0.3.3 → foamlib-0.3.5}/foamlib/_files/__init__.py +1 -2
- foamlib-0.3.5/foamlib/_files/_files.py +340 -0
- {foamlib-0.3.3 → foamlib-0.3.5}/foamlib/_files/_io.py +8 -5
- {foamlib-0.3.3 → foamlib-0.3.5}/foamlib/_files/_parsing.py +61 -4
- foamlib-0.3.5/foamlib/_files/_serialization.py +117 -0
- {foamlib-0.3.3 → foamlib-0.3.5}/foamlib/_util.py +14 -6
- {foamlib-0.3.3 → foamlib-0.3.5}/foamlib.egg-info/PKG-INFO +13 -3
- {foamlib-0.3.3 → foamlib-0.3.5}/foamlib.egg-info/SOURCES.txt +0 -1
- foamlib-0.3.3/foamlib/_files/_fields.py +0 -165
- foamlib-0.3.3/foamlib/_files/_files.py +0 -203
- foamlib-0.3.3/foamlib/_files/_serialization.py +0 -99
- {foamlib-0.3.3 → foamlib-0.3.5}/LICENSE.txt +0 -0
- {foamlib-0.3.3 → foamlib-0.3.5}/foamlib/_files/_base.py +0 -0
- {foamlib-0.3.3 → foamlib-0.3.5}/foamlib/py.typed +0 -0
- {foamlib-0.3.3 → foamlib-0.3.5}/foamlib.egg-info/dependency_links.txt +0 -0
- {foamlib-0.3.3 → foamlib-0.3.5}/foamlib.egg-info/requires.txt +0 -0
- {foamlib-0.3.3 → foamlib-0.3.5}/foamlib.egg-info/top_level.txt +0 -0
- {foamlib-0.3.3 → foamlib-0.3.5}/pyproject.toml +0 -0
- {foamlib-0.3.3 → foamlib-0.3.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: foamlib
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.5
|
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
|
@@ -58,11 +58,11 @@ Requires-Dist: numpy<2,>=1; extra == "docs"
|
|
58
58
|
|
59
59
|
**foamlib** provides a simple, modern and ergonomic Python interface for interacting with [OpenFOAM](https://www.openfoam.com).
|
60
60
|
|
61
|
-
It offers the following classes
|
61
|
+
It offers the following classes:
|
62
62
|
|
63
|
+
* [`FoamFile`](https://foamlib.readthedocs.io/en/stable/#foamlib.FoamFile) (and [`FoamFieldFile`](https://foamlib.readthedocs.io/en/stable/#foamlib.FoamFieldFile)): read-write access to OpenFOAM configuration and field files as if they were Python `dict`s, using `foamlib`'s own parser. Supports both ASCII and binary field formats.
|
63
64
|
* [`FoamCase`](https://foamlib.readthedocs.io/en/stable/#foamlib.FoamCase): a class for manipulating, executing and accessing the results of OpenFOAM cases.
|
64
65
|
* [`AsyncFoamCase`](https://foamlib.readthedocs.io/en/stable/#foamlib.AsyncFoamCase): variant of `FoamCase` with asynchronous methods for running multiple cases at once.
|
65
|
-
* [`FoamFile`](https://foamlib.readthedocs.io/en/stable/#foamlib.FoamFile): read-write access to OpenFOAM configuration and field files as if they were Python `dict`s.
|
66
66
|
|
67
67
|
## Get started
|
68
68
|
|
@@ -130,6 +130,16 @@ async def run_case():
|
|
130
130
|
asyncio.run(run_case())
|
131
131
|
```
|
132
132
|
|
133
|
+
### Parse a field using the [`FoamFieldFile`](https://foamlib.readthedocs.io/en/stable/#foamlib.FoamFieldFile) class directly
|
134
|
+
|
135
|
+
```python
|
136
|
+
from foamlib import FoamFieldFile
|
137
|
+
|
138
|
+
U = FoamFieldFile(Path(my_pitz) / "0/U")
|
139
|
+
|
140
|
+
print(U.internal_field)
|
141
|
+
```
|
142
|
+
|
133
143
|
## Documentation
|
134
144
|
|
135
145
|
For more information, check out the [documentation](https://foamlib.readthedocs.io/).
|
@@ -11,11 +11,11 @@
|
|
11
11
|
|
12
12
|
**foamlib** provides a simple, modern and ergonomic Python interface for interacting with [OpenFOAM](https://www.openfoam.com).
|
13
13
|
|
14
|
-
It offers the following classes
|
14
|
+
It offers the following classes:
|
15
15
|
|
16
|
+
* [`FoamFile`](https://foamlib.readthedocs.io/en/stable/#foamlib.FoamFile) (and [`FoamFieldFile`](https://foamlib.readthedocs.io/en/stable/#foamlib.FoamFieldFile)): read-write access to OpenFOAM configuration and field files as if they were Python `dict`s, using `foamlib`'s own parser. Supports both ASCII and binary field formats.
|
16
17
|
* [`FoamCase`](https://foamlib.readthedocs.io/en/stable/#foamlib.FoamCase): a class for manipulating, executing and accessing the results of OpenFOAM cases.
|
17
18
|
* [`AsyncFoamCase`](https://foamlib.readthedocs.io/en/stable/#foamlib.AsyncFoamCase): variant of `FoamCase` with asynchronous methods for running multiple cases at once.
|
18
|
-
* [`FoamFile`](https://foamlib.readthedocs.io/en/stable/#foamlib.FoamFile): read-write access to OpenFOAM configuration and field files as if they were Python `dict`s.
|
19
19
|
|
20
20
|
## Get started
|
21
21
|
|
@@ -83,6 +83,16 @@ async def run_case():
|
|
83
83
|
asyncio.run(run_case())
|
84
84
|
```
|
85
85
|
|
86
|
+
### Parse a field using the [`FoamFieldFile`](https://foamlib.readthedocs.io/en/stable/#foamlib.FoamFieldFile) class directly
|
87
|
+
|
88
|
+
```python
|
89
|
+
from foamlib import FoamFieldFile
|
90
|
+
|
91
|
+
U = FoamFieldFile(Path(my_pitz) / "0/U")
|
92
|
+
|
93
|
+
print(U.internal_field)
|
94
|
+
```
|
95
|
+
|
86
96
|
## Documentation
|
87
97
|
|
88
98
|
For more information, check out the [documentation](https://foamlib.readthedocs.io/).
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import asyncio
|
2
2
|
import multiprocessing
|
3
|
-
import os
|
4
3
|
import shutil
|
5
4
|
import sys
|
6
5
|
from contextlib import asynccontextmanager
|
@@ -17,13 +16,12 @@ if sys.version_info >= (3, 9):
|
|
17
16
|
Callable,
|
18
17
|
Collection,
|
19
18
|
Iterator,
|
20
|
-
Mapping,
|
21
19
|
Sequence,
|
22
20
|
Set,
|
23
21
|
)
|
24
22
|
else:
|
25
23
|
from typing import AbstractSet as Set
|
26
|
-
from typing import AsyncGenerator, Callable, Collection, Iterator,
|
24
|
+
from typing import AsyncGenerator, Callable, Collection, Iterator, Sequence
|
27
25
|
|
28
26
|
import aioshutil
|
29
27
|
|
@@ -87,7 +85,7 @@ class FoamCaseBase(Sequence["FoamCaseBase.TimeDirectory"]):
|
|
87
85
|
return str(self.path)
|
88
86
|
|
89
87
|
def __repr__(self) -> str:
|
90
|
-
return f"{type(self).
|
88
|
+
return f"{type(self).__qualname__}('{self.path}')"
|
91
89
|
|
92
90
|
def __str__(self) -> str:
|
93
91
|
return str(self.path)
|
@@ -203,12 +201,6 @@ class FoamCaseBase(Sequence["FoamCaseBase.TimeDirectory"]):
|
|
203
201
|
else:
|
204
202
|
return None
|
205
203
|
|
206
|
-
def _env(self) -> Mapping[str, str]:
|
207
|
-
"""Return the environment variables for this case."""
|
208
|
-
env = os.environ.copy()
|
209
|
-
env["PWD"] = str(self.path)
|
210
|
-
return env
|
211
|
-
|
212
204
|
def _parallel_cmd(
|
213
205
|
self, cmd: Union[Sequence[Union[str, Path]], str, Path]
|
214
206
|
) -> Union[Sequence[Union[str, Path]], str]:
|
@@ -298,7 +290,7 @@ class FoamCaseBase(Sequence["FoamCaseBase.TimeDirectory"]):
|
|
298
290
|
return str(self.path)
|
299
291
|
|
300
292
|
def __repr__(self) -> str:
|
301
|
-
return f"{type(self).
|
293
|
+
return f"{type(self).__qualname__}('{self.path}')"
|
302
294
|
|
303
295
|
def __str__(self) -> str:
|
304
296
|
return str(self.path)
|
@@ -359,7 +351,6 @@ class FoamCase(FoamCaseBase):
|
|
359
351
|
cmd,
|
360
352
|
check=check,
|
361
353
|
cwd=self.path,
|
362
|
-
env=self._env(),
|
363
354
|
)
|
364
355
|
else:
|
365
356
|
script_path = self._run_script(parallel=parallel) if script else None
|
@@ -522,7 +513,6 @@ class AsyncFoamCase(FoamCaseBase):
|
|
522
513
|
cmd,
|
523
514
|
check=check,
|
524
515
|
cwd=self.path,
|
525
|
-
env=self._env(),
|
526
516
|
)
|
527
517
|
else:
|
528
518
|
script_path = self._run_script(parallel=parallel) if script else None
|
@@ -0,0 +1,340 @@
|
|
1
|
+
import sys
|
2
|
+
from typing import Any, Tuple, Union, cast
|
3
|
+
|
4
|
+
if sys.version_info >= (3, 9):
|
5
|
+
from collections.abc import Iterator, Mapping, MutableMapping, Sequence
|
6
|
+
else:
|
7
|
+
from typing import Iterator, Mapping, MutableMapping, Sequence
|
8
|
+
|
9
|
+
from ._base import FoamDict
|
10
|
+
from ._io import FoamFileIO
|
11
|
+
from ._serialization import Kind, dumpb
|
12
|
+
|
13
|
+
try:
|
14
|
+
import numpy as np
|
15
|
+
except ModuleNotFoundError:
|
16
|
+
pass
|
17
|
+
|
18
|
+
|
19
|
+
class FoamFile(
|
20
|
+
FoamDict,
|
21
|
+
MutableMapping[
|
22
|
+
Union[str, Tuple[str, ...]], Union["FoamFile.Data", "FoamFile.SubDict"]
|
23
|
+
],
|
24
|
+
FoamFileIO,
|
25
|
+
):
|
26
|
+
"""
|
27
|
+
An OpenFOAM dictionary file.
|
28
|
+
|
29
|
+
Use as a mutable mapping (i.e., like a dict) to access and modify entries.
|
30
|
+
|
31
|
+
Use as a context manager to make multiple changes to the file while saving all changes only once at the end.
|
32
|
+
"""
|
33
|
+
|
34
|
+
class SubDict(
|
35
|
+
FoamDict,
|
36
|
+
MutableMapping[str, Union["FoamFile.Data", "FoamFile.SubDict"]],
|
37
|
+
):
|
38
|
+
"""An OpenFOAM dictionary within a file as a mutable mapping."""
|
39
|
+
|
40
|
+
def __init__(self, _file: "FoamFile", _keywords: Tuple[str, ...]) -> None:
|
41
|
+
self._file = _file
|
42
|
+
self._keywords = _keywords
|
43
|
+
|
44
|
+
def __getitem__(
|
45
|
+
self, keyword: str
|
46
|
+
) -> Union["FoamFile.Data", "FoamFile.SubDict"]:
|
47
|
+
return self._file[(*self._keywords, keyword)]
|
48
|
+
|
49
|
+
def __setitem__(
|
50
|
+
self,
|
51
|
+
keyword: str,
|
52
|
+
data: "FoamFile._SetData",
|
53
|
+
) -> None:
|
54
|
+
self._file[(*self._keywords, keyword)] = data
|
55
|
+
|
56
|
+
def __delitem__(self, keyword: str) -> None:
|
57
|
+
del self._file[(*self._keywords, keyword)]
|
58
|
+
|
59
|
+
def __iter__(self) -> Iterator[str]:
|
60
|
+
return self._file._iter(self._keywords)
|
61
|
+
|
62
|
+
def __contains__(self, keyword: object) -> bool:
|
63
|
+
return (*self._keywords, keyword) in self._file
|
64
|
+
|
65
|
+
def __len__(self) -> int:
|
66
|
+
return len(list(iter(self)))
|
67
|
+
|
68
|
+
def update(self, *args: Any, **kwargs: Any) -> None:
|
69
|
+
with self._file:
|
70
|
+
super().update(*args, **kwargs)
|
71
|
+
|
72
|
+
def clear(self) -> None:
|
73
|
+
with self._file:
|
74
|
+
super().clear()
|
75
|
+
|
76
|
+
def __repr__(self) -> str:
|
77
|
+
return f"{type(self).__qualname__}('{self._file}', {self._keywords})"
|
78
|
+
|
79
|
+
def as_dict(self) -> FoamDict._Dict:
|
80
|
+
"""Return a nested dict representation of the dictionary."""
|
81
|
+
ret = self._file.as_dict()
|
82
|
+
|
83
|
+
for k in self._keywords:
|
84
|
+
assert isinstance(ret, dict)
|
85
|
+
v = ret[k]
|
86
|
+
assert isinstance(v, dict)
|
87
|
+
ret = v
|
88
|
+
|
89
|
+
return ret
|
90
|
+
|
91
|
+
def __getitem__(
|
92
|
+
self, keywords: Union[str, Tuple[str, ...]]
|
93
|
+
) -> Union["FoamFile.Data", "FoamFile.SubDict"]:
|
94
|
+
if not isinstance(keywords, tuple):
|
95
|
+
keywords = (keywords,)
|
96
|
+
|
97
|
+
_, parsed = self._read()
|
98
|
+
|
99
|
+
value = parsed[keywords]
|
100
|
+
|
101
|
+
if value is ...:
|
102
|
+
return FoamFile.SubDict(self, keywords)
|
103
|
+
else:
|
104
|
+
return value # type: ignore [return-value]
|
105
|
+
|
106
|
+
@property
|
107
|
+
def _binary(self) -> bool:
|
108
|
+
return self.get(("FoamFile", "format"), None) == "binary"
|
109
|
+
|
110
|
+
def __setitem__(
|
111
|
+
self,
|
112
|
+
keywords: Union[str, Tuple[str, ...]],
|
113
|
+
data: "FoamFile._SetData",
|
114
|
+
*,
|
115
|
+
assume_field: bool = False,
|
116
|
+
assume_dimensions: bool = False,
|
117
|
+
) -> None:
|
118
|
+
if not isinstance(keywords, tuple):
|
119
|
+
keywords = (keywords,)
|
120
|
+
|
121
|
+
kind = Kind.DEFAULT
|
122
|
+
if keywords == ("internalField",) or (
|
123
|
+
len(keywords) == 3
|
124
|
+
and keywords[0] == "boundaryField"
|
125
|
+
and keywords[2] == "value"
|
126
|
+
):
|
127
|
+
kind = Kind.BINARY_FIELD if self._binary else Kind.FIELD
|
128
|
+
elif keywords == ("dimensions",):
|
129
|
+
kind = Kind.DIMENSIONS
|
130
|
+
|
131
|
+
contents, parsed = self._read()
|
132
|
+
|
133
|
+
if isinstance(data, Mapping):
|
134
|
+
with self:
|
135
|
+
if isinstance(data, FoamDict):
|
136
|
+
data = data.as_dict()
|
137
|
+
|
138
|
+
start, end = parsed.entry_location(keywords, missing_ok=True)
|
139
|
+
|
140
|
+
self._write(
|
141
|
+
contents[:start]
|
142
|
+
+ b"\n"
|
143
|
+
+ dumpb({keywords[-1]: {}})
|
144
|
+
+ b"\n"
|
145
|
+
+ contents[end:]
|
146
|
+
)
|
147
|
+
|
148
|
+
for k, v in data.items():
|
149
|
+
self[(*keywords, k)] = v
|
150
|
+
else:
|
151
|
+
start, end = parsed.entry_location(keywords, missing_ok=True)
|
152
|
+
|
153
|
+
self._write(
|
154
|
+
contents[:start]
|
155
|
+
+ b"\n"
|
156
|
+
+ dumpb({keywords[-1]: data}, kind=kind)
|
157
|
+
+ b"\n"
|
158
|
+
+ contents[end:]
|
159
|
+
)
|
160
|
+
|
161
|
+
def __delitem__(self, keywords: Union[str, Tuple[str, ...]]) -> None:
|
162
|
+
if not isinstance(keywords, tuple):
|
163
|
+
keywords = (keywords,)
|
164
|
+
|
165
|
+
contents, parsed = self._read()
|
166
|
+
|
167
|
+
start, end = parsed.entry_location(keywords)
|
168
|
+
|
169
|
+
self._write(contents[:start] + contents[end:])
|
170
|
+
|
171
|
+
def _iter(self, keywords: Union[str, Tuple[str, ...]] = ()) -> Iterator[str]:
|
172
|
+
if not isinstance(keywords, tuple):
|
173
|
+
keywords = (keywords,)
|
174
|
+
|
175
|
+
_, parsed = self._read()
|
176
|
+
|
177
|
+
yield from (k[-1] for k in parsed if k[:-1] == keywords)
|
178
|
+
|
179
|
+
def __iter__(self) -> Iterator[str]:
|
180
|
+
return self._iter()
|
181
|
+
|
182
|
+
def __contains__(self, keywords: object) -> bool:
|
183
|
+
if not isinstance(keywords, tuple):
|
184
|
+
keywords = (keywords,)
|
185
|
+
_, parsed = self._read()
|
186
|
+
return keywords in parsed
|
187
|
+
|
188
|
+
def __len__(self) -> int:
|
189
|
+
return len(list(iter(self)))
|
190
|
+
|
191
|
+
def update(self, *args: Any, **kwargs: Any) -> None:
|
192
|
+
with self:
|
193
|
+
super().update(*args, **kwargs)
|
194
|
+
|
195
|
+
def clear(self) -> None:
|
196
|
+
with self:
|
197
|
+
super().clear()
|
198
|
+
|
199
|
+
def __fspath__(self) -> str:
|
200
|
+
return str(self.path)
|
201
|
+
|
202
|
+
def as_dict(self) -> FoamDict._Dict:
|
203
|
+
"""Return a nested dict representation of the file."""
|
204
|
+
_, parsed = self._read()
|
205
|
+
return parsed.as_dict()
|
206
|
+
|
207
|
+
|
208
|
+
class FoamFieldFile(FoamFile):
|
209
|
+
"""An OpenFOAM dictionary file representing a field as a mutable mapping."""
|
210
|
+
|
211
|
+
class BoundariesSubDict(FoamFile.SubDict):
|
212
|
+
def __getitem__(self, keyword: str) -> "FoamFieldFile.BoundarySubDict":
|
213
|
+
value = super().__getitem__(keyword)
|
214
|
+
if not isinstance(value, FoamFieldFile.BoundarySubDict):
|
215
|
+
assert not isinstance(value, FoamFile.SubDict)
|
216
|
+
raise TypeError(f"boundary {keyword} is not a dictionary")
|
217
|
+
return value
|
218
|
+
|
219
|
+
class BoundarySubDict(FoamFile.SubDict):
|
220
|
+
"""An OpenFOAM dictionary representing a boundary condition as a mutable mapping."""
|
221
|
+
|
222
|
+
@property
|
223
|
+
def type(self) -> str:
|
224
|
+
"""Alias of `self["type"]`."""
|
225
|
+
ret = self["type"]
|
226
|
+
if not isinstance(ret, str):
|
227
|
+
raise TypeError("type is not a string")
|
228
|
+
return ret
|
229
|
+
|
230
|
+
@type.setter
|
231
|
+
def type(self, data: str) -> None:
|
232
|
+
self["type"] = data
|
233
|
+
|
234
|
+
@property
|
235
|
+
def value(
|
236
|
+
self,
|
237
|
+
) -> Union[
|
238
|
+
int,
|
239
|
+
float,
|
240
|
+
Sequence[Union[int, float, Sequence[Union[int, float]]]],
|
241
|
+
"np.ndarray[Tuple[()], np.dtype[np.generic]]",
|
242
|
+
"np.ndarray[Tuple[int], np.dtype[np.generic]]",
|
243
|
+
"np.ndarray[Tuple[int, int], np.dtype[np.generic]]",
|
244
|
+
]:
|
245
|
+
"""Alias of `self["value"]`."""
|
246
|
+
ret = self["value"]
|
247
|
+
if not isinstance(ret, (int, float, Sequence)):
|
248
|
+
raise TypeError("value is not a field")
|
249
|
+
return cast(
|
250
|
+
Union[
|
251
|
+
int, float, Sequence[Union[int, float, Sequence[Union[int, float]]]]
|
252
|
+
],
|
253
|
+
ret,
|
254
|
+
)
|
255
|
+
|
256
|
+
@value.setter
|
257
|
+
def value(
|
258
|
+
self,
|
259
|
+
value: Union[
|
260
|
+
int,
|
261
|
+
float,
|
262
|
+
Sequence[Union[int, float, Sequence[Union[int, float]]]],
|
263
|
+
"np.ndarray[Tuple[()], np.dtype[np.generic]]",
|
264
|
+
"np.ndarray[Tuple[int], np.dtype[np.generic]]",
|
265
|
+
"np.ndarray[Tuple[int, int], np.dtype[np.generic]]",
|
266
|
+
],
|
267
|
+
) -> None:
|
268
|
+
self["value"] = value
|
269
|
+
|
270
|
+
@value.deleter
|
271
|
+
def value(self) -> None:
|
272
|
+
del self["value"]
|
273
|
+
|
274
|
+
def __getitem__(
|
275
|
+
self, keywords: Union[str, Tuple[str, ...]]
|
276
|
+
) -> Union[FoamFile.Data, FoamFile.SubDict]:
|
277
|
+
if not isinstance(keywords, tuple):
|
278
|
+
keywords = (keywords,)
|
279
|
+
|
280
|
+
ret = super().__getitem__(keywords)
|
281
|
+
if keywords[0] == "boundaryField" and isinstance(ret, FoamFile.SubDict):
|
282
|
+
if len(keywords) == 1:
|
283
|
+
ret = FoamFieldFile.BoundariesSubDict(self, keywords)
|
284
|
+
elif len(keywords) == 2:
|
285
|
+
ret = FoamFieldFile.BoundarySubDict(self, keywords)
|
286
|
+
return ret
|
287
|
+
|
288
|
+
@property
|
289
|
+
def dimensions(self) -> Union[FoamFile.DimensionSet, Sequence[Union[int, float]]]:
|
290
|
+
"""Alias of `self["dimensions"]`."""
|
291
|
+
ret = self["dimensions"]
|
292
|
+
if not isinstance(ret, FoamFile.DimensionSet):
|
293
|
+
raise TypeError("dimensions is not a DimensionSet")
|
294
|
+
return ret
|
295
|
+
|
296
|
+
@dimensions.setter
|
297
|
+
def dimensions(
|
298
|
+
self, value: Union[FoamFile.DimensionSet, Sequence[Union[int, float]]]
|
299
|
+
) -> None:
|
300
|
+
self["dimensions"] = value
|
301
|
+
|
302
|
+
@property
|
303
|
+
def internal_field(
|
304
|
+
self,
|
305
|
+
) -> Union[
|
306
|
+
int,
|
307
|
+
float,
|
308
|
+
Sequence[Union[int, float, Sequence[Union[int, float]]]],
|
309
|
+
"np.ndarray[Tuple[()], np.dtype[np.generic]]",
|
310
|
+
"np.ndarray[Tuple[int], np.dtype[np.generic]]",
|
311
|
+
"np.ndarray[Tuple[int, int], np.dtype[np.generic]]",
|
312
|
+
]:
|
313
|
+
"""Alias of `self["internalField"]`."""
|
314
|
+
ret = self["internalField"]
|
315
|
+
if not isinstance(ret, (int, float, Sequence)):
|
316
|
+
raise TypeError("internalField is not a field")
|
317
|
+
return cast(Union[int, float, Sequence[Union[int, float]]], ret)
|
318
|
+
|
319
|
+
@internal_field.setter
|
320
|
+
def internal_field(
|
321
|
+
self,
|
322
|
+
value: Union[
|
323
|
+
int,
|
324
|
+
float,
|
325
|
+
Sequence[Union[int, float, Sequence[Union[int, float]]]],
|
326
|
+
"np.ndarray[Tuple[()], np.dtype[np.generic]]",
|
327
|
+
"np.ndarray[Tuple[int], np.dtype[np.generic]]",
|
328
|
+
"np.ndarray[Tuple[int, int], np.dtype[np.generic]]",
|
329
|
+
],
|
330
|
+
) -> None:
|
331
|
+
self["internalField"] = value
|
332
|
+
|
333
|
+
@property
|
334
|
+
def boundary_field(self) -> "FoamFieldFile.BoundariesSubDict":
|
335
|
+
"""Alias of `self["boundaryField"]`."""
|
336
|
+
ret = self["boundaryField"]
|
337
|
+
if not isinstance(ret, FoamFieldFile.BoundariesSubDict):
|
338
|
+
assert not isinstance(ret, FoamFile.SubDict)
|
339
|
+
raise TypeError("boundaryField is not a dictionary")
|
340
|
+
return ret
|
@@ -25,7 +25,7 @@ class FoamFileIO:
|
|
25
25
|
elif not self.path.is_file():
|
26
26
|
raise FileNotFoundError(self.path)
|
27
27
|
|
28
|
-
self.__contents: Optional[
|
28
|
+
self.__contents: Optional[bytes] = None
|
29
29
|
self.__parsed: Optional[Parsed] = None
|
30
30
|
self.__defer_io = 0
|
31
31
|
self.__dirty = False
|
@@ -48,9 +48,9 @@ class FoamFileIO:
|
|
48
48
|
self._write(self.__contents)
|
49
49
|
assert not self.__dirty
|
50
50
|
|
51
|
-
def _read(self) -> Tuple[
|
51
|
+
def _read(self) -> Tuple[bytes, Parsed]:
|
52
52
|
if not self.__defer_io:
|
53
|
-
contents = self.path.
|
53
|
+
contents = self.path.read_bytes()
|
54
54
|
if contents != self.__contents:
|
55
55
|
self.__contents = contents
|
56
56
|
self.__parsed = None
|
@@ -63,11 +63,14 @@ class FoamFileIO:
|
|
63
63
|
|
64
64
|
return self.__contents, deepcopy(self.__parsed)
|
65
65
|
|
66
|
-
def _write(self, contents:
|
66
|
+
def _write(self, contents: bytes) -> None:
|
67
67
|
self.__contents = contents
|
68
68
|
self.__parsed = None
|
69
69
|
if not self.__defer_io:
|
70
|
-
self.path.
|
70
|
+
self.path.write_bytes(contents)
|
71
71
|
self.__dirty = False
|
72
72
|
else:
|
73
73
|
self.__dirty = True
|
74
|
+
|
75
|
+
def __repr__(self) -> str:
|
76
|
+
return f"{type(self).__qualname__}('{self.path}')"
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import array
|
1
2
|
import sys
|
2
3
|
from typing import Tuple, Union
|
3
4
|
|
@@ -12,6 +13,7 @@ else:
|
|
12
13
|
from typing import Any as EllipsisType
|
13
14
|
|
14
15
|
from pyparsing import (
|
16
|
+
CharsNotIn,
|
15
17
|
Dict,
|
16
18
|
Forward,
|
17
19
|
Group,
|
@@ -74,6 +76,56 @@ def _dictionary_of(
|
|
74
76
|
return Dict(Group(keyword_entry)[len], asdict=not located)
|
75
77
|
|
76
78
|
|
79
|
+
_binary_contents = Forward()
|
80
|
+
|
81
|
+
|
82
|
+
def _binary_field_parse_action(tks: ParseResults) -> None:
|
83
|
+
global _binary_contents
|
84
|
+
|
85
|
+
kind, count = tks
|
86
|
+
if kind == "scalar":
|
87
|
+
elsize = 1
|
88
|
+
elif kind == "vector":
|
89
|
+
elsize = 3
|
90
|
+
elif kind == "symmTensor":
|
91
|
+
elsize = 6
|
92
|
+
elif kind == "tensor":
|
93
|
+
elsize = 9
|
94
|
+
|
95
|
+
def unpack(
|
96
|
+
tks: ParseResults,
|
97
|
+
) -> Sequence[Union[Sequence[float], Sequence[Sequence[float]]]]:
|
98
|
+
bytes_ = tks[0].encode("latin-1")
|
99
|
+
|
100
|
+
arr = array.array("d", bytes_)
|
101
|
+
|
102
|
+
if elsize != 1:
|
103
|
+
all = [arr[i : i + elsize].tolist() for i in range(0, len(arr), elsize)]
|
104
|
+
else:
|
105
|
+
all = arr.tolist()
|
106
|
+
|
107
|
+
return [all]
|
108
|
+
|
109
|
+
_binary_contents <<= CharsNotIn(exact=count * elsize * 8).set_parse_action(unpack)
|
110
|
+
|
111
|
+
tks.clear() # type: ignore [no-untyped-call]
|
112
|
+
|
113
|
+
|
114
|
+
_BINARY_FIELD = (
|
115
|
+
(
|
116
|
+
Keyword("nonuniform").suppress()
|
117
|
+
+ Literal("List").suppress()
|
118
|
+
+ Literal("<").suppress()
|
119
|
+
+ common.identifier
|
120
|
+
+ Literal(">").suppress()
|
121
|
+
+ common.integer
|
122
|
+
+ Literal("(").suppress()
|
123
|
+
).set_parse_action(_binary_field_parse_action, call_during_try=True)
|
124
|
+
+ _binary_contents
|
125
|
+
+ Literal(")").suppress()
|
126
|
+
)
|
127
|
+
|
128
|
+
|
77
129
|
_SWITCH = (
|
78
130
|
Keyword("yes") | Keyword("true") | Keyword("on") | Keyword("y") | Keyword("t")
|
79
131
|
).set_parse_action(lambda: True) | (
|
@@ -87,8 +139,10 @@ _IDENTIFIER = Word(identchars + "$", printables, exclude_chars=";")
|
|
87
139
|
_DIMENSIONED = (Opt(_IDENTIFIER) + _DIMENSIONS + _TENSOR).set_parse_action(
|
88
140
|
lambda tks: FoamDict.Dimensioned(*reversed(tks.as_list()))
|
89
141
|
)
|
90
|
-
_FIELD = (
|
91
|
-
Keyword("
|
142
|
+
_FIELD = (
|
143
|
+
(Keyword("uniform").suppress() + _TENSOR)
|
144
|
+
| (Keyword("nonuniform").suppress() + _list_of(_TENSOR))
|
145
|
+
| _BINARY_FIELD
|
92
146
|
)
|
93
147
|
_TOKEN = QuotedString('"', unquote_results=False) | _IDENTIFIER
|
94
148
|
_DATA = Forward()
|
@@ -109,16 +163,19 @@ _FILE = (
|
|
109
163
|
.ignore(c_style_comment)
|
110
164
|
.ignore(cpp_style_comment)
|
111
165
|
.ignore(Literal("#include") + ... + LineEnd()) # type: ignore [no-untyped-call]
|
166
|
+
.parse_with_tabs()
|
112
167
|
)
|
113
168
|
|
114
169
|
|
115
170
|
class Parsed(Mapping[Tuple[str, ...], Union[FoamDict.Data, EllipsisType]]):
|
116
|
-
def __init__(self, contents:
|
171
|
+
def __init__(self, contents: bytes) -> None:
|
117
172
|
self._parsed: MutableMapping[
|
118
173
|
Tuple[str, ...],
|
119
174
|
Tuple[int, Union[FoamDict.Data, EllipsisType], int],
|
120
175
|
] = {}
|
121
|
-
for parse_result in _FILE.parse_string(
|
176
|
+
for parse_result in _FILE.parse_string(
|
177
|
+
contents.decode("latin-1"), parse_all=True
|
178
|
+
):
|
122
179
|
self._parsed.update(self._flatten_result(parse_result))
|
123
180
|
|
124
181
|
@staticmethod
|
@@ -0,0 +1,117 @@
|
|
1
|
+
import array
|
2
|
+
import itertools
|
3
|
+
import sys
|
4
|
+
from enum import Enum, auto
|
5
|
+
|
6
|
+
if sys.version_info >= (3, 9):
|
7
|
+
from collections.abc import Mapping
|
8
|
+
else:
|
9
|
+
from typing import Mapping
|
10
|
+
|
11
|
+
from .._util import is_sequence
|
12
|
+
from ._base import FoamDict
|
13
|
+
|
14
|
+
try:
|
15
|
+
import numpy as np
|
16
|
+
|
17
|
+
numpy = True
|
18
|
+
except ModuleNotFoundError:
|
19
|
+
numpy = False
|
20
|
+
|
21
|
+
|
22
|
+
class Kind(Enum):
|
23
|
+
DEFAULT = auto()
|
24
|
+
LIST_ENTRY = auto()
|
25
|
+
FIELD = auto()
|
26
|
+
BINARY_FIELD = auto()
|
27
|
+
DIMENSIONS = auto()
|
28
|
+
|
29
|
+
|
30
|
+
def dumpb(
|
31
|
+
data: FoamDict._SetData,
|
32
|
+
*,
|
33
|
+
kind: Kind = Kind.DEFAULT,
|
34
|
+
) -> bytes:
|
35
|
+
if numpy and isinstance(data, np.ndarray):
|
36
|
+
return dumpb(data.tolist(), kind=kind)
|
37
|
+
|
38
|
+
elif isinstance(data, Mapping):
|
39
|
+
entries = []
|
40
|
+
for k, v in data.items():
|
41
|
+
b = dumpb(v, kind=kind)
|
42
|
+
if isinstance(v, Mapping):
|
43
|
+
entries.append(dumpb(k) + b"\n" + b"{\n" + b + b"\n}")
|
44
|
+
elif b:
|
45
|
+
entries.append(dumpb(k) + b" " + b + b";")
|
46
|
+
else:
|
47
|
+
entries.append(dumpb(k) + b";")
|
48
|
+
|
49
|
+
return b"\n".join(entries)
|
50
|
+
|
51
|
+
elif isinstance(data, FoamDict.DimensionSet) or (
|
52
|
+
kind == Kind.DIMENSIONS and is_sequence(data) and len(data) == 7
|
53
|
+
):
|
54
|
+
return b"[" + b" ".join(dumpb(v) for v in data) + b"]"
|
55
|
+
|
56
|
+
elif (kind == Kind.FIELD or kind == Kind.BINARY_FIELD) and (
|
57
|
+
isinstance(data, (int, float))
|
58
|
+
or is_sequence(data)
|
59
|
+
and data
|
60
|
+
and isinstance(data[0], (int, float))
|
61
|
+
and len(data) in (3, 6, 9)
|
62
|
+
):
|
63
|
+
return b"uniform " + dumpb(data)
|
64
|
+
|
65
|
+
elif (kind == Kind.FIELD or kind == Kind.BINARY_FIELD) and is_sequence(data):
|
66
|
+
if isinstance(data[0], (int, float)):
|
67
|
+
tensor_kind = b"scalar"
|
68
|
+
elif len(data[0]) == 3:
|
69
|
+
tensor_kind = b"vector"
|
70
|
+
elif len(data[0]) == 6:
|
71
|
+
tensor_kind = b"symmTensor"
|
72
|
+
elif len(data[0]) == 9:
|
73
|
+
tensor_kind = b"tensor"
|
74
|
+
else:
|
75
|
+
return dumpb(data)
|
76
|
+
|
77
|
+
if kind == Kind.BINARY_FIELD:
|
78
|
+
if tensor_kind == b"scalar":
|
79
|
+
contents = b"(" + array.array("d", data).tobytes() + b")"
|
80
|
+
else:
|
81
|
+
contents = (
|
82
|
+
b"("
|
83
|
+
+ array.array("d", itertools.chain.from_iterable(data)).tobytes()
|
84
|
+
+ b")"
|
85
|
+
)
|
86
|
+
else:
|
87
|
+
contents = dumpb(data)
|
88
|
+
|
89
|
+
return b"nonuniform List<" + tensor_kind + b"> " + dumpb(len(data)) + contents
|
90
|
+
|
91
|
+
elif kind != Kind.LIST_ENTRY and isinstance(data, tuple):
|
92
|
+
return b" ".join(dumpb(v) for v in data)
|
93
|
+
|
94
|
+
elif isinstance(data, FoamDict.Dimensioned):
|
95
|
+
if data.name is not None:
|
96
|
+
return (
|
97
|
+
dumpb(data.name)
|
98
|
+
+ b" "
|
99
|
+
+ dumpb(data.dimensions, kind=Kind.DIMENSIONS)
|
100
|
+
+ b" "
|
101
|
+
+ dumpb(data.value)
|
102
|
+
)
|
103
|
+
else:
|
104
|
+
return (
|
105
|
+
dumpb(data.dimensions, kind=Kind.DIMENSIONS) + b" " + dumpb(data.value)
|
106
|
+
)
|
107
|
+
|
108
|
+
elif is_sequence(data):
|
109
|
+
return b"(" + b" ".join(dumpb(v, kind=Kind.LIST_ENTRY) for v in data) + b")"
|
110
|
+
|
111
|
+
elif data is True:
|
112
|
+
return b"yes"
|
113
|
+
elif data is False:
|
114
|
+
return b"no"
|
115
|
+
|
116
|
+
else:
|
117
|
+
return str(data).encode("latin-1")
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import asyncio
|
2
|
+
import os
|
2
3
|
import subprocess
|
3
4
|
import sys
|
4
5
|
from pathlib import Path
|
@@ -47,12 +48,20 @@ def _check(
|
|
47
48
|
warn(f"Command {cmd} printed to stderr.\n{stderr}", CalledProcessWarning)
|
48
49
|
|
49
50
|
|
51
|
+
def _env(cwd: Optional[Union[str, Path]] = None) -> Optional[Mapping[str, str]]:
|
52
|
+
if cwd is not None:
|
53
|
+
env = os.environ.copy()
|
54
|
+
env["PWD"] = str(cwd)
|
55
|
+
return env
|
56
|
+
else:
|
57
|
+
return None
|
58
|
+
|
59
|
+
|
50
60
|
def run_process(
|
51
61
|
cmd: Union[Sequence[Union[str, Path]], str, Path],
|
52
62
|
*,
|
53
63
|
check: bool = True,
|
54
64
|
cwd: Union[None, str, Path] = None,
|
55
|
-
env: Union[None, Mapping[str, str]] = None,
|
56
65
|
) -> None:
|
57
66
|
shell = not is_sequence(cmd)
|
58
67
|
|
@@ -65,8 +74,8 @@ def run_process(
|
|
65
74
|
proc = subprocess.run(
|
66
75
|
cmd,
|
67
76
|
cwd=cwd,
|
68
|
-
env=
|
69
|
-
stdout=
|
77
|
+
env=_env(cwd),
|
78
|
+
stdout=None,
|
70
79
|
stderr=subprocess.PIPE if check else subprocess.DEVNULL,
|
71
80
|
text=True,
|
72
81
|
shell=shell,
|
@@ -81,13 +90,12 @@ async def run_process_async(
|
|
81
90
|
*,
|
82
91
|
check: bool = True,
|
83
92
|
cwd: Union[None, str, Path] = None,
|
84
|
-
env: Union[None, Mapping[str, str]] = None,
|
85
93
|
) -> None:
|
86
94
|
if not is_sequence(cmd):
|
87
95
|
proc = await asyncio.create_subprocess_shell(
|
88
96
|
str(cmd),
|
89
97
|
cwd=cwd,
|
90
|
-
env=
|
98
|
+
env=_env(cwd),
|
91
99
|
stdout=asyncio.subprocess.DEVNULL,
|
92
100
|
stderr=asyncio.subprocess.PIPE if check else asyncio.subprocess.DEVNULL,
|
93
101
|
)
|
@@ -98,7 +106,7 @@ async def run_process_async(
|
|
98
106
|
proc = await asyncio.create_subprocess_exec(
|
99
107
|
*cmd,
|
100
108
|
cwd=cwd,
|
101
|
-
env=
|
109
|
+
env=_env(cwd),
|
102
110
|
stdout=asyncio.subprocess.DEVNULL,
|
103
111
|
stderr=asyncio.subprocess.PIPE if check else asyncio.subprocess.DEVNULL,
|
104
112
|
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: foamlib
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.5
|
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
|
@@ -58,11 +58,11 @@ Requires-Dist: numpy<2,>=1; extra == "docs"
|
|
58
58
|
|
59
59
|
**foamlib** provides a simple, modern and ergonomic Python interface for interacting with [OpenFOAM](https://www.openfoam.com).
|
60
60
|
|
61
|
-
It offers the following classes
|
61
|
+
It offers the following classes:
|
62
62
|
|
63
|
+
* [`FoamFile`](https://foamlib.readthedocs.io/en/stable/#foamlib.FoamFile) (and [`FoamFieldFile`](https://foamlib.readthedocs.io/en/stable/#foamlib.FoamFieldFile)): read-write access to OpenFOAM configuration and field files as if they were Python `dict`s, using `foamlib`'s own parser. Supports both ASCII and binary field formats.
|
63
64
|
* [`FoamCase`](https://foamlib.readthedocs.io/en/stable/#foamlib.FoamCase): a class for manipulating, executing and accessing the results of OpenFOAM cases.
|
64
65
|
* [`AsyncFoamCase`](https://foamlib.readthedocs.io/en/stable/#foamlib.AsyncFoamCase): variant of `FoamCase` with asynchronous methods for running multiple cases at once.
|
65
|
-
* [`FoamFile`](https://foamlib.readthedocs.io/en/stable/#foamlib.FoamFile): read-write access to OpenFOAM configuration and field files as if they were Python `dict`s.
|
66
66
|
|
67
67
|
## Get started
|
68
68
|
|
@@ -130,6 +130,16 @@ async def run_case():
|
|
130
130
|
asyncio.run(run_case())
|
131
131
|
```
|
132
132
|
|
133
|
+
### Parse a field using the [`FoamFieldFile`](https://foamlib.readthedocs.io/en/stable/#foamlib.FoamFieldFile) class directly
|
134
|
+
|
135
|
+
```python
|
136
|
+
from foamlib import FoamFieldFile
|
137
|
+
|
138
|
+
U = FoamFieldFile(Path(my_pitz) / "0/U")
|
139
|
+
|
140
|
+
print(U.internal_field)
|
141
|
+
```
|
142
|
+
|
133
143
|
## Documentation
|
134
144
|
|
135
145
|
For more information, check out the [documentation](https://foamlib.readthedocs.io/).
|
@@ -1,165 +0,0 @@
|
|
1
|
-
import sys
|
2
|
-
from typing import Any, Tuple, Union, cast
|
3
|
-
|
4
|
-
if sys.version_info >= (3, 9):
|
5
|
-
from collections.abc import Sequence
|
6
|
-
else:
|
7
|
-
from typing import Sequence
|
8
|
-
|
9
|
-
from ._files import FoamFile
|
10
|
-
|
11
|
-
try:
|
12
|
-
import numpy as np
|
13
|
-
except ModuleNotFoundError:
|
14
|
-
pass
|
15
|
-
|
16
|
-
|
17
|
-
class FoamFieldFile(FoamFile):
|
18
|
-
"""An OpenFOAM dictionary file representing a field as a mutable mapping."""
|
19
|
-
|
20
|
-
class BoundariesSubDict(FoamFile.SubDict):
|
21
|
-
def __getitem__(self, keyword: str) -> "FoamFieldFile.BoundarySubDict":
|
22
|
-
value = super().__getitem__(keyword)
|
23
|
-
if not isinstance(value, FoamFieldFile.BoundarySubDict):
|
24
|
-
assert not isinstance(value, FoamFile.SubDict)
|
25
|
-
raise TypeError(f"boundary {keyword} is not a dictionary")
|
26
|
-
return value
|
27
|
-
|
28
|
-
class BoundarySubDict(FoamFile.SubDict):
|
29
|
-
"""An OpenFOAM dictionary representing a boundary condition as a mutable mapping."""
|
30
|
-
|
31
|
-
def __setitem__(
|
32
|
-
self,
|
33
|
-
keyword: str,
|
34
|
-
data: FoamFile._SetData,
|
35
|
-
) -> None:
|
36
|
-
if keyword == "value":
|
37
|
-
self._setitem(keyword, data, assume_field=True)
|
38
|
-
else:
|
39
|
-
self._setitem(keyword, data)
|
40
|
-
|
41
|
-
@property
|
42
|
-
def type(self) -> str:
|
43
|
-
"""Alias of `self["type"]`."""
|
44
|
-
ret = self["type"]
|
45
|
-
if not isinstance(ret, str):
|
46
|
-
raise TypeError("type is not a string")
|
47
|
-
return ret
|
48
|
-
|
49
|
-
@type.setter
|
50
|
-
def type(self, data: str) -> None:
|
51
|
-
self["type"] = data
|
52
|
-
|
53
|
-
@property
|
54
|
-
def value(
|
55
|
-
self,
|
56
|
-
) -> Union[
|
57
|
-
int,
|
58
|
-
float,
|
59
|
-
Sequence[Union[int, float, Sequence[Union[int, float]]]],
|
60
|
-
"np.ndarray[Tuple[()], np.dtype[np.generic]]",
|
61
|
-
"np.ndarray[Tuple[int], np.dtype[np.generic]]",
|
62
|
-
"np.ndarray[Tuple[int, int], np.dtype[np.generic]]",
|
63
|
-
]:
|
64
|
-
"""Alias of `self["value"]`."""
|
65
|
-
ret = self["value"]
|
66
|
-
if not isinstance(ret, (int, float, Sequence)):
|
67
|
-
raise TypeError("value is not a field")
|
68
|
-
return cast(Union[int, float, Sequence[Union[int, float]]], ret)
|
69
|
-
|
70
|
-
@value.setter
|
71
|
-
def value(
|
72
|
-
self,
|
73
|
-
value: Union[
|
74
|
-
int,
|
75
|
-
float,
|
76
|
-
Sequence[Union[int, float, Sequence[Union[int, float]]]],
|
77
|
-
"np.ndarray[Tuple[()], np.dtype[np.generic]]",
|
78
|
-
"np.ndarray[Tuple[int], np.dtype[np.generic]]",
|
79
|
-
"np.ndarray[Tuple[int, int], np.dtype[np.generic]]",
|
80
|
-
],
|
81
|
-
) -> None:
|
82
|
-
self["value"] = value
|
83
|
-
|
84
|
-
@value.deleter
|
85
|
-
def value(self) -> None:
|
86
|
-
del self["value"]
|
87
|
-
|
88
|
-
def __getitem__(
|
89
|
-
self, keywords: Union[str, Tuple[str, ...]]
|
90
|
-
) -> Union[FoamFile.Data, FoamFile.SubDict]:
|
91
|
-
if not isinstance(keywords, tuple):
|
92
|
-
keywords = (keywords,)
|
93
|
-
|
94
|
-
ret = super().__getitem__(keywords)
|
95
|
-
if keywords[0] == "boundaryField" and isinstance(ret, FoamFile.SubDict):
|
96
|
-
if len(keywords) == 1:
|
97
|
-
ret = FoamFieldFile.BoundariesSubDict(self, keywords)
|
98
|
-
elif len(keywords) == 2:
|
99
|
-
ret = FoamFieldFile.BoundarySubDict(self, keywords)
|
100
|
-
return ret
|
101
|
-
|
102
|
-
def __setitem__(self, keywords: Union[str, Tuple[str, ...]], value: Any) -> None:
|
103
|
-
if not isinstance(keywords, tuple):
|
104
|
-
keywords = (keywords,)
|
105
|
-
|
106
|
-
if keywords == ("internalField",):
|
107
|
-
self._setitem(keywords, value, assume_field=True)
|
108
|
-
elif keywords == ("dimensions",):
|
109
|
-
self._setitem(keywords, value, assume_dimensions=True)
|
110
|
-
else:
|
111
|
-
self._setitem(keywords, value)
|
112
|
-
|
113
|
-
@property
|
114
|
-
def dimensions(self) -> FoamFile.DimensionSet:
|
115
|
-
"""Alias of `self["dimensions"]`."""
|
116
|
-
ret = self["dimensions"]
|
117
|
-
if not isinstance(ret, FoamFile.DimensionSet):
|
118
|
-
raise TypeError("dimensions is not a DimensionSet")
|
119
|
-
return ret
|
120
|
-
|
121
|
-
@dimensions.setter
|
122
|
-
def dimensions(
|
123
|
-
self, value: Union[FoamFile.DimensionSet, Sequence[Union[int, float]]]
|
124
|
-
) -> None:
|
125
|
-
self["dimensions"] = value
|
126
|
-
|
127
|
-
@property
|
128
|
-
def internal_field(
|
129
|
-
self,
|
130
|
-
) -> Union[
|
131
|
-
int,
|
132
|
-
float,
|
133
|
-
Sequence[Union[int, float, Sequence[Union[int, float]]]],
|
134
|
-
"np.ndarray[Tuple[()], np.dtype[np.generic]]",
|
135
|
-
"np.ndarray[Tuple[int], np.dtype[np.generic]]",
|
136
|
-
"np.ndarray[Tuple[int, int], np.dtype[np.generic]]",
|
137
|
-
]:
|
138
|
-
"""Alias of `self["internalField"]`."""
|
139
|
-
ret = self["internalField"]
|
140
|
-
if not isinstance(ret, (int, float, Sequence)):
|
141
|
-
raise TypeError("internalField is not a field")
|
142
|
-
return cast(Union[int, float, Sequence[Union[int, float]]], ret)
|
143
|
-
|
144
|
-
@internal_field.setter
|
145
|
-
def internal_field(
|
146
|
-
self,
|
147
|
-
value: Union[
|
148
|
-
int,
|
149
|
-
float,
|
150
|
-
Sequence[Union[int, float, Sequence[Union[int, float]]]],
|
151
|
-
"np.ndarray[Tuple[()], np.dtype[np.generic]]",
|
152
|
-
"np.ndarray[Tuple[int], np.dtype[np.generic]]",
|
153
|
-
"np.ndarray[Tuple[int, int], np.dtype[np.generic]]",
|
154
|
-
],
|
155
|
-
) -> None:
|
156
|
-
self["internalField"] = value
|
157
|
-
|
158
|
-
@property
|
159
|
-
def boundary_field(self) -> "FoamFieldFile.BoundariesSubDict":
|
160
|
-
"""Alias of `self["boundaryField"]`."""
|
161
|
-
ret = self["boundaryField"]
|
162
|
-
if not isinstance(ret, FoamFieldFile.BoundariesSubDict):
|
163
|
-
assert not isinstance(ret, FoamFile.SubDict)
|
164
|
-
raise TypeError("boundaryField is not a dictionary")
|
165
|
-
return ret
|
@@ -1,203 +0,0 @@
|
|
1
|
-
import sys
|
2
|
-
from typing import (
|
3
|
-
Any,
|
4
|
-
Tuple,
|
5
|
-
Union,
|
6
|
-
)
|
7
|
-
|
8
|
-
if sys.version_info >= (3, 9):
|
9
|
-
from collections.abc import Iterator, Mapping, MutableMapping
|
10
|
-
else:
|
11
|
-
from typing import Iterator, Mapping, MutableMapping
|
12
|
-
|
13
|
-
from ._base import FoamDict
|
14
|
-
from ._io import FoamFileIO
|
15
|
-
from ._serialization import dumps
|
16
|
-
|
17
|
-
|
18
|
-
class FoamFile(
|
19
|
-
FoamDict,
|
20
|
-
MutableMapping[
|
21
|
-
Union[str, Tuple[str, ...]], Union["FoamFile.Data", "FoamFile.SubDict"]
|
22
|
-
],
|
23
|
-
FoamFileIO,
|
24
|
-
):
|
25
|
-
"""
|
26
|
-
An OpenFOAM dictionary file.
|
27
|
-
|
28
|
-
Use as a mutable mapping (i.e., like a dict) to access and modify entries.
|
29
|
-
|
30
|
-
Use as a context manager to make multiple changes to the file while saving all changes only once at the end.
|
31
|
-
"""
|
32
|
-
|
33
|
-
class SubDict(
|
34
|
-
FoamDict,
|
35
|
-
MutableMapping[str, Union["FoamFile.Data", "FoamFile.SubDict"]],
|
36
|
-
):
|
37
|
-
"""An OpenFOAM dictionary within a file as a mutable mapping."""
|
38
|
-
|
39
|
-
def __init__(self, _file: "FoamFile", _keywords: Tuple[str, ...]) -> None:
|
40
|
-
self._file = _file
|
41
|
-
self._keywords = _keywords
|
42
|
-
|
43
|
-
def __getitem__(
|
44
|
-
self, keyword: str
|
45
|
-
) -> Union["FoamFile.Data", "FoamFile.SubDict"]:
|
46
|
-
return self._file[(*self._keywords, keyword)]
|
47
|
-
|
48
|
-
def _setitem(
|
49
|
-
self,
|
50
|
-
keyword: str,
|
51
|
-
data: Any,
|
52
|
-
*,
|
53
|
-
assume_field: bool = False,
|
54
|
-
assume_dimensions: bool = False,
|
55
|
-
) -> None:
|
56
|
-
self._file._setitem(
|
57
|
-
(*self._keywords, keyword),
|
58
|
-
data,
|
59
|
-
assume_field=assume_field,
|
60
|
-
assume_dimensions=assume_dimensions,
|
61
|
-
)
|
62
|
-
|
63
|
-
def __setitem__(self, keyword: str, value: "FoamFile._SetData") -> None:
|
64
|
-
self._setitem(keyword, value)
|
65
|
-
|
66
|
-
def __delitem__(self, keyword: str) -> None:
|
67
|
-
del self._file[(*self._keywords, keyword)]
|
68
|
-
|
69
|
-
def __iter__(self) -> Iterator[str]:
|
70
|
-
return self._file._iter(self._keywords)
|
71
|
-
|
72
|
-
def __contains__(self, keyword: object) -> bool:
|
73
|
-
return (*self._keywords, keyword) in self._file
|
74
|
-
|
75
|
-
def __len__(self) -> int:
|
76
|
-
return len(list(iter(self)))
|
77
|
-
|
78
|
-
def update(self, *args: Any, **kwargs: Any) -> None:
|
79
|
-
with self._file:
|
80
|
-
super().update(*args, **kwargs)
|
81
|
-
|
82
|
-
def clear(self) -> None:
|
83
|
-
with self._file:
|
84
|
-
super().clear()
|
85
|
-
|
86
|
-
def __repr__(self) -> str:
|
87
|
-
return f"{type(self).__qualname__}({self._file}, {self._keywords})"
|
88
|
-
|
89
|
-
def as_dict(self) -> FoamDict._Dict:
|
90
|
-
"""Return a nested dict representation of the dictionary."""
|
91
|
-
ret = self._file.as_dict()
|
92
|
-
|
93
|
-
for k in self._keywords:
|
94
|
-
assert isinstance(ret, dict)
|
95
|
-
v = ret[k]
|
96
|
-
assert isinstance(v, dict)
|
97
|
-
ret = v
|
98
|
-
|
99
|
-
return ret
|
100
|
-
|
101
|
-
def __getitem__(
|
102
|
-
self, keywords: Union[str, Tuple[str, ...]]
|
103
|
-
) -> Union["FoamFile.Data", "FoamFile.SubDict"]:
|
104
|
-
if not isinstance(keywords, tuple):
|
105
|
-
keywords = (keywords,)
|
106
|
-
|
107
|
-
_, parsed = self._read()
|
108
|
-
|
109
|
-
value = parsed[keywords]
|
110
|
-
|
111
|
-
if value is ...:
|
112
|
-
return FoamFile.SubDict(self, keywords)
|
113
|
-
else:
|
114
|
-
return value # type: ignore [return-value]
|
115
|
-
|
116
|
-
def _setitem(
|
117
|
-
self,
|
118
|
-
keywords: Union[str, Tuple[str, ...]],
|
119
|
-
data: "FoamFile._SetData",
|
120
|
-
*,
|
121
|
-
assume_field: bool = False,
|
122
|
-
assume_dimensions: bool = False,
|
123
|
-
) -> None:
|
124
|
-
if not isinstance(keywords, tuple):
|
125
|
-
keywords = (keywords,)
|
126
|
-
|
127
|
-
contents, parsed = self._read()
|
128
|
-
|
129
|
-
if isinstance(data, Mapping):
|
130
|
-
with self:
|
131
|
-
if isinstance(data, FoamDict):
|
132
|
-
data = data.as_dict()
|
133
|
-
|
134
|
-
start, end = parsed.entry_location(keywords, missing_ok=True)
|
135
|
-
|
136
|
-
self._write(
|
137
|
-
f"{contents[:start]}\n{dumps({keywords[-1]: {}})}\n{contents[end:]}"
|
138
|
-
)
|
139
|
-
|
140
|
-
for k, v in data.items():
|
141
|
-
self[(*keywords, k)] = v
|
142
|
-
else:
|
143
|
-
start, end = parsed.entry_location(keywords, missing_ok=True)
|
144
|
-
|
145
|
-
self._write(
|
146
|
-
f"{contents[:start]}\n{dumps({keywords[-1]: data}, assume_field=assume_field, assume_dimensions=assume_dimensions)}\n{contents[end:]}"
|
147
|
-
)
|
148
|
-
|
149
|
-
def __setitem__(
|
150
|
-
self,
|
151
|
-
keywords: Union[str, Tuple[str, ...]],
|
152
|
-
data: "FoamFile._SetData",
|
153
|
-
) -> None:
|
154
|
-
self._setitem(keywords, data)
|
155
|
-
|
156
|
-
def __delitem__(self, keywords: Union[str, Tuple[str, ...]]) -> None:
|
157
|
-
if not isinstance(keywords, tuple):
|
158
|
-
keywords = (keywords,)
|
159
|
-
|
160
|
-
contents, parsed = self._read()
|
161
|
-
|
162
|
-
start, end = parsed.entry_location(keywords)
|
163
|
-
|
164
|
-
self._write(contents[:start] + contents[end:])
|
165
|
-
|
166
|
-
def _iter(self, keywords: Union[str, Tuple[str, ...]] = ()) -> Iterator[str]:
|
167
|
-
if not isinstance(keywords, tuple):
|
168
|
-
keywords = (keywords,)
|
169
|
-
|
170
|
-
_, parsed = self._read()
|
171
|
-
|
172
|
-
yield from (k[-1] for k in parsed if k[:-1] == keywords)
|
173
|
-
|
174
|
-
def __iter__(self) -> Iterator[str]:
|
175
|
-
return self._iter()
|
176
|
-
|
177
|
-
def __contains__(self, keywords: object) -> bool:
|
178
|
-
if not isinstance(keywords, tuple):
|
179
|
-
keywords = (keywords,)
|
180
|
-
_, parsed = self._read()
|
181
|
-
return keywords in parsed
|
182
|
-
|
183
|
-
def __len__(self) -> int:
|
184
|
-
return len(list(iter(self)))
|
185
|
-
|
186
|
-
def update(self, *args: Any, **kwargs: Any) -> None:
|
187
|
-
with self:
|
188
|
-
super().update(*args, **kwargs)
|
189
|
-
|
190
|
-
def clear(self) -> None:
|
191
|
-
with self:
|
192
|
-
super().clear()
|
193
|
-
|
194
|
-
def __fspath__(self) -> str:
|
195
|
-
return str(self.path)
|
196
|
-
|
197
|
-
def __repr__(self) -> str:
|
198
|
-
return f"{type(self).__name__}({self.path})"
|
199
|
-
|
200
|
-
def as_dict(self) -> FoamDict._Dict:
|
201
|
-
"""Return a nested dict representation of the file."""
|
202
|
-
_, parsed = self._read()
|
203
|
-
return parsed.as_dict()
|
@@ -1,99 +0,0 @@
|
|
1
|
-
import sys
|
2
|
-
|
3
|
-
if sys.version_info >= (3, 9):
|
4
|
-
from collections.abc import Mapping
|
5
|
-
else:
|
6
|
-
from typing import Mapping
|
7
|
-
|
8
|
-
from .._util import is_sequence
|
9
|
-
from ._base import FoamDict
|
10
|
-
|
11
|
-
try:
|
12
|
-
import numpy as np
|
13
|
-
|
14
|
-
numpy = True
|
15
|
-
except ModuleNotFoundError:
|
16
|
-
numpy = False
|
17
|
-
|
18
|
-
|
19
|
-
def dumps(
|
20
|
-
data: FoamDict._SetData,
|
21
|
-
*,
|
22
|
-
assume_field: bool = False,
|
23
|
-
assume_dimensions: bool = False,
|
24
|
-
assume_data_entries: bool = False,
|
25
|
-
) -> str:
|
26
|
-
if numpy and isinstance(data, np.ndarray):
|
27
|
-
return dumps(
|
28
|
-
data.tolist(),
|
29
|
-
assume_field=assume_field,
|
30
|
-
assume_dimensions=assume_dimensions,
|
31
|
-
)
|
32
|
-
|
33
|
-
elif isinstance(data, Mapping):
|
34
|
-
entries = []
|
35
|
-
for k, v in data.items():
|
36
|
-
s = dumps(
|
37
|
-
v,
|
38
|
-
assume_field=assume_field,
|
39
|
-
assume_dimensions=assume_dimensions,
|
40
|
-
assume_data_entries=True,
|
41
|
-
)
|
42
|
-
if isinstance(v, Mapping):
|
43
|
-
entries.append(f"{k}\n{{\n{s}\n}}")
|
44
|
-
elif s:
|
45
|
-
entries.append(f"{k} {s};")
|
46
|
-
else:
|
47
|
-
entries.append(f"{k};")
|
48
|
-
return "\n".join(entries)
|
49
|
-
|
50
|
-
elif isinstance(data, FoamDict.DimensionSet) or (
|
51
|
-
assume_dimensions and is_sequence(data) and len(data) == 7
|
52
|
-
):
|
53
|
-
return f"[{' '.join(str(v) for v in data)}]"
|
54
|
-
|
55
|
-
elif assume_field and isinstance(data, (int, float)):
|
56
|
-
return f"uniform {data}"
|
57
|
-
|
58
|
-
elif assume_field and is_sequence(data):
|
59
|
-
if isinstance(data[0], (int, float)) and len(data) in (3, 6, 9):
|
60
|
-
return f"uniform {dumps(data)}"
|
61
|
-
elif isinstance(data[0], (int, float)):
|
62
|
-
return f"nonuniform List<scalar> {len(data)}{dumps(data)}"
|
63
|
-
elif len(data[0]) == 3:
|
64
|
-
return f"nonuniform List<vector> {len(data)}{dumps(data)}"
|
65
|
-
elif len(data[0]) == 6:
|
66
|
-
return f"nonuniform List<symmTensor> {len(data)}{dumps(data)}"
|
67
|
-
elif len(data[0]) == 9:
|
68
|
-
return f"nonuniform List<tensor> {len(data)}{dumps(data)}"
|
69
|
-
else:
|
70
|
-
return dumps(
|
71
|
-
data,
|
72
|
-
assume_dimensions=assume_dimensions,
|
73
|
-
assume_data_entries=assume_data_entries,
|
74
|
-
)
|
75
|
-
|
76
|
-
elif assume_data_entries and isinstance(data, tuple):
|
77
|
-
return " ".join(
|
78
|
-
dumps(v, assume_field=assume_field, assume_dimensions=assume_dimensions)
|
79
|
-
for v in data
|
80
|
-
)
|
81
|
-
|
82
|
-
elif isinstance(data, FoamDict.Dimensioned):
|
83
|
-
if data.name is not None:
|
84
|
-
return f"{data.name} {dumps(data.dimensions, assume_dimensions=True)} {dumps(data.value)}"
|
85
|
-
else:
|
86
|
-
return (
|
87
|
-
f"{dumps(data.dimensions, assume_dimensions=True)} {dumps(data.value)}"
|
88
|
-
)
|
89
|
-
|
90
|
-
elif is_sequence(data):
|
91
|
-
return f"({' '.join(dumps(v) for v in data)})"
|
92
|
-
|
93
|
-
elif data is True:
|
94
|
-
return "yes"
|
95
|
-
elif data is False:
|
96
|
-
return "no"
|
97
|
-
|
98
|
-
else:
|
99
|
-
return str(data)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|