diff --git a/CHANGELOG.md b/CHANGELOG.md index 19a37036..2d55a59d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ All notable changes to this project will be documented in this file. ### Fixed +- #200: Keep the physical dimension of the mappings in `Domain.export` and `Domain.from_file`. The `topology.yml` metadata now stores `ldim` and `pdim` instead of `dim`, and `Domain.from_file` raises an error for files with the old `dim` key. - #140: Fix the rendering of the `Domain.join()` API documentation. - #166: Restore warning-free documentation builds, including bibliography and mathematical notation support. - #106: Preserve symbol assumptions and free-symbol information with modern SymPy. diff --git a/sympde/topology/domain.py b/sympde/topology/domain.py index 1a16f4f2..77271b8a 100644 --- a/sympde/topology/domain.py +++ b/sympde/topology/domain.py @@ -31,7 +31,6 @@ # TODO fix circular dependency between domain and mapping if TYPE_CHECKING: from sympde.topology.mapping import Mapping -# TODO add pdim iterable_types = (tuple, list, Tuple, Union) @@ -336,7 +335,9 @@ def get_interior(self, name): def todict(self): name = str(self.name) - dim = str(self.dim) + ldim = str(self.dim) + mappings = [m for m in self.mappings.values() if m is not None] + pdim = str(mappings[0].pdim) if mappings else ldim interior = self.interior.todict() boundary = self.boundary.todict() connectivity = self.connectivity.todict() @@ -346,7 +347,8 @@ def todict(self): dtype = 'None' d = {'name': name, - 'dim': dim, + 'ldim': ldim, + 'pdim': pdim, 'dtype': dtype, 'interior': interior, 'boundary': boundary, @@ -404,8 +406,13 @@ def from_file(cls, filename): h5 = h5py.File( filename, mode='r' ) yml = yaml.load( h5['topology.yml'][()], Loader=yaml.SafeLoader ) + if 'dim' in yml: + raise ValueError(f"'{filename}' was written by an older version of SymPDE:" + " replace its 'dim' key with the keys 'ldim' and 'pdim'") + domain_name = yml['name'] - dim = int(yml['dim']) + ldim = int(yml['ldim']) + pdim = int(yml['pdim']) dtype = yml['dtype'] d_interior = yml['interior'] d_boundary = yml['boundary'] @@ -422,7 +429,7 @@ def from_file(cls, filename): constructors = [globals()[dt['type']] for dt in dtype] interiors = [cs(i['name'], **dt['parameters']) for cs,i,dt in zip(constructors, d_interior, dtype)] - mappings = [Mapping(I['mapping'], dim=dim) if I.get('mapping', "None") != "None" else None for I in d_interior] + mappings = [Mapping(I['mapping'], ldim=ldim, pdim=pdim) if I.get('mapping', "None") != "None" else None for I in d_interior] domains = [mapping(i) if mapping else i for i,mapping in zip(interiors, mappings)] patch_index = {I.name:ind for ind,I in enumerate(interiors)} diff --git a/sympde/topology/tests/test_topology.py b/sympde/topology/tests/test_topology.py index 13002fa4..4c5546a5 100644 --- a/sympde/topology/tests/test_topology.py +++ b/sympde/topology/tests/test_topology.py @@ -1,5 +1,9 @@ import os +import h5py +import numpy as np +import pytest +import yaml from sympy import preorder_traversal from sympy.core import Basic @@ -113,6 +117,31 @@ def test_topology_1(): assert D.todict() == Omega.todict() +#============================================================================== +def test_export_surface_mapping(tmp_path): + domain = Mapping('F', ldim=2, pdim=3)(Square('Omega')) + + filename = str(tmp_path / 'surface.h5') + domain.export(filename) + D = Domain.from_file(filename) + + assert D.mapping.ldim == 2 + assert D.mapping.pdim == 3 + assert D.todict() == domain.todict() + +#============================================================================== +def test_from_file_with_old_dim_key(tmp_path): + yml = Mapping('F', dim=2)(Square('Omega')).todict() + yml['dim'] = yml.pop('ldim') + del yml['pdim'] + + filename = str(tmp_path / 'old.h5') + with h5py.File(filename, mode='w') as h5: + h5['topology.yml'] = np.array(yaml.dump(data=yml, sort_keys=None), dtype='S') + + with pytest.raises(ValueError, match="older version of SymPDE"): + Domain.from_file(filename) + #============================================================================== def test_domain_1(): Omega_1 = InteriorDomain('Omega_1', dim=2)