foamlib 0.9.1__py3-none-any.whl → 0.9.3__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 +1 -1
- foamlib/_files/_files.py +143 -48
- foamlib/_files/_parsing.py +40 -27
- foamlib/_files/_serialization.py +117 -78
- foamlib/_files/_types.py +122 -6
- {foamlib-0.9.1.dist-info → foamlib-0.9.3.dist-info}/METADATA +1 -1
- {foamlib-0.9.1.dist-info → foamlib-0.9.3.dist-info}/RECORD +9 -9
- {foamlib-0.9.1.dist-info → foamlib-0.9.3.dist-info}/WHEEL +0 -0
- {foamlib-0.9.1.dist-info → foamlib-0.9.3.dist-info}/licenses/LICENSE.txt +0 -0
foamlib/__init__.py
CHANGED
foamlib/_files/_files.py
CHANGED
@@ -17,9 +17,11 @@ else:
|
|
17
17
|
import numpy as np
|
18
18
|
|
19
19
|
from ._io import FoamFileIO
|
20
|
-
from .
|
20
|
+
from ._parsing import loads
|
21
|
+
from ._serialization import dumps, normalize_data, normalize_keyword
|
21
22
|
from ._types import (
|
22
23
|
Data,
|
24
|
+
DataLike,
|
23
25
|
Dict_,
|
24
26
|
Dimensioned,
|
25
27
|
DimensionSet,
|
@@ -31,6 +33,32 @@ from ._types import (
|
|
31
33
|
)
|
32
34
|
|
33
35
|
|
36
|
+
def _tensor_kind_for_field(
|
37
|
+
field: FieldLike,
|
38
|
+
) -> str:
|
39
|
+
shape = np.shape(field) # type: ignore [arg-type]
|
40
|
+
if not shape:
|
41
|
+
return "scalar"
|
42
|
+
if shape == (3,):
|
43
|
+
return "vector"
|
44
|
+
if shape == (6,):
|
45
|
+
return "symmTensor"
|
46
|
+
if shape == (9,):
|
47
|
+
return "tensor"
|
48
|
+
if len(shape) == 1:
|
49
|
+
return "scalar"
|
50
|
+
if len(shape) == 2:
|
51
|
+
if shape[1] == 3:
|
52
|
+
return "vector"
|
53
|
+
if shape[1] == 6:
|
54
|
+
return "symmTensor"
|
55
|
+
if shape[1] == 9:
|
56
|
+
return "tensor"
|
57
|
+
|
58
|
+
msg = f"Invalid field shape: {shape}"
|
59
|
+
raise ValueError(msg)
|
60
|
+
|
61
|
+
|
34
62
|
class FoamFile(
|
35
63
|
MutableMapping[
|
36
64
|
Optional[Union[str, Tuple[str, ...]]],
|
@@ -261,7 +289,7 @@ class FoamFile(
|
|
261
289
|
elif not isinstance(keywords, tuple):
|
262
290
|
keywords = (keywords,)
|
263
291
|
|
264
|
-
if keywords and not isinstance(
|
292
|
+
if keywords and not isinstance(normalize_keyword(keywords[-1]), str):
|
265
293
|
msg = f"Invalid keyword: {keywords[-1]}"
|
266
294
|
raise ValueError(msg)
|
267
295
|
|
@@ -283,52 +311,26 @@ class FoamFile(
|
|
283
311
|
self.path.stem if self.path.suffix == ".gz" else self.path.name
|
284
312
|
)
|
285
313
|
|
286
|
-
kind = Kind.DEFAULT
|
287
|
-
if keywords == ("internalField",) or (
|
288
|
-
len(keywords) == 3
|
289
|
-
and keywords[0] == "boundaryField"
|
290
|
-
and (
|
291
|
-
keywords[2] in ("value", "gradient")
|
292
|
-
or keywords[2].endswith("Value")
|
293
|
-
or keywords[2].endswith("Gradient")
|
294
|
-
)
|
295
|
-
):
|
296
|
-
kind = (
|
297
|
-
Kind.BINARY_FIELD if self.format == "binary" else Kind.ASCII_FIELD
|
298
|
-
)
|
299
|
-
elif keywords == ("dimensions",):
|
300
|
-
kind = Kind.DIMENSIONS
|
301
|
-
|
302
314
|
if (
|
303
|
-
|
315
|
+
keywords == ("internalField",)
|
316
|
+
or (
|
317
|
+
len(keywords) == 3
|
318
|
+
and keywords[0] == "boundaryField"
|
319
|
+
and (
|
320
|
+
keywords[2] == "value"
|
321
|
+
or keywords[2] == "gradient"
|
322
|
+
or keywords[2].endswith(("Value", "Gradient"))
|
323
|
+
)
|
324
|
+
)
|
304
325
|
) and self.class_ == "dictionary":
|
305
326
|
try:
|
306
|
-
|
327
|
+
tensor_kind = _tensor_kind_for_field(data) # type: ignore [arg-type]
|
307
328
|
except ValueError:
|
308
329
|
pass
|
309
330
|
else:
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
self.class_ = "volVectorField"
|
314
|
-
elif shape == (6,):
|
315
|
-
self.class_ = "volSymmTensorField"
|
316
|
-
elif shape == (9,):
|
317
|
-
self.class_ = "volTensorField"
|
318
|
-
elif len(shape) == 1:
|
319
|
-
self.class_ = "volScalarField"
|
320
|
-
elif len(shape) == 2:
|
321
|
-
if shape[1] == 3:
|
322
|
-
self.class_ = "volVectorField"
|
323
|
-
elif shape[1] == 6:
|
324
|
-
self.class_ = "volSymmTensorField"
|
325
|
-
elif shape[1] == 9:
|
326
|
-
self.class_ = "volTensorField"
|
327
|
-
|
328
|
-
if kind == Kind.ASCII_FIELD and self.class_.endswith("scalarField"):
|
329
|
-
kind = Kind.SCALAR_ASCII_FIELD
|
330
|
-
elif kind == Kind.BINARY_FIELD and self.class_.endswith("scalarField"):
|
331
|
-
kind = Kind.SCALAR_BINARY_FIELD
|
331
|
+
self.class_ = (
|
332
|
+
"vol" + tensor_kind[0].upper() + tensor_kind[1:] + "Field"
|
333
|
+
)
|
332
334
|
|
333
335
|
parsed = self._get_parsed(missing_ok=True)
|
334
336
|
|
@@ -360,7 +362,7 @@ class FoamFile(
|
|
360
362
|
...,
|
361
363
|
before
|
362
364
|
+ indentation
|
363
|
-
+ dumps(keywords[-1])
|
365
|
+
+ dumps(normalize_keyword(keywords[-1]))
|
364
366
|
+ b"\n"
|
365
367
|
+ indentation
|
366
368
|
+ b"{\n"
|
@@ -373,23 +375,37 @@ class FoamFile(
|
|
373
375
|
self[(*keywords, k)] = v
|
374
376
|
|
375
377
|
elif keywords:
|
376
|
-
|
378
|
+
header = self.get("FoamFile", None)
|
379
|
+
assert header is None or isinstance(header, FoamFile.SubDict)
|
380
|
+
val = dumps(
|
381
|
+
data,
|
382
|
+
keywords=keywords,
|
383
|
+
header=header,
|
384
|
+
)
|
377
385
|
parsed.put(
|
378
386
|
keywords,
|
379
|
-
|
387
|
+
normalize_data(data, keywords=keywords),
|
380
388
|
before
|
381
389
|
+ indentation
|
382
|
-
+ dumps(keywords[-1])
|
390
|
+
+ dumps(normalize_keyword(keywords[-1]))
|
383
391
|
+ ((b" " + val) if val else b"")
|
384
392
|
+ (b";" if not keywords[-1].startswith("#") else b"")
|
385
393
|
+ after,
|
386
394
|
)
|
387
395
|
|
388
396
|
else:
|
397
|
+
header = self.get("FoamFile", None)
|
398
|
+
assert header is None or isinstance(header, FoamFile.SubDict)
|
389
399
|
parsed.put(
|
390
400
|
(),
|
391
|
-
|
392
|
-
before
|
401
|
+
normalize_data(data, keywords=keywords),
|
402
|
+
before
|
403
|
+
+ dumps(
|
404
|
+
data,
|
405
|
+
keywords=(),
|
406
|
+
header=header,
|
407
|
+
)
|
408
|
+
+ after,
|
393
409
|
)
|
394
410
|
|
395
411
|
def __delitem__(self, keywords: str | tuple[str, ...] | None) -> None:
|
@@ -442,6 +458,85 @@ class FoamFile(
|
|
442
458
|
d.pop("FoamFile", None)
|
443
459
|
return deepcopy(d)
|
444
460
|
|
461
|
+
@staticmethod
|
462
|
+
def loads(
|
463
|
+
s: bytes | str,
|
464
|
+
*,
|
465
|
+
include_header: bool = False,
|
466
|
+
) -> File | Data:
|
467
|
+
"""
|
468
|
+
Standalone deserializing function.
|
469
|
+
|
470
|
+
Deserialize the OpenFOAM FoamFile format to Python objects.
|
471
|
+
|
472
|
+
:param s: The string to deserialize. This can be a dictionary, list, or any
|
473
|
+
other object that can be serialized to the OpenFOAM format.
|
474
|
+
:param include_header: Whether to include the "FoamFile" header in the output.
|
475
|
+
If `True`, the header will be included if it is present in the input object.
|
476
|
+
"""
|
477
|
+
ret = loads(s)
|
478
|
+
|
479
|
+
if not include_header and isinstance(ret, Mapping) and "FoamFile" in ret:
|
480
|
+
del ret["FoamFile"]
|
481
|
+
if len(ret) == 1 and None in ret:
|
482
|
+
val = ret[None]
|
483
|
+
assert not isinstance(val, Mapping)
|
484
|
+
return val
|
485
|
+
|
486
|
+
return ret
|
487
|
+
|
488
|
+
@staticmethod
|
489
|
+
def dumps(file: File | DataLike, *, ensure_header: bool = True) -> bytes:
|
490
|
+
"""
|
491
|
+
Standalone serializing function.
|
492
|
+
|
493
|
+
Serialize Python objects to the OpenFOAM FoamFile format.
|
494
|
+
|
495
|
+
:param file: The Python object to serialize. This can be a dictionary, list,
|
496
|
+
or any other object that can be serialized to the OpenFOAM format.
|
497
|
+
:param ensure_header: Whether to include the "FoamFile" header in the output.
|
498
|
+
If `True`, a header will be included if it is not already present in the
|
499
|
+
input object.
|
500
|
+
"""
|
501
|
+
if isinstance(file, Mapping):
|
502
|
+
header = file.get("FoamFile", None)
|
503
|
+
assert isinstance(header, FoamFile.SubDict) or header is None
|
504
|
+
entries: list[bytes] = []
|
505
|
+
for k, v in file.items():
|
506
|
+
if k is not None:
|
507
|
+
entries.append(
|
508
|
+
dumps((k, v), keywords=(), header=header, tuple_is_entry=True)
|
509
|
+
)
|
510
|
+
else:
|
511
|
+
entries.append(dumps(v, keywords=(), header=header))
|
512
|
+
ret = b" ".join(entries)
|
513
|
+
else:
|
514
|
+
header = None
|
515
|
+
ret = dumps(file)
|
516
|
+
|
517
|
+
if header is None and ensure_header:
|
518
|
+
class_ = "dictionary"
|
519
|
+
if isinstance(file, Mapping) and "internalField" in file:
|
520
|
+
try:
|
521
|
+
tensor_kind = _tensor_kind_for_field(file["internalField"]) # type: ignore [arg-type]
|
522
|
+
except (ValueError, TypeError):
|
523
|
+
pass
|
524
|
+
else:
|
525
|
+
class_ = "vol" + tensor_kind[0].upper() + tensor_kind[1:] + "Field"
|
526
|
+
|
527
|
+
header = {"version": 2.0, "format": "ascii", "class": class_}
|
528
|
+
|
529
|
+
ret = (
|
530
|
+
dumps(
|
531
|
+
{"FoamFile": header},
|
532
|
+
keywords=(),
|
533
|
+
)
|
534
|
+
+ b" "
|
535
|
+
+ ret
|
536
|
+
)
|
537
|
+
|
538
|
+
return ret
|
539
|
+
|
445
540
|
|
446
541
|
class FoamFieldFile(FoamFile):
|
447
542
|
"""
|
foamlib/_files/_parsing.py
CHANGED
@@ -363,7 +363,9 @@ _FIELD = (Keyword("uniform", _IDENTBODYCHARS).suppress() + _TENSOR) | (
|
|
363
363
|
_DIRECTIVE = Word("#", _IDENTBODYCHARS)
|
364
364
|
_TOKEN = dbl_quoted_string | _DIRECTIVE | _IDENTIFIER
|
365
365
|
_DATA = Forward()
|
366
|
-
_KEYWORD_ENTRY = _keyword_entry_of(
|
366
|
+
_KEYWORD_ENTRY = _keyword_entry_of(
|
367
|
+
_TOKEN | _list_of(_IDENTIFIER), Opt(_DATA, default="")
|
368
|
+
)
|
367
369
|
_DICT = _dict_of(_TOKEN, _DATA)
|
368
370
|
_DATA_ENTRY = Forward()
|
369
371
|
_LIST_ENTRY = _DICT | _KEYWORD_ENTRY | _DATA_ENTRY
|
@@ -380,21 +382,41 @@ _NUMBER = (
|
|
380
382
|
)
|
381
383
|
_DATA_ENTRY <<= _FIELD | _LIST | _DIMENSIONED | _DIMENSIONS | _NUMBER | _SWITCH | _TOKEN
|
382
384
|
|
383
|
-
_DATA <<= (
|
384
|
-
|
385
|
-
|
385
|
+
_DATA <<= _DATA_ENTRY[1, ...].set_parse_action(
|
386
|
+
lambda tks: [tuple(tks)] if len(tks) > 1 else [tks[0]]
|
387
|
+
)
|
388
|
+
|
389
|
+
_STANDALONE_DATA = (
|
390
|
+
_ascii_numeric_list(dtype=int, ignore=_COMMENT)
|
391
|
+
| _binary_numeric_list(dtype=np.int64)
|
392
|
+
| _binary_numeric_list(dtype=np.int32)
|
393
|
+
| _ascii_numeric_list(dtype=float, nested=3, ignore=_COMMENT)
|
394
|
+
| _binary_numeric_list(dtype=np.float64, nested=3)
|
395
|
+
| _binary_numeric_list(dtype=np.float32, nested=3)
|
396
|
+
| _DATA
|
397
|
+
).add_parse_action(lambda tks: [None, tks[0]])
|
398
|
+
|
399
|
+
|
400
|
+
_FILE = (
|
401
|
+
Dict(_KEYWORD_ENTRY[...] + Opt(Group(_STANDALONE_DATA)) + _KEYWORD_ENTRY[...])
|
386
402
|
.ignore(_COMMENT)
|
387
403
|
.parse_with_tabs()
|
388
404
|
)
|
389
405
|
|
390
406
|
|
391
|
-
def
|
392
|
-
if
|
393
|
-
|
394
|
-
|
407
|
+
def loads(s: bytes | str) -> File | Data:
|
408
|
+
if isinstance(s, bytes):
|
409
|
+
s = s.decode("latin-1")
|
410
|
+
|
411
|
+
file = _FILE.parse_string(s, parse_all=True).as_dict()
|
395
412
|
|
413
|
+
if len(file) == 1 and None in file:
|
414
|
+
return file[None] # type: ignore[no-any-return]
|
396
415
|
|
397
|
-
|
416
|
+
return file
|
417
|
+
|
418
|
+
|
419
|
+
_LOCATED_KEYWORD_ENTRIES = Group(
|
398
420
|
_keyword_entry_of(
|
399
421
|
_TOKEN,
|
400
422
|
Opt(_DATA, default=""),
|
@@ -403,22 +425,14 @@ _LOCATED_DICTIONARY = Group(
|
|
403
425
|
located=True,
|
404
426
|
)
|
405
427
|
)[...]
|
406
|
-
|
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
|
-
)
|
428
|
+
_LOCATED_STANDALONE_DATA = Group(Located(_STANDALONE_DATA))
|
419
429
|
|
420
|
-
|
421
|
-
Dict(
|
430
|
+
_LOCATED_FILE = (
|
431
|
+
Dict(
|
432
|
+
_LOCATED_KEYWORD_ENTRIES
|
433
|
+
+ Opt(_LOCATED_STANDALONE_DATA)
|
434
|
+
+ _LOCATED_KEYWORD_ENTRIES
|
435
|
+
)
|
422
436
|
.ignore(_COMMENT)
|
423
437
|
.parse_with_tabs()
|
424
438
|
)
|
@@ -430,7 +444,7 @@ class Parsed(Mapping[Tuple[str, ...], Union[Data, EllipsisType]]):
|
|
430
444
|
tuple[str, ...],
|
431
445
|
tuple[int, Data | EllipsisType, int],
|
432
446
|
] = {}
|
433
|
-
for parse_result in
|
447
|
+
for parse_result in _LOCATED_FILE.parse_string(
|
434
448
|
contents.decode("latin-1"), parse_all=True
|
435
449
|
):
|
436
450
|
self._parsed.update(self._flatten_result(parse_result))
|
@@ -453,8 +467,7 @@ class Parsed(Mapping[Tuple[str, ...], Union[Data, EllipsisType]]):
|
|
453
467
|
end = parse_result.locn_end
|
454
468
|
assert isinstance(end, int)
|
455
469
|
keyword, *data = item
|
456
|
-
|
457
|
-
if not keyword:
|
470
|
+
if keyword is None:
|
458
471
|
assert not _keywords
|
459
472
|
assert len(data) == 1
|
460
473
|
assert not isinstance(data[0], ParseResults)
|
foamlib/_files/_serialization.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import sys
|
4
|
-
from enum import Enum, auto
|
5
4
|
from typing import overload
|
6
5
|
|
7
6
|
if sys.version_info >= (3, 9):
|
@@ -11,7 +10,7 @@ else:
|
|
11
10
|
|
12
11
|
import numpy as np
|
13
12
|
|
14
|
-
from ._parsing import
|
13
|
+
from ._parsing import loads
|
15
14
|
from ._types import (
|
16
15
|
Data,
|
17
16
|
DataLike,
|
@@ -23,31 +22,32 @@ from ._types import (
|
|
23
22
|
)
|
24
23
|
|
25
24
|
|
26
|
-
class Kind(Enum):
|
27
|
-
DEFAULT = auto()
|
28
|
-
SINGLE_ENTRY = auto()
|
29
|
-
ASCII_FIELD = auto()
|
30
|
-
SCALAR_ASCII_FIELD = auto()
|
31
|
-
BINARY_FIELD = auto()
|
32
|
-
SCALAR_BINARY_FIELD = auto()
|
33
|
-
DIMENSIONS = auto()
|
34
|
-
KEYWORD = auto()
|
35
|
-
|
36
|
-
|
37
25
|
@overload
|
38
|
-
def
|
26
|
+
def normalize_data(
|
27
|
+
data: DataLike, *, keywords: tuple[str, ...] | None = None
|
28
|
+
) -> Data: ...
|
39
29
|
|
40
30
|
|
41
31
|
@overload
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
32
|
+
def normalize_data(
|
33
|
+
data: EntryLike, *, keywords: tuple[str, ...] | None = None
|
34
|
+
) -> Entry: ...
|
35
|
+
|
36
|
+
|
37
|
+
def normalize_data(
|
38
|
+
data: EntryLike, *, keywords: tuple[str, ...] | None = None
|
39
|
+
) -> Entry:
|
40
|
+
if keywords is not None and (
|
41
|
+
keywords == ("internalField",)
|
42
|
+
or (
|
43
|
+
len(keywords) == 3
|
44
|
+
and keywords[0] == "boundaryField"
|
45
|
+
and (
|
46
|
+
keywords[2] == "value"
|
47
|
+
or keywords[2] == "gradient"
|
48
|
+
or keywords[2].endswith(("Value", "Gradient"))
|
49
|
+
)
|
50
|
+
)
|
51
51
|
):
|
52
52
|
if is_sequence(data):
|
53
53
|
try:
|
@@ -61,41 +61,50 @@ def normalize(data: EntryLike, *, kind: Kind = Kind.DEFAULT) -> Entry:
|
|
61
61
|
if arr.ndim == 1 or (arr.ndim == 2 and arr.shape[1] in (3, 6, 9)):
|
62
62
|
return arr # type: ignore [return-value]
|
63
63
|
|
64
|
-
return [
|
64
|
+
return [normalize_data(d) for d in data]
|
65
65
|
|
66
66
|
if isinstance(data, int):
|
67
67
|
return float(data)
|
68
68
|
|
69
|
-
return
|
69
|
+
return normalize_data(data)
|
70
|
+
|
71
|
+
if isinstance(data, Mapping):
|
72
|
+
return {normalize_keyword(k): normalize_data(v) for k, v in data.items()} # type: ignore [misc]
|
70
73
|
|
71
74
|
if isinstance(data, np.ndarray):
|
72
75
|
ret = data.tolist()
|
73
76
|
assert isinstance(ret, (int, float, list))
|
74
77
|
return ret
|
75
78
|
|
76
|
-
if isinstance(data, Mapping):
|
77
|
-
return {k: normalize(v, kind=kind) for k, v in data.items()}
|
78
|
-
|
79
79
|
if (
|
80
|
-
|
80
|
+
not isinstance(data, DimensionSet)
|
81
|
+
and keywords is not None
|
82
|
+
and keywords == ("dimensions",)
|
81
83
|
and is_sequence(data)
|
82
84
|
and len(data) <= 7
|
83
85
|
and all(isinstance(d, (int, float)) for d in data)
|
84
86
|
):
|
85
87
|
return DimensionSet(*data)
|
86
88
|
|
87
|
-
if isinstance(data, tuple) and
|
89
|
+
if keywords is None and isinstance(data, tuple) and len(data) == 2:
|
88
90
|
k, v = data
|
89
|
-
|
91
|
+
assert not isinstance(k, Mapping)
|
92
|
+
return (
|
93
|
+
normalize_keyword(k),
|
94
|
+
normalize_data(v) if not isinstance(v, Mapping) else v,
|
95
|
+
) # type: ignore [return-value]
|
90
96
|
|
91
|
-
if
|
92
|
-
|
97
|
+
if (
|
98
|
+
is_sequence(data)
|
99
|
+
and not isinstance(data, DimensionSet)
|
100
|
+
and (keywords is None or not isinstance(data, tuple))
|
101
|
+
):
|
102
|
+
return [normalize_data(d) for d in data]
|
93
103
|
|
94
104
|
if isinstance(data, str):
|
95
|
-
|
96
|
-
if
|
97
|
-
return
|
98
|
-
return parsed_data
|
105
|
+
s = loads(data)
|
106
|
+
if isinstance(s, (str, tuple, bool)):
|
107
|
+
return s
|
99
108
|
|
100
109
|
if isinstance(
|
101
110
|
data,
|
@@ -107,52 +116,70 @@ def normalize(data: EntryLike, *, kind: Kind = Kind.DEFAULT) -> Entry:
|
|
107
116
|
raise TypeError(msg)
|
108
117
|
|
109
118
|
|
119
|
+
def normalize_keyword(data: DataLike) -> Data:
|
120
|
+
ret = normalize_data(data)
|
121
|
+
|
122
|
+
if isinstance(data, str) and isinstance(ret, bool):
|
123
|
+
return data
|
124
|
+
|
125
|
+
return ret
|
126
|
+
|
127
|
+
|
110
128
|
def dumps(
|
111
129
|
data: EntryLike,
|
112
130
|
*,
|
113
|
-
|
131
|
+
keywords: tuple[str, ...] | None = None,
|
132
|
+
header: Mapping[str, Entry] | None = None,
|
133
|
+
tuple_is_entry: bool = False,
|
114
134
|
) -> bytes:
|
115
|
-
data =
|
135
|
+
data = normalize_data(data, keywords=keywords)
|
116
136
|
|
117
137
|
if isinstance(data, Mapping):
|
118
138
|
return (
|
119
139
|
b"{"
|
120
|
-
+ b" ".join(
|
140
|
+
+ b" ".join(
|
141
|
+
dumps(
|
142
|
+
(k, v),
|
143
|
+
keywords=keywords,
|
144
|
+
tuple_is_entry=True,
|
145
|
+
)
|
146
|
+
for k, v in data.items()
|
147
|
+
)
|
121
148
|
+ b"}"
|
122
149
|
)
|
123
150
|
|
124
|
-
if
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
151
|
+
if (
|
152
|
+
keywords is not None
|
153
|
+
and (
|
154
|
+
keywords == ("internalField",)
|
155
|
+
or (
|
156
|
+
len(keywords) == 3
|
157
|
+
and keywords[0] == "boundaryField"
|
158
|
+
and (
|
159
|
+
keywords[2] == "value"
|
160
|
+
or keywords[2] == "gradient"
|
161
|
+
or keywords[2].endswith(("Value", "Gradient"))
|
162
|
+
)
|
163
|
+
)
|
164
|
+
)
|
165
|
+
and isinstance(data, (int, float, np.ndarray))
|
166
|
+
):
|
167
|
+
data = np.asarray(data) # type: ignore [assignment]
|
168
|
+
class_ = header.get("class", "") if header else ""
|
169
|
+
assert isinstance(class_, str)
|
170
|
+
scalar = "Scalar" in class_
|
136
171
|
|
137
|
-
if kind in (
|
138
|
-
Kind.ASCII_FIELD,
|
139
|
-
Kind.SCALAR_ASCII_FIELD,
|
140
|
-
Kind.BINARY_FIELD,
|
141
|
-
Kind.SCALAR_BINARY_FIELD,
|
142
|
-
) and (isinstance(data, (int, float, np.ndarray))):
|
143
172
|
shape = np.shape(data)
|
144
|
-
if not shape or (
|
145
|
-
|
146
|
-
and shape in ((3,), (6,), (9,))
|
147
|
-
):
|
148
|
-
return b"uniform " + dumps(data, kind=Kind.SINGLE_ENTRY)
|
173
|
+
if not shape or (not scalar and shape in ((3,), (6,), (9,))):
|
174
|
+
return b"uniform " + dumps(data)
|
149
175
|
|
150
176
|
assert isinstance(data, np.ndarray)
|
151
|
-
ndim =
|
177
|
+
ndim = np.ndim(data)
|
152
178
|
if ndim == 1:
|
153
179
|
tensor_kind = b"scalar"
|
154
180
|
|
155
181
|
elif ndim == 2:
|
182
|
+
assert len(shape) == 2
|
156
183
|
if shape[1] == 3:
|
157
184
|
tensor_kind = b"vector"
|
158
185
|
elif shape[1] == 6:
|
@@ -165,34 +192,46 @@ def dumps(
|
|
165
192
|
else:
|
166
193
|
return dumps(data)
|
167
194
|
|
168
|
-
|
169
|
-
|
170
|
-
else
|
171
|
-
assert kind in (Kind.ASCII_FIELD, Kind.SCALAR_ASCII_FIELD)
|
172
|
-
contents = dumps(data, kind=Kind.SINGLE_ENTRY)
|
195
|
+
binary = (header.get("format", "") if header else "") == "binary"
|
196
|
+
|
197
|
+
contents = b"(" + data.tobytes() + b")" if binary else dumps(data)
|
173
198
|
|
174
199
|
return b"nonuniform List<" + tensor_kind + b"> " + dumps(len(data)) + contents
|
175
200
|
|
201
|
+
if isinstance(data, DimensionSet):
|
202
|
+
return b"[" + b" ".join(dumps(v) for v in data) + b"]"
|
203
|
+
|
176
204
|
if isinstance(data, Dimensioned):
|
177
205
|
if data.name is not None:
|
178
206
|
return (
|
179
207
|
dumps(data.name)
|
180
208
|
+ b" "
|
181
|
-
+ dumps(data.dimensions
|
209
|
+
+ dumps(data.dimensions)
|
182
210
|
+ b" "
|
183
|
-
+ dumps(data.value
|
211
|
+
+ dumps(data.value)
|
184
212
|
)
|
185
|
-
return (
|
186
|
-
dumps(data.dimensions, kind=Kind.DIMENSIONS)
|
187
|
-
+ b" "
|
188
|
-
+ dumps(data.value, kind=Kind.SINGLE_ENTRY)
|
189
|
-
)
|
213
|
+
return dumps(data.dimensions) + b" " + dumps(data.value)
|
190
214
|
|
191
215
|
if isinstance(data, tuple):
|
216
|
+
if tuple_is_entry:
|
217
|
+
k, v = data
|
218
|
+
ret = dumps(k)
|
219
|
+
val = dumps(
|
220
|
+
v,
|
221
|
+
keywords=(*keywords, k)
|
222
|
+
if keywords is not None and isinstance(k, str)
|
223
|
+
else None,
|
224
|
+
)
|
225
|
+
if val:
|
226
|
+
ret += b" " + val
|
227
|
+
if not isinstance(v, Mapping):
|
228
|
+
ret += b";"
|
229
|
+
return ret
|
230
|
+
|
192
231
|
return b" ".join(dumps(v) for v in data)
|
193
232
|
|
194
|
-
if is_sequence(data)
|
195
|
-
return b"(" + b" ".join(dumps(v,
|
233
|
+
if is_sequence(data):
|
234
|
+
return b"(" + b" ".join(dumps(v, tuple_is_entry=True) for v in data) + b")"
|
196
235
|
|
197
236
|
if data is True:
|
198
237
|
return b"yes"
|
foamlib/_files/_types.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import sys
|
4
|
-
from typing import Dict, NamedTuple, Optional, Union
|
4
|
+
from typing import Any, Dict, NamedTuple, Optional, Union
|
5
5
|
|
6
6
|
import numpy as np
|
7
7
|
|
@@ -28,6 +28,47 @@ class DimensionSet(NamedTuple):
|
|
28
28
|
def __repr__(self) -> str:
|
29
29
|
return f"{type(self).__name__}({', '.join(f'{n}={v}' for n, v in zip(self._fields, self) if v != 0)})"
|
30
30
|
|
31
|
+
def __add__(self, other: DimensionSet) -> DimensionSet: # type: ignore[override]
|
32
|
+
if not isinstance(other, DimensionSet):
|
33
|
+
return NotImplemented
|
34
|
+
|
35
|
+
if self != other:
|
36
|
+
msg = f"Cannot add DimensionSet with different dimensions: {self} + {other}"
|
37
|
+
raise ValueError(msg)
|
38
|
+
|
39
|
+
return self
|
40
|
+
|
41
|
+
def __sub__(self, other: DimensionSet) -> DimensionSet:
|
42
|
+
if not isinstance(other, DimensionSet):
|
43
|
+
return NotImplemented
|
44
|
+
|
45
|
+
if self != other:
|
46
|
+
msg = f"Cannot subtract DimensionSet with different dimensions: {self} - {other}"
|
47
|
+
raise ValueError(msg)
|
48
|
+
|
49
|
+
return self
|
50
|
+
|
51
|
+
def __mul__(self, other: DimensionSet) -> DimensionSet: # type: ignore[override]
|
52
|
+
if not isinstance(other, DimensionSet):
|
53
|
+
return NotImplemented
|
54
|
+
|
55
|
+
return DimensionSet(*(a + b for a, b in zip(self, other)))
|
56
|
+
|
57
|
+
def __truediv__(self, other: DimensionSet) -> DimensionSet:
|
58
|
+
if not isinstance(other, DimensionSet):
|
59
|
+
return NotImplemented
|
60
|
+
|
61
|
+
return DimensionSet(*(a - b for a, b in zip(self, other)))
|
62
|
+
|
63
|
+
def __pow__(self, exponent: float) -> DimensionSet:
|
64
|
+
if not isinstance(exponent, (int, float)):
|
65
|
+
return NotImplemented
|
66
|
+
|
67
|
+
return DimensionSet(*(a * exponent for a in self))
|
68
|
+
|
69
|
+
def __bool__(self) -> bool:
|
70
|
+
return any(v != 0 for v in self)
|
71
|
+
|
31
72
|
|
32
73
|
Tensor = Union[
|
33
74
|
float,
|
@@ -61,16 +102,91 @@ class Dimensioned:
|
|
61
102
|
|
62
103
|
self.name = name
|
63
104
|
|
64
|
-
def
|
105
|
+
def __repr__(self) -> str:
|
106
|
+
if self.name is not None:
|
107
|
+
return (
|
108
|
+
f"{type(self).__name__}({self.value}, {self.dimensions}, {self.name})"
|
109
|
+
)
|
110
|
+
return f"{type(self).__name__}({self.value}, {self.dimensions})"
|
111
|
+
|
112
|
+
def __add__(self, other: Dimensioned | Tensor) -> Dimensioned:
|
113
|
+
if not isinstance(other, Dimensioned):
|
114
|
+
other = Dimensioned(other, DimensionSet())
|
115
|
+
|
116
|
+
return Dimensioned(
|
117
|
+
self.value + other.value, # type: ignore [arg-type]
|
118
|
+
self.dimensions + other.dimensions,
|
119
|
+
f"{self.name} + {other.name}"
|
120
|
+
if self.name is not None and other.name is not None
|
121
|
+
else None,
|
122
|
+
)
|
123
|
+
|
124
|
+
def __sub__(self, other: Dimensioned | Tensor) -> Dimensioned:
|
125
|
+
if not isinstance(other, Dimensioned):
|
126
|
+
other = Dimensioned(other, DimensionSet())
|
127
|
+
|
128
|
+
return Dimensioned(
|
129
|
+
self.value - other.value, # type: ignore [arg-type]
|
130
|
+
self.dimensions - other.dimensions,
|
131
|
+
f"{self.name} - {other.name}"
|
132
|
+
if self.name is not None and other.name is not None
|
133
|
+
else None,
|
134
|
+
)
|
135
|
+
|
136
|
+
def __mul__(self, other: Dimensioned | Tensor) -> Dimensioned:
|
137
|
+
if not isinstance(other, Dimensioned):
|
138
|
+
other = Dimensioned(other, DimensionSet())
|
139
|
+
|
140
|
+
return Dimensioned(
|
141
|
+
self.value * other.value, # type: ignore [arg-type]
|
142
|
+
self.dimensions * other.dimensions,
|
143
|
+
f"{self.name} * {other.name}"
|
144
|
+
if self.name is not None and other.name is not None
|
145
|
+
else None,
|
146
|
+
)
|
147
|
+
|
148
|
+
def __truediv__(self, other: Dimensioned | Tensor) -> Dimensioned:
|
65
149
|
if not isinstance(other, Dimensioned):
|
150
|
+
other = Dimensioned(other, DimensionSet())
|
151
|
+
|
152
|
+
return Dimensioned(
|
153
|
+
self.value / other.value, # type: ignore [arg-type]
|
154
|
+
self.dimensions / other.dimensions,
|
155
|
+
f"{self.name} / {other.name}"
|
156
|
+
if self.name is not None and other.name is not None
|
157
|
+
else None,
|
158
|
+
)
|
159
|
+
|
160
|
+
def __pow__(self, exponent: float) -> Dimensioned:
|
161
|
+
if not isinstance(exponent, (int, float)):
|
66
162
|
return NotImplemented
|
67
163
|
|
68
|
-
return (
|
69
|
-
self.
|
70
|
-
|
71
|
-
|
164
|
+
return Dimensioned(
|
165
|
+
self.value**exponent, # type: ignore [arg-type]
|
166
|
+
self.dimensions**exponent,
|
167
|
+
f"{self.name} ** {exponent}" if self.name is not None else None,
|
72
168
|
)
|
73
169
|
|
170
|
+
def __float__(self) -> float:
|
171
|
+
if self.dimensions:
|
172
|
+
msg = f"Cannot convert non-dimensionless Dimensioned object to float: {self.dimensions}"
|
173
|
+
raise ValueError(msg)
|
174
|
+
return float(self.value)
|
175
|
+
|
176
|
+
def __int__(self) -> int:
|
177
|
+
if self.dimensions:
|
178
|
+
msg = f"Cannot convert non-dimensionless Dimensioned object to int: {self.dimensions}"
|
179
|
+
raise ValueError(msg)
|
180
|
+
return int(self.value)
|
181
|
+
|
182
|
+
def __array__(
|
183
|
+
self, dtype: Any = None, *, copy: Any = None
|
184
|
+
) -> np.ndarray[tuple[()] | tuple[int], np.dtype[np.float64]]:
|
185
|
+
if self.dimensions:
|
186
|
+
msg = f"Cannot convert non-dimensionless Dimensioned object to array: {self.dimensions}"
|
187
|
+
raise ValueError(msg)
|
188
|
+
return np.array(self.value, dtype=dtype, copy=copy)
|
189
|
+
|
74
190
|
|
75
191
|
Field = Union[
|
76
192
|
float,
|
@@ -1,4 +1,4 @@
|
|
1
|
-
foamlib/__init__.py,sha256=
|
1
|
+
foamlib/__init__.py,sha256=sKW9pgKveQO8QUcC-4ZGxJAHmRBBUsEZzFXumN9SXpY,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
|
@@ -9,12 +9,12 @@ foamlib/_cases/_subprocess.py,sha256=VHV2SuOLqa711an6kCuvN6UlIkeh4qqFfdrpNoKzQps
|
|
9
9
|
foamlib/_cases/_sync.py,sha256=yhrkwStKri7u41YImYCGBH4REcKn8Ar-32VW_WPa40c,9641
|
10
10
|
foamlib/_cases/_util.py,sha256=QCizfbuJdOCeF9ogU2R-y-iWX5kfaOA4U2W68t6QlOM,2544
|
11
11
|
foamlib/_files/__init__.py,sha256=q1vkjXnjnSZvo45jPAICpWeF2LZv5V6xfzAR6S8fS5A,96
|
12
|
-
foamlib/_files/_files.py,sha256=
|
12
|
+
foamlib/_files/_files.py,sha256=7Si-C-2O0h27Ga4bC0W-i6_PKq7B4K2Gg1tDGh8AZFo,22416
|
13
13
|
foamlib/_files/_io.py,sha256=BGbbm6HKxL2ka0YMCmHqZQZ1R4PPQlkvWWb4FHMAS8k,2217
|
14
|
-
foamlib/_files/_parsing.py,sha256=
|
15
|
-
foamlib/_files/_serialization.py,sha256=
|
16
|
-
foamlib/_files/_types.py,sha256=
|
17
|
-
foamlib-0.9.
|
18
|
-
foamlib-0.9.
|
19
|
-
foamlib-0.9.
|
20
|
-
foamlib-0.9.
|
14
|
+
foamlib/_files/_parsing.py,sha256=CWALQclNOXUlPylI98h2DxMNz5OE0KZf9Gioc9h3OWU,17659
|
15
|
+
foamlib/_files/_serialization.py,sha256=R9-oXYywZubevuPkYaTIAyM0_BfF9rC819j9AXOCcg8,6451
|
16
|
+
foamlib/_files/_types.py,sha256=BJaRzICgnl2SOI6Nc2f8F8fendqmIK8VAdeb5zroYm0,7254
|
17
|
+
foamlib-0.9.3.dist-info/METADATA,sha256=LCtjDlLD_fJaiEUyo847EcF1jbhXVufh2iJUa_ck_Ng,12906
|
18
|
+
foamlib-0.9.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
19
|
+
foamlib-0.9.3.dist-info/licenses/LICENSE.txt,sha256=5Dte9TUnLZzPRs4NQzl-Jc2-Ljd-t_v0ZR5Ng5r0UsY,35131
|
20
|
+
foamlib-0.9.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|