foamlib 0.1.15__py3-none-any.whl → 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
foamlib/__init__.py CHANGED
@@ -1,26 +1,12 @@
1
- __version__ = "0.1.15"
1
+ __version__ = "0.2.1"
2
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
- )
3
+ from ._cases import FoamCase, AsyncFoamCase, FoamCaseBase
4
+ from ._dictionaries import FoamFile, FoamFieldFile
13
5
 
14
6
  __all__ = [
15
7
  "FoamCase",
16
8
  "AsyncFoamCase",
17
- "FoamTimeDirectory",
18
9
  "FoamCaseBase",
19
10
  "FoamFile",
20
11
  "FoamFieldFile",
21
- "FoamDictionary",
22
- "FoamBoundariesDictionary",
23
- "FoamBoundaryDictionary",
24
- "FoamDimensioned",
25
- "FoamDimensionSet",
26
12
  ]
foamlib/_cases.py CHANGED
@@ -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)
foamlib/_dictionaries.py CHANGED
@@ -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,82 +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
- yield from self._cmd(["-keywords"]).splitlines()
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
290
137
 
291
138
  def __len__(self) -> int:
292
139
  return len(list(iter(self)))
293
140
 
294
141
  def __repr__(self) -> str:
295
- return type(self).__name__
296
-
142
+ return "FoamFile.Dictionary"
297
143
 
298
- class FoamBoundaryDictionary(FoamDictionary):
299
- """An OpenFOAM dictionary representing a boundary condition as a mutable mapping."""
300
144
 
301
- def __setitem__(self, key: str, value: Any) -> None:
302
- if key == "value":
303
- self._setitem(key, value, assume_field=True)
304
- else:
305
- self._setitem(key, value)
306
-
307
- @property
308
- def type(self) -> str:
309
- """
310
- Alias of `self["type"]`.
311
- """
312
- ret = self["type"]
313
- if not isinstance(ret, str):
314
- raise TypeError("type is not a string")
315
- return ret
316
-
317
- @type.setter
318
- def type(self, value: str) -> None:
319
- self["type"] = value
320
-
321
- @property
322
- def value(
323
- self,
324
- ) -> Union[
325
- int,
326
- float,
327
- Sequence[Union[int, float, Sequence[Union[int, float]]]],
328
- "NDArray[np.generic]",
329
- ]:
330
- """
331
- Alias of `self["value"]`.
332
- """
333
- ret = self["value"]
334
- if not isinstance(ret, (int, float, Sequence)):
335
- raise TypeError("value is not a field")
336
- return cast(Union[int, float, Sequence[Union[int, float]]], ret)
145
+ class FoamFile(_FoamDictionary):
146
+ """An OpenFOAM dictionary file as a mutable mapping."""
337
147
 
338
- @value.setter
339
- def value(
340
- self,
341
- value: Union[
342
- int,
343
- float,
344
- Sequence[Union[int, float, Sequence[Union[int, float]]]],
345
- "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",
346
160
  ],
347
- ) -> None:
348
- self["value"] = value
349
-
350
- @value.deleter
351
- def value(self) -> None:
352
- del self["value"]
353
-
161
+ defaults=(0, 0, 0, 0, 0, 0, 0),
162
+ )
354
163
 
355
- class FoamBoundariesDictionary(FoamDictionary):
356
- def __getitem__(self, key: str) -> Union[FoamValue, FoamBoundaryDictionary]:
357
- ret = super().__getitem__(key)
358
- if isinstance(ret, FoamDictionary):
359
- ret = FoamBoundaryDictionary(self._file, [*self._keywords, key])
360
- 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
361
169
 
170
+ def __post_init__(self) -> None:
171
+ if not isinstance(self.dimensions, FoamFile.DimensionSet):
172
+ self.dimensions = FoamFile.DimensionSet(*self.dimensions)
362
173
 
363
- class FoamFile(FoamDictionary):
364
- """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
+ """
365
178
 
366
179
  def __init__(self, path: Union[str, Path]) -> None:
367
180
  super().__init__(self, [])
@@ -381,10 +194,83 @@ class FoamFile(FoamDictionary):
381
194
  class FoamFieldFile(FoamFile):
382
195
  """An OpenFOAM dictionary file representing a field as a mutable mapping."""
383
196
 
384
- 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
+ def __repr__(self) -> str:
209
+ return "FoamFieldFile.BoundariesDictionary"
210
+
211
+ class BoundaryDictionary(_FoamDictionary):
212
+ """An OpenFOAM dictionary representing a boundary condition as a mutable mapping."""
213
+
214
+ def __setitem__(self, key: str, value: Any) -> None:
215
+ if key == "value":
216
+ self._setitem(key, value, assume_field=True)
217
+ else:
218
+ self._setitem(key, value)
219
+
220
+ @property
221
+ def type(self) -> str:
222
+ """
223
+ Alias of `self["type"]`.
224
+ """
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, value: str) -> None:
232
+ self["type"] = value
233
+
234
+ @property
235
+ def value(
236
+ self,
237
+ ) -> Union[
238
+ int,
239
+ float,
240
+ Sequence[Union[int, float, Sequence[Union[int, float]]]],
241
+ "NDArray[np.generic]",
242
+ ]:
243
+ """
244
+ Alias of `self["value"]`.
245
+ """
246
+ ret = self["value"]
247
+ if not isinstance(ret, (int, float, Sequence)):
248
+ raise TypeError("value is not a field")
249
+ return cast(Union[int, float, Sequence[Union[int, float]]], ret)
250
+
251
+ @value.setter
252
+ def value(
253
+ self,
254
+ value: Union[
255
+ int,
256
+ float,
257
+ Sequence[Union[int, float, Sequence[Union[int, float]]]],
258
+ "NDArray[np.generic]",
259
+ ],
260
+ ) -> None:
261
+ self["value"] = value
262
+
263
+ @value.deleter
264
+ def value(self) -> None:
265
+ del self["value"]
266
+
267
+ def __repr__(self) -> str:
268
+ return "FoamFieldFile.BoundaryDictionary"
269
+
270
+ def __getitem__(self, key: str) -> Union[FoamFile.Value, _FoamDictionary]:
385
271
  ret = super().__getitem__(key)
