From 4b71fbeaacbd58b21e98a8747cfb4a0628f9d018 Mon Sep 17 00:00:00 2001 From: njzjz-bot Date: Thu, 16 Jul 2026 11:59:04 +0800 Subject: [PATCH 1/2] fix(vasp): interpret negative POSCAR scale as volume Convert negative POSCAR scale factors to a positive linear factor that realizes the requested cell volume, while retaining direct multiplier behavior for positive scales. Add a regression for volume and Cartesian coordinate scaling; the previous fixtures only used positive factors. Coding-Agent: Codex Codex-Version: codex-cli 0.144.4 Model: gpt-5.6-sol Reasoning-Effort: xhigh --- dpdata/formats/vasp/poscar.py | 12 ++++++++++++ tests/test_vasp_poscar_to_system.py | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/dpdata/formats/vasp/poscar.py b/dpdata/formats/vasp/poscar.py index 78b8dbbeb..29468cc16 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 9e5d7f37f..a83916cd6 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,22 @@ 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]) + + if __name__ == "__main__": unittest.main() From e3fbedeff7ea8f1454c431a547936e148d5eed46 Mon Sep 17 00:00:00 2001 From: njzjz-bot Date: Tue, 21 Jul 2026 14:07:29 +0800 Subject: [PATCH 2/2] test(vasp): cover negative Cartesian scaling Cover Cartesian coordinate scaling and the zero-volume guard for negative POSCAR target-volume factors. Coding-Agent: Codex Codex-Version: codex-cli 0.144.6 Model: gpt-5.6-sol Reasoning-Effort: xhigh --- tests/test_vasp_poscar_to_system.py | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/test_vasp_poscar_to_system.py b/tests/test_vasp_poscar_to_system.py index a83916cd6..51c541030 100644 --- a/tests/test_vasp_poscar_to_system.py +++ b/tests/test_vasp_poscar_to_system.py @@ -107,6 +107,38 @@ def test_negative_scale_is_target_volume(self): 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()