Skip to content

Commit

Permalink
Merge pull request #15 from gerlero/dictionary
Browse files Browse the repository at this point in the history
Refactor dictionary manipulation
  • Loading branch information
gerlero authored Mar 20, 2024
2 parents 255e649 + fb16737 commit 93bf2c5
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 48 deletions.
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Documentation for `foamlib <https://github.com/gerlero/foamlib>`_
.. automodule:: foamlib
:members:
:undoc-members:
:inherited-members:
:show-inheritance:

Indices and tables
==================
Expand Down
13 changes: 12 additions & 1 deletion foamlib/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
__version__ = "0.1.3"

from ._cases import FoamCase, AsyncFoamCase, FoamTimeDirectory
from ._dictionaries import FoamFile, FoamDictionary, FoamDimensioned, FoamDimensionSet
from ._dictionaries import (
FoamFile,
FoamFieldFile,
FoamDictionary,
FoamBoundariesDictionary,
FoamBoundaryDictionary,
FoamDimensioned,
FoamDimensionSet,
)

__all__ = [
"FoamCase",
"AsyncFoamCase",
"FoamTimeDirectory",
"FoamFile",
"FoamFieldFile",
"FoamDictionary",
"FoamBoundariesDictionary",
"FoamBoundaryDictionary",
"FoamDimensioned",
"FoamDimensionSet",
]
8 changes: 4 additions & 4 deletions foamlib/_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import aioshutil

from ._subprocesses import run_process, run_process_async, CalledProcessError
from ._dictionaries import FoamFile
from ._dictionaries import FoamFile, FoamFieldFile


class _FoamCaseBase(Sequence["FoamTimeDirectory"]):
Expand Down Expand Up @@ -601,7 +601,7 @@ async def clone(self, dest: Union[Path, str]) -> "AsyncFoamCase":
return AsyncFoamCase(dest)


class FoamTimeDirectory(Mapping[str, FoamFile]):
class FoamTimeDirectory(Mapping[str, FoamFieldFile]):
"""
An OpenFOAM time directory in a case.
Expand Down Expand Up @@ -629,9 +629,9 @@ def name(self) -> str:
"""
return self.path.name

def __getitem__(self, key: str) -> FoamFile:
def __getitem__(self, key: str) -> FoamFieldFile:
try:
return FoamFile(self.path / key)
return FoamFieldFile(self.path / key)
except FileNotFoundError as e:
raise KeyError(key) from e

Expand Down
137 changes: 105 additions & 32 deletions foamlib/_dictionaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,23 +164,6 @@ def _parse(value: str) -> FoamValue:
return value


def _serialize_mapping(mapping: Any) -> str:
if isinstance(mapping, FoamDictionary):
return mapping._cmd(["-value"])
elif isinstance(mapping, Mapping):
m = {
k: _serialize(
v,
assume_field=(k == "internalField" or k == "value"),
assume_dimensions=(k == "dimensions"),
)
for k, v in mapping.items()
}
return f"{{{' '.join(f'{k} {v};' for k, v in m.items())}}}"
else:
raise TypeError(f"Not a mapping: {type(mapping)}")


def _serialize_bool(value: Any) -> str:
if value is True:
return "yes"
Expand Down Expand Up @@ -251,9 +234,6 @@ def _serialize_dimensioned(value: Any) -> str:
def _serialize(
value: Any, *, assume_field: bool = False, assume_dimensions: bool = False
) -> str:
with suppress(TypeError):
return _serialize_mapping(value)

if isinstance(value, FoamDimensionSet) or assume_dimensions:
with suppress(TypeError):
return _serialize_dimensions(value)
Expand Down Expand Up @@ -316,12 +296,27 @@ def __getitem__(self, key: str) -> Union[FoamValue, "FoamDictionary"]:
else:
return _parse(value)

def __setitem__(self, key: str, value: Any) -> None:
value = _serialize(
value,
assume_field=(key == "internalField" or key == "value"),
assume_dimensions=(key == "dimensions"),
)
def _setitem(
self,
key: str,
value: Any,
*,
assume_field: bool = False,
assume_dimensions: bool = False,
) -> None:
if isinstance(value, FoamDictionary):
value = value._cmd(["-value"])
elif isinstance(value, Mapping):
self._cmd(["-set", "{}"], key=key)
subdict = self[key]
assert isinstance(subdict, FoamDictionary)
for k, v in value.items():
subdict[k] = v
return
else:
value = _serialize(
value, assume_field=assume_field, assume_dimensions=assume_dimensions
)

if len(value) < 1000:
self._cmd(["-set", value], key=key)
Expand All @@ -331,6 +326,9 @@ def __setitem__(self, key: str, value: Any) -> None:
contents = contents.replace("_foamlib_value_", value, 1)
self._file.path.write_text(contents)

def __setitem__(self, key: str, value: Any) -> None:
self._setitem(key, value)

