Skip to content
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

Added ABACUS MD interface #208

Merged
merged 4 commits into from
Oct 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ The `System` or `LabeledSystem` can be constructed from the following file forma
| Amber | multi | True | True | LabeledSystem | 'amber/md' |
| Amber/sqm | sqm.out | False | False | System | 'sqm/out' |
| Gromacs | gro | True | False | System | 'gromacs/gro' |
| ABACUS | STRU | False | True | LabeledSystem | 'abacus/scf' |
| ABACUS | cif | True | True | LabeledSystem | 'abacus/md' |


The Class `dpdata.MultiSystems` can read data from a dir which may contains many files of different systems, or from single xyz file which contains different systems.
Expand Down
190 changes: 190 additions & 0 deletions dpdata/abacus/md.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import os,sys
import numpy as np
from .scf import ry2ev, kbar2evperang3, get_block, get_geometry_in, get_cell, get_coords

# Read in geometries from an ABACUS MD trajectory.
# The atomic coordinates are read in from generated files in OUT.XXXX.
# Energies, forces
# IMPORTANT: the program defaultly takes STRU input file as standard cell information,
# therefore the direct and cartesan coordinates read could be different from the ones in
# the output cif files!!!
# It is highly recommanded to use ORTHOGANAL coordinates in STRU file if you wish to get
# same coordinates in both dpdata and output cif files.

def get_path_out(fname, inlines):
# This function is different from the same-name function in scf.py.
# This function returns OUT.XXXX's base directory.
path_out = os.path.join(fname, "OUT.ABACUS/")
for line in inlines:
if len(line)>0 and "suffix" in line and "suffix"==line.split()[0]:
suffix = line.split()[1]
path_out = os.path.join(fname, "OUT.%s/" % suffix)
break
return path_out

def get_coord_dump_freq(inlines):
for line in inlines:
if len(line)>0 and "md_dumpmdfred" in line and "md_dumpmdfred" == line.split()[0]:
return int(line.split()[1])
return 1

# set up a cell according to cell info in cif file.
# maybe useful later
'''
def setup_cell(a, b, c, alpha, beta, gamma):
cell = np.zeros(3, 3)
cell[0, 0] = a
cell[1, 0] = b*np.cos(gamma/180*np.pi)
cell[1, 1] = b*np.sin(gamma/180*np.pi)
cell[2, 0] = c*np.cos(beta/180*np.pi)
cell[2, 1] = c*(b*np.cos(alpha/180*np.pi) - cell[1, 0]*np.cos(beta/180*np.pi))/cell[1, 1]
cell[2, 2] = np.sqrt(c**2 - cell[2, 0]**2 - cell[2, 1]**2)
return cell
'''

def get_single_coord_from_cif(pos_file, atom_names, natoms, cell):
assert(len(atom_names) == len(natoms))
nele = len(atom_names)
total_natoms = sum(natoms)
coord = np.zeros([total_natoms, 3])
a = 0
b = 0
c = 0
alpha = 0
beta = 0
gamma = 0
with open(pos_file, "r") as fp:
lines = fp.read().split("\n")
for line in lines:
if "_cell_length_a" in line:
a = float(line.split()[1])
if "_cell_length_b" in line:
b = float(line.split()[1])
if "_cell_length_c" in line:
c = float(line.split()[1])
if "_cell_angle_alpha" in line:
alpha = float(line.split()[1])
if "_cell_angle_beta" in line:
beta = float(line.split()[1])
if "_cell_angle_gamma" in line:
gamma = float(line.split()[1])
assert(a > 0 and b > 0 and c > 0 and alpha > 0 and beta > 0 and gamma > 0)
#cell = setup_cell(a, b, c, alpha, beta, gamma)
coord_lines = get_block(lines=lines, keyword="_atom_site_fract_z", skip=0, nlines = total_natoms)

ia_idx = 0
for it in range(nele):
for ia in range(natoms[it]):
coord_line = coord_lines[ia_idx].split()
assert(coord_line[0] == atom_names[it])
coord[ia_idx, 0] = float(coord_line[1])
coord[ia_idx, 1] = float(coord_line[2])
coord[ia_idx, 2] = float(coord_line[3])
ia_idx+=1
coord = np.matmul(coord, cell)
# important! Coordinates are converted to Cartesian coordinate.
return coord


def get_coords_from_cif(ndump, dump_freq, atom_names, natoms, types, path_out, cell):
total_natoms = sum(natoms)
#cell = np.zeros(ndump, 3, 3)
coords = np.zeros([ndump, total_natoms, 3])
pos_file = os.path.join(path_out, "STRU_READIN_ADJUST.cif")
# frame 0 file is different from any other frames
coords[0] = get_single_coord_from_cif(pos_file, atom_names, natoms, cell)
for dump_idx in range(1, ndump):
pos_file = os.path.join(path_out, "md_pos_%d.cif" %(dump_idx*dump_freq))
#print("dump_idx = %s" %dump_idx)
coords[dump_idx] = get_single_coord_from_cif(pos_file, atom_names, natoms, cell)
return coords

