Repository navigation
fix: reject non-finite model deviations #389
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,109 @@ | ||
| import json | ||
| import os | ||
| import unittest | ||
| from pathlib import ( | ||
| Path, | ||
| ) | ||
|
|
||
| import dpdata | ||
| import h5py | ||
| import numpy as np | ||
| from dflow.python import ( | ||
| FatalError, | ||
| ) | ||
| from dflow.python.opio import ( | ||
| HDF5Dataset, | ||
| ) | ||
|
|
||
| # isort: off | ||
| from .context import ( | ||
| dpgen2, | ||
| ) | ||
| from dpgen2.exploration.render import TrajRenderLammps | ||
| from dpgen2.exploration.deviation import DeviManager | ||
|
|
||
| # isort: on | ||
|
|
||
|
|
||
| class TestTrajRenderLammps(unittest.TestCase): | ||
| 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, | ||
| ) | ||
|
|
||
| def test_rejects_non_finite_model_deviation(self): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not blocking, but this pins less than the PR claims. The description says all six columns and NaN and infinity; this covers one NaN in one column. Both of these mutations of the source survive the suite: I checked the rest of Credit where due -- the test is not vacuous. Removing the guard entirely does make it fail.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expanded in 4726860 to test NaN, +inf and -inf in each of the six columns, on both text and real HDF5 datasets. Added finite single/multi-row and DBL_MAX coverage. Both proposed mutations ( Agent: dot |
||
| """Reject NaN and either infinity in every exported column.""" | ||
| model_devi = Path("model_devi.out") | ||
| for column, name in enumerate(self.deviation_names, start=1): | ||
| for value in (np.nan, np.inf, -np.inf): | ||
| with self.subTest(column=name, value=value): | ||
| data = np.zeros((2, 7)) | ||
| data[1, column] = value | ||
| np.savetxt(model_devi, data) | ||
| with self.assertRaisesRegex( | ||
| FatalError, | ||
| rf"Non-finite model-deviation value in model_devi.out: row 2 \({name}\)", | ||
| ): | ||
| TrajRenderLammps().get_model_devi([model_devi]) | ||
|
|
||
| def test_rejects_non_finite_hdf5_model_deviation(self): | ||
| """An HDF5 error names the dataset, not its Python object address.""" | ||
| key = "task.000000/model_devi" | ||
| for column, name in enumerate(self.deviation_names, start=1): | ||
| for value in (np.nan, np.inf, -np.inf): | ||
| with self.subTest(column=name, value=value): | ||
| data = np.zeros((2, 7)) | ||
| data[1, column] = value | ||
| with h5py.File("model_devi.h5", "w") as h5: | ||
| h5.create_dataset(key, data=data) | ||
| dataset = HDF5Dataset(h5, key) | ||
| with self.assertRaises(FatalError) as error: | ||
| TrajRenderLammps().get_model_devi([dataset]) | ||
| message = str(error.exception) | ||
| self.assertIn(key, message) | ||
| self.assertIn(f"row 2 ({name})", message) | ||
| self.assertNotIn("object at", message) | ||
|
|
||
| def test_limits_non_finite_diagnostics(self): | ||
| """Large failed trajectories give a bounded message and total count.""" | ||
| model_devi = Path("model_devi.out") | ||
| data = np.full((20000, 7), np.nan) | ||
| data[:, 0] = np.arange(len(data)) | ||
| np.savetxt(model_devi, data) | ||
| with self.assertRaises(FatalError) as error: | ||
| TrajRenderLammps().get_model_devi([model_devi]) | ||
| message = str(error.exception) | ||
| self.assertEqual(message.count("row "), 10) | ||
| self.assertIn("120000 non-finite values in total", message) | ||
| self.assertLess(len(message), 1000) | ||
|
|
||
| def test_loads_finite_model_deviation(self): | ||
| """Keep single/multiple rows and large finite sentinel values valid.""" | ||
| for nrows in (1, 2): | ||
| for use_hdf5 in (False, True): | ||
| with self.subTest(nrows=nrows, use_hdf5=use_hdf5): | ||
| data = np.arange(nrows * 7, dtype=float).reshape(nrows, 7) | ||
| data[0, 4] = np.finfo(float).max | ||
| if use_hdf5: | ||
| with h5py.File("model_devi.h5", "w") as h5: | ||
| h5.create_dataset( | ||
| "model_devi", data=data[0] if nrows == 1 else data | ||
| ) | ||
| source = HDF5Dataset(h5, "model_devi") | ||
| deviations = TrajRenderLammps().get_model_devi([source]) | ||
| else: | ||
| source = Path("model_devi.out") | ||
| np.savetxt(source, data) | ||
| deviations = TrajRenderLammps().get_model_devi([source]) | ||
| for column, name in enumerate(self.deviation_names, start=1): | ||
| np.testing.assert_array_equal( | ||
| deviations.get(name)[0], data[:, column] | ||
| ) | ||
|
|
||
| def test_use_ele_temp_1(self): | ||
| with open("job.json", "w") as f: | ||
| json.dump({"ele_temp": 6.6}, f) | ||
|
|
@@ -58,5 +147,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", "model_devi.h5"): | ||
| if os.path.exists(filename): | ||
| os.remove(filename) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line is one of the two that turn the pyright job red:
ddcan beNonebecause it comes fromHDF5Dataset.get_data(), which is why the reshape above and all sixmodel_devi.add(dd[:, N])calls below carry# type: ignore. This new access is the only one without it. Line 85'sfor row, column in invaliddraws the second error ("intp" is not iterable). The Type checker workflow is green on master, so both are new here.Separately on scope: this slice covers the three virial columns, and
MIN_DEVI_VandAVG_DEVI_Vare read by no report class at all, whileMAX_DEVI_Vis only compared when virial trust levels are set, which they are not by default. I am not asking you to change it, but it is worth deciding deliberately:dd[:, 4]alone would still have closed #276, since that file'smax_devi_fis-nantoo.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 4726860 and ea782dc without adding suppressions: normalize HDF5 data to an ndarray, then index the capped diagnostic coordinates explicitly. Actual CI exposed a NumPy 2.2.6 typing difference, so the follow-up was checked with that version too: full-repository pyright 1.1.318 now reports zero errors locally. I deliberately kept validation of all six quantities exported by this loader, including virial metrics, and documented that choice; all six are covered on both input paths. Large finite sentinel values remain accepted.
Agent: dot