diff --git a/dpdata/formats/lammps/lmp.py b/dpdata/formats/lammps/lmp.py index c9d60ec5..87358755 100644 --- a/dpdata/formats/lammps/lmp.py +++ b/dpdata/formats/lammps/lmp.py @@ -70,6 +70,10 @@ def detect_atom_style(lines: list[str]) -> str | None: # If it's a small float, likely a charge if abs(val) < 10 and val != int(val): return "charge" + # Neutral charge is numerically integral (0.0), but the + # decimal/exponent notation still identifies a charge column. + if any(ch in first_line[2].lower() for ch in (".", "e")): + return "charge" else: # Likely molecule ID (integer), so bond/molecular style return "bond" @@ -327,6 +331,11 @@ def get_charges(lines: list[str], atom_style: str = "atomic") -> np.ndarray | No def get_spins(lines: list[str], atom_style: str = "atomic") -> np.ndarray | None: + # This branch predates explicit LAMMPS spin-style support and stores spin + # columns only in dpdata's legacy atomic layout. Other registered styles + # use their extra columns for unrelated physical quantities. + if atom_style != "atomic": + return None atom_lines = get_atoms(lines) if len(atom_lines[0].split()) < 8: return None diff --git a/tests/test_lammps_atom_styles.py b/tests/test_lammps_atom_styles.py index 04c98c66..93842898 100644 --- a/tests/test_lammps_atom_styles.py +++ b/tests/test_lammps_atom_styles.py @@ -237,6 +237,49 @@ def test_charge_style_no_comment_detection(self) -> None: """Test automatic detection of charge style without style comment.""" self._test_style_parsing("charge_no_comment") + def test_neutral_charge_style_no_comment_detection(self) -> None: + """Integral-looking ``0.0`` charges must not be mistaken for bonds.""" + path = "/tmp/test_neutral_charge_style.lmp" + self.test_files["neutral_charge"] = path + with open(path, "w") as fp: + fp.write( + """2 atoms +1 atom types +0.0 2.0 xlo xhi +0.0 2.0 ylo yhi +0.0 2.0 zlo zhi + +Atoms + +1 1 0.0 0.0 0.0 0.0 +2 1 0.0 1.0 1.0 1.0 +""" + ) + system = self._load_system("neutral_charge") + self.assertIn("charges", system.data) + np.testing.assert_allclose(system["charges"][0], [0.0, 0.0]) + + def test_dipole_style_does_not_create_spins(self) -> None: + """Dipole moments are not magnetic spin vectors.""" + path = "/tmp/test_dipole_style.lmp" + self.test_files["dipole"] = path + with open(path, "w") as fp: + fp.write( + """1 atoms +1 atom types +0.0 10.0 xlo xhi +0.0 10.0 ylo yhi +0.0 10.0 zlo zhi + +Atoms # dipole + +1 1 0.0 1.0 2.0 3.0 0.1 0.2 0.3 +""" + ) + system = self._load_system("dipole") + self.assertIn("charges", system.data) + self.assertNotIn("spins", system.data) + def test_bond_style_no_comment_detection(self) -> None: """Test automatic detection of bond style without style comment.""" self._test_style_parsing("bond_no_comment")