foamlib 0.1.14__tar.gz → 0.2.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foamlib
3
- Version: 0.1.14
3
+ Version: 0.2.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
@@ -62,7 +62,7 @@ It offers the following classes (among a few others):
62
62
 
63
63
  * [`FoamCase`](https://foamlib.readthedocs.io/en/stable/#foamlib.FoamCase): a class for manipulating, executing and accessing the results of OpenFOAM cases.
64
64
  * [`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 (uses OpenFOAM's `foamDictionary` utility with custom parsing).
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
 
@@ -15,7 +15,7 @@ It offers the following classes (among a few others):
15
15
 
16
16
  * [`FoamCase`](https://foamlib.readthedocs.io/en/stable/#foamlib.FoamCase): a class for manipulating, executing and accessing the results of OpenFOAM cases.
17
17
  * [`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 (uses OpenFOAM's `foamDictionary` utility with custom parsing).
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
 
@@ -0,0 +1,12 @@
1
+ __version__ = "0.2.0"
2
+
3
+ from ._cases import FoamCase, AsyncFoamCase, FoamCaseBase
4
+ from ._dictionaries import FoamFile, FoamFieldFile
5
+
6
+ __all__ = [
7
+ "FoamCase",
8
+ "AsyncFoamCase",
9
+ "FoamCaseBase",
10
+ "FoamFile",
11
+ "FoamFieldFile",
12
+ ]
@@ -24,14 +24,65 @@ from ._subprocesses import run_process, run_process_async, CalledProcessError
24
24
  from ._dictionaries import FoamFile, FoamFieldFile
25
25
 
26
26
 
27
- class FoamCaseBase(Sequence["FoamTimeDirectory"]):
27
+ class FoamCaseBase(Sequence["FoamCaseBase.TimeDirectory"]):
28
28
  def __init__(self, path: Union[Path, str]):
29
29
  self.path = Path(path).absolute()
30
30
  if not self.path.is_dir():
31
31
  raise NotADirectoryError(f"{self.path} is not a directory")
32
32
 
33
+ class TimeDirectory(Set[FoamFieldFile]):
34
+ """
35
+ An OpenFOAM time directory in a case.
36
+
37
+ Use to access field files in the directory, e.g. `time["U"]`.
38
+
39
+ :param path: The path to the time directory.
40
+ """
41
+
42
+ def __init__(self, path: Union[Path, str]):
43
+ self.path = Path(path).absolute()
44
+ if not self.path.is_dir():
45
+ raise NotADirectoryError(f"{self.path} is not a directory")
46
+
47
+ @property
48
+ def time(self) -> float:
49
+ """
50
+ The time that corresponds to this directory.
51
+ """
52
+ return float(self.path.name)
53
+
54
+ @property
55
+ def name(self) -> str:
56
+ """
57
+ The name of this time directory.
58
+ """
59
+ return self.path.name
60
+
61
+ def __getitem__(self, key: str) -> FoamFieldFile:
62
+ try:
63
+ return FoamFieldFile(self.path / key)
64
+ except FileNotFoundError as e:
65
+ raise KeyError(key) from e
66
+
67
+ def __iter__(self) -> Iterator[FoamFieldFile]:
68
+ for p in self.path.iterdir():
69
+ if p.is_file():
70
+ yield FoamFieldFile(p.name)
71
+
72
+ def __len__(self) -> int:
73
+ return len(list(iter(self)))
74
+
75
+ def __fspath__(self) -> str:
76
+ return str(self.path)
77
+
78
+ def __repr__(self) -> str:
79
+ return f"{type(self).__name__}({self.path})"
80
+
81
+ def __str__(self) -> str:
82
+ return str(self.path)
83
+
33
84
  @property
34
- def _times(self) -> Sequence["FoamTimeDirectory"]:
85
+ def _times(self) -> Sequence["FoamCaseBase.TimeDirectory"]:
35
86
  times = []
36
87
  for p in self.path.iterdir():
37
88
  if p.is_dir():
@@ -40,23 +91,25 @@ class FoamCaseBase(Sequence["FoamTimeDirectory"]):
40
91
  except ValueError:
41
92
  pass
42
93
  else:
43
- times.append(FoamTimeDirectory(p))
94
+ times.append(FoamCaseBase.TimeDirectory(p))
44
95
 
45
96
  times.sort(key=lambda t: t.time)
46
97
 
47
98
  return times
48
99
 
49
100
  @overload
50
- def __getitem__(self, index: Union[int, float, str]) -> "FoamTimeDirectory": ...
101
+ def __getitem__(
102
+ self, index: Union[int, float, str]
103
+ ) -> "FoamCaseBase.TimeDirectory": ...
51
104
 
52
105
  @overload
53
- def __getitem__(self, index: slice) -> Sequence["FoamTimeDirectory"]: ...
106
+ def __getitem__(self, index: slice) -> Sequence["FoamCaseBase.TimeDirectory"]: ...
54
107
 
55
108
  def __getitem__(
56
109
  self, index: Union[int, slice, float, str]
57
- ) -> Union["FoamTimeDirectory", Sequence["FoamTimeDirectory"]]:
110
+ ) -> Union["FoamCaseBase.TimeDirectory", Sequence["FoamCaseBase.TimeDirectory"]]:
58
111
  if isinstance(index, str):
59
- return FoamTimeDirectory(self.path / str(index))
112
+ return FoamCaseBase.TimeDirectory(self.path / str(index))
60
113
  elif isinstance(index, float):
61
114
  for time in self._times:
62
115
  if time.time == index:
@@ -598,55 +651,3 @@ class AsyncFoamCase(FoamCaseBase):
598
651
  )
599
652
 
600
653
  return AsyncFoamCase(dest)
601
-
602
-
603
- class FoamTimeDirectory(Mapping[str, FoamFieldFile]):
604
- """
605
- An OpenFOAM time directory in a case.
606
-
607
- Use as a mapping to access field files in the directory, e.g. `time["U"]`.
608
-
609
- :param path: The path to the time directory.
610
- """
611
-
612
- def __init__(self, path: Union[Path, str]):
613
- self.path = Path(path).absolute()
614
- if not self.path.is_dir():
615
- raise NotADirectoryError(f"{self.path} is not a directory")
616
-
617
- @property
618
- def time(self) -> float:
619
- """
620
- The time that corresponds to this directory.
621
- """
622
- return float(self.path.name)
623
-
624
- @property
625
- def name(self) -> str:
626
- """
627
- The name of this time directory.
628
- """
629
- return self.path.name
630
-
631
- def __getitem__(self, key: str) -> FoamFieldFile:
632
- try:
633
- return FoamFieldFile(self.path / key)
634
- except FileNotFoundError as e:
635
- raise KeyError(key) from e
636
-
637
- def __iter__(self) -> Iterator[str]:
638
- for p in self.path.iterdir():
639
- if p.is_file():
640
- yield p.name
641
-
642
- def __len__(self) -> int:
643
- return len(list(iter(self)))
644
-
645
- def __fspath__(self) -> str:
646
- return str(self.path)
647
-
648
- def __repr__(self) -> str:
649
- return f"{type(self).__name__}({self.path})"
650
-
651
- def __str__(self) -> str:
652
- return str(self.path)
@@ -1,4 +1,7 @@
1
1
  from pathlib import Path
2
+ from dataclasses import dataclass
3
+ from collections import namedtuple
4
+ from contextlib import suppress
2
5
  from typing import (
3
6
  Any,
4
7
  Union,
@@ -9,204 +12,38 @@ from typing import (
9
12
  MutableMapping,
10
13
  cast,
11
14
  )
12
- from collections import namedtuple
13
- from dataclasses import dataclass
14
- from contextlib import suppress
15
-
16
- from ._subprocesses import run_process, CalledProcessError
17
-
18
- try:
19
- import numpy as np
20
- from numpy.typing import NDArray
21
- except ModuleNotFoundError:
22
- numpy = False
23
- else:
24
- numpy = True
25
15
 
26
16
  from pyparsing import (
17
+ Dict,
27
18
  Forward,
28
19
  Group,
29
20
  Keyword,
21
+ LineEnd,
30
22
  Literal,
23
+ Located,
31
24
  Opt,
32
25
  QuotedString,
33
26
  Word,
27
+ c_style_comment,
34
28
  common,
29
+ cpp_style_comment,
35
30
  printables,
31
+ identchars,
32
+ identbodychars,
36
33
  )
37
34
 
38
- FoamDimensionSet = namedtuple(
39
- "FoamDimensionSet",
40
- [
41
- "mass",
42
- "length",
43
- "time",
44
- "temperature",
45
- "moles",
46
- "current",
47
- "luminous_intensity",
48
- ],
49
- defaults=(0, 0, 0, 0, 0, 0, 0),
50
- )
51
-
52
-
53
- @dataclass
54
- class FoamDimensioned:
55
- value: Union[int, float, Sequence[Union[int, float]]] = 0
56
- dimensions: Union[FoamDimensionSet, Sequence[Union[int, float]]] = (
57
- FoamDimensionSet()
58
- )
59
- name: Optional[str] = None
60
-
61
- def __post_init__(self) -> None:
62
- if not isinstance(self.dimensions, FoamDimensionSet):
63
- self.dimensions = FoamDimensionSet(*self.dimensions)
64
-
65
-
66
- FoamValue = Union[
67
- str, int, float, bool, FoamDimensioned, FoamDimensionSet, Sequence["FoamValue"]
68
- ]
69
- """
70
- A value that can be stored in an OpenFOAM dictionary.
71
- """
72
-
73
- _YES = Keyword("yes").set_parse_action(lambda s, loc, tks: True)
74
- _NO = Keyword("no").set_parse_action(lambda s, loc, tks: False)
75
- _DIMENSIONS = (
76
- Literal("[").suppress() + common.number * 7 + Literal("]").suppress()
77
- ).set_parse_action(lambda s, loc, tks: FoamDimensionSet(*tks))
78
- _TOKEN = common.identifier | QuotedString('"', unquote_results=False)
79
- _ITEM = Forward()
80
- _LIST = Opt(
81
- Literal("List") + Literal("<") + common.identifier + Literal(">")
82
- ).suppress() + (
83
- (
84
- Opt(common.integer).suppress()
85
- + Literal("(").suppress()
86
- + Group(_ITEM[...])
87
- + Literal(")").suppress()
88
- )
89
- | (
90
- common.integer + Literal("{").suppress() + _ITEM + Literal("}").suppress()
91
- ).set_parse_action(lambda s, loc, tks: [tks[1]] * tks[0])
92
- )
93
- _FIELD = (Keyword("uniform").suppress() + _ITEM) | (
94
- Keyword("nonuniform").suppress() + _LIST
95
- )
96
- _DIMENSIONED = (Opt(common.identifier) + _DIMENSIONS + _ITEM).set_parse_action(
97
- lambda s, loc, tks: FoamDimensioned(*reversed(tks.as_list()))
98
- )
99
- _ITEM <<= (
100
- _FIELD | _LIST | _DIMENSIONED | _DIMENSIONS | common.number | _YES | _NO | _TOKEN
101
- )
102
-
103
- _TOKENS = (QuotedString('"', unquote_results=False) | Word(printables))[
104
- 1, ...
105
- ].set_parse_action(lambda s, loc, tks: " ".join(tks))
106
-
107
- _VALUE = _ITEM ^ _TOKENS
108
-
109
-
110
- def _parse(value: str) -> FoamValue:
111
- return cast(FoamValue, _VALUE.parse_string(value, parse_all=True).as_list()[0])
112
-
113
-
114
- def _serialize_bool(value: Any) -> str:
115
- if value is True:
116
- return "yes"
117
- elif value is False:
118
- return "no"
119
- else:
120
- raise TypeError(f"Not a bool: {type(value)}")
121
-
122
-
123
- def _is_sequence(value: Any) -> bool:
124
- return (
125
- isinstance(value, Sequence)
126
- and not isinstance(value, str)
127
- or numpy
128
- and isinstance(value, np.ndarray)
129
- )
130
-
131
-
132
- def _serialize_list(value: Any) -> str:
133
- if _is_sequence(value):
134
- return f"({' '.join(_serialize(v) for v in value)})"
135
- else:
136
- raise TypeError(f"Not a valid sequence: {type(value)}")
137
-
138
-
139
- def _serialize_field(value: Any) -> str:
140
- if _is_sequence(value):
141
- try:
142
- s = _serialize_list(value)
143
- except TypeError:
144
- raise TypeError(f"Not a valid field: {type(value)}") from None
145
- else:
146
- if len(value) < 10:
147
- return f"uniform {s}"
148
- else:
149
- if isinstance(value[0], (int, float)):
150
- kind = "scalar"
151
- elif len(value[0]) == 3:
152
- kind = "vector"
153
- elif len(value[0]) == 6:
154
- kind = "symmTensor"
155
- elif len(value[0]) == 9:
156
- kind = "tensor"
157
- else:
158
- raise TypeError(
159
- f"Unsupported sequence length for field: {len(value[0])}"
160
- )
161
- return f"nonuniform List<{kind}> {len(value)}{s}"
162
- else:
163
- return f"uniform {value}"
164
-
165
-
166
- def _serialize_dimensions(value: Any) -> str:
167
- if _is_sequence(value) and len(value) == 7:
168
- return f"[{' '.join(str(v) for v in value)}]"
169
- else:
170
- raise TypeError(f"Not a valid dimension set: {type(value)}")
171
-
172
-
173
- def _serialize_dimensioned(value: Any) -> str:
174
- if isinstance(value, FoamDimensioned):
175
- if value.name is not None:
176
- return f"{value.name} {_serialize_dimensions(value.dimensions)} {_serialize(value.value)}"
177
- else:
178
- return (
179
- f"{_serialize_dimensions(value.dimensions)} {_serialize(value.value)}"
180
- )
181
- else:
182
- raise TypeError(f"Not a valid dimensioned value: {type(value)}")
183
-
184
-
185
- def _serialize(
186
- value: Any, *, assume_field: bool = False, assume_dimensions: bool = False
187
- ) -> str:
188
- if isinstance(value, FoamDimensionSet) or assume_dimensions:
189
- with suppress(TypeError):
190
- return _serialize_dimensions(value)
191
-
192
- if assume_field:
193
- with suppress(TypeError):
194
- return _serialize_field(value)
195
-
196
- with suppress(TypeError):
197
- return _serialize_dimensioned(value)
198
-
199
- with suppress(TypeError):
200
- return _serialize_list(value)
201
-
202
- with suppress(TypeError):
203
- return _serialize_bool(value)
35
+ try:
36
+ import numpy as np
37
+ from numpy.typing import NDArray
38
+ except ModuleNotFoundError:
39
+ numpy = False
40
+ else:
41
+ numpy = True
204
42
 
205
- return str(value)
43
+ from ._subprocesses import run_process, CalledProcessError
206
44
 
207
45
 
208
- class FoamDictionary(MutableMapping[str, Union[FoamValue, "FoamDictionary"]]):
209
- Value = FoamValue # for backwards compatibility
46
+ class _FoamDictionary(MutableMapping[str, Union["FoamFile.Value", "_FoamDictionary"]]):
210
47
 
211
48
  def __init__(self, _file: "FoamFile", _keywords: Sequence[str]) -> None:
212
49
  self._file = _file
@@ -238,14 +75,18 @@ class FoamDictionary(MutableMapping[str, Union[FoamValue, "FoamDictionary"]]):
238
75
  f"{e.cmd} failed with return code {e.returncode}\n{e.stderr.decode()}"
239
76
  ) from None
240
77
 
241
- def __getitem__(self, key: str) -> Union[FoamValue, "FoamDictionary"]:
242
- value = self._cmd(["-value"], key=key)
78
+ def __getitem__(self, key: str) -> Union["FoamFile.Value", "_FoamDictionary"]:
79
+ contents = self._file.path.read_text()
80
+ value = _DICTIONARY.parse_string(contents, parse_all=True).as_dict()
81
+
82
+ for key in [*self._keywords, key]:
83
+ value = value[key]
243
84
 
244
- if value.startswith("{"):
245
- assert value.endswith("}")
246
- return FoamDictionary(self._file, [*self._keywords, key])
85
+ if isinstance(value, dict):
86
+ return _FoamDictionary(self._file, [*self._keywords, key])
247
87
  else:
248
- return _parse(value)
88
+ start, end = value
89
+ return _VALUE.parse_string(contents[start:end], parse_all=True).as_list()[0]
249
90
 
250
91
  def _setitem(
251
92
  self,
@@ -255,17 +96,18 @@ class FoamDictionary(MutableMapping[str, Union[FoamValue, "FoamDictionary"]]):
255
96
  assume_field: bool = False,
256
97
  assume_dimensions: bool = False,
257
98
  ) -> None:
258
- if isinstance(value, FoamDictionary):
99
+ if isinstance(value, _FoamDictionary):
259
100
  value = value._cmd(["-value"])
260
101
  elif isinstance(value, Mapping):
261
102
  self._cmd(["-set", "{}"], key=key)
262
103
  subdict = self[key]
263
- assert isinstance(subdict, FoamDictionary)
104
+ print(subdict)
105
+ assert isinstance(subdict, _FoamDictionary)
264
106
  for k, v in value.items():
265
107
  subdict[k] = v
266
108
  return
267
109
  else:
268
- value = _serialize(
110
+ value = serialize(
269
111
  value, assume_field=assume_field, assume_dimensions=assume_dimensions
270
112
  )
271
113
 
@@ -286,84 +128,53 @@ class FoamDictionary(MutableMapping[str, Union[FoamValue, "FoamDictionary"]]):
286
128
  self._cmd(["-remove"], key=key)
287
129
 
288
130
  def __iter__(self) -> Iterator[str]:
289
- for key in self._cmd(["-keywords"]).splitlines():
290
- if not key.startswith('"'):
291
- yield key
131
+ value = _DICTIONARY.parse_file(self._file.path, parse_all=True).as_dict()
132
+
133
+ for key in self._keywords:
134
+ value = value[key]
135
+
136
+ yield from value
292
137
 
293
138
  def __len__(self) -> int:
294
139
  return len(list(iter(self)))
295
140
 
296
141
  def __repr__(self) -> str:
297
- return type(self).__name__
298
-
299
-
300
- class FoamBoundaryDictionary(FoamDictionary):
301
- """An OpenFOAM dictionary representing a boundary condition as a mutable mapping."""
302
-
303
- def __setitem__(self, key: str, value: Any) -> None:
304
- if key == "value":
305
- self._setitem(key, value, assume_field=True)
306
- else:
307
- self._setitem(key, value)
308
-
309
- @property
310
- def type(self) -> str:
311
- """
312
- Alias of `self["type"]`.
313
- """
314
- ret = self["type"]
315
- if not isinstance(ret, str):
316
- raise TypeError("type is not a string")
317
- return ret
142
+ return "FoamFile.Dictionary"
318
143
 
319
- @type.setter
320
- def type(self, value: str) -> None:
321
- self["type"] = value
322
144
 
323
- @property
324
- def value(
325
- self,
326
- ) -> Union[
327
- int,
328
- float,
329
- Sequence[Union[int, float, Sequence[Union[int, float]]]],
330
- "NDArray[np.generic]",
331
- ]:
332
- """
333
- Alias of `self["value"]`.
334
- """
335
- ret = self["value"]
336
- if not isinstance(ret, (int, float, Sequence)):
337
- raise TypeError("value is not a field")
338
- return cast(Union[int, float, Sequence[Union[int, float]]], ret)
145
+ class FoamFile(_FoamDictionary):
146
+ """An OpenFOAM dictionary file as a mutable mapping."""
339
147
 
340
- @value.setter
341
- def value(
342
- self,
343
- value: Union[
344
- int,
345
- float,
346
- Sequence[Union[int, float, Sequence[Union[int, float]]]],
347
- "NDArray[np.generic]",
148
+ Dictionary = _FoamDictionary
149
+
150
+ DimensionSet = namedtuple(
151
+ "DimensionSet",
152
+ [
153
+ "mass",
154
+ "length",
155
+ "time",
156
+ "temperature",
157
+ "moles",
158
+ "current",
159
+ "luminous_intensity",
348
160
  ],
349
- ) -> None:
350
- self["value"] = value
351
-
352
- @value.deleter
353
- def value(self) -> None:
354
- del self["value"]
355
-
161
+ defaults=(0, 0, 0, 0, 0, 0, 0),
162
+ )
356
163
 
357
- class FoamBoundariesDictionary(FoamDictionary):
358
- def __getitem__(self, key: str) -> Union[FoamValue, FoamBoundaryDictionary]:
359
- ret = super().__getitem__(key)
360
- if isinstance(ret, FoamDictionary):
361
- ret = FoamBoundaryDictionary(self._file, [*self._keywords, key])
362
- return ret
164
+ @dataclass
165
+ class Dimensioned:
166
+ value: Union[int, float, Sequence[Union[int, float]]] = 0
167
+ dimensions: Union["FoamFile.DimensionSet", Sequence[Union[int, float]]] = ()
168
+ name: Optional[str] = None
363
169
 
170
+ def __post_init__(self) -> None:
171
+ if not isinstance(self.dimensions, FoamFile.DimensionSet):
172
+ self.dimensions = FoamFile.DimensionSet(*self.dimensions)
364
173
 
365
- class FoamFile(FoamDictionary):
366
- """An OpenFOAM dictionary file as a mutable mapping."""
174
+ Value = Union[str, int, float, bool, Dimensioned, DimensionSet, Sequence["Value"]]
175
+ """
176
+ A value that can be stored in an OpenFOAM dictionary.
177
+ """
367
178
 
368
179
  def __init__(self, path: Union[str, Path]) -> None:
369
180
  super().__init__(self, [])
@@ -383,10 +194,80 @@ class FoamFile(FoamDictionary):
383
194
  class FoamFieldFile(FoamFile):
384
195
  """An OpenFOAM dictionary file representing a field as a mutable mapping."""
385
196
 
386
- def __getitem__(self, key: str) -> Union[FoamValue, FoamDictionary]:
197
+ class BoundariesDictionary(_FoamDictionary):
198
+ def __getitem__(
199
+ self, key: str
200
+ ) -> Union["FoamFile.Value", "FoamFieldFile.BoundaryDictionary"]:
201
+ ret = super().__getitem__(key)
202
+ if isinstance(ret, _FoamDictionary):
203
+ ret = FoamFieldFile.BoundaryDictionary(
204
+ self._file, [*self._keywords, key]
205
+ )
206
+ return ret
207
+
208
+ class BoundaryDictionary(_FoamDictionary):
209
+ """An OpenFOAM dictionary representing a boundary condition as a mutable mapping."""
210
+
211
+ def __setitem__(self, key: str, value: Any) -> None:
212
+ if key == "value":
213
+ self._setitem(key, value, assume_field=True)
214
+ else:
215
+ self._setitem(key, value)
216
+
217
+ @property
218
+ def type(self) -> str:
219
+ """
220
+ Alias of `self["type"]`.
221
+ """
222
+ ret = self["type"]
223
+ if not isinstance(ret, str):
224
+ raise TypeError("type is not a string")
225
+ return ret
226
+
227
+ @type.setter
228
+ def type(self, value: str) -> None:
229
+ self["type"] = value
230
+
231
+ @property
232
+ def value(
233
+ self,
234
+ ) -> Union[
235
+ int,
236
+ float,
237
+ Sequence[Union[int, float, Sequence[Union[int, float]]]],
238
+ "NDArray[np.generic]",
239
+ ]:
240
+ """
241
+ Alias of `self["value"]`.
242
+ """
243
+ ret = self["value"]
244
+ if not isinstance(ret, (int, float, Sequence)):
245
+ raise TypeError("value is not a field")
246
+ return cast(Union[int, float, Sequence[Union[int, float]]], ret)
247
+
248
+ @value.setter
249
+ def value(
250
+ self,
251
+ value: Union[
252
+ int,
253
+ float,
254
+ Sequence[Union[int, float, Sequence[Union[int, float]]]],
255
+ "NDArray[np.generic]",
256
+ ],
257
+ ) -> None:
258
+ self["value"] = value
259
+
260
+ @value.deleter
261
+ def value(self) -> None:
262
+ del self["value"]
263
+
264
+ def __repr__(self) -> str:
265
+ return "FoamFieldFile.BoundaryDictionary"
266
+
267
+ def __getitem__(self, key: str) -> Union[FoamFile.Value, _FoamDictionary]:
387
268
  ret = super().__getitem__(key)
388
- if key == "boundaryField" and isinstance(ret, FoamDictionary):
389
- ret = FoamBoundariesDictionary(self, [key])
269
+ if key == "boundaryField" and isinstance(ret, _FoamDictionary):
270
+ ret = FoamFieldFile.BoundariesDictionary(self, [key])
390
271
  return ret
391
272
 
392
273
  def __setitem__(self, key: str, value: Any) -> None:
@@ -397,19 +278,22 @@ class FoamFieldFile(FoamFile):
397
278
  else:
398
279
  self._setitem(key, value)
399
280
 
281
+ def __repr__(self) -> str:
282
+ return "FoamFieldFile.BoundariesDictionary"
283
+
400
284
  @property
401
- def dimensions(self) -> FoamDimensionSet:
285
+ def dimensions(self) -> FoamFile.DimensionSet:
402
286
  """
403
287
  Alias of `self["dimensions"]`.
404
288
  """
405
289
  ret = self["dimensions"]
406
- if not isinstance(ret, FoamDimensionSet):
290
+ if not isinstance(ret, FoamFile.DimensionSet):
407
291
  raise TypeError("dimensions is not a DimensionSet")
408
292
  return ret
409
293
 
410
294
  @dimensions.setter
411
295
  def dimensions(
412
- self, value: Union[FoamDimensionSet, Sequence[Union[int, float]]]
296
+ self, value: Union[FoamFile.DimensionSet, Sequence[Union[int, float]]]
413
297
  ) -> None:
414
298
  self["dimensions"] = value
415
299
 
@@ -443,12 +327,168 @@ class FoamFieldFile(FoamFile):
443
327
  self["internalField"] = value
444
328
 
445
329
  @property
446
- def boundary_field(self) -> FoamBoundariesDictionary:
330
+ def boundary_field(self) -> "FoamFieldFile.BoundariesDictionary":
447
331
  """
448
332
  Alias of `self["boundaryField"]`.
449
333
  """
450
334
  ret = self["boundaryField"]
451
- if not isinstance(ret, FoamBoundariesDictionary):
452
- assert not isinstance(ret, FoamDictionary)
335
+ if not isinstance(ret, FoamFieldFile.BoundariesDictionary):
336
+ assert not isinstance(ret, _FoamDictionary)
453
337
  raise TypeError("boundaryField is not a dictionary")
454
338
  return ret
339
+
340
+
341
+ _YES = Keyword("yes").set_parse_action(lambda: True)
342
+ _NO = Keyword("no").set_parse_action(lambda: False)
343
+ _DIMENSIONS = (
344
+ Literal("[").suppress() + common.number * 7 + Literal("]").suppress()
345
+ ).set_parse_action(lambda tks: FoamFile.DimensionSet(*tks))
346
+ _TOKEN = QuotedString('"', unquote_results=False) | Word(
347
+ identchars + "$", identbodychars
348
+ )
349
+ _ITEM = Forward()
350
+ _LIST = Opt(
351
+ Literal("List") + Literal("<") + common.identifier + Literal(">")
352
+ ).suppress() + (
353
+ (
354
+ Opt(common.integer).suppress()
355
+ + Literal("(").suppress()
356
+ + Group(_ITEM[...])
357
+ + Literal(")").suppress()
358
+ )
359
+ | (
360
+ common.integer + Literal("{").suppress() + _ITEM + Literal("}").suppress()
361
+ ).set_parse_action(lambda tks: [tks[1]] * tks[0])
362
+ )
363
+ _FIELD = (Keyword("uniform").suppress() + _ITEM) | (
364
+ Keyword("nonuniform").suppress() + _LIST
365
+ )
366
+ _DIMENSIONED = (Opt(common.identifier) + _DIMENSIONS + _ITEM).set_parse_action(
367
+ lambda tks: FoamFile.Dimensioned(*reversed(tks.as_list()))
368
+ )
369
+ _ITEM <<= (
370
+ _FIELD | _LIST | _DIMENSIONED | _DIMENSIONS | common.number | _YES | _NO | _TOKEN
371
+ )
372
+
373
+ _TOKENS = (
374
+ QuotedString('"', unquote_results=False)
375
+ | Word(printables.replace(";", "").replace("{", "").replace("}", ""))
376
+ )[2, ...].set_parse_action(lambda tks: " ".join(tks))
377
+
378
+ _VALUE = (_ITEM ^ _TOKENS).ignore(c_style_comment).ignore(cpp_style_comment)
379
+
380
+
381
+ _UNPARSED_VALUE = (
382
+ QuotedString('"', unquote_results=False)
383
+ | Word(printables.replace(";", "").replace("{", "").replace("}", ""))
384
+ )[...]
385
+ _KEYWORD = QuotedString('"', unquote_results=False) | Word(
386
+ identchars + "$(,.)", identbodychars + "$(,.)"
387
+ )
388
+ _DICTIONARY = Forward()
389
+ _ENTRY = _KEYWORD + (
390
+ (
391
+ Located(_UNPARSED_VALUE).set_parse_action(lambda tks: (tks[0], tks[2]))
392
+ + Literal(";").suppress()
393
+ )
394
+ | (Literal("{").suppress() + _DICTIONARY + Literal("}").suppress())
395
+ )
396
+ _DICTIONARY <<= (
397
+ Dict(Group(_ENTRY)[...])
398
+ .set_parse_action(lambda tks: {} if not tks else tks)
399
+ .ignore(c_style_comment)
400
+ .ignore(cpp_style_comment)
401
+ .ignore(Literal("#include") + ... + LineEnd()) # type: ignore
402
+ )
403
+
404
+
405
+ def _serialize_bool(value: Any) -> str:
406
+ if value is True:
407
+ return "yes"
408
+ elif value is False:
409
+ return "no"
410
+ else:
411
+ raise TypeError(f"Not a bool: {type(value)}")
412
+
413
+
414
+ def _is_sequence(value: Any) -> bool:
415
+ return (
416
+ isinstance(value, Sequence)
417
+ and not isinstance(value, str)
418
+ or numpy
419
+ and isinstance(value, np.ndarray)
420
+ )
421
+
422
+
423
+ def _serialize_list(value: Any) -> str:
424
+ if _is_sequence(value):
425
+ return f"({' '.join(serialize(v) for v in value)})"
426
+ else:
427
+ raise TypeError(f"Not a valid sequence: {type(value)}")
428
+
429
+
430
+ def _serialize_field(value: Any) -> str:
431
+ if _is_sequence(value):
432
+ try:
433
+ s = _serialize_list(value)
434
+ except TypeError:
435
+ raise TypeError(f"Not a valid field: {type(value)}") from None
436
+ else:
437
+ if len(value) < 10:
438
+ return f"uniform {s}"
439
+ else:
440
+ if isinstance(value[0], (int, float)):
441
+ kind = "scalar"
442
+ elif len(value[0]) == 3:
443
+ kind = "vector"
444
+ elif len(value[0]) == 6:
445
+ kind = "symmTensor"
446
+ elif len(value[0]) == 9:
447
+ kind = "tensor"
448
+ else:
449
+ raise TypeError(
450
+ f"Unsupported sequence length for field: {len(value[0])}"
451
+ )
452
+ return f"nonuniform List<{kind}> {len(value)}{s}"
453
+ else:
454
+ return f"uniform {value}"
455
+
456
+
457
+ def _serialize_dimensions(value: Any) -> str:
458
+ if _is_sequence(value) and len(value) == 7:
459
+ return f"[{' '.join(str(v) for v in value)}]"
460
+ else:
461
+ raise TypeError(f"Not a valid dimension set: {type(value)}")
462
+
463
+
464
+ def _serialize_dimensioned(value: Any) -> str:
465
+ if isinstance(value, FoamFile.Dimensioned):
466
+ if value.name is not None:
467
+ return f"{value.name} {_serialize_dimensions(value.dimensions)} {serialize(value.value)}"
468
+ else:
469
+ return f"{_serialize_dimensions(value.dimensions)} {serialize(value.value)}"
470
+ else:
471
+ raise TypeError(f"Not a valid dimensioned value: {type(value)}")
472
+
473
+
474
+ def serialize(
475
+ value: Any, *, assume_field: bool = False, assume_dimensions: bool = False
476
+ ) -> str:
477
+ if isinstance(value, FoamFile.DimensionSet) or assume_dimensions:
478
+ with suppress(TypeError):
479
+ return _serialize_dimensions(value)
480
+
481
+ if assume_field:
482
+ with suppress(TypeError):
483
+ return _serialize_field(value)
484
+
485
+ with suppress(TypeError):
486
+ return _serialize_dimensioned(value)
487
+
488
+ with suppress(TypeError):
489
+ return _serialize_list(value)
490
+
491
+ with suppress(TypeError):
492
+ return _serialize_bool(value)
493
+
494
+ return str(value)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foamlib
3
- Version: 0.1.14
3
+ Version: 0.2.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
@@ -62,7 +62,7 @@ It offers the following classes (among a few others):
62
62
 
63
63
  * [`FoamCase`](https://foamlib.readthedocs.io/en/stable/#foamlib.FoamCase): a class for manipulating, executing and accessing the results of OpenFOAM cases.
64
64
  * [`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 (uses OpenFOAM's `foamDictionary` utility with custom parsing).
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
 
@@ -61,9 +61,6 @@ Homepage = "https://github.com/gerlero/foamlib"
61
61
  Repository = "https://github.com/gerlero/foamlib"
62
62
  Documentation = "https://foamlib.readthedocs.io"
63
63
 
64
- [tool.setuptools]
65
- packages = ["foamlib"]
66
-
67
64
  [tool.setuptools.dynamic]
68
65
  version = {attr = "foamlib.__version__"}
69
66
 
@@ -7,40 +7,54 @@ from typing import Sequence
7
7
  import numpy as np
8
8
 
9
9
  from foamlib import *
10
- from foamlib._dictionaries import _parse
11
-
12
-
13
- def test_parse() -> None:
14
- assert _parse("1") == 1
15
- assert _parse("1.0") == 1.0
16
- assert _parse("1.0e-3") == 1.0e-3
17
- assert _parse("yes") is True
18
- assert _parse("no") is False
19
- assert _parse("word") == "word"
20
- assert _parse("word word") == "word word"
21
- assert _parse('"a string"') == '"a string"'
22
- assert _parse("uniform 1") == 1
23
- assert _parse("uniform 1.0") == 1.0
24
- assert _parse("uniform 1.0e-3") == 1.0e-3
25
- assert _parse("(1.0 2.0 3.0)") == [1.0, 2.0, 3.0]
26
- assert _parse("nonuniform List<scalar> 2(1 2)") == [1, 2]
27
- assert _parse("3(1 2 3)") == [1, 2, 3]
28
- assert _parse("2((1 2 3) (4 5 6))") == [[1, 2, 3], [4, 5, 6]]
29
- assert _parse("nonuniform List<vector> 2((1 2 3) (4 5 6))") == [
10
+ from foamlib._dictionaries import _VALUE
11
+
12
+
13
+ def test_parse_value() -> None:
14
+ assert _VALUE.parse_string("1").as_list()[0] == 1
15
+ assert _VALUE.parse_string("1.0").as_list()[0] == 1.0
16
+ assert _VALUE.parse_string("1.0e-3").as_list()[0] == 1.0e-3
17
+ assert _VALUE.parse_string("yes").as_list()[0] is True
18
+ assert _VALUE.parse_string("no").as_list()[0] is False
19
+ assert _VALUE.parse_string("word").as_list()[0] == "word"
20
+ assert _VALUE.parse_string("word word").as_list()[0] == "word word"
21
+ assert _VALUE.parse_string('"a string"').as_list()[0] == '"a string"'
22
+ assert _VALUE.parse_string("uniform 1").as_list()[0] == 1
23
+ assert _VALUE.parse_string("uniform 1.0").as_list()[0] == 1.0
24
+ assert _VALUE.parse_string("uniform 1.0e-3").as_list()[0] == 1.0e-3
25
+ assert _VALUE.parse_string("(1.0 2.0 3.0)").as_list()[0] == [1.0, 2.0, 3.0]
26
+ assert _VALUE.parse_string("uniform (1 2 3)").as_list()[0] == [1, 2, 3]
27
+ assert _VALUE.parse_string("nonuniform List<scalar> 2(1 2)").as_list()[0] == [1, 2]
28
+ assert _VALUE.parse_string("3(1 2 3)").as_list()[0] == [1, 2, 3]
29
+ assert _VALUE.parse_string("2((1 2 3) (4 5 6))").as_list()[0] == [
30
30
  [1, 2, 3],
31
31
  [4, 5, 6],
32
32
  ]
33
- assert _parse("[1 1 -2 0 0 0 0]") == FoamDimensionSet(mass=1, length=1, time=-2)
34
- assert _parse("g [1 1 -2 0 0 0 0] (0 0 -9.81)") == FoamDimensioned(
33
+ assert _VALUE.parse_string("nonuniform List<vector> 2((1 2 3) (4 5 6))").as_list()[
34
+ 0
35
+ ] == [
36
+ [1, 2, 3],
37
+ [4, 5, 6],
38
+ ]
39
+ assert _VALUE.parse_string("[1 1 -2 0 0 0 0]").as_list()[
40
+ 0
41
+ ] == FoamFile.DimensionSet(mass=1, length=1, time=-2)
42
+ assert _VALUE.parse_string("g [1 1 -2 0 0 0 0] (0 0 -9.81)").as_list()[
43
+ 0
44
+ ] == FoamFile.Dimensioned(
35
45
  name="g",
36
- dimensions=FoamDimensionSet(mass=1, length=1, time=-2),
46
+ dimensions=FoamFile.DimensionSet(mass=1, length=1, time=-2),
37
47
  value=[0, 0, -9.81],
38
48
  )
39
- assert _parse("[1 1 -2 0 0 0 0] 9.81") == FoamDimensioned(
40
- dimensions=FoamDimensionSet(mass=1, length=1, time=-2), value=9.81
49
+ assert _VALUE.parse_string("[1 1 -2 0 0 0 0] 9.81").as_list()[
50
+ 0
51
+ ] == FoamFile.Dimensioned(
52
+ dimensions=FoamFile.DimensionSet(mass=1, length=1, time=-2), value=9.81
41
53
  )
42
54
  assert (
43
- _parse("hex (0 1 2 3 4 5 6 7) (1 1 1) simpleGrading (1 1 1)")
55
+ _VALUE.parse_string(
56
+ "hex (0 1 2 3 4 5 6 7) (1 1 1) simpleGrading (1 1 1)"
57
+ ).as_list()[0]
44
58
  == "hex (0 1 2 3 4 5 6 7) (1 1 1) simpleGrading (1 1 1)"
45
59
  )
46
60
 
@@ -70,21 +84,21 @@ def test_write_read(tmp_path: Path) -> None:
70
84
 
71
85
  d["subdict"] = {"key": "value"}
72
86
  sd = d["subdict"]
73
- assert isinstance(sd, FoamDictionary)
87
+ assert isinstance(sd, FoamFile.Dictionary)
74
88
  assert sd["key"] == "value"
75
89
  assert len(sd) == 1
76
90
  assert list(sd) == ["key"]
77
91
 
78
92
  d["subdict2"] = d["subdict"]
79
93
  sd2 = d["subdict2"]
80
- assert isinstance(sd2, FoamDictionary)
94
+ assert isinstance(sd2, FoamFile.Dictionary)
81
95
  assert sd2["key"] == "value"
82
96
  assert len(sd) == 1
83
97
  assert list(sd) == ["key"]
84
98
 
85
99
  sd["subsubdict"] = d["subdict"]
86
100
  ssd = sd["subsubdict"]
87
- assert isinstance(ssd, FoamDictionary)
101
+ assert isinstance(ssd, FoamFile.Dictionary)
88
102
  assert ssd["key"] == "value"
89
103
 
90
104
  sd["list"] = [1, 2, 3]
@@ -93,12 +107,12 @@ def test_write_read(tmp_path: Path) -> None:
93
107
  sd["nestedList"] = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
94
108
  assert sd["nestedList"] == [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
95
109
 
96
- sd["g"] = FoamDimensioned(
110
+ sd["g"] = FoamFile.Dimensioned(
97
111
  name="g", dimensions=[1, 1, -2, 0, 0, 0, 0], value=[0, 0, -9.81]
98
112
  )
99
- assert sd["g"] == FoamDimensioned(
113
+ assert sd["g"] == FoamFile.Dimensioned(
100
114
  name="g",
101
- dimensions=FoamDimensionSet(mass=1, length=1, time=-2),
115
+ dimensions=FoamFile.DimensionSet(mass=1, length=1, time=-2),
102
116
  value=[0, 0, -9.81],
103
117
  )
104
118
 
@@ -114,17 +128,17 @@ def pitz(tmp_path: Path) -> FoamCase:
114
128
 
115
129
 
116
130
  def test_dimensions(pitz: FoamCase) -> None:
117
- assert pitz[0]["p"].dimensions == FoamDimensionSet(length=2, time=-2)
118
- assert pitz[0]["U"].dimensions == FoamDimensionSet(length=1, time=-1)
131
+ assert pitz[0]["p"].dimensions == FoamFile.DimensionSet(length=2, time=-2)
132
+ assert pitz[0]["U"].dimensions == FoamFile.DimensionSet(length=1, time=-1)
119
133
 
120
- pitz[0]["p"].dimensions = FoamDimensionSet(mass=1, length=1, time=-2)
134
+ pitz[0]["p"].dimensions = FoamFile.DimensionSet(mass=1, length=1, time=-2)
121
135
 
122
- assert pitz[0]["p"].dimensions == FoamDimensionSet(mass=1, length=1, time=-2)
136
+ assert pitz[0]["p"].dimensions == FoamFile.DimensionSet(mass=1, length=1, time=-2)
123
137
 
124
138
 
125
139
  def test_boundary_field(pitz: FoamCase) -> None:
126
140
  outlet = pitz[0]["p"].boundary_field["outlet"]
127
- assert isinstance(outlet, FoamBoundaryDictionary)
141
+ assert isinstance(outlet, FoamFieldFile.BoundaryDictionary)
128
142
  assert outlet.type == "fixedValue"
129
143
  assert outlet.value == 0
130
144
 
@@ -182,7 +196,7 @@ def test_internal_field(pitz: FoamCase) -> None:
182
196
 
183
197
  def test_fv_schemes(pitz: FoamCase) -> None:
184
198
  div_schemes = pitz.fv_schemes["divSchemes"]
185
- assert isinstance(div_schemes, FoamDictionary)
199
+ assert isinstance(div_schemes, FoamFile.Dictionary)
186
200
  scheme = div_schemes["div(phi,U)"]
187
201
  assert isinstance(scheme, str)
188
202
  assert scheme == "bounded Gauss linearUpwind grad(U)"
@@ -1,26 +0,0 @@
1
- __version__ = "0.1.14"
2
-
3
- from ._cases import FoamCase, AsyncFoamCase, FoamTimeDirectory, FoamCaseBase
4
- from ._dictionaries import (
5
- FoamFile,
6
- FoamFieldFile,
7
- FoamDictionary,
8
- FoamBoundariesDictionary,
9
- FoamBoundaryDictionary,
10
- FoamDimensioned,
11
- FoamDimensionSet,
12
- )
13
-
14
- __all__ = [
15
- "FoamCase",
16
- "AsyncFoamCase",
17
- "FoamTimeDirectory",
18
- "FoamCaseBase",
19
- "FoamFile",
20
- "FoamFieldFile",
21
- "FoamDictionary",
22
- "FoamBoundariesDictionary",
23
- "FoamBoundaryDictionary",
24
- "FoamDimensioned",
25
- "FoamDimensionSet",
26
- ]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes