From f7d6662c5d2f444bc9d8cb897de737297b1efef5 Mon Sep 17 00:00:00 2001 From: yaosk Date: Fri, 25 Oct 2024 19:35:13 +0800 Subject: [PATCH 01/14] Add support for converting move flags from VASP to Abacus --- dpdata/abacus/scf.py | 3 +++ dpdata/vasp/poscar.py | 22 +++++++++++++--- tests/test_abacus_stru_dump.py | 39 ++++++++++++++++++++++++++++- tests/test_vasp_poscar_to_system.py | 4 +++ 4 files changed, 64 insertions(+), 4 deletions(-) diff --git a/dpdata/abacus/scf.py b/dpdata/abacus/scf.py index b1b2cfed..b97a2a20 100644 --- a/dpdata/abacus/scf.py +++ b/dpdata/abacus/scf.py @@ -683,6 +683,9 @@ def process_file_input(file_input, atom_names, input_name): if mag is None and data.get("spins") is not None and len(data["spins"]) > 0: mag = data["spins"][frame_idx] + + if move is None and data.get("move", None) is not None and len(data["move"]) > 0: + move = data["move"] atom_numbs = sum(data["atom_numbs"]) for key in [move, velocity, mag, angle1, angle2, sc, lambda_]: diff --git a/dpdata/vasp/poscar.py b/dpdata/vasp/poscar.py index 102e7904..d4ee93cf 100644 --- a/dpdata/vasp/poscar.py +++ b/dpdata/vasp/poscar.py @@ -4,13 +4,22 @@ import numpy as np -def _to_system_data_lower(lines, cartesian=True): +def _to_system_data_lower(lines, cartesian=True, selective_dynamics=False): + def move_flag_mapper(flag): + if flag == "T": + return True + elif flag == "F": + return False + else: + raise RuntimeError(f"Invalid selective dynamics flag: {flag}") + """Treat as cartesian poscar.""" system = {} 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]) cell = [] + move_flags = [] for ii in range(2, 5): boxv = [float(jj) for jj in lines[ii].split()] boxv = np.array(boxv) * scale @@ -19,12 +28,16 @@ def _to_system_data_lower(lines, cartesian=True): natoms = sum(system["atom_numbs"]) coord = [] for ii in range(8, 8 + natoms): - tmpv = [float(jj) for jj in lines[ii].split()[:3]] + tmp = lines[ii].split() + tmpv = [float(jj) for jj in tmp[:3]] if cartesian: tmpv = np.array(tmpv) * scale else: tmpv = np.matmul(np.array(tmpv), system["cells"][0]) coord.append(tmpv) + if selective_dynamics and len(tmp) == 6: + move_flags.append(list(map(move_flag_mapper, tmp[3:]))) + system["coords"] = [np.array(coord)] system["orig"] = np.zeros(3) atom_types = [] @@ -34,12 +47,15 @@ def _to_system_data_lower(lines, cartesian=True): system["atom_types"] = np.array(atom_types, dtype=int) system["cells"] = np.array(system["cells"]) system["coords"] = np.array(system["coords"]) + system["move"] = np.array(move_flags, dtype=bool) return system def to_system_data(lines): # remove the line that has 'selective dynamics' + selective_dynamics = False if lines[7][0] == "S" or lines[7][0] == "s": + selective_dynamics = True lines.pop(7) is_cartesian = lines[7][0] in ["C", "c", "K", "k"] if not is_cartesian: @@ -47,7 +63,7 @@ def to_system_data(lines): raise RuntimeError( "seem not to be a valid POSCAR of vasp 5.x, may be a POSCAR of vasp 4.x?" ) - return _to_system_data_lower(lines, is_cartesian) + return _to_system_data_lower(lines, is_cartesian, selective_dynamics) def from_system_data(system, f_idx=0, skip_zeros=True): diff --git a/tests/test_abacus_stru_dump.py b/tests/test_abacus_stru_dump.py index 4549b6d1..ce5daa3c 100644 --- a/tests/test_abacus_stru_dump.py +++ b/tests/test_abacus_stru_dump.py @@ -145,7 +145,44 @@ def test_dump_spin(self): 5.499851012568 4.003388899277 5.342621842622 1 1 1 mag 3.000000000000 3.000000000000 3.000000000000 """ self.assertTrue(stru_ref in c) - + + def test_dump_move_from_vasp(self): + self.system = dpdata.System() + self.system.from_vasp_poscar(os.path.join("poscars", "POSCAR.oh.c")) + self.system.to("abacus/stru", "STRU_tmp") + assert os.path.isfile("STRU_tmp") + with open("STRU_tmp") as f: + c = f.read() + + stru_ref = """O +0.0 +1 +0.000000000000 0.000000000000 0.000000000000 1 1 0 +H +0.0 +1 +1.262185604418 0.701802783513 0.551388341420 0 0 0 +""" + self.assertTrue(stru_ref in c) + + self.system.to("abacus/stru", "STRU_tmp", move=[[True, False, True], [False, True, False]]) + assert os.path.isfile("STRU_tmp") + with open("STRU_tmp") as f: + c = f.read() + + stru_ref = """O +0.0 +1 +0.000000000000 0.000000000000 0.000000000000 1 0 1 +H +0.0 +1 +1.262185604418 0.701802783513 0.551388341420 0 1 0 +""" + self.assertTrue(stru_ref in c) + + + class TestABACUSParseStru(unittest.TestCase): def test_parse_stru_post(self): diff --git a/tests/test_vasp_poscar_to_system.py b/tests/test_vasp_poscar_to_system.py index 7457d33d..3e43afdf 100644 --- a/tests/test_vasp_poscar_to_system.py +++ b/tests/test_vasp_poscar_to_system.py @@ -13,6 +13,10 @@ class TestPOSCARCart(unittest.TestCase, TestPOSCARoh): def setUp(self): self.system = dpdata.System() self.system.from_vasp_poscar(os.path.join("poscars", "POSCAR.oh.c")) + + def test_move_flags(self): + expected = np.array([[True, True, False], [False, False, False]]) + self.assertTrue(np.array_equal(self.system["move"], expected)) class TestPOSCARDirect(unittest.TestCase, TestPOSCARoh): From fabba4511816a447cc4cb12037be94e179fba93f Mon Sep 17 00:00:00 2001 From: yaosk Date: Sat, 26 Oct 2024 13:47:33 +0800 Subject: [PATCH 02/14] Add support for converting move flags from Abacus to VASP --- dpdata/abacus/md.py | 4 +++- dpdata/abacus/relax.py | 6 ++++-- dpdata/abacus/scf.py | 11 ++++++++--- dpdata/vasp/poscar.py | 31 +++++++++++++++++++++++++++++-- tests/abacus.scf/STRU.ch4 | 8 ++++---- tests/abacus.scf/stru_test | 8 ++++---- tests/test_abacus_stru_dump.py | 4 ++-- tests/test_vasp_poscar_dump.py | 15 +++++++++++++++ 8 files changed, 69 insertions(+), 18 deletions(-) diff --git a/dpdata/abacus/md.py b/dpdata/abacus/md.py index bbb02343..aa4215d8 100644 --- a/dpdata/abacus/md.py +++ b/dpdata/abacus/md.py @@ -167,7 +167,7 @@ def get_frame(fname): with open_file(geometry_path_in) as fp: geometry_inlines = fp.read().split("\n") celldm, cell = get_cell(geometry_inlines) - atom_names, natoms, types, coords = get_coords( + atom_names, natoms, types, coords, move = get_coords( celldm, cell, geometry_inlines, inlines ) # This coords is not to be used. @@ -221,5 +221,7 @@ def get_frame(fname): data["spins"] = magmom if len(magforce) > 0: data["mag_forces"] = magforce + if len(move) > 0: + data["move"] = move return data diff --git a/dpdata/abacus/relax.py b/dpdata/abacus/relax.py index be80bc90..ec97b680 100644 --- a/dpdata/abacus/relax.py +++ b/dpdata/abacus/relax.py @@ -47,7 +47,7 @@ def get_coords_from_log(loglines, natoms): natoms_log += int(line.split()[-1]) assert natoms_log > 0 and natoms_log == natoms, ( - "ERROR: detected atom number in log file is %d" % natoms + f"ERROR: detected atom number in log file is {natoms_log}, while the number in STRU file is {natoms}" ) energy = [] @@ -183,7 +183,7 @@ def get_frame(fname): with open_file(geometry_path_in) as fp: geometry_inlines = fp.read().split("\n") celldm, cell = get_cell(geometry_inlines) - atom_names, natoms, types, coord_tmp = get_coords( + atom_names, natoms, types, coord_tmp, move = get_coords( celldm, cell, geometry_inlines, inlines ) @@ -218,5 +218,7 @@ def get_frame(fname): data["spins"] = magmom if len(magforce) > 0: data["mag_forces"] = magforce + if len(move) > 0: + data["move"] = move return data diff --git a/dpdata/abacus/scf.py b/dpdata/abacus/scf.py index b97a2a20..1102f299 100644 --- a/dpdata/abacus/scf.py +++ b/dpdata/abacus/scf.py @@ -310,7 +310,8 @@ def get_coords(celldm, cell, geometry_inlines, inlines=None): line_idx += 1 coords = np.array(coords) # need transformation!!! atom_types = np.array(atom_types) - return atom_names, atom_numbs, atom_types, coords + move = np.array(move, dtype=bool) + return atom_names, atom_numbs, atom_types, coords, move def get_energy(outlines): @@ -477,7 +478,7 @@ def get_frame(fname): outlines = fp.read().split("\n") celldm, cell = get_cell(geometry_inlines) - atom_names, natoms, types, coords = get_coords( + atom_names, natoms, types, coords, move = get_coords( celldm, cell, geometry_inlines, inlines ) magmom, magforce = get_mag_force(outlines) @@ -510,6 +511,8 @@ def get_frame(fname): data["spins"] = magmom if len(magforce) > 0: data["mag_forces"] = magforce + if len(move) > 0: + data["move"] = move # print("atom_names = ", data['atom_names']) # print("natoms = ", data['atom_numbs']) # print("types = ", data['atom_types']) @@ -561,7 +564,7 @@ def get_frame_from_stru(fname): nele = get_nele_from_stru(geometry_inlines) inlines = [f"ntype {nele}"] celldm, cell = get_cell(geometry_inlines) - atom_names, natoms, types, coords = get_coords( + atom_names, natoms, types, coords, move = get_coords( celldm, cell, geometry_inlines, inlines ) data = {} @@ -571,6 +574,8 @@ def get_frame_from_stru(fname): data["cells"] = cell[np.newaxis, :, :] data["coords"] = coords[np.newaxis, :, :] data["orig"] = np.zeros(3) + if len(move) > 0: + data["move"] = move return data diff --git a/dpdata/vasp/poscar.py b/dpdata/vasp/poscar.py index d4ee93cf..7ece2535 100644 --- a/dpdata/vasp/poscar.py +++ b/dpdata/vasp/poscar.py @@ -88,6 +88,10 @@ def from_system_data(system, f_idx=0, skip_zeros=True): continue ret += "%d " % ii ret += "\n" + move = system.get("move", np.array([])) + if len(move) > 0: + ret += "Selective Dynamics\n" + # should use Cartesian for VESTA software ret += "Cartesian\n" atype = system["atom_types"] @@ -97,9 +101,32 @@ def from_system_data(system, f_idx=0, skip_zeros=True): sort_idx = np.lexsort((np.arange(len(atype)), atype)) atype = atype[sort_idx] posis = posis[sort_idx] + if len(move) > 0: + move = move[sort_idx] + + if isinstance(move, np.ndarray): + move = move.tolist() + posi_list = [] - for ii in posis: - posi_list.append(f"{ii[0]:15.10f} {ii[1]:15.10f} {ii[2]:15.10f}") + for idx in range(len(posis)): + ii_posi = posis[idx] + line = f"{ii_posi[0]:15.10f} {ii_posi[1]:15.10f} {ii_posi[2]:15.10f}" + if len(move) > 0: + move_flags = move[idx] + if ( + isinstance(move_flags, list) + and len(move_flags) == 3 + ): + line += " " + " ".join(["T" if flag else "F" for flag in move_flags]) + elif isinstance(move_flags, (int, float, bool)): + line += " " + " ".join(["T" if move_flags else "F"] * 3) + else: + raise RuntimeError( + f"Invalid move flags: {move_flags}, should be a list or a bool" + ) + + posi_list.append(line) + posi_list.append("") ret += "\n".join(posi_list) return ret diff --git a/tests/abacus.scf/STRU.ch4 b/tests/abacus.scf/STRU.ch4 index cf97747a..bc33cbe9 100644 --- a/tests/abacus.scf/STRU.ch4 +++ b/tests/abacus.scf/STRU.ch4 @@ -18,11 +18,11 @@ Cartesian #Cartesian(Unit is LATTICE_CONSTANT) C #Name of element 0.0 #Magnetic for this element. 1 #Number of atoms -0.981274803 0.861285385 0.838442496 0 0 0 +0.981274803 0.861285385 0.838442496 1 1 1 H 0.0 4 1.023557202 0.758025625 0.66351336 0 0 0 -0.78075702 0.889445935 0.837363468 0 0 0 -1.064091613 1.043438905 0.840995502 0 0 0 -1.039321214 0.756530859 1.009609207 0 0 0 +0.78075702 0.889445935 0.837363468 1 0 1 +1.064091613 1.043438905 0.840995502 1 0 1 +1.039321214 0.756530859 1.009609207 0 1 1 diff --git a/tests/abacus.scf/stru_test b/tests/abacus.scf/stru_test index 22d619c9..e4036409 100644 --- a/tests/abacus.scf/stru_test +++ b/tests/abacus.scf/stru_test @@ -26,7 +26,7 @@ C H 0.0 4 -5.416431453540 4.011298860305 3.511161492417 1 1 1 -4.131588222365 4.706745191323 4.431136645083 1 1 1 -5.630930319126 5.521640894956 4.450356541303 1 1 1 -5.499851012568 4.003388899277 5.342621842622 1 1 1 +5.416431453540 4.011298860305 3.511161492417 0 0 0 +4.131588222365 4.706745191323 4.431136645083 1 0 1 +5.630930319126 5.521640894956 4.450356541303 1 0 1 +5.499851012568 4.003388899277 5.342621842622 0 1 1 diff --git a/tests/test_abacus_stru_dump.py b/tests/test_abacus_stru_dump.py index ce5daa3c..6f3a9f9c 100644 --- a/tests/test_abacus_stru_dump.py +++ b/tests/test_abacus_stru_dump.py @@ -141,8 +141,8 @@ def test_dump_spin(self): 4 5.416431453540 4.011298860305 3.511161492417 1 1 1 mag 4.000000000000 5.000000000000 6.000000000000 4.131588222365 4.706745191323 4.431136645083 1 1 1 mag 1.000000000000 1.000000000000 1.000000000000 -5.630930319126 5.521640894956 4.450356541303 1 1 1 mag 2.000000000000 2.000000000000 2.000000000000 -5.499851012568 4.003388899277 5.342621842622 1 1 1 mag 3.000000000000 3.000000000000 3.000000000000 +5.630930319126 5.521640894956 4.450356541303 0 0 0 mag 2.000000000000 2.000000000000 2.000000000000 +5.499851012568 4.003388899277 5.342621842622 0 0 0 mag 3.000000000000 3.000000000000 3.000000000000 """ self.assertTrue(stru_ref in c) diff --git a/tests/test_vasp_poscar_dump.py b/tests/test_vasp_poscar_dump.py index 62f21598..c04023fd 100644 --- a/tests/test_vasp_poscar_dump.py +++ b/tests/test_vasp_poscar_dump.py @@ -34,6 +34,21 @@ def setUp(self): self.system = dpdata.System() self.system.from_vasp_poscar("tmp.POSCAR") + def test_dump_move_flags(self): + tmp_system = dpdata.System() + tmp_system.from_vasp_poscar(os.path.join("poscars", "POSCAR.oh.c")) + tmp_system.to_vasp_poscar("tmp.POSCAR") + self.system = dpdata.System() + self.system.from_vasp_poscar("tmp.POSCAR") + with open("tmp.POSCAR", "r") as f: + content = f.read() + + stru_ref = f"""Cartesian + 0.0000000000 0.0000000000 0.0000000000 T T F + 1.2621856044 0.7018027835 0.5513883414 F F F +""" + self.assertTrue(stru_ref in content) + class TestPOSCARSkipZeroAtomNumb(unittest.TestCase): def tearDown(self): From 70ab0832c7149d3c7f4c98b84df0de8e97b49c9c Mon Sep 17 00:00:00 2001 From: yaosk Date: Sat, 26 Oct 2024 14:22:51 +0800 Subject: [PATCH 03/14] update --- dpdata/abacus/relax.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dpdata/abacus/relax.py b/dpdata/abacus/relax.py index ec97b680..63f13678 100644 --- a/dpdata/abacus/relax.py +++ b/dpdata/abacus/relax.py @@ -47,7 +47,7 @@ def get_coords_from_log(loglines, natoms): natoms_log += int(line.split()[-1]) assert natoms_log > 0 and natoms_log == natoms, ( - f"ERROR: detected atom number in log file is {natoms_log}, while the number in STRU file is {natoms}" + "ERROR: detected atom number in log file is %d" % natoms ) energy = [] From 97448dd55f625de7c5f273b7509e90bc5a819684 Mon Sep 17 00:00:00 2001 From: yaosk Date: Sat, 26 Oct 2024 14:34:18 +0800 Subject: [PATCH 04/14] fix bug --- tests/test_abacus_stru_dump.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tests/test_abacus_stru_dump.py b/tests/test_abacus_stru_dump.py index 6f3a9f9c..8891dee3 100644 --- a/tests/test_abacus_stru_dump.py +++ b/tests/test_abacus_stru_dump.py @@ -145,15 +145,19 @@ def test_dump_spin(self): 5.499851012568 4.003388899277 5.342621842622 0 0 0 mag 3.000000000000 3.000000000000 3.000000000000 """ self.assertTrue(stru_ref in c) - + def test_dump_move_from_vasp(self): self.system = dpdata.System() self.system.from_vasp_poscar(os.path.join("poscars", "POSCAR.oh.c")) - self.system.to("abacus/stru", "STRU_tmp") + self.system.to( + "abacus/stru", + "STRU_tmp", + pp_file={"O": "O.upf", "H": "H.upf"}, + ) assert os.path.isfile("STRU_tmp") with open("STRU_tmp") as f: c = f.read() - + stru_ref = """O 0.0 1 @@ -164,12 +168,17 @@ def test_dump_move_from_vasp(self): 1.262185604418 0.701802783513 0.551388341420 0 0 0 """ self.assertTrue(stru_ref in c) - - self.system.to("abacus/stru", "STRU_tmp", move=[[True, False, True], [False, True, False]]) + + self.system.to( + "abacus/stru", + "STRU_tmp", + pp_file={"O": "O.upf", "H": "H.upf"}, + move=[[True, False, True], [False, True, False]], + ) assert os.path.isfile("STRU_tmp") with open("STRU_tmp") as f: c = f.read() - + stru_ref = """O 0.0 1 @@ -180,9 +189,7 @@ def test_dump_move_from_vasp(self): 1.262185604418 0.701802783513 0.551388341420 0 1 0 """ self.assertTrue(stru_ref in c) - - - + class TestABACUSParseStru(unittest.TestCase): def test_parse_stru_post(self): From 5cf49eac9aacd3a3bb548459dc7ce0626cec9b55 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 26 Oct 2024 06:35:46 +0000 Subject: [PATCH 05/14] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- dpdata/abacus/scf.py | 2 +- dpdata/vasp/poscar.py | 5 +---- tests/test_vasp_poscar_dump.py | 4 ++-- tests/test_vasp_poscar_to_system.py | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/dpdata/abacus/scf.py b/dpdata/abacus/scf.py index 1102f299..87b95c28 100644 --- a/dpdata/abacus/scf.py +++ b/dpdata/abacus/scf.py @@ -688,7 +688,7 @@ def process_file_input(file_input, atom_names, input_name): if mag is None and data.get("spins") is not None and len(data["spins"]) > 0: mag = data["spins"][frame_idx] - + if move is None and data.get("move", None) is not None and len(data["move"]) > 0: move = data["move"] diff --git a/dpdata/vasp/poscar.py b/dpdata/vasp/poscar.py index 7ece2535..5b73b71b 100644 --- a/dpdata/vasp/poscar.py +++ b/dpdata/vasp/poscar.py @@ -113,10 +113,7 @@ def from_system_data(system, f_idx=0, skip_zeros=True): line = f"{ii_posi[0]:15.10f} {ii_posi[1]:15.10f} {ii_posi[2]:15.10f}" if len(move) > 0: move_flags = move[idx] - if ( - isinstance(move_flags, list) - and len(move_flags) == 3 - ): + if isinstance(move_flags, list) and len(move_flags) == 3: line += " " + " ".join(["T" if flag else "F" for flag in move_flags]) elif isinstance(move_flags, (int, float, bool)): line += " " + " ".join(["T" if move_flags else "F"] * 3) diff --git a/tests/test_vasp_poscar_dump.py b/tests/test_vasp_poscar_dump.py index c04023fd..d5522811 100644 --- a/tests/test_vasp_poscar_dump.py +++ b/tests/test_vasp_poscar_dump.py @@ -40,10 +40,10 @@ def test_dump_move_flags(self): tmp_system.to_vasp_poscar("tmp.POSCAR") self.system = dpdata.System() self.system.from_vasp_poscar("tmp.POSCAR") - with open("tmp.POSCAR", "r") as f: + with open("tmp.POSCAR") as f: content = f.read() - stru_ref = f"""Cartesian + stru_ref = """Cartesian 0.0000000000 0.0000000000 0.0000000000 T T F 1.2621856044 0.7018027835 0.5513883414 F F F """ diff --git a/tests/test_vasp_poscar_to_system.py b/tests/test_vasp_poscar_to_system.py index 3e43afdf..745c0bb7 100644 --- a/tests/test_vasp_poscar_to_system.py +++ b/tests/test_vasp_poscar_to_system.py @@ -13,7 +13,7 @@ class TestPOSCARCart(unittest.TestCase, TestPOSCARoh): def setUp(self): self.system = dpdata.System() self.system.from_vasp_poscar(os.path.join("poscars", "POSCAR.oh.c")) - + def test_move_flags(self): expected = np.array([[True, True, False], [False, False, False]]) self.assertTrue(np.array_equal(self.system["move"], expected)) From badc09d1093d7f35ce01b71bedf12528cf26c19d Mon Sep 17 00:00:00 2001 From: yaosk Date: Mon, 28 Oct 2024 16:44:58 +0800 Subject: [PATCH 06/14] register the move shape --- dpdata/abacus/scf.py | 9 +++++---- dpdata/plugins/abacus.py | 9 +++++++++ dpdata/plugins/vasp.py | 16 ++++++++++++++++ dpdata/vasp/poscar.py | 15 +++++++++------ tests/test_vasp_poscar_to_system.py | 2 +- 5 files changed, 40 insertions(+), 11 deletions(-) diff --git a/dpdata/abacus/scf.py b/dpdata/abacus/scf.py index 87b95c28..d762c4dc 100644 --- a/dpdata/abacus/scf.py +++ b/dpdata/abacus/scf.py @@ -299,7 +299,8 @@ def get_coords(celldm, cell, geometry_inlines, inlines=None): coords.append(xyz) atom_types.append(it) - move.append(imove) + if imove is not None: + move.append(imove) velocity.append(ivelocity) mag.append(imagmom) angle1.append(iangle1) @@ -512,7 +513,7 @@ def get_frame(fname): if len(magforce) > 0: data["mag_forces"] = magforce if len(move) > 0: - data["move"] = move + data["move"] = move[np.newaxis, :, :] # print("atom_names = ", data['atom_names']) # print("natoms = ", data['atom_numbs']) # print("types = ", data['atom_types']) @@ -575,7 +576,7 @@ def get_frame_from_stru(fname): data["coords"] = coords[np.newaxis, :, :] data["orig"] = np.zeros(3) if len(move) > 0: - data["move"] = move + data["move"] = move[np.newaxis, :, :] return data @@ -690,7 +691,7 @@ def process_file_input(file_input, atom_names, input_name): mag = data["spins"][frame_idx] if move is None and data.get("move", None) is not None and len(data["move"]) > 0: - move = data["move"] + move = data["move"][frame_idx] atom_numbs = sum(data["atom_numbs"]) for key in [move, velocity, mag, angle1, angle2, sc, lambda_]: diff --git a/dpdata/plugins/abacus.py b/dpdata/plugins/abacus.py index cbd1475f..a57e2d00 100644 --- a/dpdata/plugins/abacus.py +++ b/dpdata/plugins/abacus.py @@ -65,6 +65,15 @@ def register_mag_data(data): deepmd_name="force_mag", ) dpdata.LabeledSystem.register_data_type(dt) + if "move" in data: + dt = DataType( + "move", + np.ndarray, + (Axis.NFRAMES, Axis.NATOMS, 3), + required=False, + deepmd_name="move", + ) + dpdata.System.register_data_type(dt) @Format.register("abacus/scf") diff --git a/dpdata/plugins/vasp.py b/dpdata/plugins/vasp.py index 0160bde2..db71cf71 100644 --- a/dpdata/plugins/vasp.py +++ b/dpdata/plugins/vasp.py @@ -9,11 +9,24 @@ import dpdata.vasp.xml from dpdata.format import Format from dpdata.utils import open_file, uniq_atom_names +from dpdata.data_type import Axis, DataType if TYPE_CHECKING: from dpdata.utils import FileType +def register_move_data(data): + if "move" in data: + dt = DataType( + "move", + np.ndarray, + (Axis.NFRAMES, Axis.NATOMS, 3), + required=False, + deepmd_name="move", + ) + dpdata.System.register_data_type(dt) + + @Format.register("poscar") @Format.register("contcar") @Format.register("vasp/poscar") @@ -25,6 +38,7 @@ def from_system(self, file_name: FileType, **kwargs): lines = [line.rstrip("\n") for line in fp] data = dpdata.vasp.poscar.to_system_data(lines) data = uniq_atom_names(data) + register_move_data(data) return data def to_system(self, data, file_name: FileType, frame_idx=0, **kwargs): @@ -99,6 +113,7 @@ def from_labeled_system( vol = np.linalg.det(np.reshape(data["cells"][ii], [3, 3])) data["virials"][ii] *= v_pref * vol data = uniq_atom_names(data) + register_move_data(data) return data @@ -135,4 +150,5 @@ def from_labeled_system(self, file_name, begin=0, step=1, **kwargs): vol = np.linalg.det(np.reshape(data["cells"][ii], [3, 3])) data["virials"][ii] *= v_pref * vol data = uniq_atom_names(data) + register_move_data(data) return data diff --git a/dpdata/vasp/poscar.py b/dpdata/vasp/poscar.py index 5b73b71b..d323d692 100644 --- a/dpdata/vasp/poscar.py +++ b/dpdata/vasp/poscar.py @@ -47,7 +47,10 @@ def move_flag_mapper(flag): system["atom_types"] = np.array(atom_types, dtype=int) system["cells"] = np.array(system["cells"]) system["coords"] = np.array(system["coords"]) - system["move"] = np.array(move_flags, dtype=bool) + if move_flags: + move_flags = np.array(move_flags, dtype=bool) + move_flags = move_flags.reshape((1, natoms, 3)) + system["move"] = np.array(move_flags, dtype=bool) return system @@ -88,8 +91,8 @@ def from_system_data(system, f_idx=0, skip_zeros=True): continue ret += "%d " % ii ret += "\n" - move = system.get("move", np.array([])) - if len(move) > 0: + move = system.get("move", None) + if move is not None and len(move) > 0: ret += "Selective Dynamics\n" # should use Cartesian for VESTA software @@ -101,8 +104,8 @@ def from_system_data(system, f_idx=0, skip_zeros=True): sort_idx = np.lexsort((np.arange(len(atype)), atype)) atype = atype[sort_idx] posis = posis[sort_idx] - if len(move) > 0: - move = move[sort_idx] + if move is not None and len(move) > 0: + move = move[f_idx][sort_idx] if isinstance(move, np.ndarray): move = move.tolist() @@ -111,7 +114,7 @@ def from_system_data(system, f_idx=0, skip_zeros=True): for idx in range(len(posis)): ii_posi = posis[idx] line = f"{ii_posi[0]:15.10f} {ii_posi[1]:15.10f} {ii_posi[2]:15.10f}" - if len(move) > 0: + if move is not None and len(move) > 0: move_flags = move[idx] if isinstance(move_flags, list) and len(move_flags) == 3: line += " " + " ".join(["T" if flag else "F" for flag in move_flags]) diff --git a/tests/test_vasp_poscar_to_system.py b/tests/test_vasp_poscar_to_system.py index 745c0bb7..155f5588 100644 --- a/tests/test_vasp_poscar_to_system.py +++ b/tests/test_vasp_poscar_to_system.py @@ -15,7 +15,7 @@ def setUp(self): self.system.from_vasp_poscar(os.path.join("poscars", "POSCAR.oh.c")) def test_move_flags(self): - expected = np.array([[True, True, False], [False, False, False]]) + expected = np.array([[[True, True, False], [False, False, False]]]) self.assertTrue(np.array_equal(self.system["move"], expected)) From 30c7c3f2f47faf78513335c5e8c537d93d38d9b0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 08:47:15 +0000 Subject: [PATCH 07/14] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- dpdata/plugins/vasp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dpdata/plugins/vasp.py b/dpdata/plugins/vasp.py index db71cf71..d25d0c25 100644 --- a/dpdata/plugins/vasp.py +++ b/dpdata/plugins/vasp.py @@ -7,9 +7,9 @@ import dpdata.vasp.outcar import dpdata.vasp.poscar import dpdata.vasp.xml +from dpdata.data_type import Axis, DataType from dpdata.format import Format from dpdata.utils import open_file, uniq_atom_names -from dpdata.data_type import Axis, DataType if TYPE_CHECKING: from dpdata.utils import FileType From af32fc222b8d79545b974efeb39458cc27bcf0ac Mon Sep 17 00:00:00 2001 From: yaosk Date: Mon, 28 Oct 2024 18:46:48 +0800 Subject: [PATCH 08/14] add ut for move flags --- dpdata/abacus/scf.py | 4 ++-- dpdata/vasp/poscar.py | 15 +++++++++------ tests/test_vasp_poscar_to_system.py | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/dpdata/abacus/scf.py b/dpdata/abacus/scf.py index d762c4dc..43e65f7e 100644 --- a/dpdata/abacus/scf.py +++ b/dpdata/abacus/scf.py @@ -615,8 +615,8 @@ def make_unlabeled_stru( numerical descriptor file mass : list of float, optional List of atomic masses - move : list of list of bool, optional - List of the move flag of each xyz direction of each atom + move : list of (list of list of bool), optional + List of the move flag of each xyz direction of each atom for each frame velocity : list of list of float, optional List of the velocity of each xyz direction of each atom mag : list of (list of float or float), optional diff --git a/dpdata/vasp/poscar.py b/dpdata/vasp/poscar.py index d323d692..075d8f2b 100644 --- a/dpdata/vasp/poscar.py +++ b/dpdata/vasp/poscar.py @@ -11,7 +11,7 @@ def move_flag_mapper(flag): elif flag == "F": return False else: - raise RuntimeError(f"Invalid selective dynamics flag: {flag}") + raise RuntimeError(f"Invalid move flag: {flag}") """Treat as cartesian poscar.""" system = {} @@ -35,8 +35,13 @@ def move_flag_mapper(flag): else: tmpv = np.matmul(np.array(tmpv), system["cells"][0]) coord.append(tmpv) - if selective_dynamics and len(tmp) == 6: - move_flags.append(list(map(move_flag_mapper, tmp[3:]))) + if selective_dynamics: + if len(tmp) == 6: + move_flags.append(list(map(move_flag_mapper, tmp[3:]))) + else: + raise RuntimeError( + f"Invalid move flags, should be 6 columns, got {tmp}" + ) system["coords"] = [np.array(coord)] system["orig"] = np.zeros(3) @@ -118,11 +123,9 @@ def from_system_data(system, f_idx=0, skip_zeros=True): move_flags = move[idx] if isinstance(move_flags, list) and len(move_flags) == 3: line += " " + " ".join(["T" if flag else "F" for flag in move_flags]) - elif isinstance(move_flags, (int, float, bool)): - line += " " + " ".join(["T" if move_flags else "F"] * 3) else: raise RuntimeError( - f"Invalid move flags: {move_flags}, should be a list or a bool" + f"Invalid move flags: {move_flags}, should be a list of 3 bools" ) posi_list.append(line) diff --git a/tests/test_vasp_poscar_to_system.py b/tests/test_vasp_poscar_to_system.py index 155f5588..8d642a2e 100644 --- a/tests/test_vasp_poscar_to_system.py +++ b/tests/test_vasp_poscar_to_system.py @@ -19,6 +19,21 @@ def test_move_flags(self): self.assertTrue(np.array_equal(self.system["move"], expected)) +class TestPOSCARCart(unittest.TestCase): + def test_move_flags_error1(self): + with self.assertRaisesRegex(RuntimeError, "Invalid move flags.*?"): + dpdata.System().from_vasp_poscar(os.path.join("poscars", "POSCAR.oh.err1")) + + def test_move_flags_error2(self): + with self.assertRaisesRegex(RuntimeError, "Invalid move flag: a"): + dpdata.System().from_vasp_poscar(os.path.join("poscars", "POSCAR.oh.err2")) + + def test_move_flags_error3(self): + system = dpdata.System().from_vasp_poscar(os.path.join("poscars", "POSCAR.oh.c")) + system.data["move"] = np.array([[[True, True], [False, False]]]) + with self.assertRaisesRegex(RuntimeError, "Invalid move flags:.*?should be a list of 3 bools"): + system.to_vasp_poscar("POSCAR.tmp.1") + class TestPOSCARDirect(unittest.TestCase, TestPOSCARoh): def setUp(self): self.system = dpdata.System() From 3a53c4a1b721fcc17a28688f319400bc2b633da9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:47:25 +0000 Subject: [PATCH 09/14] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_vasp_poscar_to_system.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/test_vasp_poscar_to_system.py b/tests/test_vasp_poscar_to_system.py index 8d642a2e..23b1e982 100644 --- a/tests/test_vasp_poscar_to_system.py +++ b/tests/test_vasp_poscar_to_system.py @@ -23,17 +23,22 @@ class TestPOSCARCart(unittest.TestCase): def test_move_flags_error1(self): with self.assertRaisesRegex(RuntimeError, "Invalid move flags.*?"): dpdata.System().from_vasp_poscar(os.path.join("poscars", "POSCAR.oh.err1")) - + def test_move_flags_error2(self): with self.assertRaisesRegex(RuntimeError, "Invalid move flag: a"): dpdata.System().from_vasp_poscar(os.path.join("poscars", "POSCAR.oh.err2")) def test_move_flags_error3(self): - system = dpdata.System().from_vasp_poscar(os.path.join("poscars", "POSCAR.oh.c")) + system = dpdata.System().from_vasp_poscar( + os.path.join("poscars", "POSCAR.oh.c") + ) system.data["move"] = np.array([[[True, True], [False, False]]]) - with self.assertRaisesRegex(RuntimeError, "Invalid move flags:.*?should be a list of 3 bools"): + with self.assertRaisesRegex( + RuntimeError, "Invalid move flags:.*?should be a list of 3 bools" + ): system.to_vasp_poscar("POSCAR.tmp.1") + class TestPOSCARDirect(unittest.TestCase, TestPOSCARoh): def setUp(self): self.system = dpdata.System() From a59fb4a26b229d806e5db2bb06a737a7b35f7af0 Mon Sep 17 00:00:00 2001 From: yaosk Date: Mon, 28 Oct 2024 18:49:26 +0800 Subject: [PATCH 10/14] add ut files --- tests/poscars/POSCAR.oh.err1 | 11 +++++++++++ tests/poscars/POSCAR.oh.err2 | 11 +++++++++++ 2 files changed, 22 insertions(+) create mode 100644 tests/poscars/POSCAR.oh.err1 create mode 100644 tests/poscars/POSCAR.oh.err2 diff --git a/tests/poscars/POSCAR.oh.err1 b/tests/poscars/POSCAR.oh.err1 new file mode 100644 index 00000000..1b7521e6 --- /dev/null +++ b/tests/poscars/POSCAR.oh.err1 @@ -0,0 +1,11 @@ +Cubic BN + 3.57 + 0.00 0.50 0.50 + 0.45 0.00 0.50 + 0.55 0.51 0.00 + O H + 1 1 +Selective dynamics +Cartesian + 0.00 0.00 0.00 T T F + 0.25 0.25 0.25 F F diff --git a/tests/poscars/POSCAR.oh.err2 b/tests/poscars/POSCAR.oh.err2 new file mode 100644 index 00000000..ed52d4d0 --- /dev/null +++ b/tests/poscars/POSCAR.oh.err2 @@ -0,0 +1,11 @@ +Cubic BN + 3.57 + 0.00 0.50 0.50 + 0.45 0.00 0.50 + 0.55 0.51 0.00 + O H + 1 1 +Selective dynamics +Cartesian + 0.00 0.00 0.00 T T F + 0.25 0.25 0.25 a T F From 55193a73c0943dde05016209a744a1528cadea1d Mon Sep 17 00:00:00 2001 From: yaosk Date: Mon, 28 Oct 2024 19:29:45 +0800 Subject: [PATCH 11/14] opt code --- dpdata/vasp/poscar.py | 5 ++--- tests/test_vasp_poscar_to_system.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/dpdata/vasp/poscar.py b/dpdata/vasp/poscar.py index 075d8f2b..30073e2b 100644 --- a/dpdata/vasp/poscar.py +++ b/dpdata/vasp/poscar.py @@ -121,12 +121,11 @@ def from_system_data(system, f_idx=0, skip_zeros=True): line = f"{ii_posi[0]:15.10f} {ii_posi[1]:15.10f} {ii_posi[2]:15.10f}" if move is not None and len(move) > 0: move_flags = move[idx] - if isinstance(move_flags, list) and len(move_flags) == 3: - line += " " + " ".join(["T" if flag else "F" for flag in move_flags]) - else: + if not isinstance(move_flags, list) or len(move_flags) != 3: raise RuntimeError( f"Invalid move flags: {move_flags}, should be a list of 3 bools" ) + line += " " + " ".join("T" if flag else "F" for flag in move_flags) posi_list.append(line) diff --git a/tests/test_vasp_poscar_to_system.py b/tests/test_vasp_poscar_to_system.py index 23b1e982..16cd64b8 100644 --- a/tests/test_vasp_poscar_to_system.py +++ b/tests/test_vasp_poscar_to_system.py @@ -19,7 +19,7 @@ def test_move_flags(self): self.assertTrue(np.array_equal(self.system["move"], expected)) -class TestPOSCARCart(unittest.TestCase): +class TestPOSCARMoveFlags(unittest.TestCase): def test_move_flags_error1(self): with self.assertRaisesRegex(RuntimeError, "Invalid move flags.*?"): dpdata.System().from_vasp_poscar(os.path.join("poscars", "POSCAR.oh.err1")) From 322c10cbe0b02bbf58bf94d373ce56a20656ada3 Mon Sep 17 00:00:00 2001 From: yaosk Date: Mon, 28 Oct 2024 19:38:19 +0800 Subject: [PATCH 12/14] opt code --- tests/test_vasp_poscar_to_system.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_vasp_poscar_to_system.py b/tests/test_vasp_poscar_to_system.py index 16cd64b8..c34d85ee 100644 --- a/tests/test_vasp_poscar_to_system.py +++ b/tests/test_vasp_poscar_to_system.py @@ -20,6 +20,12 @@ def test_move_flags(self): class TestPOSCARMoveFlags(unittest.TestCase): + def setUp(self): + self.tmp_file = "POSCAR.tmp.1" + + def tearDown(self): + if os.path.exists(self.tmp_file): + os.remove(self.tmp_file) def test_move_flags_error1(self): with self.assertRaisesRegex(RuntimeError, "Invalid move flags.*?"): dpdata.System().from_vasp_poscar(os.path.join("poscars", "POSCAR.oh.err1")) @@ -36,7 +42,7 @@ def test_move_flags_error3(self): with self.assertRaisesRegex( RuntimeError, "Invalid move flags:.*?should be a list of 3 bools" ): - system.to_vasp_poscar("POSCAR.tmp.1") + system.to_vasp_poscar(self.tmp_file) class TestPOSCARDirect(unittest.TestCase, TestPOSCARoh): From 461a4ed98a0d0c6136fc80551b0428704e9aa316 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:38:52 +0000 Subject: [PATCH 13/14] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_vasp_poscar_to_system.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_vasp_poscar_to_system.py b/tests/test_vasp_poscar_to_system.py index c34d85ee..9e5d7f37 100644 --- a/tests/test_vasp_poscar_to_system.py +++ b/tests/test_vasp_poscar_to_system.py @@ -26,6 +26,7 @@ def setUp(self): def tearDown(self): if os.path.exists(self.tmp_file): os.remove(self.tmp_file) + def test_move_flags_error1(self): with self.assertRaisesRegex(RuntimeError, "Invalid move flags.*?"): dpdata.System().from_vasp_poscar(os.path.join("poscars", "POSCAR.oh.err1")) From dec635092159c64323a1d265fa2559d92079180f Mon Sep 17 00:00:00 2001 From: yaosk Date: Tue, 29 Oct 2024 12:00:22 +0800 Subject: [PATCH 14/14] opt code --- dpdata/plugins/abacus.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dpdata/plugins/abacus.py b/dpdata/plugins/abacus.py index a57e2d00..b3e7c98a 100644 --- a/dpdata/plugins/abacus.py +++ b/dpdata/plugins/abacus.py @@ -65,6 +65,9 @@ def register_mag_data(data): deepmd_name="force_mag", ) dpdata.LabeledSystem.register_data_type(dt) + + +def register_move_data(data): if "move" in data: dt = DataType( "move", @@ -84,6 +87,7 @@ class AbacusSCFFormat(Format): def from_labeled_system(self, file_name, **kwargs): data = dpdata.abacus.scf.get_frame(file_name) register_mag_data(data) + register_move_data(data) return data @@ -95,6 +99,7 @@ class AbacusMDFormat(Format): def from_labeled_system(self, file_name, **kwargs): data = dpdata.abacus.md.get_frame(file_name) register_mag_data(data) + register_move_data(data) return data @@ -106,4 +111,5 @@ class AbacusRelaxFormat(Format): def from_labeled_system(self, file_name, **kwargs): data = dpdata.abacus.relax.get_frame(file_name) register_mag_data(data) + register_move_data(data) return data