Source code for EPWpy.BGW.EPWpy_BGW

#
from __future__ import annotations

__author__= "Sabyasachi Tiwari"
__copyright__= "Copyright 2024, EPWpy project"
__version__= "1.0"
__maintainer__= "Sabyasachi Tiwari"
__maintainer_email__= "sabyasachi.tiwari@austin.utexas.edu"
__status__= "Production"
__date__= "May 03, 2024"

import numpy as np
import subprocess as sp
import os
from EPWpy.utilities.set_files import *
from EPWpy.default.default import *
from EPWpy.utilities.k_gen import *
from EPWpy.utilities.Band import *
from EPWpy.BGW.BGW_set_links import set_links
from EPWpy.BGW.write_BGW import WriteBGWFiles
from EPWpy.BGW.write_QE_BGW import WriteQEBGWFiles
from EPWpy.QE.PW_util import find_fermi

[docs] class PyBGW(SetDir, WriteBGWFiles, WriteQEBGWFiles): """ Interface to the BerkeleyGW package. This class provides a high-level interface to perform GW and Bethe–Salpeter equation (BSE) calculations using BerkeleyGW. It manages file preparation, data linking between Quantum ESPRESSO and BerkeleyGW, and parameter consistency across modules. Parameters ---------- structure : object A structure object containing information about the material (usually obtained from Quantum ESPRESSO or another interface). Notes ----- - BerkeleyGW requires prior DFT calculations (e.g., QE) to provide wavefunction and eigenvalue inputs. - The class currently initializes a basic GW flow but can be extended to include epsilon, sigma, kernel, and absorption calculations. - To do: Implement full workflow automation and dependency tracking. """ def __init__(self, structure): self.inp=structure
[docs] def GW(self, GW=None): if GW is None: GW = {} """ Prepares GW calculations """ self.prepare_GW() self.BGW_init = BGW_init self.BGW_pw2bgw = BGW_pw2bgw self.BGW_sig2wan = BGW_sig2wan self.BGW_epsilon = BGW_epsilon self.BGW_sigma = BGW_sigma self.BGW_kernel = BGW_kernel self.BGW_absorption = BGW_absorption self.BGW_epsilon['number_bands'] = self.nbnd_co if self.nbnd_co is None: print('Manually set the number of bands in epsilon') self.BGW_epsilon['number_bands'] = BGW_init['nbnd'] self.pw_control['calculation'] ='\'bands\'' self.prepare_link() for key in GW.keys(): if(key in self.BGW_init.keys()): self.BGW_init[key] = GW[key]
[docs] def epsilon(self, epsilon=None, type_calc=1): if epsilon is None: epsilon = {} """ prepare epsilon calculation """ if (type_calc==1): print('Preparing ESPRESSO files, else use type_calc=2') self.prepare_wfn(kpoints = {'grid':self.BGW_init['grid_co']}) self.prepare_wfnq(kpoints = {'grid':self.BGW_init['grid_co'], 'shift':self.BGW_init['shift']}) self.prepare_wfnfi(kpoints = {'grid':self.BGW_init['grid_fi']}) self.prepare_wfnfiq(kpoints ={'grid':self.BGW_init['grid_fi'], 'shift':self.BGW_init['shift']}) self.set_work() print('shifted grid',self.shift, self.GW_k[0,:3]) for i in range(len(self.GW_k[:,0])): for l in range(3): self.GW_k[i,l]="{:.8f}".format(self.GW_k[i,l]) self.GW_ks = self.GW_k self.GW_ks[0,:3] += self.shift self.write_epsilon(epsilonin=epsilon) self.Prepare.run('./epsilon.inp','./GW/epsilon/') self.set_home()
[docs] def sigma(self, sigma=None): if sigma is None: sigma = {} """ prepare sigma calculation """ self.set_work() (self.nk1,self.nk2,self.nk3) = self.BGW_init['grid_co'] self.BGW_sigma['band_index_min'] = 1 self.BGW_sigma['band_index_max'] = self.BGW_epsilon['number_bands'] for key in sigma.keys(): self.BGW_sigma[key] = sigma[key] self.write_sigma() self.BGW_sig2wan['nbands'] = self.BGW_sigma['band_index_max']-self.BGW_sigma['band_index_min'] self.BGW_sig2wan['ib_start'] = self.BGW_sigma['band_index_min'] self.write_sig2wan() #sp.Popen('cp -r sigma.inp GW/sigma/',shell=True) #sp.Popen('cp -r sig2wan.inp GW/sigma/',shell=True) self.Prepare.run('./sigma.inp','./GW/sigma/') self.Prepare.run('./sig2wan.inp','./GW/sigma/') self.set_home()
[docs] def kernel(self, kernel=None): if kernel is None: kernel = {} """ prepare kernel calculation """ print('Kernel calculation') (self.nk1,self.nk2,self.nk3) = self.BGW_init['grid_co'] self.BGW_kernel['number_val_bands'] = self.nbnd_val self.BGW_kernel['number_cond_bands'] = self.nbnd_cond self.BGW_kernel['screening_semiconductor'] = ' ' for key in kernel.keys(): self.BGW_kernel[key] = kernel[key] print(self.BGW_kernel) self.set_work() self.write_kernel() self.Prepare.run('kernel.inp','GW/kernel/') #sp.Popen('cp -r kernel.inp GW/kernel/',shell=True) self.set_home()
[docs] def absorption(self, absorption=None): if absorption is None: absorption = {} """ prepare absorption calculation """ (self.nk1,self.nk2,self.nk3) = self.BGW_init['grid_co'] self.BGW_absorption['number_cond_bands_coarse'] = self.nbnd_val self.BGW_absorption['number_val_bands_coarse'] = self.nbnd_cond for key in absorption.keys(): self.BGW_absorption[key] = absorption[key] self.set_work() self.write_absorption() # sp.Popen('cp -r absorption.inp GW/absorption/',shell=True) self.Prepare.run('./absorption.inp','GW/absorption/') self.set_home()
[docs] def prepare_wfn(self, kpoints=None, control=None, system=None, electrons=None, cell=None, pw2bgw=None, filename='GW/wfn'): if kpoints is None: kpoints = {'grid':[6,6,6]} if control is None: control = {} if system is None: system = {} if electrons is None: electrons = {} if cell is None: cell = {} if pw2bgw is None: pw2bgw = {} self.set_work() self.wfn_filename = filename self.changedir(self.wfn_filename) if 'grid' in kpoints.keys(): (self.nk1,self.nk2,self.nk3)=kpoints['grid'] self.BGW_init['grid_co']=[self.nk1,self.nk2,self.nk3] else: (self.nk1,self.nk2,self.nk3)=self.BGW_init['grid_co'] self.pw_kpoints['kpoints'] = self.GW_k self.pw_kpoints['kpoints_type'] = 'crystal' self.BGW_pw2bgw['wfng_nk1'] = self.nk1 self.BGW_pw2bgw['wfng_nk2'] = self.nk2 self.BGW_pw2bgw['wfng_nk3'] = self.nk3 self.pw_system['nbnd']=self.BGW_init['nbnd'] if 'nbnd' in system.keys(): self.BGW_pw2bgw['vxc_diag_nmax']=system['nbnd'] self.write_scf_QE(control=control,system=system,electrons=electrons,ions={},cell=cell,name='wfn') self.write_pw2bgw(pw2bgw={}) self.set_home()
[docs] def prepare_wfnq(self, kpoints=None, control=None, system=None, electrons=None, cell=None, pw2bgw=None, filename='GW/wfnq'): if kpoints is None: kpoints = {'grid':[6,6,6],'shift':[0.0,0.0,0.001]} if control is None: control = {} if system is None: system = {} if electrons is None: electrons = {} if cell is None: cell = {} if pw2bgw is None: pw2bgw = {} self.set_work() self.wfnq_filename = filename self.changedir(self.wfnq_filename) if 'grid' in kpoints.keys(): (self.nk1,self.nk2,self.nk3) = kpoints['grid'] self.BGW_init['grid_co'] = [self.nk1,self.nk2,self.nk3] else: (self.nk1,self.nk2,self.nk3) = self.BGW_init['grid_co'] kpoints['shift'] = self.BGW_init['shift'] self.shift = np.array(kpoints['shift']) self.GW_k[0,:3] += np.array(kpoints['shift']) self.pw_kpoints['kpoints'] = self.GW_k self.pw_kpoints['kpoints'][:,:3] += np.array(kpoints['shift']) self.pw_kpoints['kpoints_type']='crystal' self.BGW_pw2bgw['wfng_nk1'] = self.nk1 self.BGW_pw2bgw['wfng_nk2'] = self.nk2 self.BGW_pw2bgw['wfng_nk3'] = self.nk3 self.BGW_pw2bgw['wfng_dk1'] = kpoints['shift'][0] self.BGW_pw2bgw['wfng_dk2'] = kpoints['shift'][1] self.BGW_pw2bgw['wfng_dk3'] = kpoints['shift'][2] self.pw_system['nbnd']=self.BGW_init['nbnd'] if 'nbnd' in system.keys(): self.BGW_pw2bgw['vxc_diag_nmax']=system['nbnd'] self.write_scf_QE(control=control,system=system,electrons=electrons,ions={},cell=cell,name='wfnq') self.write_pw2bgw(pw2bgw={}) self.set_home()
[docs] def prepare_wfnfi(self, kpoints=None, control=None, system=None, electrons=None, cell=None, pw2bgw=None, filename='GW/wfnfi'): if kpoints is None: kpoints = {'grid':[6,6,6]} if control is None: control = {} if system is None: system = {} if electrons is None: electrons = {} if cell is None: cell = {} if pw2bgw is None: pw2bgw = {} self.set_work() self.wfnfi_filename = filename self.changedir(self.wfnfi_filename) if 'grid' in kpoints.keys(): (self.nk1,self.nk2,self.nk3) = kpoints['grid'] self.BGW_init['grid_fi'] = [self.nk1,self.nk2,self.nk3] else: (self.nk1,self.nk2,self.nk3) = self.BGW_init['grid_fi'] self.pw_kpoints['kpoints'] = self.GW_k self.pw_kpoints['kpoints_type'] = 'crystal' self.BGW_pw2bgw['wfng_nk1'] = self.nk1 self.BGW_pw2bgw['wfng_nk2'] = self.nk2 self.BGW_pw2bgw['wfng_nk3'] = self.nk3 self.BGW_pw2bgw['wfng_dk1'] = 0.0 self.BGW_pw2bgw['wfng_dk2'] = 0.0 self.BGW_pw2bgw['wfng_dk3'] = 0.0 self.pw_system['nbnd'] = self.BGW_init['nbnd'] if 'nbnd' in system.keys(): self.BGW_pw2bgw['vxc_diag_nmax'] = system['nbnd'] self.write_scf_QE(control=control,system=system,electrons=electrons,ions={},cell=cell,name='wfnfi') self.write_pw2bgw(pw2bgw={}) self.set_home()
[docs] def prepare_wfnfiq(self, kpoints=None, control=None, system=None, electrons=None, cell=None, pw2bgw=None, filename='GW/wfnfiq'): if kpoints is None: kpoints = {'grid':[6,6,6]} if control is None: control = {} if system is None: system = {} if electrons is None: electrons = {} if cell is None: cell = {} if pw2bgw is None: pw2bgw = {} self.set_work() self.wfnfiq_filename = filename self.changedir(self.wfnfiq_filename) if 'grid' in kpoints.keys(): (self.nk1,self.nk2,self.nk3) = kpoints['grid'] self.BGW_init['grid_fi'] = [self.nk1,self.nk2,self.nk3] else: (self.nk1,self.nk2,self.nk3) = self.BGW_init['grid_fi'] self.pw_kpoints['kpoints'] = self.GW_k self.pw_kpoints['kpoints'][:,:3] += np.array(kpoints['shift']) self.pw_kpoints['kpoints_type'] = 'crystal' self.BGW_pw2bgw['wfng_nk1'] = self.nk1 self.BGW_pw2bgw['wfng_nk2'] = self.nk2 self.BGW_pw2bgw['wfng_nk3'] = self.nk3 self.BGW_pw2bgw['wfng_dk1'] = kpoints['shift'][0] self.BGW_pw2bgw['wfng_dk2'] = kpoints['shift'][1] self.BGW_pw2bgw['wfng_dk3'] = kpoints['shift'][2] self.pw_system['nbnd'] = self.BGW_init['nbnd'] if 'nbnd' in system.keys(): self.BGW_pw2bgw['vxc_diag_nmax'] = system['nbnd'] self.write_scf_QE(control=control,system=system,electrons=electrons,ions={},cell=cell,name='wfnfiq') self.write_pw2bgw(pw2bgw={}) self.set_home()
[docs] def prepare_GW(self): self.set_work() prefix=self.prefix.replace('\'','') self.makedir('GW') self.makedir('GW/wfn') self.makedir('GW/wfnq') self.makedir('GW/wfnfi') self.makedir('GW/wfnfiq') self.makedir('GW/epsilon') self.makedir('GW/sigma') self.makedir('GW/kernel') self.makedir('GW/absorption') self.makedir('GW/sig2wan') try: self.Prepare.run(f'./{self.scf_fold}/{prefix}.save','./GW/wfn/') self.Prepare.run(f'./{self.scf_fold}/{prefix}.save','./GW/wfnq/') self.Prepare.run(f'./{self.scf_fold}/{prefix}.save','./GW/wfnfi/') self.Prepare.run(f'./{self.scf_fold}/{prefix}.save','./GW/wfnfiq/') except FileNotFoundError: print('No DFT save file found') print('Run scf first') self.set_home()
@property def GW_k(self): k = np.zeros((self.nk1*self.nk2*self.nk3,4),dtype=float) kx = [1,0,0] ky = [0,1,0] kz = [0,0,0] h = np.linspace(0.0,1,self.nk1,endpoint=False) k = np.linspace(0.0,1,self.nk2,endpoint=False) l = np.linspace(0.0,1,self.nk3,endpoint=False) k = k_mesh(h,k,l,kx,ky,kz) return(np.array(k)) @property def nbnd_co(self): self.set_work() try: Bands = extract_band_scf('GW/wfn/wfn.out') return(len(Bands[0,:])-1) except FileNotFoundError: return(None) self.set_home() @property def nbnd_cond(self): self.set_work() try: Bands = extract_band_scf('GW/wfn/wfn.out') ef = find_fermi(f'{self.scf_fold}/{self.scf_file}') minband = self.BGW_sigma['band_index_min'] maxband = self.BGW_sigma['band_index_max'] nband = maxband - minband print('nband',nband) print('Fermi',ef) for i in range(nband): if (min(Bands[:,minband+i-1]) > ef): print('minband',minband+i-1) return(minband+i-1) except FileNotFoundError: print('no prior coarse grid calculation') self.set_home() @property def nbnd_val(self): self.set_work() try: Bands = extract_band_scf('GW/wfn/wfn.out') ef = find_fermi(f'{self.scf_fold}/{self.scf_file}') minband = self.BGW_sigma['band_index_min'] maxband = self.BGW_sigma['band_index_max'] nband = maxband - minband for i in range(nband): if (min(Bands[:,minband+i-1]) > ef): return(minband+i-2) except FileNotFoundError: print('no prior coarse grid calculation') self.set_home() @property def nbnd_fi(self): self.set_work() try: Bands = extract_band_scf('GW/wfnfi/wfnfi.out') return(len(Bands[0,:])-1) except FileNotFoundError: return(None) self.set_home()