Skip to content

Commit a7a50bf

Browse files
wanghan-iapcmHan Wang
andauthored
fix: skip empty frame properties when dumping deepmd format (#977) (#982)
## Summary Fixes #977. When an ABACUS SCF run converges but does **not** compute forces/stress (e.g. `cal_force`/`cal_stress` disabled — which is what users hit with the new GPU `cusolver` `ks_solver`), dpdata's ABACUS parser correctly produces a `LabeledSystem` with size-0 forces (this is an intended state, covered by `TestABACUSLabeledOutputNoFS`). The bug is in the **deepmd dump→load round-trip**: - `dump` did `np.reshape(forces, [nframes, -1])`, which for empty forces writes `force.npy` of shape `(nframes, 0)`. - On load, `np.reshape((nframes, 0)-array, [nframes, natoms, 3])` raises: ``` ValueError: cannot reshape array of size 0 into shape (20,64,3) ``` This is exactly the traceback reported in #977 (surfacing in dpgen's `post_fp_check_fail`). ## Root cause investigation - ABACUS (LTSv3.10.0) prints forces via `ModuleIO::print_force` independent of the KS solver; `cusolver` fills the wavefunctions just like `genelpa`/`scalapack_gvx`, and forces are computed from the density matrix afterward. There is no code path that disables force output for `cusolver`/`device gpu`. - dpdata parses the v3.10.0 `FmtTable` force block correctly **when present**. - The crash therefore only requires a converged log with **no `TOTAL-FORCE` block**, which dpdata represents as empty forces — and the deepmd round-trip then corrupts it. ## Fix In both `dpdata/formats/deepmd/comp.py` (npy) and `dpdata/formats/deepmd/raw.py` (raw) `dump`, skip an optional frame property whose array is empty while the system still has frames, rather than writing a meaningless `(nframes, 0)` array: ```python if nframes > 0 and np.asarray(data[dtype.name]).size == 0: continue ``` A missing `force`/`virial` file is already a supported state on load (`_cond_load_data` returns `None` and skips it), so the round-trip is now consistent. ## Tests Added two tests to `TestABACUSLabeledOutputNoFS` (reusing the existing `INPUT.ch4-noforcestress` fixture): - `test_noforcestress_deepmd_roundtrip` - `test_noforcestress_deepmd_raw_roundtrip` Both fail on `master` with the reported `ValueError` and pass with this change. abacus + deepmd comp/raw/mixed/empty suites pass (312 tests); no new failures in the full suite. ## Out of scope (user/dpgen side) This makes dpdata robust to force-less ABACUS results. Producing usable training labels still requires `cal_force 1`/`cal_stress 1` in the generated `INPUT` and adding `cusolver` to dpgen's `ks_solver` whitelist. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Bug Fixes * Fixed an issue where empty optional properties (such as forces and virials) were incorrectly exported as invalid empty arrays in DeePMD format files. These properties are now properly skipped during export when data is unavailable. ## Tests * Added validation tests for proper handling of missing optional properties during DeePMD format conversions. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Han Wang <wang_han@iapcm.ac.cn>
1 parent c71799f commit a7a50bf

3 files changed

Lines changed: 41 additions & 0 deletions

File tree

dpdata/formats/deepmd/comp.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ def dump(folder, data, set_size=5000, comp_prec=np.float32, remove_sets=True):
152152
f"Shape of {dtype.name} is not (nframes, ...), but {dtype.shape}. This type of data will not converted to deepmd/npy format."
153153
)
154154
continue
155+
if nframes > 0 and np.asarray(data[dtype.name]).size == 0:
156+
# an optional frame property (e.g. forces/virials when
157+
# cal_force/cal_stress is disabled) may be empty while the
158+
# system still has frames. Skip it instead of writing a
159+
# meaningless (nframes, 0) array that cannot be reshaped on load.
160+
continue
155161
ddata = np.reshape(data[dtype.name], [nframes, -1])
156162
if np.issubdtype(ddata.dtype, np.floating):
157163
ddata = ddata.astype(comp_prec)

dpdata/formats/deepmd/raw.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,5 +136,11 @@ def dump(folder, data):
136136
f"Shape of {dtype.name} is not (nframes, ...), but {dtype.shape}. This type of data will not converted to deepmd/raw format."
137137
)
138138
continue
139+
if nframes > 0 and np.asarray(data[dtype.name]).size == 0:
140+
# an optional frame property (e.g. forces/virials when
141+
# cal_force/cal_stress is disabled) may be empty while the
142+
# system still has frames. Skip it instead of writing a
143+
# meaningless (nframes, 0) array that cannot be reshaped on load.
144+
continue
139145
ddata = np.reshape(data[dtype.name], [nframes, -1])
140146
np.savetxt(os.path.join(folder, f"{dtype.deepmd_name}.raw"), ddata)

tests/test_abacus_pw_scf.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import os
44
import shutil
5+
import tempfile
56
import unittest
67

78
import numpy as np
@@ -163,6 +164,34 @@ def test_noforcestress_job(self):
163164
# test append self
164165
system_ch4.append(system_ch4)
165166

167+
def test_noforcestress_deepmd_roundtrip(self):
168+
# a converged scf without force/stress should survive a
169+
# round-trip through deepmd/npy without raising a reshape error
170+
system_ch4 = dpdata.LabeledSystem("abacus.scf", fmt="abacus/scf")
171+
tmp_dir = tempfile.mkdtemp()
172+
try:
173+
system_ch4.to("deepmd/npy", tmp_dir)
174+
reloaded = dpdata.LabeledSystem(tmp_dir, fmt="deepmd/npy")
175+
self.assertEqual(reloaded.get_nframes(), system_ch4.get_nframes())
176+
# empty force/virial should not be written as bogus data
177+
self.assertFalse(reloaded.data.get("forces", np.empty(0)).size)
178+
self.assertTrue("virials" not in reloaded.data)
179+
finally:
180+
shutil.rmtree(tmp_dir)
181+
182+
def test_noforcestress_deepmd_raw_roundtrip(self):
183+
# same as above but for the deepmd/raw format
184+
system_ch4 = dpdata.LabeledSystem("abacus.scf", fmt="abacus/scf")
185+
tmp_dir = tempfile.mkdtemp()
186+
try:
187+
system_ch4.to("deepmd/raw", tmp_dir)
188+
reloaded = dpdata.LabeledSystem(tmp_dir, fmt="deepmd/raw")
189+
self.assertEqual(reloaded.get_nframes(), system_ch4.get_nframes())
190+
self.assertFalse(reloaded.data.get("forces", np.empty(0)).size)
191+
self.assertTrue("virials" not in reloaded.data)
192+
finally:
193+
shutil.rmtree(tmp_dir)
194+
166195

167196
if __name__ == "__main__":
168197
unittest.main()

0 commit comments

Comments
 (0)