diff --git a/dpdata/formats/gromacs/gro.py b/dpdata/formats/gromacs/gro.py index 0c61544fd..413a1ff23 100644 --- a/dpdata/formats/gromacs/gro.py +++ b/dpdata/formats/gromacs/gro.py @@ -77,7 +77,12 @@ def file_to_system_data(fname: FileType, format_atom_name=True, **kwargs): posis = np.array(posis) if frame == 1: system["orig"] = np.zeros(3) - system["atom_names"] = list(set(names)) + # A .gro file does not carry a separate atom-type table. The + # original reader sorted inferred names alphabetically, but + # the multi-frame refactor accidentally retained an unsorted + # set. Use first occurrence as the explicit contract so type + # IDs remain stable and follow the file's atom order. + system["atom_names"] = list(dict.fromkeys(names)) system["atom_numbs"] = [ names.count(ii) for ii in system["atom_names"] ] diff --git a/tests/gromacs/type_order.gro b/tests/gromacs/type_order.gro new file mode 100644 index 000000000..37b0a331f --- /dev/null +++ b/tests/gromacs/type_order.gro @@ -0,0 +1,15 @@ +Generated with MDTraj, t= 0.0 + 12 + 1MOL LI 1 0.254 0.254 0.024 + 2MOL CL 2 0.507 0.000 0.000 + 3MOL P 3 0.507 0.507 0.507 + 4MOL S1 4 0.896 0.389 0.118 + 5MOL O 5 0.100 0.200 0.300 + 6MOL N 6 0.200 0.300 0.400 + 7MOL C 7 0.300 0.400 0.500 + 8MOL H 8 0.400 0.500 0.600 + 9MOL F 9 0.500 0.600 0.700 + 10MOL BR 10 0.600 0.700 0.800 + 11MOL I 11 0.700 0.800 0.900 + 12MOL NA 12 0.800 0.900 1.000 + 1.01400 1.01400 1.01400 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 diff --git a/tests/test_gromacs_gro.py b/tests/test_gromacs_gro.py index 674c65100..2b554210f 100644 --- a/tests/test_gromacs_gro.py +++ b/tests/test_gromacs_gro.py @@ -7,6 +7,32 @@ class TestGromacsGro(unittest.TestCase): + def test_infer_atom_types_in_first_seen_order(self): + # Without an explicit type_map, atom names define type IDs in first-seen + # order. The fixture uses many distinct names so the former set-based + # implementation cannot routinely pass by coincidental hash ordering. + system = dpdata.System("gromacs/type_order.gro") + + expected_names = [ + "Li", + "Cl", + "P", + "S", + "O", + "N", + "C", + "H", + "F", + "Br", + "I", + "Na", + ] + self.assertEqual(system["atom_names"], expected_names) + self.assertEqual(system["atom_numbs"], [1] * len(expected_names)) + self.assertEqual( + system["atom_types"].tolist(), list(range(len(expected_names))) + ) + def test_read_file(self): system = dpdata.System("gromacs/1h.gro", type_map=["H", "O"]) self.assertTrue("H" in system["atom_names"])