def __delitem__(self, key: str) -> None:
if key not in self:
raise KeyError(key)
Expand All @@ -348,6 +346,63 @@ def __repr__(self) -> str:
return type(self).__name__


class FoamBoundaryDictionary(FoamDictionary):
"""An OpenFOAM dictionary representing a boundary condition as a mutable mapping."""

def __setitem__(self, key: str, value: Any) -> None:
if key == "value":
self._setitem(key, value, assume_field=True)
else:
self._setitem(key, value)

@property
def type(self) -> str:
"""
Alias of `self["type"]`.
"""
ret = self["type"]
if not isinstance(ret, str):
raise TypeError("type is not a string")
return ret

@type.setter
def type(self, value: str) -> None:
self["type"] = value

@property
def value(
self,
) -> Union[int, float, Sequence[Union[int, float, Sequence[Union[int, float]]]]]:
"""
Alias of `self["value"]`.
"""
ret = self["value"]
if not isinstance(ret, (int, float, Sequence)):
raise TypeError("value is not a field")
return cast(Union[int, float, Sequence[Union[int, float]]], ret)

@value.setter
def value(
self,
value: Union[
int, float, Sequence[Union[int, float, Sequence[Union[int, float]]]]
],
) -> None:
self["value"] = value

@value.deleter
def value(self) -> None:
del self["value"]


class FoamBoundariesDictionary(FoamDictionary):
def __getitem__(self, key: str) -> Union[FoamValue, FoamBoundaryDictionary]:
ret = super().__getitem__(key)
if isinstance(ret, FoamDictionary):
ret = FoamBoundaryDictionary(self._file, [*self._keywords, key])
return ret


class FoamFile(FoamDictionary):
"""An OpenFOAM dictionary file as a mutable mapping."""

Expand All @@ -359,6 +414,30 @@ def __init__(self, path: Union[str, Path]) -> None:
elif not self.path.is_file():
raise FileNotFoundError(self.path)

def __fspath__(self) -> str:
return str(self.path)

def __repr__(self) -> str:
return f"{type(self).__name__}({self.path})"


class FoamFieldFile(FoamFile):
"""An OpenFOAM dictionary file representing a field as a mutable mapping."""

def __getitem__(self, key: str) -> Union[FoamValue, FoamDictionary]:
ret = super().__getitem__(key)
if key == "boundaryField" and isinstance(ret, FoamDictionary):
ret = FoamBoundariesDictionary(self, [key])
return ret

def __setitem__(self, key: str, value: Any) -> None:
if key == "internalField":
self._setitem(key, value, assume_field=True)
elif key == "dimensions":
self._setitem(key, value, assume_dimensions=True)
else:
self._setitem(key, value)

@property
def dimensions(self) -> FoamDimensionSet:
"""
Expand Down Expand Up @@ -405,9 +484,3 @@ def boundary_field(self) -> FoamDictionary:
if not isinstance(ret, FoamDictionary):
raise TypeError("boundaryField is not a dictionary")
return ret

def __fspath__(self) -> str:
return str(self.path)

def __repr__(self) -> str:
return f"{type(self).__name__}({self.path})"
33 changes: 23 additions & 10 deletions tests/test_dictionaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,29 @@ def pitz(tmp_path: Path) -> FoamCase:
return PITZ.clone(tmp_path / PITZ.name)


def test_field(pitz: FoamCase) -> None:
def test_dimensions(pitz: FoamCase) -> None:
assert pitz[0]["p"].dimensions == FoamDimensionSet(length=2, time=-2)
assert pitz[0]["U"].dimensions == FoamDimensionSet(length=1, time=-1)

pitz[0]["p"].dimensions = FoamDimensionSet(mass=1, length=1, time=-2)

assert pitz[0]["p"].dimensions == FoamDimensionSet(mass=1, length=1, time=-2)


def test_boundary_field(pitz: FoamCase) -> None:
outlet = pitz[0]["p"].boundary_field["outlet"]
assert isinstance(outlet, FoamBoundaryDictionary)
assert outlet.type == "fixedValue"
assert outlet.value == 0

outlet.type = "zeroGradient"
del outlet.value

assert outlet.type == "zeroGradient"
assert "value" not in outlet


def test_internal_field(pitz: FoamCase) -> None:
pitz[0]["p"].internal_field = 0.5
pitz[0]["U"].internal_field = [1.5, 2.0, 3]

Expand Down Expand Up @@ -128,12 +150,3 @@ def test_field(pitz: FoamCase) -> None:
assert u == pytest.approx(u_arr)

pitz.run()


def test_dimensions(pitz: FoamCase) -> None:
assert pitz[0]["p"].dimensions == FoamDimensionSet(length=2, time=-2)
assert pitz[0]["U"].dimensions == FoamDimensionSet(length=1, time=-1)

pitz[0]["p"].dimensions = FoamDimensionSet(mass=1, length=1, time=-2)

assert pitz[0]["p"].dimensions == FoamDimensionSet(mass=1, length=1, time=-2)

0 comments on commit 93bf2c5

Please sign in to comment.