def get_energy_force_stress(outlines, inlines, dump_freq, ndump, natoms, atom_names):
stress = None
total_natoms = sum(natoms)
for line in inlines:
if len(line)>0 and "stress" in line and "stress" == line.split()[0] and "1" == line.split()[1]:
stress = np.zeros([ndump, 3, 3])
break
if type(stress) != np.ndarray:
print("The ABACUS program has no stress output. Stress will not be read.")
nenergy = 0
nforce = 0
nstress = 0
energy = np.zeros(ndump)
force = np.zeros([ndump, total_natoms, 3])

for line_idx, line in enumerate(outlines):
if "final etot is" in line:
if nenergy%dump_freq == 0:
energy[int(nenergy/dump_freq)] = float(line.split()[-2])
nenergy+=1
if "TOTAL-FORCE (eV/Angstrom)" in line:
for iatom in range(0, total_natoms):
force_line = outlines[line_idx+5+iatom]
atom_force = [float(i) for i in force_line.split()[1:]]
assert(len(atom_force) == 3)
atom_force = np.array(atom_force)
if nforce%dump_freq == 0:
force[int(nforce/dump_freq), iatom] = atom_force
nforce+=1
assert(nforce==nenergy)
if "TOTAL-STRESS (KBAR)" in line:
for idx in range(0, 3):
stress_line = outlines[line_idx+4+idx]
single_stress = [float(i) for i in stress_line.split()]
if len(single_stress) != 3:
print(single_stress)
assert(len(single_stress) == 3)
single_stress = np.array(single_stress)
if nstress%dump_freq == 0:
stress[int(nstress/dump_freq), idx] = single_stress
nstress+=1
assert(nstress==nforce)
if type(stress) == np.ndarray:
stress *= kbar2evperang3
return energy, force, stress


def get_frame (fname):
if type(fname) == str:
# if the input parameter is only one string, it is assumed that it is the
# base directory containing INPUT file;
path_in = os.path.join(fname, "INPUT")
else:
raise RuntimeError('invalid input')
with open(path_in, 'r') as fp:
inlines = fp.read().split('\n')
geometry_path_in = get_geometry_in(fname, inlines) # base dir of STRU
path_out = get_path_out(fname, inlines)

with open(geometry_path_in, 'r') as fp:
geometry_inlines = fp.read().split('\n')
celldm, cell = get_cell(geometry_inlines)
atom_names, natoms, types, coords = get_coords(celldm, cell, geometry_inlines, inlines)
# This coords is not to be used.
dump_freq = get_coord_dump_freq(inlines = inlines)
ndump = int(os.popen("ls --l %s | grep 'md_pos_' | wc -l" %path_out).readlines()[0])
# number of dumped geometry files
coords = get_coords_from_cif(ndump, dump_freq, atom_names, natoms, types, path_out, cell)

# TODO: Read in energies, forces and pressures.
with open(os.path.join(path_out, "running_md.log"), 'r') as fp:
outlines = fp.read().split('\n')
energy, force, stress = get_energy_force_stress(outlines, inlines, dump_freq, ndump, natoms, atom_names)
if type(stress) == np.ndarray:
stress *= np.linalg.det(cell)
data = {}
data['atom_names'] = atom_names
data['atom_numbs'] = natoms
data['atom_types'] = types
data['cells'] = np.zeros([ndump, 3, 3])
for idx in range(ndump):
data['cells'][:, :, :] = cell
data['coords'] = coords
data['energies'] = energy
data['forces'] = force
data['virials'] = stress
data['orig'] = np.zeros(3)

return data
6 changes: 3 additions & 3 deletions dpdata/abacus/scf.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,6 @@ def get_frame (fname):
# print("virial = ", data['virials'])
return data

if __name__ == "__main__":
path = "/home/lrx/work/12_ABACUS_dpgen_interface/dpdata/dpdata/tests/abacus.scf"
data = get_frame(path)
#if __name__ == "__main__":
# path = "/home/lrx/work/12_ABACUS_dpgen_interface/dpdata/dpdata/tests/abacus.scf"
# data = get_frame(path)
11 changes: 10 additions & 1 deletion dpdata/plugins/abacus.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import dpdata.abacus.scf
import dpdata.abacus.md
from dpdata.format import Format


@Format.register("abacus/scf")
@Format.register("abacus/pw/scf")
class AbacusSCFFormat(Format):
@Format.post("rot_lower_triangular")
#@Format.post("rot_lower_triangular")
def from_labeled_system(self, file_name, **kwargs):
return dpdata.abacus.scf.get_frame(file_name)

