Skip to content

Commit 4c94171

Browse files
wanghan-iapcmHan Wang
andauthored
fix: propagate frame-level data modifier failures (#5709)
## Problem `DeepmdData.get_single_frame()` runs the data modifier through a one-off `ThreadPoolExecutor` but never calls `future.result()`. The context manager waits for the submitted task to finish, but any exception raised inside `modifier.modify_data(...)` stays stored on the `Future` and is never surfaced. As a result, frame-level data loading silently ignores modifier failures: callers receive the unmodified frame, and when caching is enabled that bad frame can be cached for future use. Other code paths call the modifier directly and do propagate errors, so the behavior was inconsistent. ## Fix Call `future.result()` before leaving the modifier block so an exception raised inside the modifier propagates instead of being swallowed (and the unmodified frame cached). ## Test A new test constructs a `DeepmdData` with a modifier whose `modify_data` always raises, and asserts that `get_single_frame` re-raises the error and does not cache the failed frame. On the current code the error is swallowed (no exception, frame cached); after the fix the error propagates. Fix #5690 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved error handling when preparing a single frame so failures from data modifiers are no longer hidden. * Prevented invalid frames from being saved and reused after a modifier error. * **Tests** * Added coverage to verify modifier failures are reported correctly and do not populate the frame cache. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Han Wang <wang_han@iapcm.ac.cn>
1 parent 9bd1f16 commit 4c94171

2 files changed

Lines changed: 41 additions & 0 deletions

File tree

deepmd/utils/data.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,9 @@ def get_single_frame(self, index: int, num_worker: int) -> dict:
530530
frame_data,
531531
self,
532532
)
533+
# propagate any exception raised inside the modifier instead of
534+
# silently returning (and caching) an unmodified frame
535+
future.result()
533536
if self.use_modifier_cache:
534537
# Cache the modified frame to avoid recomputation
535538
self._modified_frame_cache[index] = copy.deepcopy(frame_data)

source/tests/common/test_deepmd_data.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,41 @@ def test_remap_with_unused_types(self) -> None:
4646
loaded = data._load_set(self.set_dir)
4747
expected_sorted = expected_atom_types[data.idx_map]
4848
np.testing.assert_array_equal(loaded["type"], np.tile(expected_sorted, (1, 1)))
49+
50+
51+
class _RaisingModifier:
52+
"""A modifier whose ``modify_data`` always fails."""
53+
54+
use_cache = True
55+
56+
def modify_data(self, data: dict, data_sys: DeepmdData) -> None:
57+
raise ValueError("modifier failure")
58+
59+
60+
class TestDeepmdDataModifierError(unittest.TestCase):
61+
def setUp(self) -> None:
62+
self.tmpdir = tempfile.TemporaryDirectory()
63+
self.root = Path(self.tmpdir.name)
64+
set_dir = self.root / "set.000"
65+
set_dir.mkdir()
66+
atom_types = np.array([0, 1], dtype=np.int32)
67+
np.savetxt(self.root / "type.raw", atom_types, fmt="%d")
68+
np.save(
69+
set_dir / "coord.npy",
70+
np.zeros((3, atom_types.size * 3), dtype=np.float32),
71+
)
72+
np.save(
73+
set_dir / "box.npy",
74+
np.tile(np.eye(3, dtype=np.float32).reshape(9), (3, 1)),
75+
)
76+
77+
def tearDown(self) -> None:
78+
self.tmpdir.cleanup()
79+
80+
def test_get_single_frame_propagates_modifier_error(self) -> None:
81+
data = DeepmdData(str(self.root), modifier=_RaisingModifier())
82+
# a failing modifier must surface its error, not be swallowed
83+
with self.assertRaises(ValueError):
84+
data.get_single_frame(0, num_worker=1)
85+
# and the unmodified frame must not be cached
86+
self.assertNotIn(0, data._modified_frame_cache)

0 commit comments

Comments
 (0)