This commit is contained in:
2026-01-08 19:47:32 +03:00
commit 4d7676a79e
89 changed files with 62260 additions and 0 deletions

View File

@@ -0,0 +1,227 @@
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)