-
Notifications
You must be signed in to change notification settings - Fork 135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for converting move flags between Abacus and VASP #744
Changes from all commits
f7d6662
fabba45
70ab083
97448dd
5cf49ea
badc09d
30c7c3f
af32fc2
3a53c4a
a59fb4a
55193a7
322c10c
461a4ed
dec6350
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,13 +7,26 @@ | |
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 | ||
|
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Move flags are not supported in XML format Based on the examination of the XML parsing implementation in
🔗 Analysis chainVerify move flag support in XML format. While the registration is placed consistently, we should verify that the XML format supports move flags and that they are being properly extracted in 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Check if XML parsing handles move flags
# Test: Search for move flag handling in xml.py
rg -A 5 "move|selective" "dpdata/vasp/xml.py"
Length of output: 244 Script: #!/bin/bash
# Let's check for move flag handling in both xml.py and the test files
rg -A 5 "move|selective" "dpdata/vasp/"
# Also check test files specifically for XML move flag tests
rg -A 5 "xml.*move|move.*xml" "tests/"
# Check the actual implementation of from_labeled_system in vasp.py
ast-grep --pattern 'def from_labeled_system'
Length of output: 4755 Script: #!/bin/bash
# Let's check the XML parsing implementation
ast-grep --pattern 'class VASPXMLFormat'
# Check if there are any XML test files that might contain move flags
fd -e xml . tests/
# Look for any move-related functionality in the XML format class
rg -A 5 "from_labeled_system|analyze_vasprun" "dpdata/vasp/xml.py"
Length of output: 403 Script: #!/bin/bash
# Let's examine one of the test XML files to see if it contains selective dynamics
head -n 50 "tests/poscars/vasprun.h2o.md.xml"
# Check if there are any test cases related to selective dynamics in XML
rg -A 5 "selective.*xml|move.*xml" "tests/"
# Look at the implementation of analyze_vasprun in xml.py
cat "dpdata/vasp/xml.py"
Length of output: 6104 |
||
return data |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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 move flag: {flag}") | ||||||||||||||||||||||||||||||
Comment on lines
+8
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make move flag parsing case-insensitive The function should handle both uppercase and lowercase flags as both are commonly used in VASP files. Apply this diff: def move_flag_mapper(flag):
- if flag == "T":
+ if flag.upper() == "T":
return True
- elif flag == "F":
+ elif flag.upper() == "F":
return False
else:
raise RuntimeError(f"Invalid move flag: {flag}") 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
"""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,21 @@ 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: | ||||||||||||||||||||||||||||||
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) | ||||||||||||||||||||||||||||||
atom_types = [] | ||||||||||||||||||||||||||||||
|
@@ -34,20 +52,26 @@ 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"]) | ||||||||||||||||||||||||||||||
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 | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
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: | ||||||||||||||||||||||||||||||
if lines[7][0] not in ["d", "D"]: | ||||||||||||||||||||||||||||||
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): | ||||||||||||||||||||||||||||||
|
@@ -72,6 +96,10 @@ def from_system_data(system, f_idx=0, skip_zeros=True): | |||||||||||||||||||||||||||||
continue | ||||||||||||||||||||||||||||||
ret += "%d " % ii | ||||||||||||||||||||||||||||||
ret += "\n" | ||||||||||||||||||||||||||||||
move = system.get("move", None) | ||||||||||||||||||||||||||||||
if move is not None and len(move) > 0: | ||||||||||||||||||||||||||||||
ret += "Selective Dynamics\n" | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
# should use Cartesian for VESTA software | ||||||||||||||||||||||||||||||
ret += "Cartesian\n" | ||||||||||||||||||||||||||||||
atype = system["atom_types"] | ||||||||||||||||||||||||||||||
|
@@ -81,9 +109,26 @@ 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 move is not None and len(move) > 0: | ||||||||||||||||||||||||||||||
move = move[f_idx][sort_idx] | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
if isinstance(move, np.ndarray): | ||||||||||||||||||||||||||||||
move = move.tolist() | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
Comment on lines
+115
to
+117
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Add test coverage for invalid move flags error handling The review comment is correct. Based on the test files inspection:
Add test cases to cover:
🔗 Analysis chainImprove test coverage and simplify move flags formatting
Let's verify the test coverage for invalid move flags: Consider simplifying the move flags formatting: - 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"
- )
+ flags = move_flags if isinstance(move_flags, list) else [bool(move_flags)] * 3
+ if not isinstance(flags, list) or len(flags) != 3:
+ raise RuntimeError(f"Invalid move flags: {move_flags}, should be a list or a bool")
+ line += " " + " ".join("T" if flag else "F" for flag in flags) Also applies to: 117-126 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Check test coverage for invalid move flags error handling
rg -l "Invalid move flags.*should be a list or a bool" tests/
Length of output: 63 Script: #!/bin/bash
# Let's check for test files related to POSCAR and move flags
fd "test.*poscar.*\.py" tests/
# Then look for any test cases involving move flags
rg -l "move.*flags?" tests/
Length of output: 196 Script: #!/bin/bash
# Let's also check the actual implementation and error handling
rg -A 3 "Invalid move flags" dpdata/
Length of output: 260 Script: #!/bin/bash
# Check test files content for move flags testing
ast-grep --pattern 'def test_$_($$$) {
$$$
move_$$$
$$$
}'
Length of output: 66 Script: #!/bin/bash
# Let's check the content of the test files for move flags testing
rg -B2 -A5 "move.*flags?" tests/test_vasp_poscar_dump.py tests/test_vasp_poscar_to_system.py
Length of output: 1286 Script: #!/bin/bash
# Let's also check for any test cases that might be testing error conditions
rg "def test.*" tests/test_vasp_poscar_dump.py tests/test_vasp_poscar_to_system.py
Length of output: 409 |
||||||||||||||||||||||||||||||
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 move is not None and len(move) > 0: | ||||||||||||||||||||||||||||||
move_flags = move[idx] | ||||||||||||||||||||||||||||||
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) | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
posi_list.append("") | ||||||||||||||||||||||||||||||
ret += "\n".join(posi_list) | ||||||||||||||||||||||||||||||
return ret |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider optimizing the move flag check.
The current implementation checks both the existence and length of move data. This could be simplified.
Consider this more concise implementation:
This achieves the same result but is more readable and handles empty lists naturally.
📝 Committable suggestion