386
- if key == "boundaryField" and isinstance(ret, FoamDictionary):
387
- ret = FoamBoundariesDictionary(self, [key])
272
+ if key == "boundaryField" and isinstance(ret, _FoamDictionary):
273
+ ret = FoamFieldFile.BoundariesDictionary(self, [key])
388
274
  return ret
389
275
 
390
276
  def __setitem__(self, key: str, value: Any) -> None:
@@ -396,18 +282,18 @@ class FoamFieldFile(FoamFile):
396
282
  self._setitem(key, value)
397
283
 
398
284
  @property
399
- def dimensions(self) -> FoamDimensionSet:
285
+ def dimensions(self) -> FoamFile.DimensionSet:
400
286
  """
401
287
  Alias of `self["dimensions"]`.
402
288
  """
403
289
  ret = self["dimensions"]
404
- if not isinstance(ret, FoamDimensionSet):
290
+ if not isinstance(ret, FoamFile.DimensionSet):
405
291
  raise TypeError("dimensions is not a DimensionSet")
406
292
  return ret
407
293
 
408
294
  @dimensions.setter
409
295
  def dimensions(
410
- self, value: Union[FoamDimensionSet, Sequence[Union[int, float]]]
296
+ self, value: Union[FoamFile.DimensionSet, Sequence[Union[int, float]]]
411
297
  ) -> None:
412
298
  self["dimensions"] = value
413
299
 
@@ -441,12 +327,168 @@ class FoamFieldFile(FoamFile):
441
327
  self["internalField"] = value
442
328
 
443
329
  @property
444
- def boundary_field(self) -> FoamBoundariesDictionary:
330
+ def boundary_field(self) -> "FoamFieldFile.BoundariesDictionary":
445
331
  """
446
332
  Alias of `self["boundaryField"]`.
447
333
  """
448
334
  ret = self["boundaryField"]
449
- if not isinstance(ret, FoamBoundariesDictionary):
450
- assert not isinstance(ret, FoamDictionary)
335
+ if not isinstance(ret, FoamFieldFile.BoundariesDictionary):
336
+ assert not isinstance(ret, _FoamDictionary)
451
337
  raise TypeError("boundaryField is not a dictionary")
452
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.15
3
+ Version: 0.2.1
4
4
  Summary: A Python interface for interacting with OpenFOAM
5
5
  Author-email: "Gabriel S. Gerlero" <ggerlero@cimec.unl.edu.ar>
6
6
  Project-URL: Homepage, https://github.com/gerlero/foamlib
@@ -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
 
@@ -0,0 +1,10 @@
1
+ foamlib/__init__.py,sha256=XdOd0DaoULxz-haiaIbfRNQjU3_oX2Rz7ZmKYkng1kQ,241
2
+ foamlib/_cases.py,sha256=FL6d2_fCB9KCu9ps8Ur8ITlla3rSFsuvdvMYEXGCBM4,20923
3
+ foamlib/_dictionaries.py,sha256=admW5ZhNvmNi6cpnOEEbESoEL5wLeiQa62o9l7ojZ8M,14828
4
+ foamlib/_subprocesses.py,sha256=5vqdQvpN_2v4GgDqxi-s88NGhZ6doFxkh0XY89ZWuHA,1926
5
+ foamlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ foamlib-0.2.1.dist-info/LICENSE.txt,sha256=5Dte9TUnLZzPRs4NQzl-Jc2-Ljd-t_v0ZR5Ng5r0UsY,35131
7
+ foamlib-0.2.1.dist-info/METADATA,sha256=tlM7gkY0jb3U4by5u0O59zbvSw3V8L2l5b0UrYTvMhE,4600
8
+ foamlib-0.2.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
9
+ foamlib-0.2.1.dist-info/top_level.txt,sha256=ZdVYtetXGwPwyfL-WhlhbTFQGAwKX5P_gXxtH9JYFPI,8
10
+ foamlib-0.2.1.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- foamlib/__init__.py,sha256=u9D3VEcJ9I2qQu1QINi25gF-NtHvfWWCPZcPAbQl-w0,551
2
- foamlib/_cases.py,sha256=Q5c8vPzKT5coOy4q_H6s5gHgEArhLHByh6GSvDVCRdY,20683
3
- foamlib/_dictionaries.py,sha256=dMpIfllM6JtxP5ShAYRD9iiZ2ynnAw00wj68pnCggwA,13093
4
- foamlib/_subprocesses.py,sha256=5vqdQvpN_2v4GgDqxi-s88NGhZ6doFxkh0XY89ZWuHA,1926
5
- foamlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- foamlib-0.1.15.dist-info/LICENSE.txt,sha256=5Dte9TUnLZzPRs4NQzl-Jc2-Ljd-t_v0ZR5Ng5r0UsY,35131
7
- foamlib-0.1.15.dist-info/METADATA,sha256=wn0S3Up_l7CXTRRmLQWAhcZFDekFBQvmA7D4Z8NMbF8,4664
8
- foamlib-0.1.15.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
9
- foamlib-0.1.15.dist-info/top_level.txt,sha256=ZdVYtetXGwPwyfL-WhlhbTFQGAwKX5P_gXxtH9JYFPI,8
10
- foamlib-0.1.15.dist-info/RECORD,,