foamlib 0.8.11__py3-none-any.whl → 0.9.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,6 +1,6 @@
1
1
  """A Python interface for interacting with OpenFOAM."""
2
2
 
3
- __version__ = "0.8.11"
3
+ __version__ = "0.9.1"
4
4
 
5
5
  from ._cases import (
6
6
  AsyncFoamCase,
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import re
4
4
  import sys
5
- from typing import Tuple, Union, cast
5
+ from typing import TYPE_CHECKING, Tuple, Union, cast
6
6
 
7
7
  if sys.version_info >= (3, 9):
8
8
  from collections.abc import Iterator, Mapping, MutableMapping, Sequence
@@ -25,6 +25,7 @@ from pyparsing import (
25
25
  LineEnd,
26
26
  Literal,
27
27
  Located,
28
+ NoMatch,
28
29
  Opt,
29
30
  ParserElement,
30
31
  ParseResults,
@@ -37,131 +38,172 @@ from pyparsing import (
37
38
  printables,
38
39
  )
39
40
 
40
- from ._types import Data, Dimensioned, DimensionSet, File, TensorKind
41
+ from ._types import Data, Dimensioned, DimensionSet, File
41
42
 
43
+ if TYPE_CHECKING:
44
+ from numpy.typing import DTypeLike
42
45
 
43
- def _tensor(tensor_kind: TensorKind, *, ignore: Regex | None = None) -> Regex:
44
- float_pattern = r"(?i:[+-]?(?:(?:\d+\.?\d*(?:e[+-]?\d+)?)|nan|inf(?:inity)?))"
45
46
 
46
- if tensor_kind == TensorKind.SCALAR:
47
- ret = Regex(float_pattern)
48
- ret.add_parse_action(lambda tks: [float(tks[0])])
49
- return ret
50
-
51
- ignore_pattern = rf"(?:\s|{ignore.re.pattern})+" if ignore is not None else r"\s+"
47
+ def _ascii_numeric_list(
48
+ dtype: DTypeLike,
49
+ *,
50
+ nested: int | None = None,
51
+ ignore: Regex | None = None,
52
+ empty_ok: bool = False,
53
+ ) -> ParserElement:
54
+ dtype = np.dtype(dtype)
55
+
56
+ if np.issubdtype(dtype, np.floating):
57
+ element = common.ieee_float
58
+ element_pattern = r"(?i:[+-]?(?:(?:\d+\.?\d*(?:e[+-]?\d+)?)|nan|inf(?:inity)?))"
59
+ elif np.issubdtype(dtype, np.integer):
60
+ element = common.integer
61
+ element_pattern = r"(?:-?\d+)"
62
+ else:
63
+ msg = f"Unsupported dtype: {dtype}"
64
+ raise TypeError(msg)
52
65
 
53
- ret = Regex(
54
- rf"\((?:{ignore_pattern})?(?:{float_pattern}{ignore_pattern}){{{tensor_kind.size - 1}}}{float_pattern}(?:{ignore_pattern})?\)"
66
+ spacing_pattern = (
67
+ rf"(?:(?:\s|{ignore.re.pattern})+)" if ignore is not None else r"(?:\s+)"
55
68
  )
56
- ret.add_parse_action(
57
- lambda tks: np.fromstring(
58
- re.sub(ignore.re, " ", tks[0][1:-1])
59
- if ignore is not None
60
- else tks[0][1:-1],
61
- sep=" ",
69
+
70
+ if nested is not None:
71
+ element = (
72
+ Literal("(").suppress() + Group(element[nested]) + Literal(")").suppress()
62
73
  )
63
- )
64
- return ret
74
+ element_pattern = rf"(?:{spacing_pattern}?\({element_pattern}?(?:{element_pattern}{spacing_pattern}){{{nested - 1}}}{element_pattern}{spacing_pattern}?\))"
65
75
 
76
+ list_ = Forward()
66
77
 
67
- def _list_of(entry: ParserElement) -> ParserElement:
68
- return Opt(
69
- Literal("List") + Literal("<") + _IDENTIFIER + Literal(">")
70
- ).suppress() + (
71
- (
72
- counted_array(entry, common.integer + Literal("(").suppress())
73
- + Literal(")").suppress()
74
- ).set_parse_action(lambda tks: [tks.as_list()])
75
- | (
76
- Literal("(").suppress()
77
- + Group((entry)[...], aslist=True)
78
- + Literal(")").suppress()
78
+ def process_count(tks: ParseResults) -> None:
79
+ nonlocal list_
80
+
81
+ if not tks:
82
+ count = None
83
+ else:
84
+ (count,) = tks
85
+ assert isinstance(count, int)
86
+
87
+ if count is None:
88
+ if not empty_ok:
89
+ list_pattern = rf"\({spacing_pattern}?(?:{element_pattern}{spacing_pattern})*{element_pattern}{spacing_pattern}?\)"
90
+ else:
91
+ list_pattern = rf"\({spacing_pattern}?(?:{element_pattern}{spacing_pattern})*{element_pattern}?{spacing_pattern}?\)"
92
+
93
+ elif count == 0:
94
+ if not empty_ok:
95
+ list_ <<= NoMatch()
96
+ else:
97
+ list_ <<= (Literal("(") + Literal(")")).add_parse_action(
98
+ lambda: np.empty((0, nested) if nested else 0, dtype=dtype)
99
+ )
100
+ return
101
+
102
+ else:
103
+ list_pattern = rf"\({spacing_pattern}?(?:{element_pattern}{spacing_pattern}){{{count - 1}}}{element_pattern}{spacing_pattern}?\)"
104
+
105
+ list_ <<= Regex(list_pattern).add_parse_action(
106
+ lambda tks: to_array(tks, dtype=dtype)
79
107
  )
80
- | (
81
- common.integer + Literal("{").suppress() + entry + Literal("}").suppress()
82
- ).set_parse_action(lambda tks: [[tks[1]] * tks[0]])
83
- )
84
108
 
109
+ def to_array(
110
+ tks: ParseResults, *, dtype: DTypeLike
111
+ ) -> np.ndarray[tuple[int] | tuple[int, int], np.dtype[np.integer | np.floating]]:
112
+ (s,) = tks
113
+ assert s.startswith("(")
114
+ assert s.endswith(")")
115
+ s = s[1:-1]
116
+ if ignore is not None:
117
+ s = re.sub(ignore.re, " ", s)
118
+ if nested is not None:
119
+ s = s.replace("(", " ").replace(")", " ")
120
+
121
+ ret: np.ndarray[
122
+ tuple[int] | tuple[int, int], np.dtype[np.integer | np.floating]
123
+ ] = np.fromstring(s, sep=" ", dtype=dtype) # type: ignore[assignment]
124
+
125
+ if nested is not None:
126
+ ret = ret.reshape(-1, nested)
85
127
 
86
- def _parse_ascii_field(
87
- s: str, tensor_kind: TensorKind, *, ignore: Regex | None
88
- ) -> np.ndarray[tuple[int] | tuple[int, int], np.dtype[np.float64]]:
89
- if ignore is not None:
90
- s = re.sub(ignore.re, " ", s)
91
- s = s.replace("(", " ").replace(")", " ")
128
+ return ret
129
+
130
+ def to_full_array(
131
+ tks: ParseResults, *, dtype: type
132
+ ) -> np.ndarray[tuple[int] | tuple[int, int], np.dtype[np.integer | np.floating]]:
133
+ count, lst = tks
134
+ assert isinstance(count, int)
92
135
 
93
- return np.fromstring(s, sep=" ").reshape(-1, *tensor_kind.shape) # type: ignore [return-value]
136
+ if nested is None:
137
+ return np.full(count, lst, dtype=dtype)
94
138
 
139
+ return np.full((count, nested), lst, dtype=dtype) # type: ignore[return-value]
95
140
 
96
- def _unpack_binary_field(
97
- b: bytes, tensor_kind: TensorKind, *, length: int
98
- ) -> np.ndarray[tuple[int] | tuple[int, int], np.dtype[np.float64 | np.float32]]:
99
- float_size = len(b) / tensor_kind.size / length
100
- assert float_size in (4, 8)
141
+ ret = ((Opt(common.integer).add_parse_action(process_count)).suppress() + list_) | (
142
+ common.integer + Literal("{").suppress() + element + Literal("}").suppress()
143
+ ).add_parse_action(lambda tks: to_full_array(tks, dtype=float))
144
+
145
+ if ignore is not None:
146
+ ret.ignore(ignore)
101
147
 
102
- dtype = np.float32 if float_size == 4 else float
103
- return np.frombuffer(b, dtype=dtype).reshape(-1, *tensor_kind.shape) # type: ignore [return-value]
148
+ return ret
104
149
 
105
150
 
106
- def _tensor_list(
107
- tensor_kind: TensorKind, *, ignore: Regex | None = None
151
+ def _binary_numeric_list(
152
+ dtype: DTypeLike, *, nested: int | None = None, empty_ok: bool = False
108
153
  ) -> ParserElement:
109
- tensor = _tensor(tensor_kind, ignore=ignore)
110
- ignore_pattern = rf"(?:\s|{ignore.re.pattern})+" if ignore is not None else r"\s+"
154
+ dtype = np.dtype(dtype)
111
155
 
112
- list_ = Forward()
156
+ elsize = nested if nested is not None else 1
113
157
 
114
- list_ <<= Regex(
115
- rf"\((?:{ignore_pattern})?(?:{tensor.re.pattern}{ignore_pattern})*{tensor.re.pattern}(?:{ignore_pattern})?\)"
116
- ).add_parse_action(
117
- lambda tks: [_parse_ascii_field(tks[0], tensor_kind, ignore=ignore)]
118
- ) | (
119
- (Literal("(") + Literal(")"))
120
- .suppress()
121
- .add_parse_action(lambda: np.array([]).reshape(0, *tensor_kind.shape))
122
- )
158
+ list_ = Forward()
123
159
 
124
- def count_parse_action(tks: ParseResults) -> None:
160
+ def process_count(tks: ParseResults) -> None:
125
161
  nonlocal list_
126
- length = tks[0]
127
- assert isinstance(length, int)
128
-
129
- if not length:
130
- list_ <<= (
131
- ((Literal("(") + Literal(")")) | (Literal("{") + Literal("}")))
132
- .suppress()
133
- .add_parse_action(lambda: np.array([]).reshape(0, *tensor_kind.shape))
134
- )
162
+ (size,) = tks
163
+ assert isinstance(size, int)
164
+
165
+ if size == 0 and not empty_ok:
166
+ list_ <<= NoMatch()
135
167
  return
136
168
 
137
- list_ <<= (
138
- Regex(
139
- rf"\((?:{ignore_pattern})?(?:{tensor.re.pattern}{ignore_pattern}){{{length - 1}}}{tensor.re.pattern}(?:{ignore_pattern})?\)"
140
- ).add_parse_action(
141
- lambda tks: [_parse_ascii_field(tks[0], tensor_kind, ignore=ignore)]
142
- )
143
- | Regex(
144
- rf"\((?s:({'.' * 8 * tensor_kind.size}|{'.' * 4 * tensor_kind.size}){{{length}}})\)"
145
- ).add_parse_action(
146
- lambda tks: [
147
- _unpack_binary_field(
148
- tks[0][1:-1].encode("latin-1"), tensor_kind, length=length
149
- )
150
- ]
151
- )
152
- | (
153
- Literal("{").suppress() + tensor + Literal("}").suppress()
154
- ).add_parse_action(
155
- lambda tks: [np.full((length, *tensor_kind.shape), tks[0], dtype=float)]
156
- )
157
- )
169
+ list_ <<= Regex(rf"\((?s:{'.' * dtype.itemsize * elsize}){{{size}}}\)")
158
170
 
159
- count = common.integer.copy().add_parse_action(count_parse_action)
171
+ def to_array(
172
+ tks: ParseResults,
173
+ ) -> np.ndarray[tuple[int] | tuple[int, int], np.dtype[np.integer | np.floating]]:
174
+ size, s = tks
175
+ assert isinstance(size, int)
176
+ assert isinstance(s, str)
177
+ assert s[0] == "("
178
+ assert s[-1] == ")"
179
+ s = s[1:-1]
180
+
181
+ ret = np.frombuffer(s.encode("latin-1"), dtype=dtype)
182
+
183
+ if nested is not None:
184
+ ret = ret.reshape(-1, nested)
185
+
186
+ return ret # type: ignore[return-value]
160
187
 
161
188
  return (
162
- Opt(Literal("List") + Literal("<") + str(tensor_kind) + Literal(">")).suppress()
163
- + Opt(count).suppress()
164
- + list_
189
+ common.integer.copy().add_parse_action(process_count) + list_
190
+ ).add_parse_action(to_array)
191
+
192
+
193
+ def _list_of(entry: ParserElement) -> ParserElement:
194
+ return (
195
+ (
196
+ counted_array(entry, common.integer + Literal("(").suppress())
197
+ + Literal(")").suppress()
198
+ ).set_parse_action(lambda tks: [tks.as_list()])
199
+ | (
200
+ Literal("(").suppress()
201
+ + Group((entry)[...], aslist=True)
202
+ + Literal(")").suppress()
203
+ )
204
+ | (
205
+ common.integer + Literal("{").suppress() + entry + Literal("}").suppress()
206
+ ).set_parse_action(lambda tks: [[tks[1]] * tks[0]])
165
207
  )
166
208
 
167
209
 
@@ -250,12 +292,11 @@ _SWITCH = (
250
292
  _DIMENSIONS = (
251
293
  Literal("[").suppress() + common.number[0, 7] + Literal("]").suppress()
252
294
  ).set_parse_action(lambda tks: DimensionSet(*tks))
253
- _TENSOR = (
254
- _tensor(TensorKind.SCALAR)
255
- | _tensor(TensorKind.VECTOR)
256
- | _tensor(TensorKind.SYMM_TENSOR)
257
- | _tensor(TensorKind.TENSOR)
258
- )
295
+ _TENSOR = common.ieee_float | (
296
+ Literal("(").suppress()
297
+ + Group(common.ieee_float[3] | common.ieee_float[6] | common.ieee_float[9])
298
+ + Literal(")").suppress()
299
+ ).add_parse_action(lambda tks: np.array(tks[0], dtype=float))
259
300
  _PARENTHESIZED = Forward()
260
301
  _IDENTIFIER = Combine(Word(_IDENTCHARS, _IDENTBODYCHARS) + Opt(_PARENTHESIZED))
261
302
  _PARENTHESIZED <<= Combine(
@@ -270,12 +311,55 @@ _DIMENSIONED = (Opt(_IDENTIFIER) + _DIMENSIONS + _TENSOR).set_parse_action(
270
311
  _FIELD = (Keyword("uniform", _IDENTBODYCHARS).suppress() + _TENSOR) | (
271
312
  Keyword("nonuniform", _IDENTBODYCHARS).suppress()
272
313
  + (
273
- _tensor_list(TensorKind.SCALAR, ignore=_COMMENT)
274
- | _tensor_list(TensorKind.VECTOR, ignore=_COMMENT)
275
- | _tensor_list(TensorKind.SYMM_TENSOR, ignore=_COMMENT)
276
- | _tensor_list(TensorKind.TENSOR, ignore=_COMMENT)
314
+ (
315
+ Opt(
316
+ Literal("List") + Literal("<") + Literal("scalar") + Literal(">")
317
+ ).suppress()
318
+ + (
319
+ _ascii_numeric_list(dtype=float, ignore=_COMMENT, empty_ok=True)
320
+ | _binary_numeric_list(dtype=np.float64, empty_ok=True)
321
+ | _binary_numeric_list(dtype=np.float32, empty_ok=True)
322
+ )
323
+ )
324
+ | (
325
+ Opt(
326
+ Literal("List") + Literal("<") + Literal("vector") + Literal(">")
327
+ ).suppress()
328
+ + (
329
+ _ascii_numeric_list(
330
+ dtype=float, nested=3, ignore=_COMMENT, empty_ok=True
331
+ )
332
+ | _binary_numeric_list(np.float64, nested=3, empty_ok=True)
333
+ | _binary_numeric_list(np.float32, nested=3, empty_ok=True)
334
+ )
335
+ )
336
+ | (
337
+ Opt(
338
+ Literal("List") + Literal("<") + Literal("symmTensor") + Literal(">")
339
+ ).suppress()
340
+ + (
341
+ _ascii_numeric_list(
342
+ dtype=float, nested=6, ignore=_COMMENT, empty_ok=True
343
+ )
344
+ | _binary_numeric_list(np.float64, nested=6, empty_ok=True)
345
+ | _binary_numeric_list(np.float32, nested=6, empty_ok=True)
346
+ )
347
+ )
348
+ | (
349
+ Opt(
350
+ Literal("List") + Literal("<") + Literal("tensor") + Literal(">")
351
+ ).suppress()
352
+ + (
353
+ _ascii_numeric_list(
354
+ dtype=float, nested=9, ignore=_COMMENT, empty_ok=True
355
+ )
356
+ | _binary_numeric_list(np.float64, nested=9, empty_ok=True)
357
+ | _binary_numeric_list(np.float32, nested=9, empty_ok=True)
358
+ )
359
+ )
277
360
  )
278
361
  )
362
+
279
363
  _DIRECTIVE = Word("#", _IDENTBODYCHARS)
280
364
  _TOKEN = dbl_quoted_string | _DIRECTIVE | _IDENTIFIER
281
365
  _DATA = Forward()
@@ -319,7 +403,19 @@ _LOCATED_DICTIONARY = Group(
319
403
  located=True,
320
404
  )
321
405
  )[...]
322
- _LOCATED_DATA = Group(Located(_DATA.copy().add_parse_action(lambda tks: ["", tks[0]])))
406
+ _LOCATED_DATA = Group(
407
+ Located(
408
+ (
409
+ _ascii_numeric_list(dtype=int, ignore=_COMMENT)
410
+ | _binary_numeric_list(dtype=np.int64)
411
+ | _binary_numeric_list(dtype=np.int32)
412
+ | _ascii_numeric_list(dtype=float, nested=3, ignore=_COMMENT)
413
+ | _binary_numeric_list(dtype=np.float64, nested=3)
414
+ | _binary_numeric_list(dtype=np.float32, nested=3)
415
+ | _DATA
416
+ ).add_parse_action(lambda tks: ["", tks[0]])
417
+ )
418
+ )
323
419
 
324
420
  _FILE = (
325
421
  Dict(_LOCATED_DICTIONARY + Opt(_LOCATED_DATA) + _LOCATED_DICTIONARY)
foamlib/_files/_types.py CHANGED
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import sys
4
- from enum import Enum
5
4
  from typing import Dict, NamedTuple, Optional, Union
6
5
 
7
6
  import numpy as np
@@ -42,39 +41,6 @@ TensorLike = Union[
42
41
  ]
43
42
 
44
43
 
45
- class TensorKind(Enum):
46
- SCALAR = ()
47
- VECTOR = (3,)
48
- SYMM_TENSOR = (6,)
49
- TENSOR = (9,)
50
-
51
- @property
52
- def shape(self) -> tuple[()] | tuple[int]:
53
- shape: tuple[()] | tuple[int] = self.value
54
- return shape
55
-
56
- @property
57
- def size(self) -> int:
58
- return int(np.prod(self.shape))
59
-
60
- def __str__(self) -> str:
61
- return {
62
- TensorKind.SCALAR: "scalar",
63
- TensorKind.VECTOR: "vector",
64
- TensorKind.SYMM_TENSOR: "symmTensor",
65
- TensorKind.TENSOR: "tensor",
66
- }[self]
67
-
68
- @staticmethod
69
- def from_shape(shape: tuple[int, ...]) -> TensorKind:
70
- for kind in TensorKind:
71
- if kind.shape == shape:
72
- return kind
73
-
74
- msg = f"No tensor kind for shape {shape!r}"
75
- raise ValueError(msg)
76
-
77
-
78
44
  class Dimensioned:
79
45
  def __init__(
80
46
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: foamlib
3
- Version: 0.8.11
3
+ Version: 0.9.1
4
4
  Summary: A Python interface for interacting with OpenFOAM
5
5
  Project-URL: Homepage, https://github.com/gerlero/foamlib
6
6
  Project-URL: Repository, https://github.com/gerlero/foamlib
@@ -1,4 +1,4 @@
1
- foamlib/__init__.py,sha256=xlrhVvXcBOo_DZcH-CRydXubCOSnMevKoopDBA6S5XQ,453
1
+ foamlib/__init__.py,sha256=0HqaL6QRggvb5wLBXgC3aMmHL7gltD8K2njaeYZJezI,452
2
2
  foamlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  foamlib/_cases/__init__.py,sha256=_A1TTHuQfS9FH2_33lSEyLtOJZGFHZBco1tWJCVOHks,358
4
4
  foamlib/_cases/_async.py,sha256=e4lGTcQBbFGwfG6SmJks5aa5LWd_0dy01kgKZWAgTGQ,11655
@@ -11,10 +11,10 @@ foamlib/_cases/_util.py,sha256=QCizfbuJdOCeF9ogU2R-y-iWX5kfaOA4U2W68t6QlOM,2544
11
11
  foamlib/_files/__init__.py,sha256=q1vkjXnjnSZvo45jPAICpWeF2LZv5V6xfzAR6S8fS5A,96
12
12
  foamlib/_files/_files.py,sha256=gSJQjvB1f7N2yJtCTx9kpivKqSSNjDj37qNMpned5CM,19505
13
13
  foamlib/_files/_io.py,sha256=BGbbm6HKxL2ka0YMCmHqZQZ1R4PPQlkvWWb4FHMAS8k,2217
14
- foamlib/_files/_parsing.py,sha256=FwLcK09bU5utrMcFQ3MYfxYemJ_Y9e47SJeTMc9KMGY,14532
14
+ foamlib/_files/_parsing.py,sha256=Nhktc7RnF7QIydUFm1umgQKiIW0s8GoPmagWRqfvaLQ,17404
15
15
  foamlib/_files/_serialization.py,sha256=QJ-F6BKizVe0gpjnpIfPxNGTqWwalY4PQtCKdDY9D70,5502
16
- foamlib/_files/_types.py,sha256=PDhFW5hUzcoQsLx7M0Va1oaYV6km02jFgrvKJof0JKQ,3750
17
- foamlib-0.8.11.dist-info/METADATA,sha256=SiW-IKCibMoiEVvX9l1UZfFviUu1z8VdHQTjarzueX0,12907
18
- foamlib-0.8.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
- foamlib-0.8.11.dist-info/licenses/LICENSE.txt,sha256=5Dte9TUnLZzPRs4NQzl-Jc2-Ljd-t_v0ZR5Ng5r0UsY,35131
20
- foamlib-0.8.11.dist-info/RECORD,,
16
+ foamlib/_files/_types.py,sha256=q5O_x680XhGfvPHCNF_3objK1imUG2kgQYNRF2Z4qJ0,2918
17
+ foamlib-0.9.1.dist-info/METADATA,sha256=ZSdTaK1ejV7YBHJVoQX384eudRHxTIxfjRiK7kH0Bxk,12906
18
+ foamlib-0.9.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
+ foamlib-0.9.1.dist-info/licenses/LICENSE.txt,sha256=5Dte9TUnLZzPRs4NQzl-Jc2-Ljd-t_v0ZR5Ng5r0UsY,35131
20
+ foamlib-0.9.1.dist-info/RECORD,,