diff --git a/dpdata/formats/vasp/poscar.py b/dpdata/formats/vasp/poscar.py index 78b8dbbe..29468cc1 100644 --- a/dpdata/formats/vasp/poscar.py +++ b/dpdata/formats/vasp/poscar.py @@ -18,6 +18,18 @@ def move_flag_mapper(flag): system["atom_names"] = [str(ii) for ii in lines[5].split()] system["atom_numbs"] = [int(ii) for ii in lines[6].split()] scale = float(lines[1]) + # VASP interprets a negative scale as a target cell volume (in A^3), not + # as a negative vector multiplier. Convert it to a positive linear scale + # after reading the unscaled lattice vectors. + if scale < 0: + target_volume = abs(scale) + raw_cell = np.array( + [[float(jj) for jj in lines[ii].split()] for ii in range(2, 5)] + ) + raw_volume = abs(np.linalg.det(raw_cell)) + if raw_volume == 0: + raise ValueError("negative POSCAR scale requires a non-zero cell volume") + scale = (target_volume / raw_volume) ** (1.0 / 3.0) cell = [] move_flags = [] for ii in range(2, 5): diff --git a/tests/test_vasp_poscar_to_system.py b/tests/test_vasp_poscar_to_system.py index 9e5d7f37..51c54103 100644 --- a/tests/test_vasp_poscar_to_system.py +++ b/tests/test_vasp_poscar_to_system.py @@ -8,6 +8,8 @@ from context import dpdata from poscars.poscar_ref_oh import TestPOSCARoh +from dpdata.formats.vasp.poscar import to_system_data + class TestPOSCARCart(unittest.TestCase, TestPOSCARoh): def setUp(self): @@ -89,5 +91,54 @@ def setUp(self): self.v_places = 6 +class TestVaspNegativeScale(unittest.TestCase): + def test_negative_scale_is_target_volume(self): + lines = """test +-8 +1 0 0 +0 1 0 +0 0 1 +H +1 +Direct +0.5 0.0 0.0 +""".splitlines() + data = to_system_data(lines) + np.testing.assert_allclose(np.linalg.det(data["cells"][0]), 8.0) + np.testing.assert_allclose(data["coords"][0, 0], [1.0, 0.0, 0.0]) + + def test_negative_scale_rescales_cartesian_coordinates(self): + lines = """test +-8 +1 0 0 +0 1 0 +0 0 1 +H +1 +Cartesian +0.25 0.5 0.75 +""".splitlines() + + data = to_system_data(lines) + + np.testing.assert_allclose(np.linalg.det(data["cells"][0]), 8.0) + np.testing.assert_allclose(data["coords"][0, 0], [0.5, 1.0, 1.5]) + + def test_negative_scale_rejects_zero_volume_cell(self): + lines = """test +-8 +1 0 0 +0 0 0 +0 0 1 +H +1 +Direct +0.5 0.0 0.0 +""".splitlines() + + with self.assertRaisesRegex(ValueError, "non-zero cell volume"): + to_system_data(lines) + + if __name__ == "__main__": unittest.main()