Skip to content

Commit d0a7868

Browse files
Add support for converting move flags between Abacus and VASP (#744)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes - **New Features** - Enhanced atomic movement handling with the addition of a `move` parameter across multiple functions, enabling more detailed tracking of atom movements. - Introduced selective dynamics support in VASP POSCAR data handling, allowing for better control over atomic movement flags. - Added structured representation for cubic boron nitride crystal in new test files. - **Bug Fixes** - Updated atomic positions and magnetic properties in test structures to reflect accurate simulation data. - **Tests** - Added new test methods to validate the handling of movement flags and ensure the integrity of output structures across various scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent fba50f1 commit d0a7868

File tree

13 files changed

+224
-23
lines changed

13 files changed

+224
-23
lines changed

dpdata/abacus/md.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def get_frame(fname):
167167
with open_file(geometry_path_in) as fp:
168168
geometry_inlines = fp.read().split("\n")
169169
celldm, cell = get_cell(geometry_inlines)
170-
atom_names, natoms, types, coords = get_coords(
170+
atom_names, natoms, types, coords, move = get_coords(
171171
celldm, cell, geometry_inlines, inlines
172172
)
173173
# This coords is not to be used.
@@ -221,5 +221,7 @@ def get_frame(fname):
221221
data["spins"] = magmom
222222
if len(magforce) > 0:
223223
data["mag_forces"] = magforce
224+
if len(move) > 0:
225+
data["move"] = move
224226

225227
return data

dpdata/abacus/relax.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def get_frame(fname):
183183
with open_file(geometry_path_in) as fp:
184184
geometry_inlines = fp.read().split("\n")
185185
celldm, cell = get_cell(geometry_inlines)
186-
atom_names, natoms, types, coord_tmp = get_coords(
186+
atom_names, natoms, types, coord_tmp, move = get_coords(
187187
celldm, cell, geometry_inlines, inlines
188188
)
189189

@@ -218,5 +218,7 @@ def get_frame(fname):
218218
data["spins"] = magmom
219219
if len(magforce) > 0:
220220
data["mag_forces"] = magforce
221+
if len(move) > 0:
222+
data["move"] = move
221223

222224
return data

dpdata/abacus/scf.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,8 @@ def get_coords(celldm, cell, geometry_inlines, inlines=None):
299299
coords.append(xyz)
300300
atom_types.append(it)
301301

302-
move.append(imove)
302+
if imove is not None:
303+
move.append(imove)
303304
velocity.append(ivelocity)
304305
mag.append(imagmom)
305306
angle1.append(iangle1)
@@ -310,7 +311,8 @@ def get_coords(celldm, cell, geometry_inlines, inlines=None):
310311
line_idx += 1
311312
coords = np.array(coords) # need transformation!!!
312313
atom_types = np.array(atom_types)
313-
return atom_names, atom_numbs, atom_types, coords
314+
move = np.array(move, dtype=bool)
315+
return atom_names, atom_numbs, atom_types, coords, move
314316

315317

316318
def get_energy(outlines):
@@ -477,7 +479,7 @@ def get_frame(fname):
477479
outlines = fp.read().split("\n")
478480

479481
celldm, cell = get_cell(geometry_inlines)
480-
atom_names, natoms, types, coords = get_coords(
482+
atom_names, natoms, types, coords, move = get_coords(
481483
celldm, cell, geometry_inlines, inlines
482484
)
483485
magmom, magforce = get_mag_force(outlines)
@@ -510,6 +512,8 @@ def get_frame(fname):
510512
data["spins"] = magmom
511513
if len(magforce) > 0:
512514
data["mag_forces"] = magforce
515+
if len(move) > 0:
516+
data["move"] = move[np.newaxis, :, :]
513517
# print("atom_names = ", data['atom_names'])
514518
# print("natoms = ", data['atom_numbs'])
515519
# print("types = ", data['atom_types'])
@@ -561,7 +565,7 @@ def get_frame_from_stru(fname):
561565
nele = get_nele_from_stru(geometry_inlines)
562566
inlines = [f"ntype {nele}"]
563567
celldm, cell = get_cell(geometry_inlines)
564-
atom_names, natoms, types, coords = get_coords(
568+
atom_names, natoms, types, coords, move = get_coords(
565569
celldm, cell, geometry_inlines, inlines
566570
)
567571
data = {}
@@ -571,6 +575,8 @@ def get_frame_from_stru(fname):
571575
data["cells"] = cell[np.newaxis, :, :]
572576
data["coords"] = coords[np.newaxis, :, :]
573577
data["orig"] = np.zeros(3)
578+
if len(move) > 0:
579+
data["move"] = move[np.newaxis, :, :]
574580

575581
return data
576582

@@ -609,8 +615,8 @@ def make_unlabeled_stru(
609615
numerical descriptor file
610616
mass : list of float, optional
611617
List of atomic masses
612-
move : list of list of bool, optional
613-
List of the move flag of each xyz direction of each atom
618+
move : list of (list of list of bool), optional
619+
List of the move flag of each xyz direction of each atom for each frame
614620
velocity : list of list of float, optional
615621
List of the velocity of each xyz direction of each atom
616622
mag : list of (list of float or float), optional
@@ -684,6 +690,9 @@ def process_file_input(file_input, atom_names, input_name):
684690
if mag is None and data.get("spins") is not None and len(data["spins"]) > 0:
685691
mag = data["spins"][frame_idx]
686692

693+
if move is None and data.get("move", None) is not None and len(data["move"]) > 0:
694+
move = data["move"][frame_idx]
695+
687696
atom_numbs = sum(data["atom_numbs"])
688697
for key in [move, velocity, mag, angle1, angle2, sc, lambda_]:
689698
if key is not None:

dpdata/plugins/abacus.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,18 @@ def register_mag_data(data):
6767
dpdata.LabeledSystem.register_data_type(dt)
6868

6969

70+
def register_move_data(data):
71+
if "move" in data:
72+
dt = DataType(
73+
"move",
74+
np.ndarray,
75+
(Axis.NFRAMES, Axis.NATOMS, 3),
76+
required=False,
77+
deepmd_name="move",
78+
)
79+
dpdata.System.register_data_type(dt)
80+
81+
7082
@Format.register("abacus/scf")
7183
@Format.register("abacus/pw/scf")
7284
@Format.register("abacus/lcao/scf")
@@ -75,6 +87,7 @@ class AbacusSCFFormat(Format):
7587
def from_labeled_system(self, file_name, **kwargs):
7688
data = dpdata.abacus.scf.get_frame(file_name)
7789
register_mag_data(data)
90+
register_move_data(data)
7891
return data
7992

8093

@@ -86,6 +99,7 @@ class AbacusMDFormat(Format):
8699
def from_labeled_system(self, file_name, **kwargs):
87100
data = dpdata.abacus.md.get_frame(file_name)
88101
register_mag_data(data)
102+
register_move_data(data)
89103
return data
90104

91105

@@ -97,4 +111,5 @@ class AbacusRelaxFormat(Format):
97111
def from_labeled_system(self, file_name, **kwargs):
98112
data = dpdata.abacus.relax.get_frame(file_name)
99113
register_mag_data(data)
114+
register_move_data(data)
100115
return data

dpdata/plugins/vasp.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,26 @@
77
import dpdata.vasp.outcar
88
import dpdata.vasp.poscar
99
import dpdata.vasp.xml
10+
from dpdata.data_type import Axis, DataType
1011
from dpdata.format import Format
1112
from dpdata.utils import open_file, uniq_atom_names
1213

1314
if TYPE_CHECKING:
1415
from dpdata.utils import FileType
1516

1617

18+
def register_move_data(data):
19+
if "move" in data:
20+
dt = DataType(
21+
"move",
22+
np.ndarray,
23+
(Axis.NFRAMES, Axis.NATOMS, 3),
24+
required=False,
25+
deepmd_name="move",
26+
)
27+
dpdata.System.register_data_type(dt)
28+
29+
1730
@Format.register("poscar")
1831
@Format.register("contcar")
1932
@Format.register("vasp/poscar")
@@ -25,6 +38,7 @@ def from_system(self, file_name: FileType, **kwargs):
2538
lines = [line.rstrip("\n") for line in fp]
2639
data = dpdata.vasp.poscar.to_system_data(lines)
2740
data = uniq_atom_names(data)
41+
register_move_data(data)
2842
return data
2943

3044
def to_system(self, data, file_name: FileType, frame_idx=0, **kwargs):
@@ -99,6 +113,7 @@ def from_labeled_system(
99113
vol = np.linalg.det(np.reshape(data["cells"][ii], [3, 3]))
100114
data["virials"][ii] *= v_pref * vol
101115
data = uniq_atom_names(data)
116+
register_move_data(data)
102117
return data
103118

104119

@@ -135,4 +150,5 @@ def from_labeled_system(self, file_name, begin=0, step=1, **kwargs):
135150
vol = np.linalg.det(np.reshape(data["cells"][ii], [3, 3]))
136151
data["virials"][ii] *= v_pref * vol
137152
data = uniq_atom_names(data)
153+
register_move_data(data)
138154
return data

dpdata/vasp/poscar.py

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,22 @@
44
import numpy as np
55

66

7-
def _to_system_data_lower(lines, cartesian=True):
7+
def _to_system_data_lower(lines, cartesian=True, selective_dynamics=False):
8+
def move_flag_mapper(flag):
9+
if flag == "T":
10+
return True
11+
elif flag == "F":
12+
return False
13+
else:
14+
raise RuntimeError(f"Invalid move flag: {flag}")
15+
816
"""Treat as cartesian poscar."""
917
system = {}
1018
system["atom_names"] = [str(ii) for ii in lines[5].split()]
1119
system["atom_numbs"] = [int(ii) for ii in lines[6].split()]
1220
scale = float(lines[1])
1321
cell = []
22+
move_flags = []
1423
for ii in range(2, 5):
1524
boxv = [float(jj) for jj in lines[ii].split()]
1625
boxv = np.array(boxv) * scale
@@ -19,12 +28,21 @@ def _to_system_data_lower(lines, cartesian=True):
1928
natoms = sum(system["atom_numbs"])
2029
coord = []
2130
for ii in range(8, 8 + natoms):
22-
tmpv = [float(jj) for jj in lines[ii].split()[:3]]
31+
tmp = lines[ii].split()
32+
tmpv = [float(jj) for jj in tmp[:3]]
2333
if cartesian:
2434
tmpv = np.array(tmpv) * scale
2535
else:
2636
tmpv = np.matmul(np.array(tmpv), system["cells"][0])
2737
coord.append(tmpv)
38+
if selective_dynamics:
39+
if len(tmp) == 6:
40+
move_flags.append(list(map(move_flag_mapper, tmp[3:])))
41+
else:
42+
raise RuntimeError(
43+
f"Invalid move flags, should be 6 columns, got {tmp}"
44+
)
45+
2846
system["coords"] = [np.array(coord)]
2947
system["orig"] = np.zeros(3)
3048
atom_types = []
@@ -34,20 +52,26 @@ def _to_system_data_lower(lines, cartesian=True):
3452
system["atom_types"] = np.array(atom_types, dtype=int)
3553
system["cells"] = np.array(system["cells"])
3654
system["coords"] = np.array(system["coords"])
55+
if move_flags:
56+
move_flags = np.array(move_flags, dtype=bool)
57+
move_flags = move_flags.reshape((1, natoms, 3))
58+
system["move"] = np.array(move_flags, dtype=bool)
3759
return system
3860

3961

4062
def to_system_data(lines):
4163
# remove the line that has 'selective dynamics'
64+
selective_dynamics = False
4265
if lines[7][0] == "S" or lines[7][0] == "s":
66+
selective_dynamics = True
4367
lines.pop(7)
4468
is_cartesian = lines[7][0] in ["C", "c", "K", "k"]
4569
if not is_cartesian:
4670
if lines[7][0] not in ["d", "D"]:
4771
raise RuntimeError(
4872
"seem not to be a valid POSCAR of vasp 5.x, may be a POSCAR of vasp 4.x?"
4973
)
50-
return _to_system_data_lower(lines, is_cartesian)
74+
return _to_system_data_lower(lines, is_cartesian, selective_dynamics)
5175

5276

5377
def from_system_data(system, f_idx=0, skip_zeros=True):
@@ -72,6 +96,10 @@ def from_system_data(system, f_idx=0, skip_zeros=True):
7296
continue
7397
ret += "%d " % ii
7498
ret += "\n"
99+
move = system.get("move", None)
100+
if move is not None and len(move) > 0:
101+
ret += "Selective Dynamics\n"
102+
75103
# should use Cartesian for VESTA software
76104
ret += "Cartesian\n"
77105
atype = system["atom_types"]
@@ -81,9 +109,26 @@ def from_system_data(system, f_idx=0, skip_zeros=True):
81109
sort_idx = np.lexsort((np.arange(len(atype)), atype))
82110
atype = atype[sort_idx]
83111
posis = posis[sort_idx]
112+
if move is not None and len(move) > 0:
113+
move = move[f_idx][sort_idx]
114+
115+
if isinstance(move, np.ndarray):
116+
move = move.tolist()
117+
84118
posi_list = []
85-
for ii in posis:
86-
posi_list.append(f"{ii[0]:15.10f} {ii[1]:15.10f} {ii[2]:15.10f}")
119+
for idx in range(len(posis)):
120+
ii_posi = posis[idx]
121+
line = f"{ii_posi[0]:15.10f} {ii_posi[1]:15.10f} {ii_posi[2]:15.10f}"
122+
if move is not None and len(move) > 0:
123+
move_flags = move[idx]
124+
if not isinstance(move_flags, list) or len(move_flags) != 3:
125+
raise RuntimeError(
126+
f"Invalid move flags: {move_flags}, should be a list of 3 bools"
127+
)
128+
line += " " + " ".join("T" if flag else "F" for flag in move_flags)
129+
130+
posi_list.append(line)
131+
87132
posi_list.append("")
88133
ret += "\n".join(posi_list)
89134
return ret

tests/abacus.scf/STRU.ch4

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ Cartesian #Cartesian(Unit is LATTICE_CONSTANT)
1818
C #Name of element
1919
0.0 #Magnetic for this element.
2020
1 #Number of atoms
21-
0.981274803 0.861285385 0.838442496 0 0 0
21+
0.981274803 0.861285385 0.838442496 1 1 1
2222
H
2323
0.0
2424
4
2525
1.023557202 0.758025625 0.66351336 0 0 0
26-
0.78075702 0.889445935 0.837363468 0 0 0
27-
1.064091613 1.043438905 0.840995502 0 0 0
28-
1.039321214 0.756530859 1.009609207 0 0 0
26+
0.78075702 0.889445935 0.837363468 1 0 1
27+
1.064091613 1.043438905 0.840995502 1 0 1
28+
1.039321214 0.756530859 1.009609207 0 1 1

tests/abacus.scf/stru_test

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ C
2626
H
2727
0.0
2828
4
29-
5.416431453540 4.011298860305 3.511161492417 1 1 1
30-
4.131588222365 4.706745191323 4.431136645083 1 1 1
31-
5.630930319126 5.521640894956 4.450356541303 1 1 1
32-
5.499851012568 4.003388899277 5.342621842622 1 1 1
29+
5.416431453540 4.011298860305 3.511161492417 0 0 0
30+
4.131588222365 4.706745191323 4.431136645083 1 0 1
31+
5.630930319126 5.521640894956 4.450356541303 1 0 1
32+
5.499851012568 4.003388899277 5.342621842622 0 1 1

tests/poscars/POSCAR.oh.err1

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Cubic BN
2+
3.57
3+
0.00 0.50 0.50
4+
0.45 0.00 0.50
5+
0.55 0.51 0.00
6+
O H
7+
1 1
8+
Selective dynamics
9+
Cartesian
10+
0.00 0.00 0.00 T T F
11+
0.25 0.25 0.25 F F

tests/poscars/POSCAR.oh.err2

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Cubic BN
2+
3.57
3+
0.00 0.50 0.50
4+
0.45 0.00 0.50
5+
0.55 0.51 0.00
6+
O H
7+
1 1
8+
Selective dynamics
9+
Cartesian
10+
0.00 0.00 0.00 T T F
11+
0.25 0.25 0.25 a T F

0 commit comments

Comments
 (0)