228 lines
12 KiB
Python
228 lines
12 KiB
Python
|
|
import os
|
||
|
|
import re
|
||
|
|
import shutil
|
||
|
|
from typing import Callable
|
||
|
|
from echem.io_data.jdftx import Ionpos, Lattice
|
||
|
|
from echem.io_data.vasp import Poscar
|
||
|
|
from echem.core.structure import Structure
|
||
|
|
from echem.core.constants import THz2eV
|
||
|
|
from echem.core.thermal_properties import ThermalProperties
|
||
|
|
from InterPhon.core import PreProcess, PostProcess
|
||
|
|
from nptyping import NDArray, Shape, Number
|
||
|
|
from typing import Union
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
|
||
|
|
class InterPhonInterface(ThermalProperties):
|
||
|
|
def __init__(self,
|
||
|
|
folder_to_jdftx_files: Union[str, Path],
|
||
|
|
folder_files_to_copy: Union[str, Path] = None,
|
||
|
|
select_fun: Callable[[Structure], list[list[str]]] = None,
|
||
|
|
user_args: dict = None,
|
||
|
|
sym_flag: bool = True):
|
||
|
|
|
||
|
|
if isinstance(folder_to_jdftx_files, str):
|
||
|
|
folder_to_jdftx_files = Path(folder_to_jdftx_files)
|
||
|
|
if isinstance(folder_files_to_copy, str):
|
||
|
|
folder_files_to_copy = Path(folder_files_to_copy)
|
||
|
|
|
||
|
|
self.folder_to_jdftx_files = folder_to_jdftx_files
|
||
|
|
self.folder_files_to_copy = folder_files_to_copy
|
||
|
|
self.select_fun = select_fun
|
||
|
|
self.user_args = user_args
|
||
|
|
self.sym_flag = sym_flag
|
||
|
|
|
||
|
|
self.post_process = None
|
||
|
|
self.eigen_freq = None
|
||
|
|
self.weights = None
|
||
|
|
|
||
|
|
def _create_poscar_for_interphon(self,
|
||
|
|
folder_to_jdftx_files: Path,
|
||
|
|
select_fun: Callable[[Structure], list[list[str]]] = None) -> None:
|
||
|
|
"""
|
||
|
|
Function creates POSCAR with unitcell for InterPhon adding selective dynamics data
|
||
|
|
|
||
|
|
Args:
|
||
|
|
folder_to_jdftx_files (str): path to folder with jdft.ionpos and jdft.lattice files
|
||
|
|
select_fun (Callable, optional): function that take Structure as input and provides list with
|
||
|
|
selective dynamics data for POSCAR class. All atoms are allowed to move in default.
|
||
|
|
"""
|
||
|
|
|
||
|
|
ionpos = Ionpos.from_file(folder_to_jdftx_files / 'jdft.ionpos')
|
||
|
|
lattice = Lattice.from_file(folder_to_jdftx_files / 'jdft.lattice')
|
||
|
|
poscar = ionpos.convert('vasp', lattice)
|
||
|
|
|
||
|
|
if select_fun is not None:
|
||
|
|
sd_data = select_fun(poscar.structure)
|
||
|
|
else:
|
||
|
|
sd_data = [['T', 'T', 'T'] for _ in range(poscar.structure.natoms)]
|
||
|
|
|
||
|
|
poscar.sdynamics_data = sd_data
|
||
|
|
|
||
|
|
poscar.to_file(folder_to_jdftx_files / 'POSCAR_unitcell_InterPhon')
|
||
|
|
|
||
|
|
def _make_preprocess(self,
|
||
|
|
poscar_unitcell: Path,
|
||
|
|
folder_to_disps: Path,
|
||
|
|
folder_files_to_copy: Path = None,
|
||
|
|
user_args: dict = None,
|
||
|
|
sym_flag: bool = True) -> None:
|
||
|
|
"""
|
||
|
|
Function creates folders with POSCARs with displaced atoms and all other necessary files for calculation
|
||
|
|
|
||
|
|
Args:
|
||
|
|
poscar_unitcell (str): path to the POSCAR file that contains the unitcell for IterPhon
|
||
|
|
with defined sd dynamics
|
||
|
|
folder_to_disps (str): path to a folder where all new folders with corresponding POSCARs with
|
||
|
|
displaced atoms will be created
|
||
|
|
folder_files_to_copy (str, optional): path to a folder from which all files will be copied to each
|
||
|
|
new folder with new POSCARs
|
||
|
|
user_args (dict, optional): dist with all necessary information for the InterPhon PreProcess class.
|
||
|
|
Only 2D periodicity is supported. If you want switch off symmetries, you have to define 'periodicity'
|
||
|
|
in user_args and set sym_flag=False
|
||
|
|
Example and default value: user_args = {'dft_code': 'vasp', 'displacement': 0.05,
|
||
|
|
'enlargement': "1 1 1", 'periodicity': "1 1 0"}
|
||
|
|
sym_flag (bool, optional): if True the symmetry will be applied. Only 2D symmetries are supported
|
||
|
|
|
||
|
|
"""
|
||
|
|
if user_args is None:
|
||
|
|
user_args = {'dft_code': 'vasp',
|
||
|
|
'displacement': 0.05,
|
||
|
|
'enlargement': '1 1 1',
|
||
|
|
'periodicity': '1 1 0'}
|
||
|
|
|
||
|
|
if poscar_unitcell != folder_to_disps / 'POSCAR_unitcell_InterPhon':
|
||
|
|
shutil.copyfile(poscar_unitcell, folder_to_disps / 'POSCAR_unitcell_InterPhon')
|
||
|
|
|
||
|
|
pre_process = PreProcess()
|
||
|
|
pre_process.set_user_arg(user_args)
|
||
|
|
|
||
|
|
pre_process.set_unit_cell(in_file=str(poscar_unitcell),
|
||
|
|
code_name='vasp')
|
||
|
|
pre_process.set_super_cell(out_file=str(folder_to_disps / 'POSCAR_supercell_InterPhon'),
|
||
|
|
code_name='vasp')
|
||
|
|
pre_process.write_displace_cell(out_file=str(folder_to_disps / 'POSCAR'),
|
||
|
|
code_name='vasp',
|
||
|
|
sym_flag=sym_flag)
|
||
|
|
|
||
|
|
poscars_disp = [f for f in folder_to_disps.iterdir() if f.is_file() and bool(re.search(r'POSCAR-\d{4}$',
|
||
|
|
f.name))]
|
||
|
|
|
||
|
|
for poscar_disp in poscars_disp:
|
||
|
|
poscar = Poscar.from_file(poscar_disp)
|
||
|
|
ionpos, lattice = poscar.convert('jdftx')
|
||
|
|
|
||
|
|
subfolder_to_disp = folder_to_disps / poscar_disp.name[-4:]
|
||
|
|
if not os.path.isdir(subfolder_to_disp):
|
||
|
|
os.mkdir(subfolder_to_disp)
|
||
|
|
ionpos.to_file(subfolder_to_disp / 'jdft.ionpos')
|
||
|
|
lattice.to_file(subfolder_to_disp / 'jdft.lattice')
|
||
|
|
|
||
|
|
shutil.copyfile(folder_to_disps / poscar_disp, subfolder_to_disp / 'POSCAR')
|
||
|
|
|
||
|
|
if folder_files_to_copy is not None:
|
||
|
|
files_to_copy = [f for f in folder_files_to_copy.iterdir() if f.is_file()]
|
||
|
|
for file in files_to_copy:
|
||
|
|
shutil.copyfile(file, subfolder_to_disp / file.name)
|
||
|
|
|
||
|
|
with open(folder_to_disps / 'user_args_InterPhon', 'w') as file:
|
||
|
|
for key, value in user_args.items():
|
||
|
|
file.write(f'{key}: {value}\n')
|
||
|
|
|
||
|
|
def _make_postprocess(self,
|
||
|
|
folder_to_disps: Path,
|
||
|
|
filepath_unitcell: Path,
|
||
|
|
filepath_supercell: Path,
|
||
|
|
filepath_kpoints: Path,
|
||
|
|
user_args: dict = None,
|
||
|
|
sym_flag: bool = True) -> None:
|
||
|
|
"""
|
||
|
|
Function process the output files after all calculations with displaced atoms are finished
|
||
|
|
|
||
|
|
Args:
|
||
|
|
folder_to_disps (str): path to the folder contains all folders with performed calculations with
|
||
|
|
atom displacements
|
||
|
|
filepath_unitcell (str): path to the POSCAR file that contains the unitcell for IterPhon
|
||
|
|
with defined sd dynamics
|
||
|
|
filepath_supercell (str): path to the POSCAR file produced by InterPhon with proper enlargement
|
||
|
|
filepath_kpoints (str): path to the KPOINTS file. The phonons will be assessed in the given k-points
|
||
|
|
user_args (dict, optional): dist with all necessary information for the InterPhon PreProcess class.
|
||
|
|
Example and default value: user_args = {'dft_code': 'vasp', 'displacement': 0.05,
|
||
|
|
'enlargement': "1 1 1", 'periodicity': "1 1 0"}
|
||
|
|
sym_flag (bool, optional): if True the symmetry will be applied. Only 2D symmetries are supported
|
||
|
|
"""
|
||
|
|
if user_args is None:
|
||
|
|
user_args = {'dft_code': 'vasp',
|
||
|
|
'displacement': 0.05,
|
||
|
|
'enlargement': '1 1 1',
|
||
|
|
'periodicity': '1 1 0'}
|
||
|
|
|
||
|
|
output_paths = [f / 'output.out' for f in folder_to_disps.iterdir()
|
||
|
|
if f.is_dir() and bool(re.search(r'\d{4}$', f.name))]
|
||
|
|
|
||
|
|
post_process = PostProcess(in_file_unit_cell=str(filepath_unitcell),
|
||
|
|
in_file_super_cell=str(filepath_supercell),
|
||
|
|
code_name='vasp')
|
||
|
|
post_process.set_user_arg(user_args)
|
||
|
|
post_process.set_reciprocal_lattice()
|
||
|
|
post_process.set_force_constant(force_files=[str(f) for f in output_paths],
|
||
|
|
code_name='jdftx',
|
||
|
|
sym_flag=sym_flag)
|
||
|
|
post_process.set_k_points(k_file=str(filepath_kpoints))
|
||
|
|
post_process.eval_phonon()
|
||
|
|
|
||
|
|
self.post_process = post_process
|
||
|
|
ThermalProperties.__init__(self, self.post_process.w_q * THz2eV)
|
||
|
|
|
||
|
|
def create_displacements_jdftx(self):
|
||
|
|
self._create_poscar_for_interphon(folder_to_jdftx_files=self.folder_to_jdftx_files,
|
||
|
|
select_fun=self.select_fun)
|
||
|
|
self._make_preprocess(poscar_unitcell=self.folder_to_jdftx_files / 'POSCAR_unitcell_InterPhon',
|
||
|
|
folder_to_disps=self.folder_to_jdftx_files,
|
||
|
|
folder_files_to_copy=self.folder_files_to_copy,
|
||
|
|
user_args=self.user_args,
|
||
|
|
sym_flag=self.sym_flag)
|
||
|
|
|
||
|
|
def get_phonons(self) -> NDArray[Shape['Nkpts, Nfreq'], Number]:
|
||
|
|
if self.post_process is None:
|
||
|
|
self._make_postprocess(folder_to_disps=self.folder_to_jdftx_files,
|
||
|
|
filepath_unitcell=self.folder_to_jdftx_files / 'POSCAR_unitcell_InterPhon',
|
||
|
|
filepath_supercell=self.folder_to_jdftx_files / 'POSCAR_supercell_InterPhon',
|
||
|
|
filepath_kpoints=self.folder_to_jdftx_files / 'KPOINTS',
|
||
|
|
user_args=self.user_args,
|
||
|
|
sym_flag=self.sym_flag)
|
||
|
|
|
||
|
|
return self.eigen_freq
|
||
|
|
|
||
|
|
def get_Gibbs_ZPE(self) -> float:
|
||
|
|
if self.eigen_freq is None:
|
||
|
|
self._make_postprocess(folder_to_disps=self.folder_to_jdftx_files,
|
||
|
|
filepath_unitcell=self.folder_to_jdftx_files / 'POSCAR_unitcell_InterPhon',
|
||
|
|
filepath_supercell=self.folder_to_jdftx_files / 'POSCAR_supercell_InterPhon',
|
||
|
|
filepath_kpoints=self.folder_to_jdftx_files / 'KPOINTS',
|
||
|
|
user_args=self.user_args,
|
||
|
|
sym_flag=self.sym_flag)
|
||
|
|
return ThermalProperties.get_Gibbs_ZPE(self)
|
||
|
|
|
||
|
|
def get_enthalpy_vib(self,
|
||
|
|
T: float) -> float:
|
||
|
|
if self.eigen_freq is None:
|
||
|
|
self._make_postprocess(folder_to_disps=self.folder_to_jdftx_files,
|
||
|
|
filepath_unitcell=self.folder_to_jdftx_files / 'POSCAR_unitcell_InterPhon',
|
||
|
|
filepath_supercell=self.folder_to_jdftx_files / 'POSCAR_supercell_InterPhon',
|
||
|
|
filepath_kpoints=self.folder_to_jdftx_files / 'KPOINTS',
|
||
|
|
user_args=self.user_args,
|
||
|
|
sym_flag=self.sym_flag)
|
||
|
|
return ThermalProperties.get_enthalpy_vib(self, T)
|
||
|
|
|
||
|
|
def get_TS_vib(self,
|
||
|
|
T: float) -> float:
|
||
|
|
if self.eigen_freq is None:
|
||
|
|
self._make_postprocess(folder_to_disps=self.folder_to_jdftx_files,
|
||
|
|
filepath_unitcell=self.folder_to_jdftx_files / 'POSCAR_unitcell_InterPhon',
|
||
|
|
filepath_supercell=self.folder_to_jdftx_files / 'POSCAR_supercell_InterPhon',
|
||
|
|
filepath_kpoints=self.folder_to_jdftx_files / 'KPOINTS',
|
||
|
|
user_args=self.user_args,
|
||
|
|
sym_flag=self.sym_flag)
|
||
|
|
return ThermalProperties.get_TS_vib(self, T)
|