@Format.register("abacus/md")
@Format.register("abacus/pw/md")
@Format.register("abacus/lcao/md")
class AbacusMDFormat(Format):
#@Format.post("rot_lower_triangular")
def from_labeled_system(self, file_name, **kwargs):
return dpdata.abacus.md.get_frame(file_name)
3 changes: 2 additions & 1 deletion dpdata/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ def __init__ (self,
- ``vasp/poscar``: vasp POSCAR
- ``qe/cp/traj``: Quantum Espresso CP trajectory files. should have: file_name+'.in' and file_name+'.pos'
- ``qe/pw/scf``: Quantum Espresso PW single point calculations. Both input and output files are required. If file_name is a string, it denotes the output file name. Input file name is obtained by replacing 'out' by 'in' from file_name. Or file_name is a list, with the first element being the input file name and the second element being the output filename.
- ``abacus/scf``: ABACUS plane wave scf. The directory containing INPUT file is required.
- ``abacus/scf``: ABACUS pw/lcao scf. The directory containing INPUT file is required.
- ``abacus/md``: ABACUS pw/lcao MD. The directory containing INPUT file is required.
- ``siesta/output``: siesta SCF output file
- ``siesta/aimd_output``: siesta aimd output file
- ``pwmat/atom.config``: pwmat atom.config
Expand Down
30 changes: 30 additions & 0 deletions tests/abacus.md/INPUT
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
INPUT_PARAMETERS
#Parameters (General)
suffix autotest
pseudo_dir ./
ntype 1
nbands 8
calculation md
read_file_dir ./

#Parameters (Accuracy)
ecutwfc 20
niter 20

basis_type pw
nstep 21 # number of MD/relaxation steps

stress 1
stress_thr 1e-6
force 1
force_thr_ev 1.0e-3

ks_solver cg
mixing_type pulay
mixing_beta 0.7

md_mdtype 1 # 0 for NVE; 1 for NVT with Nose Hoover; 2 for NVT with velocity rescaling
md_tfirst 10 # temperature, unit: K
md_dt 1 # unit: fs
md_rstmd 0 # 1 for restart
md_dumpmdfred 5
21 changes: 21 additions & 0 deletions tests/abacus.md/OUT.autotest/STRU_READIN_ADJUST.cif
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
data_test

_audit_creation_method generated by ABACUS

_cell_length_a 3.81668
_cell_length_b 3.81668
_cell_length_c 3.81668
_cell_angle_alpha 60
_cell_angle_beta 60
_cell_angle_gamma 60

_symmetry_space_group_name_H-M
_symmetry_Int_Tables_number

loop_
_atom_site_label
_atom_site_fract_x
_atom_site_fract_y
_atom_site_fract_z
Si 0 0 0
Si 0.245 0.237 0.265
21 changes: 21 additions & 0 deletions tests/abacus.md/OUT.autotest/md_pos_1.cif
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
data_test

_audit_creation_method generated by ABACUS

_cell_length_a 3.81668
_cell_length_b 3.81668
_cell_length_c 3.81668
_cell_angle_alpha 60
_cell_angle_beta 60
_cell_angle_gamma 60

_symmetry_space_group_name_H-M
_symmetry_Int_Tables_number

loop_
_atom_site_label
_atom_site_fract_x
_atom_site_fract_y
_atom_site_fract_z
Si 0.00106888 0.99849 0.000247409
Si 0.245535 0.237394 0.262886
21 changes: 21 additions & 0 deletions tests/abacus.md/OUT.autotest/md_pos_10.cif
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
data_test

_audit_creation_method generated by ABACUS

_cell_length_a 3.81668
_cell_length_b 3.81668
_cell_length_c 3.81668
_cell_angle_alpha 60
_cell_angle_beta 60
_cell_angle_gamma 60

_symmetry_space_group_name_H-M
_symmetry_Int_Tables_number

loop_
_atom_site_label
_atom_site_fract_x
_atom_site_fract_y
_atom_site_fract_z
Si 0.00711478 0.993904 0.9916
Si 0.253316 0.23197 0.255312
21 changes: 21 additions & 0 deletions tests/abacus.md/OUT.autotest/md_pos_15.cif
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
data_test

_audit_creation_method generated by ABACUS

_cell_length_a 3.81668
_cell_length_b 3.81668
_cell_length_c 3.81668
_cell_angle_alpha 60
_cell_angle_beta 60
_cell_angle_gamma 60

_symmetry_space_group_name_H-M
_symmetry_Int_Tables_number

loop_
_atom_site_label
_atom_site_fract_x
_atom_site_fract_y
_atom_site_fract_z
Si 0.00722617 0.979891 0.000667301
Si 0.260608 0.240666 0.237553
21 changes: 21 additions & 0 deletions tests/abacus.md/OUT.autotest/md_pos_20.cif
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
data_test

_audit_creation_method generated by ABACUS

_cell_length_a 3.81668
_cell_length_b 3.81668
_cell_length_c 3.81668
_cell_angle_alpha 60
_cell_angle_beta 60
_cell_angle_gamma 60

_symmetry_space_group_name_H-M
_symmetry_Int_Tables_number

loop_
_atom_site_label
_atom_site_fract_x
_atom_site_fract_y
_atom_site_fract_z
Si 0.0136053 0.987589 0.984304
Si 0.261345 0.227707 0.245529
Loading