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
7 changes: 7 additions & 0 deletions dpdata/formats/deepmd/hdf5.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,13 @@ def dump(
for dt, prop in data_types.items():
if dt in data:
if prop["dump"]:
if nframes > 0 and np.asarray(data[dt]).size == 0:
# An optional frame property (e.g. forces/virials when
# cal_force/cal_stress is disabled) may be empty while the
# system still has frames. Skip it instead of writing a
# (nframes, 0) dataset that cannot be reshaped on load. This
# matches the behaviour of the deepmd/raw and deepmd/npy writers.
continue
ddata = np.reshape(data[dt], prop["shape"])
if np.issubdtype(ddata.dtype, np.floating):
ddata = ddata.astype(comp_prec)
Expand Down
47 changes: 47 additions & 0 deletions tests/test_deepmd_hdf5.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,50 @@ def test_regular_hdf5_groups_are_not_mixed(self):
)

self.assertEqual(len(systems), 0)


class TestHDF5EmptyOptionalArray(unittest.TestCase):
"""Regression test for #996.

An empty optional frame array (e.g. forces when they are not computed) must not
break the deepmd/hdf5 write/read round-trip. Previously the writer stored a
``(nframes, 0)`` dataset that could not be reshaped on load.
"""

def tearDown(self):
if os.path.isfile("tmp.deepmd.empty.hdf5"):
os.remove("tmp.deepmd.empty.hdf5")

@staticmethod
def _make(forces):
return dpdata.LabeledSystem(
data={
"atom_names": ["H"],
"atom_numbs": [1],
"atom_types": np.array([0]),
"cells": np.eye(3).reshape(1, 3, 3),
"coords": np.zeros((1, 1, 3)),
"energies": np.zeros(1),
"orig": np.zeros(3),
"forces": forces,
}
)

def test_empty_forces_round_trip(self):
system = self._make(np.zeros((1, 0, 3)))
system.to("deepmd/hdf5", "tmp.deepmd.empty.hdf5")
reloaded = dpdata.LabeledSystem("tmp.deepmd.empty.hdf5", fmt="deepmd/hdf5")
# The empty optional array is skipped rather than written as (nframes, 0).
self.assertNotIn("forces", reloaded.data)
np.testing.assert_allclose(reloaded["coords"], system["coords"])

def test_real_forces_preserved(self):
forces = np.arange(3, dtype=float).reshape(1, 1, 3)
system = self._make(forces)
system.to("deepmd/hdf5", "tmp.deepmd.empty.hdf5")
reloaded = dpdata.LabeledSystem("tmp.deepmd.empty.hdf5", fmt="deepmd/hdf5")
np.testing.assert_allclose(reloaded["forces"], forces)


if __name__ == "__main__":
unittest.main()
Loading