Skip to content

Commit

Permalink
Added ABACUS MD interface (#208)
Browse files Browse the repository at this point in the history
* add ABACUS MD interface.

* Update system.py

* Add files via upload
  • Loading branch information
Liu-RX authored Oct 18, 2021
1 parent 9b35c81 commit f864b36
Show file tree
Hide file tree
Showing 18 changed files with 3,113 additions and 5 deletions.
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 @@ -77,7 +77,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

0 comments on commit f864b36

Please sign in to comment.