Skip to content

Commit bcfb4f9

Browse files
Merge pull request #79 from XFShii/main
DecohesionEnergy + Lat_param_T (property+report + unittest) update 27 Aug
2 parents 1d55634 + f516896 commit bcfb4f9

File tree

12 files changed

+1083
-0
lines changed

12 files changed

+1083
-0
lines changed

apex/core/calculator/Lammps.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,13 +531,17 @@ def _prepare_result_dict(self, atom_numbs, type_map_list, type_list, box, coord,
531531
def forward_files(self, property_type="relaxation"):
532532
if self.inter_type in MULTI_MODELS_INTER_TYPE:
533533
return ["conf.lmp", "in.lammps"] + list(map(os.path.basename, self.model))
534+
elif property_type == "Lat_param_T":
535+
return ["in.lammps", "variable_Lat_param_T.in", os.path.basename(self.model)]
534536
else:
535537
return ["conf.lmp", "in.lammps", os.path.basename(self.model)]
536538

537539
def forward_common_files(self, property_type="relaxation"):
538540
if property_type not in ["eos"]:
539541
if self.inter_type in MULTI_MODELS_INTER_TYPE:
540542
return ["in.lammps"] + list(map(os.path.basename, self.model))
543+
elif property_type == "Lat_param_T":
544+
return ["in.lammps", "variable_Lat_param_T.in", os.path.basename(self.model)]
541545
else:
542546
return ["in.lammps", os.path.basename(self.model)]
543547
else:
@@ -549,6 +553,8 @@ def forward_common_files(self, property_type="relaxation"):
549553
def backward_files(self, property_type="relaxation"):
550554
if property_type == "phonon":
551555
return ["outlog", "FORCE_CONSTANTS"]
556+
elif property_type == "Lat_param_T":
557+
return ["log.lammps", "outlog", "dump.relax", "average_box.txt"]
552558
else:
553559
return ["log.lammps", "outlog", "dump.relax"]
554560

apex/core/calculator/lib/lammps_utils.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,63 @@ def make_lammps_elastic(
436436
ret += 'print "Final Stress (xx yy zz xy xz yz) = ${Pxx} ${Pyy} ${Pzz} ${Pxy} ${Pxz} ${Pyz}"\n'
437437
return ret
438438

439+
def make_lammps_Lat_param_T(conf, type_map, interaction, param):
440+
type_map_list = element_list(type_map)
441+
deepmd_version = param.get("deepmd_version", None)
442+
dump_step = 100
443+
# detour sychronizing problem of dumping in new version of deepmd-kit >=2.1.5
444+
tdamp = 100
445+
ret = ""
446+
ret += "include variable_Lat_param_T.in\n"
447+
ret += "clear\n"
448+
ret += "units metal\n"
449+
ret += "dimension 3\n"
450+
ret += "boundary p p p\n"
451+
ret += "atom_style atomic\n"
452+
ret += "box tilt large\n"
453+
ret += "read_data %s\n" % conf
454+
ret += "replicate ${nx} ${ny} ${nz}\n"
455+
for ii in range(len(type_map)):
456+
ret += "mass %d %.3f\n" % (ii + 1, Element(type_map_list[ii]).mass)
457+
ret += "neigh_modify every 1 delay 0 check no\n"
458+
ret += interaction(param)
459+
ret += "compute mype all pe\n"
460+
ret += "thermo 100\n"
461+
ret += (
462+
"thermo_style custom step pe pxx pyy pzz pxy pxz pyz lx ly lz vol c_mype\n"
463+
)
464+
ret += "velocity all create ${temperature} 12345 mom yes rot yes dist gaussian\n"
465+
ret += f"fix 1 all npt temp ${{temperature}} ${{temperature}} {tdamp} x 1.0 1.0 1000 y 1.0 1.0 1000 xy 1.0 1.0 1000\n"
466+
ret += "run ${equi_step}\n"
467+
ret += "reset_timestep 0 \n"
468+
ret += f"dump 1 all custom {dump_step} dump.relax id type xs ys zs fx fy fz\n"
469+
ret += "variable lx equal lx \n"
470+
ret += "variable ly equal ly \n"
471+
ret += "variable lz equal lz \n"
472+
ret += "fix 2 all ave/time ${N_every} ${N_repeat} ${N_freq} v_lx v_ly v_lz ave running file average_box.txt\n"
473+
ret += "run ${ave_step} \n"
474+
ret += "variable N equal count(all)\n"
475+
ret += "variable V equal vol\n"
476+
ret += "variable E equal \"c_mype\"\n"
477+
ret += "variable tmplx equal lx\n"
478+
ret += "variable tmply equal ly\n"
479+
ret += "variable Pxx equal pxx\n"
480+
ret += "variable Pyy equal pyy\n"
481+
ret += "variable Pzz equal pzz\n"
482+
ret += "variable Pxy equal pxy\n"
483+
ret += "variable Pxz equal pxz\n"
484+
ret += "variable Pyz equal pyz\n"
485+
ret += "variable Epa equal ${E}/${N}\n"
486+
ret += "variable Vpa equal ${V}/${N}\n"
487+
ret += "variable AA equal (${tmplx}*${tmply})\n"
488+
ret += "print \"All done\"\n"
489+
ret += "print \"Total number of atoms = ${N}\"\n"
490+
ret += "print \"Final energy per atoms = ${Epa}\"\n"
491+
ret += "print \"Final volume per atoms = ${Vpa}\"\n"
492+
ret += "print \"Final Base area = ${AA}\"\n"
493+
ret += "print \"Final Stress (xx yy zz xy xz yz) = ${Pxx} ${Pyy} ${Pzz} ${Pxy} ${Pxz} ${Pyz}\"\n"
494+
ret += "print \"Final Length (box_x box_y box_z) = ${lx} ${ly} ${lz}\"\n"
495+
return ret
439496

440497
def make_lammps_press_relax(
441498
conf,

apex/core/common_prop.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from apex.core.property.Surface import Surface
1212
from apex.core.property.Vacancy import Vacancy
1313
from apex.core.property.Phonon import Phonon
14+
from apex.core.property.DecohesionEnergy import DecohesionEnergy
15+
from apex.core.property.Lat_param_T import Lat_param_T
1416
from apex.core.lib.utils import create_path
1517
from apex.core.lib.util import collect_task
1618
from apex.core.lib.dispatcher import make_submission
@@ -39,6 +41,10 @@ def make_property_instance(parameters, inter_param):
3941
return Gamma(parameters, inter_param)
4042
elif prop_type == "phonon":
4143
return Phonon(parameters, inter_param)
44+
elif prop_type == "DecohesionEnergy":
45+
return DecohesionEnergy(parameters, inter_param)
46+
elif prop_type == "Lat_param_T":
47+
return Lat_param_T(parameters, inter_param)
4248
else:
4349
raise RuntimeError(f"unknown APEX type {prop_type}")
4450

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
import glob
2+
import json
3+
import logging
4+
import os
5+
import re
6+
import dpdata
7+
import numpy as np
8+
from monty.serialization import dumpfn, loadfn
9+
from pymatgen.core.structure import Structure
10+
from pymatgen.core.surface import SlabGenerator
11+
12+
from apex.core.calculator.lib import abacus_utils
13+
from apex.core.calculator.lib import vasp_utils
14+
from apex.core.property.Property import Property
15+
from apex.core.refine import make_refine
16+
from apex.core.reproduce import make_repro, post_repro
17+
from dflow.python import upload_packages
18+
upload_packages.append(__file__)
19+
20+
class DecohesionEnergy(Property):
21+
def __init__(self, parameter, inter_param=None):
22+
'''
23+
core parameter for make_confs[POSCAR]:
24+
min_slab_size max_vacuum_size vacuum_size_step
25+
miller_index
26+
'''
27+
parameter["reproduce"] = parameter.get("reproduce", False)
28+
self.reprod = parameter["reproduce"]
29+
if not self.reprod:
30+
if not ("init_from_suffix" in parameter and "output_suffix" in parameter):
31+
self.min_slab_size = parameter["min_slab_size"]
32+
parameter["pert_xz"] = parameter.get("pert_xz", 0.01)
33+
self.pert_xz = parameter["pert_xz"]
34+
parameter["max_vacuum_size"] = parameter.get("max_vacuum_size", 15)
35+
self.max_vacuum_size = parameter["max_vacuum_size"]
36+
parameter["vacuum_size_step"]=parameter.get("vacuum_size_step", 1)
37+
self.vacuum_size_step = parameter["vacuum_size_step"]
38+
self.miller_index = tuple(parameter["miller_index"])
39+
parameter["cal_type"] = parameter.get("cal_type", "relaxation")
40+
default_cal_setting = {
41+
"relax_pos": False,
42+
"relax_shape": False,
43+
"relax_vol": False,
44+
}
45+
else:
46+
parameter["cal_type"] = "static"
47+
self.cal_type = parameter["cal_type"]
48+
default_cal_setting = {
49+
"relax_pos": False,
50+
"relax_shape": False,
51+
"relax_vol": False,
52+
}
53+
parameter["init_from_suffix"] = parameter.get("init_from_suffix", "00")
54+
self.init_from_suffix = parameter["init_from_suffix"]
55+
self.cal_type = parameter["cal_type"]
56+
parameter["cal_setting"] = parameter.get("cal_setting", default_cal_setting)
57+
for key in default_cal_setting:
58+
parameter["cal_setting"].setdefault(key, default_cal_setting[key])
59+
self.cal_setting = parameter["cal_setting"]
60+
self.parameter = parameter
61+
self.inter_param = inter_param if inter_param != None else {"type": "vasp"}
62+
63+
def make_confs(self, path_to_work, path_to_equi, refine=False):
64+
path_to_work = os.path.abspath(path_to_work)
65+
if os.path.exists(path_to_work):
66+
logging.warning("%s already exists" % path_to_work)
67+
else:
68+
os.makedirs(path_to_work)
69+
path_to_equi = os.path.abspath(path_to_equi)
70+
71+
task_list = []
72+
cwd = os.getcwd()
73+
74+
if self.reprod:
75+
print("surface reproduce starts")
76+
if "init_data_path" not in self.parameter:
77+
raise RuntimeError("please provide the initial data path to reproduce")
78+
init_data_path = os.path.abspath(self.parameter["init_data_path"])
79+
task_list = make_repro(
80+
self.inter_param,
81+
init_data_path,
82+
self.init_from_suffix,
83+
path_to_work,
84+
self.parameter.get("reprod_last_frame", True),
85+
)
86+
87+
else:
88+
if refine:
89+
logging.info("surface refine starts")
90+
task_list = make_refine(
91+
self.parameter["init_from_suffix"],
92+
self.parameter["output_suffix"],
93+
path_to_work,
94+
)
95+
# record miller
96+
init_from_path = re.sub(
97+
self.parameter["output_suffix"][::-1],
98+
self.parameter["init_from_suffix"][::-1],
99+
path_to_work[::-1],
100+
count=1,
101+
)[::-1]
102+
task_list_basename = list(map(os.path.basename, task_list))
103+
104+
for ii in task_list_basename:
105+
init_from_task = os.path.join(init_from_path, ii)
106+
output_task = os.path.join(path_to_work, ii)
107+
os.chdir(output_task)
108+
if os.path.isfile("decohesion_energy.json"):
109+
os.remove("decohesion_energy.json")
110+
if os.path.islink("decohesion_energy.json"):
111+
os.remove("decohesion_energy.json")
112+
os.symlink(
113+
os.path.relpath(os.path.join(init_from_task, "decohesion_energy.json")),
114+
"decohesion_energy.json",)
115+
else:
116+
if self.inter_param["type"] == "abacus":
117+
CONTCAR = abacus_utils.final_stru(path_to_equi)
118+
POSCAR = "STRU"
119+
else:
120+
# refine = false && reproduce = false && self.inter_param["type"]== "vasp"
121+
CONTCAR = "CONTCAR"
122+
POSCAR = "POSCAR"
123+
equi_contcar = os.path.join(path_to_equi, CONTCAR)
124+
if not os.path.exists(equi_contcar):
125+
raise RuntimeError("please do relaxation first")
126+
127+
if self.inter_param["type"] == "abacus":
128+
stru = dpdata.System(equi_contcar, fmt="stru")
129+
stru.to("contcar", "CONTCAR.tmp")
130+
ptypes = vasp_utils.get_poscar_types("CONTCAR.tmp")
131+
ss = Structure.from_file("CONTCAR.tmp")
132+
os.remove("CONTCAR.tmp")
133+
else:
134+
ptypes = vasp_utils.get_poscar_types(equi_contcar)
135+
# element type read vasp fifth line
136+
ss = Structure.from_file(equi_contcar)
137+
138+
# gen POSCAR of Slab
139+
all_slabs = []
140+
vacuum = []
141+
num = 0
142+
while self.vacuum_size_step * num <= self.max_vacuum_size:
143+
vacuum_size = self.vacuum_size_step * num
144+
slabs = self.__gen_slab_pmg(ss, self.miller_index, self.min_slab_size, vacuum_size)
145+
num = num + 1
146+
all_slabs.append(slabs)
147+
vacuum.append(vacuum_size)
148+
149+
os.chdir(path_to_work)
150+
if os.path.exists(POSCAR):
151+
os.remove(
152+
POSCAR)
153+
os.symlink(os.path.relpath(equi_contcar), POSCAR)
154+
for ii in range(len(all_slabs)):
155+
output_task = os.path.join(path_to_work, "task.%06d" % ii)
156+
os.makedirs(output_task, exist_ok=True)
157+
os.chdir(output_task)
158+
for jj in [
159+
"INCAR",
160+
"POTCAR",
161+
"POSCAR",
162+
"conf.lmp",
163+
"in.lammps",
164+
"STRU",
165+
]:
166+
if os.path.exists(jj):
167+
os.remove(jj)
168+
task_list.append(output_task)
169+
170+
logging.info(
171+
"# %03d generate " % ii,
172+
output_task,
173+
" \t %d atoms" % len(all_slabs[ii].sites),
174+
)
175+
176+
all_slabs[ii].to("POSCAR.tmp", "POSCAR")
177+
vasp_utils.regulate_poscar("POSCAR.tmp", "POSCAR")
178+
vasp_utils.sort_poscar("POSCAR", "POSCAR", ptypes)
179+
vasp_utils.perturb_xz("POSCAR", "POSCAR", self.pert_xz)
180+
if self.inter_param["type"] == "abacus":
181+
abacus_utils.poscar2stru("POSCAR", self.inter_param, "STRU")
182+
os.remove("POSCAR")
183+
decohesion_energy = {"miller_index": self.miller_index, "vacuum_size":vacuum[ii]}
184+
dumpfn(decohesion_energy, "decohesion_energy.json", indent=4)
185+
os.chdir(cwd)
186+
return task_list
187+
188+
def post_process(self, task_list):
189+
pass
190+
191+
def task_type(self):
192+
return self.parameter["type"]
193+
194+
def task_param(self):
195+
return self.parameter
196+
197+
def _compute_lower(self, output_file, all_tasks, all_res) -> [dict, str]:
198+
output_file = os.path.abspath(output_file)
199+
res_data = {}
200+
ptr_data = os.path.dirname(output_file) + "\n"
201+
if not self.reprod:
202+
vacuum_size_step = loadfn(os.path.join(os.path.dirname(output_file), "param.json"))["vacuum_size_step"]
203+
ptr_data += ("Miller Index: " + str(loadfn(os.path.join(os.path.dirname(output_file), "param.json"))["miller_index"]) + "\n")
204+
ptr_data += "Vacuum_size(e-10 m):\tDecohesion_E(J/m^2) Decohesion_S(Pa)\n"
205+
pre_task_result = loadfn(os.path.join(all_tasks[0],"result_task.json"))
206+
pre_evac = 0
207+
equi_evac = pre_task_result["energies"][-1]
208+
for ii in all_tasks:
209+
task_result = loadfn(os.path.join(ii, "result_task.json"))
210+
AA = np.linalg.norm(
211+
np.cross(task_result["cells"][0][0], task_result["cells"][0][1])
212+
)
213+
structure_dir = os.path.basename(ii)
214+
Cf = 1.60217657e-16 / 1e-20 * 0.001
215+
evac = (task_result["energies"][-1] - equi_evac) / AA * Cf
216+
vacuum_size = loadfn(os.path.join(ii, "decohesion_energy.json"))["vacuum_size"]
217+
stress = (evac - pre_evac) / vacuum_size_step * 1e10
218+
219+
ptr_data += "%-30s % 7.3f %10.3e \n" % (
220+
str(vacuum_size) + "-" + structure_dir + ":",
221+
evac,
222+
stress,
223+
)
224+
res_data[str(vacuum_size) + "_" + structure_dir] = [
225+
evac,
226+
stress,
227+
vacuum_size,
228+
]
229+
pre_evac = evac
230+
231+
else:
232+
if "init_data_path" not in self.parameter:
233+
raise RuntimeError("please provide the initial data path to reproduce")
234+
init_data_path = os.path.abspath(self.parameter["init_data_path"])
235+
res_data, ptr_data = post_repro(
236+
init_data_path,
237+
self.parameter["init_from_suffix"],
238+
all_tasks,
239+
ptr_data,
240+
self.parameter.get("reprod_last_frame", True),
241+
)
242+
243+
with open(output_file, "w") as fp:
244+
json.dump(res_data, fp, indent=4)
245+
246+
return res_data, ptr_data
247+
248+
def __gen_slab_pmg(self, structure: Structure,
249+
plane_miller, slab_size, vacuum_size) -> Structure:
250+
251+
# Generate slab via Pymatgen
252+
slabGen = SlabGenerator(structure, miller_index=plane_miller,
253+
min_slab_size=slab_size, min_vacuum_size=0,
254+
center_slab=True, in_unit_planes=False,
255+
lll_reduce=True, reorient_lattice=False,
256+
primitive=False)
257+
slabs_pmg = slabGen.get_slabs(ftol=0.001)
258+
slab = [s for s in slabs_pmg if s.miller_index == plane_miller][0]
259+
# If a transform matrix is passed, reorient the slab
260+
order = zip(slab.frac_coords, slab.species)
261+
c_order = sorted(order, key=lambda x: x[0][2])
262+
sorted_frac_coords = []
263+
sorted_species = []
264+
for (frac_coord, species) in c_order:
265+
sorted_frac_coords.append(frac_coord)
266+
sorted_species.append(species)
267+
# add vacuum layer to the slab with height unit of angstrom
268+
a, b, c = slab.lattice.matrix
269+
slab_height = slab.lattice.matrix[2][2]
270+
if slab_height >= 0:
271+
self.is_flip = False
272+
elong_scale = 1 + (vacuum_size / slab_height)
273+
else:
274+
self.is_flip = True
275+
elong_scale = 1 + (-vacuum_size / slab_height)
276+
new_lattice = [a, b, elong_scale * c]
277+
new_frac_coords = []
278+
for ii in range(len(sorted_frac_coords)):
279+
coord = sorted_frac_coords[ii].copy()
280+
coord[2] = coord[2] / elong_scale
281+
new_frac_coords.append(coord)
282+
slab_new = Structure(lattice=np.array(new_lattice),
283+
coords=new_frac_coords, species=sorted_species)
284+
285+
return slab_new
286+
287+

0 commit comments

Comments
 (0)