Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 28 additions & 9 deletions src/underworld3/discretisation/discretisation_mesh_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -1229,11 +1229,20 @@ def _petsc_payload_name(data_file, data_name):
if uw.mpi.rank == 0:
try:
with h5py.File(data_file, "r") as h5f:
topologies = h5f.get("topologies")
for topology in (topologies or {}):
dms = topologies[topology].get("dms")
if dms is not None and data_name in dms:
found = data_name
# The payload sat at the file root before #756 and under
# /restart/petsc after it. Look in both: this is only an
# existence check, and a file written by either version
# has to stay readable. Checking one place is what
# regressed #640 -- the writer still emits the per-field
# section and its vector, just one group deeper.
for group in ("topologies", "restart/petsc/topologies"):
topologies = h5f.get(group)
for topology in (topologies or {}):
dms = topologies[topology].get("dms")
if dms is not None and data_name in dms:
found = data_name
break
if found is not None:
break
except (OSError, KeyError):
found = None
Expand All @@ -1253,10 +1262,20 @@ def _guard_coincident_dofs(self, data_file, data_name, is_v1_1,
if self._lvec is None:
self._set_vec(available=True)
live_rows = self._gvec.getSize() // self.num_components
payload = None if is_v1_1 else self._petsc_payload_name(
data_file, data_name)

if payload is not None and saved_rows == live_rows:
# Look for the section in either format. `_petsc_payload_name` is a
# structural lookup (topologies/*/dms/<name>) and does not care which
# wrapper the file uses, and `read_timestep` calls
# `_read_native_payload` on a v1.1 file straight after this guard --
# so treating v1.1 as payload-free refused the very files the native
# path was about to read correctly (#640 regressed by #756).
payload = self._petsc_payload_name(data_file, data_name)

# v1.1 shares one compact geometry across the fields (#756), so there
# is no per-field saved cloud to count and `saved_rows` is 0 above by
# construction; the row comparison only means something for a legacy
# file. The payload carries the section, which is what resolves the
# cut, so in v1.1 its presence is the whole test.
if payload is not None and (is_v1_1 or saved_rows == live_rows):
return # `_read_native_payload` will take it

raise RuntimeError(
Expand Down
42 changes: 42 additions & 0 deletions tests/test_0864_split_checkpoint_roundtrip.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,45 @@ def test_unsplit_mesh_still_uses_the_coordinate_remap(tmp_path):
coords = np.asarray(loaded.coords_nd)
assert np.allclose(loaded.data[:, 0],
100.0 * coords[:, 0] + 10.0 * coords[:, 1], atol=1e-8)


@pytest.mark.level_1
@pytest.mark.tier_b
def test_the_payload_is_found_wherever_the_writer_put_it(tmp_path):
"""The existence check must not be tied to one file layout.

#756 moved the reload payload from the file root into /restart/petsc.
The writer still emitted the per-field section and its vector, one
group deeper, but `_petsc_payload_name` looked only at the root, so it
reported "no PETSc-native payload" for a file written WITH
petsc_reload=True and the #640 guard refused the good path as well as
the bad. The round-trip test above catches the symptom; this pins the
cause, so a future relocation fails here with the reason rather than
as a confusing refusal.
"""
import h5py

mesh = _split_mesh()
var = _stamped_variable(mesh)
_write(tmp_path, mesh, var, petsc_reload=True)
written = tmp_path / f"rt.mesh.{var.clean_name}.00000.h5"

# The section and its global vector are both on disk, somewhere.
with h5py.File(written, "r") as h5f:
groups = []
h5f.visititems(
lambda name, obj: groups.append(name)
if isinstance(obj, h5py.Group) else None)
assert any(g.endswith(f"dms/{var.clean_name}/section") for g in groups), groups
assert any(g.endswith(f"dms/{var.clean_name}/vecs/{var.clean_name}")
for g in groups), groups

# ... and the guard's existence check finds it there. The method lives on
# _BaseMeshVariable; EnhancedMeshVariable is a proxy and does not inherit
# from it, so reach for the defining class rather than type(var).
from underworld3.discretisation.discretisation_mesh_variables import (
_BaseMeshVariable,
)

assert _BaseMeshVariable._petsc_payload_name(
str(written), var.clean_name) == var.clean_name
Loading