diff --git a/dpgen/generator/run.py b/dpgen/generator/run.py index 0ce513a96..5190d91f9 100644 --- a/dpgen/generator/run.py +++ b/dpgen/generator/run.py @@ -2436,6 +2436,13 @@ def _read_model_devi_file( ) os.rename(traj_files_sorted[ibead][itraj], new_filename) model_devi = np.loadtxt(os.path.join(task_path, "model_devi.out")) + if model_devi.ndim == 2: + # Some LAMMPS fixes evaluate a model repeatedly at the same timestep. + # The final evaluation describes the accepted configuration that is + # written to the trajectory, so discard earlier intermediate rows. + _, reverse_indices = np.unique(model_devi[::-1, 0], return_index=True) + last_indices = model_devi.shape[0] - 1 - reverse_indices + model_devi = model_devi[np.sort(last_indices)] if model_devi_f_avg_relative: if model_devi_merge_traj is True: all_traj = os.path.join(task_path, "all.lammpstrj") diff --git a/tests/generator/test_make_md.py b/tests/generator/test_make_md.py index ab36225cc..b1f3e9c21 100644 --- a/tests/generator/test_make_md.py +++ b/tests/generator/test_make_md.py @@ -312,6 +312,25 @@ def test_read_model_devi_file_pimd(self): os.path.isfile(os.path.join(path, f"traj/{istep}.lammpstrj")) ) + def test_read_model_devi_file_keeps_last_row_per_timestep(self): + """Intermediate model evaluations must not select stale structures.""" + with tempfile.TemporaryDirectory() as task_path: + model_devi = np.array( + [ + [0, 0.01, 0, 0, 0.02, 0, 0], + [10, 0.03, 0, 0, 0.04, 0, 0], + [10, 0.05, 0, 0, 0.06, 0, 0], + [20, 0.07, 0, 0, 0.08, 0, 0], + [20, 0.09, 0, 0, 0.10, 0, 0], + [20, 0.11, 0, 0, 0.12, 0, 0], + ] + ) + np.savetxt(os.path.join(task_path, "model_devi.out"), model_devi) + + result = _read_model_devi_file(task_path) + + np.testing.assert_array_equal(result, model_devi[[0, 2, 5]]) + class TestMakeModelDeviRevMat(unittest.TestCase): def tearDown(self):