diff --git a/dpgen2/exploration/render/traj_render_lammps.py b/dpgen2/exploration/render/traj_render_lammps.py index 00b6a3de..941ad198 100644 --- a/dpgen2/exploration/render/traj_render_lammps.py +++ b/dpgen2/exploration/render/traj_render_lammps.py @@ -68,6 +68,28 @@ def _load_one_model_devi(self, fname, model_devi): ): # In case model-devi.out is 1-dimensional dd = dd.reshape((1, len(dd))) # type: ignore + # A NaN or infinity would otherwise reach the report and scheduler, where + # comparisons silently produce invalid trust levels. Fail at the artifact + # boundary so users can inspect the corresponding LAMMPS task directly. + deviation_names = ( + DeviManager.MAX_DEVI_V, + DeviManager.MIN_DEVI_V, + DeviManager.AVG_DEVI_V, + DeviManager.MAX_DEVI_F, + DeviManager.MIN_DEVI_F, + DeviManager.AVG_DEVI_F, + ) + invalid = np.argwhere(~np.isfinite(dd[:, 1:7])) + if invalid.size: + locations = ", ".join( + f"row {row + 1} ({deviation_names[column]})" for row, column in invalid + ) + raise ValueError( + f"Non-finite model-deviation value in {fname}: {locations}. " + "Inspect the LAMMPS/DeePMD task output before scheduling the " + "next exploration iteration." + ) + model_devi.add(DeviManager.MAX_DEVI_V, dd[:, 1]) # type: ignore model_devi.add(DeviManager.MIN_DEVI_V, dd[:, 2]) # type: ignore model_devi.add(DeviManager.AVG_DEVI_V, dd[:, 3]) # type: ignore diff --git a/tests/exploration/test_traj_render_lammps.py b/tests/exploration/test_traj_render_lammps.py index 3659ffd8..068677c8 100644 --- a/tests/exploration/test_traj_render_lammps.py +++ b/tests/exploration/test_traj_render_lammps.py @@ -1,6 +1,9 @@ import json import os import unittest +from pathlib import ( + Path, +) import dpdata import numpy as np @@ -15,6 +18,16 @@ class TestTrajRenderLammps(unittest.TestCase): + def test_rejects_non_finite_model_deviation(self): + model_devi = Path("model_devi.out") + model_devi.write_text("0 0.1 0.0 0.0 0.2 0.0 0.0\n1 0.2 0.0 0.0 nan 0.0 0.0\n") + + with self.assertRaisesRegex( + ValueError, + r"Non-finite model-deviation value.*row 2 \(max_devi_f\)", + ): + TrajRenderLammps().get_model_devi([model_devi]) + def test_use_ele_temp_1(self): with open("job.json", "w") as f: json.dump({"ele_temp": 6.6}, f) @@ -58,5 +71,6 @@ def test_use_ele_temp_2(self): np.testing.assert_array_almost_equal(system.data["aparam"], np.array([[[6.6]]])) def tearDown(self): - if os.path.exists("job.json"): - os.remove("job.json") + for filename in ("job.json", "model_devi.out"): + if os.path.exists(filename): + os.remove(filename)