def plot_all_hartree_pot(self): #plot planar averaged locpots, along with the difference for each axis if not self._locpot_bulk: print( 'did not feed path to locpot file! Need this for plotting hartree pot' ) return import matplotlib.pyplot as plt fig = plt.figure() ax = fig.add_subplot(3, 1, 1) ax.set_title('Locpot planar averaged potentials and difference') bulkloc = Locpot.from_file(self._locpot_bulk) defloc = Locpot.from_file(self._locpot_defect) for axis in [0, 1, 2]: ax = fig.add_subplot(3, 1, axis + 1) defect_axis = defloc.get_axis_grid(axis) defect_pot = defloc.get_average_along_axis(axis) pure_pot = bulkloc.get_average_along_axis(axis) ax.plot(defect_axis, pure_pot, 'r', label="Bulk potential") ax.plot(defect_axis, defect_pot, 'b', label="Defect potential") ax.plot(defect_axis, defect_pot - pure_pot, 'k', label='Defect-Bulk difference') ax.plot([self._frac_coords[axis]*self._lengths[axis]],\ [0], 'or', markersize=4.0, label='Defect site') ax.set_ylabel("axis " + str(axis + 1)) if not axis: ax.legend() ax.set_xlabel("distance (Angstrom)") #plt.savefig('locpotavgdiffplot.png') plt.show()
def plot_hartree_pot(self): #plot planar averages of bulk and defect (good for seeing global changes) import matplotlib.pyplot as plt fig = plt.figure() ax = fig.add_subplot(3,1,1) ax.set_title('Locpot planar averaged potentials') bulkloc=Locpot.from_file(self._locpot_bulk) defloc=Locpot.from_file(self._locpot_bulk) get_agrid = bulkloc.get_axis_grid get_baavg = bulkloc.get_average_along_axis get_daavg = defloc.get_average_along_axis for axis in [0,1,2]: ax = fig.add_subplot(3, 1, axis+1) latt_len = self._lengths[axis] ax.plot(get_agrid(axis),get_baavg(axis),'r', label="Bulk potential") ax.plot(get_agrid(axis), get_daavg(axis),'b', label="Defect potential") ax.plot([self._frac_coords[axis]*latt_len], [0], 'or', markersize=4.0, label="Defect site") ax.set_ylabel("axis "+str(axis+1)) if not axis: ax.legend() ax.set_xlabel("distance (Angstrom)") #plt.savefig('locpotavgplot.png') plt.show()
def get_freysoldt_correction(defect_type, defect_specie, path_to_defect_locpot,path_to_pure_locpot,charge, dielectric_constant,defect_site_coordinates,energy_cutoff=500,get_plot=False): ''' Function to perform charge corrections according to the method proposed py Freysoldt If this correction is used, please reference Freysoldt's original paper. doi: 10.1103/PhysRevLett.102.016402 Args: defect_type: 'vacancy' or 'interstitial' defect_specie: string with element occupying the defect site path_to_defect_locpot: path to LOCPOT file of defect structure path_to_pure_locpot: path to LOCPOT file of Pure structure charge: Charge of the defected system dielectric_constant: Dielectric constant defect_site_coordinates: numpy array with fractional coordinates of defect site energy_cutoff: Cut-off of plane wave expansion get_plot: return also Matplotlib object with plot Returns: Freysoldt corrections values as a dictionary ''' # acquiring data from LOCPOT files locpot_pure = Locpot.from_file(path_to_pure_locpot) vol_data_pure = VolumetricData(locpot_pure.structure,locpot_pure.data) locpot_defect = Locpot.from_file(path_to_defect_locpot) vol_data_defect = VolumetricData(locpot_defect.structure,locpot_defect.data) parameters = {} parameters['axis_grid'] = [] parameters['bulk_planar_averages'] = [] parameters['defect_planar_averages'] = [] for i in range(0,3): parameters['axis_grid'].append(vol_data_pure.get_axis_grid(i)) parameters['bulk_planar_averages'].append(vol_data_pure.get_average_along_axis(i)) parameters['defect_planar_averages'].append(vol_data_defect.get_average_along_axis(i)) parameters['initial_defect_structure'] = locpot_defect.structure parameters['defect_frac_sc_coords'] = defect_site_coordinates structure_bulk = locpot_pure.structure defect_site = PeriodicSite(defect_specie, coords=defect_site_coordinates, lattice = locpot_pure.structure.lattice) module = importlib.import_module("pymatgen.analysis.defects.core") defect_class = getattr(module,defect_type) defect = defect_class(structure_bulk, defect_site, charge=charge, multiplicity=None) defect_entry = DefectEntry(defect,None,corrections=None,parameters=parameters) freysoldt_class = FreysoldtCorrection(dielectric_constant,energy_cutoff=energy_cutoff) freysoldt_corrections = freysoldt_class.get_correction(defect_entry) if get_plot: plt = freysoldt_class.plot(1) return freysoldt_corrections , plt else: return freysoldt_corrections
def correction(self, title=None, partflag='All'): """ Args: title: set if you want to plot the planar averaged potential partflag: four options 'pc' for just point charge correction, or 'potalign' for just potalign correction, or 'All' for both, or 'AllSplit' for individual parts split up (form [PC,potterm,full]) """ logger = logging.getLogger(__name__) logger.info('This is Freysoldt Correction.') if not self._q: if partflag == 'AllSplit': return [0.0, 0.0, 0.0] else: return 0.0 if not type(self._purelocpot) is Locpot: logger.debug('Load bulk locpot') self._purelocpot = Locpot.from_file(self._purelocpot) logger.debug('\nRun PC energy') if partflag != 'potalign': energy_pc = self.pc() logger.debug('PC calc done, correction = %f', round(energy_pc, 4)) logger.debug('Now run potenttial alignment script') if partflag != 'pc': if not type(self._deflocpot) is Locpot: logger.debug('Load defect locpot') self._deflocpot = Locpot.from_file(self._deflocpot) potalign = self.potalign(title=title) logger.info('\n\nFreysoldt Correction details:') if partflag != 'potalign': logger.info('PCenergy (E_lat) = %f', round(energy_pc, 5)) if partflag != 'pc': logger.info('potential alignment (-q*delta V) = %f', round(potalign, 5)) if partflag in ['All', 'AllSplit']: logger.info('TOTAL Freysoldt correction = %f', round(energy_pc + potalign, 5)) if partflag == 'pc': return round(energy_pc, 5) elif partflag == 'potalign': return round(potalign, 5) elif partflag == 'All': return round(energy_pc + potalign, 5) else: return map(lambda x: round(x, 5), [energy_pc, potalign, energy_pc + potalign])
def freysoldt_loader(self, bulk_locpot=None): """Load metadata required for performing Freysoldt correction requires "bulk_path" and "defect_path" to be loaded to DefectEntry parameters dict. Can read gunzipped "LOCPOT.gz" files as well. Args: bulk_locpot (Locpot): Add bulk Locpot object for expedited parsing. If None, will load from file path variable bulk_path Return: bulk_locpot object for reuse by another defect entry (for expedited parsing) """ if not self.defect_entry.charge: #dont need to load locpots if charge is zero return None if not bulk_locpot: bulk_locpot_path = os.path.join( self.defect_entry.parameters["bulk_path"], "LOCPOT") if os.path.exists(bulk_locpot_path): bulk_locpot = Locpot.from_file(bulk_locpot_path) elif os.path.exists(bulk_locpot_path+".gz"): bulk_locpot = Locpot.from_file(bulk_locpot_path + ".gz") else: raise FileNotFoundError(f"""Well I can't f*****g find a LOCPOT(.gz) in {self.defect_entry.parameters['bulk_path']}. You sure there's one there pal? I need it to get the Freysoldt correction""") def_locpot_path = os.path.join( self.defect_entry.parameters["defect_path"], "LOCPOT") if os.path.exists(def_locpot_path): def_locpot = Locpot.from_file(def_locpot_path) elif os.path.exists(def_locpot_path + ".gz"): def_locpot = Locpot.from_file(def_locpot_path + ".gz") else: raise FileNotFoundError(f"""Well I can't f*****g find a LOCPOT(.gz) in {self.defect_entry.parameters['defect_path']}. You sure there's one there pal? I need it to get the Freysoldt correction""") axis_grid = [def_locpot.get_axis_grid(i) for i in range(3)] bulk_planar_averages = [bulk_locpot.get_average_along_axis(i) for i in range(3)] defect_planar_averages = [def_locpot.get_average_along_axis(i) for i in range(3)] defect_frac_sc_coords = self.defect_entry.site.frac_coords self.defect_entry.parameters.update({"axis_grid": axis_grid, "bulk_planar_averages": bulk_planar_averages, "defect_planar_averages": defect_planar_averages, "initial_defect_structure": def_locpot.structure, "defect_frac_sc_coords": defect_frac_sc_coords }) return bulk_locpot
def vacuum(path=None): ''' Gets the energy of the vacuum level. It either parses potential.csv file if available or tries to calculate planar potential from LOCPOT. If neither file is available, function returns np.nan. Args: path (`str`, optional): the path to potential.csv or LOCPOT files. Can be the path to a directory in which either file is or you can specify a path that must end in .csv or contain LOCPOT. Defaults to looking for potential.csv or LOCPOT in cwd. Returns: Maximum value of planar potential ''' if type(path) == str and path.endswith('.csv'): df = pd.read_csv(path) max_potential = df['planar'].max() max_potential = round(max_potential, 3) elif type(path) == str and 'LOCPOT' in path: lpt = Locpot.from_file(path) planar = lpt.get_average_along_axis(2) max_potential = float(f"{np.max(planar): .3f}") else: if path is None: cwd = os.getcwd() else: cwd = path if os.path.isfile('{}/potential.csv'.format(cwd)): df = pd.read_csv('{}/potential.csv'.format(cwd)) max_potential = df['planar'].max() max_potential = round(max_potential, 3) elif os.path.isfile('{}/LOCPOT'.format(cwd)): lpt = Locpot.from_file('{}/LOCPOT'.format(cwd)) planar = lpt.get_average_along_axis(2) max_potential = float(f"{np.max(planar): .3f}") else: max_potential = np.nan warnings.formatwarning = _custom_formatwarning warnings.warn( 'Vacuum electrostatic potential was not parsed from {} ' 'no LOCPOT or potential.csv files were provided.'.format(path)) return max_potential
def freysoldt_loader(self, bulk_locpot=None): """Load metadata required for performing Freysoldt correction requires "bulk_path" and "defect_path" to be loaded to DefectEntry parameters dict. Args: bulk_locpot (Locpot): Add bulk Locpot object for expedited parsing. If None, will load from file path variable bulk_path Return: bulk_locpot object for reuse by another defect entry (for expedited parsing) """ if not self.defect_entry.charge: #dont need to load locpots if charge is zero return None if not bulk_locpot: bulk_locpot_path = os.path.join( self.defect_entry.parameters["bulk_path"], "LOCPOT") bulk_locpot = Locpot.from_file(bulk_locpot_path) def_locpot_path = os.path.join( self.defect_entry.parameters["defect_path"], "LOCPOT") def_locpot = Locpot.from_file(def_locpot_path) axis_grid = [def_locpot.get_axis_grid(i) for i in range(3)] bulk_planar_averages = [ bulk_locpot.get_average_along_axis(i) for i in range(3) ] defect_planar_averages = [ def_locpot.get_average_along_axis(i) for i in range(3) ] defect_frac_sc_coords = self.defect_entry.site.frac_coords self.defect_entry.parameters.update({ "axis_grid": axis_grid, "bulk_planar_averages": bulk_planar_averages, "defect_planar_averages": defect_planar_averages, "initial_defect_structure": def_locpot.structure, "defect_frac_sc_coords": defect_frac_sc_coords }) return bulk_locpot
def test_init(self): filepath = os.path.join(test_dir, "LOCPOT") locpot = Locpot.from_file(filepath) self.assertAlmostEqual(-217.05226954, sum(locpot.get_average_along_axis(0))) self.assertAlmostEqual(locpot.get_axis_grid(0)[-1], 2.87629, 2) self.assertAlmostEqual(locpot.get_axis_grid(1)[-1], 2.87629, 2) self.assertAlmostEqual(locpot.get_axis_grid(2)[-1], 2.87629, 2)
def __init__(self, locpot_bulk_path, locpot_defect_path, charge, epsilon, site_frac_coords, encut, lengths=None, name=''): """ Args: locpot_bulk: Location of LOCPOT of bulk locpot_defect: Location of LOCPOT of defect charge: Charge relative to neutral defect (not relative to bulk) epsilon: Dielectric constant obtained from relaxation run site_frac_coords: Fractional coordinates of defect site as list encut: Planewave basis energy cutoff used in VASP run (in eV) name: Name of the defect to write files lengths: Length of lattice vectors. """ self._locpot_bulk = locpot_bulk_path self._locpot_defect = locpot_defect_path self._charge = charge self._epsilon = epsilon self._frac_coords = site_frac_coords self._encut = encut if not lengths: struct=Locpot.from_file(locpot_bulk_path) self._lengths=struct.structure.lattice.abc print('had to import lengths, if want to speed up set lengths='+str(self._lengths)) else: self._lengths = lengths self._name = name
def from_files(poscar_filename, locpot_filename, outcar_filename, shift=0): p = Poscar.from_file(poscar_filename) l = Locpot.from_file(locpot_filename) o = Outcar(outcar_filename) return WorkFunctionAnalyzer(p.structure, l.get_average_along_axis(int(sys.argv[1])), o.efermi, shift=shift)
def get_band_edges(): """ Calculate the band edge locations relative to the vacuum level for a semiconductor. If spin-polarized, returns all 4 band edges. """ # Vacuum level energy from LOCPOT. locpot = Locpot.from_file("LOCPOT") evac = max(locpot.get_average_along_axis(2)) vasprun = Vasprun("vasprun.xml") efermi = vasprun.efermi - evac if vasprun.get_band_structure().is_spin_polarized: eigenvals = {Spin.up: [], Spin.down: []} for band in vasprun.eigenvalues: for eigenvalue in vasprun.eigenvalues[band]: eigenvals[band[0]].append(eigenvalue) up_cbm = min([e[0] for e in eigenvals[Spin.up] if not e[1]]) - evac up_vbm = max([e[0] for e in eigenvals[Spin.up] if e[1]]) - evac dn_cbm = min([e[0] for e in eigenvals[Spin.down] if not e[1]]) - evac dn_vbm = max([e[0] for e in eigenvals[Spin.down] if e[1]]) - evac edges = {"up_cbm": up_cbm, "up_vbm": up_vbm, "dn_cbm": dn_cbm, "dn_vbm": dn_vbm, "efermi": efermi} else: bs = vasprun.get_band_structure() cbm = bs.get_cbm()["energy"] - evac vbm = bs.get_vbm()["energy"] - evac edges = {"cbm": cbm, "vbm": vbm, "efermi": efermi} return edges
def plot_local_potential(axis=2, ylim=(-20, 0), fmt='pdf'): """ Plot data from the LOCPOT file along any of the 3 primary axes. Useful for determining surface dipole moments and electric potentials on the interior of the material. Args: axis (int): 0 = x, 1 = y, 2 = z ylim (tuple): minimum and maximum potentials for the plot's y-axis. fmt (str): matplotlib format style. Check the matplotlib docs for options. """ ax = plt.figure(figsize=(16, 10)).gca() locpot = Locpot.from_file('LOCPOT') structure = Structure.from_file('CONTCAR') vd = VolumetricData(structure, locpot.data) abs_potentials = vd.get_average_along_axis(axis) vacuum_level = max(abs_potentials) vasprun = Vasprun('vasprun.xml') bs = vasprun.get_band_structure() if not bs.is_metal(): cbm = bs.get_cbm()['energy'] - vacuum_level vbm = bs.get_vbm()['energy'] - vacuum_level potentials = [potential - vacuum_level for potential in abs_potentials] axis_length = structure.lattice.lengths[axis] positions = np.arange(0, axis_length, axis_length / len(potentials)) ax.plot(positions, potentials, linewidth=2, color='k') ax.set_xlim(0, axis_length) ax.set_ylim(ylim[0], ylim[1]) ax.set_xticklabels( [r'$\mathrm{%s}$' % tick for tick in ax.get_xticks()], size=20) ax.set_yticklabels( [r'$\mathrm{%s}$' % tick for tick in ax.get_yticks()], size=20) ax.set_xlabel(r'$\mathrm{\AA}$', size=24) ax.set_ylabel(r'$\mathrm{V\/(eV)}$', size=24) if not bs.is_metal(): ax.text(ax.get_xlim()[1], cbm, r'$\mathrm{CBM}$', horizontalalignment='right', verticalalignment='bottom', size=20) ax.text(ax.get_xlim()[1], vbm, r'$\mathrm{VBM}$', horizontalalignment='right', verticalalignment='top', size=20) ax.fill_between(ax.get_xlim(), cbm, ax.get_ylim()[1], facecolor=plt.cm.jet(0.3), zorder=0, linewidth=0) ax.fill_between(ax.get_xlim(), ax.get_ylim()[0], vbm, facecolor=plt.cm.jet(0.7), zorder=0, linewidth=0) if fmt == "None": return ax else: plt.savefig('locpot.{}'.format(fmt)) plt.close()
def plot_local_potential(axis=2, ylim=(-20, 0), fmt='pdf'): """ Plot data from the LOCPOT file along any of the 3 primary axes. Useful for determining surface dipole moments and electric potentials on the interior of the material. Args: axis (int): 0 = x, 1 = y, 2 = z ylim (tuple): minimum and maximum potentials for the plot's y-axis. fmt (str): matplotlib format style. Check the matplotlib docs for options. """ ax = plt.figure(figsize=(16, 10)).gca() locpot = Locpot.from_file('LOCPOT') structure = Structure.from_file('CONTCAR') vd = VolumetricData(structure, locpot.data) abs_potentials = vd.get_average_along_axis(axis) vacuum_level = max(abs_potentials) vasprun = Vasprun('vasprun.xml') bs = vasprun.get_band_structure() if not bs.is_metal(): cbm = bs.get_cbm()['energy'] - vacuum_level vbm = bs.get_vbm()['energy'] - vacuum_level potentials = [potential - vacuum_level for potential in abs_potentials] axis_length = structure.lattice._lengths[axis] positions = np.arange(0, axis_length, axis_length / len(potentials)) ax.plot(positions, potentials, linewidth=2, color='k') ax.set_xlim(0, axis_length) ax.set_ylim(ylim[0], ylim[1]) ax.set_xticklabels( [r'$\mathrm{%s}$' % tick for tick in ax.get_xticks()], size=20) ax.set_yticklabels( [r'$\mathrm{%s}$' % tick for tick in ax.get_yticks()], size=20) ax.set_xlabel(r'$\mathrm{\AA}$', size=24) ax.set_ylabel(r'$\mathrm{V\/(eV)}$', size=24) if not bs.is_metal(): ax.text(ax.get_xlim()[1], cbm, r'$\mathrm{CBM}$', horizontalalignment='right', verticalalignment='bottom', size=20) ax.text(ax.get_xlim()[1], vbm, r'$\mathrm{VBM}$', horizontalalignment='right', verticalalignment='top', size=20) ax.fill_between(ax.get_xlim(), cbm, ax.get_ylim()[1], facecolor=plt.cm.jet(0.3), zorder=0, linewidth=0) ax.fill_between(ax.get_xlim(), ax.get_ylim()[0], vbm, facecolor=plt.cm.jet(0.7), zorder=0, linewidth=0) if fmt == "None": return ax else: plt.savefig('locpot.{}'.format(fmt)) plt.close()
def test_init(self): filepath = os.path.join(test_dir, 'LOCPOT') locpot = Locpot.from_file(filepath) self.assertAlmostEqual(-217.05226954, sum(locpot.get_average_along_axis(0))) self.assertAlmostEqual(locpot.get_axis_grid(0)[-1], 2.87629, 2) self.assertAlmostEqual(locpot.get_axis_grid(1)[-1], 2.87629, 2) self.assertAlmostEqual(locpot.get_axis_grid(2)[-1], 2.87629, 2)
def setUp(self): self.bl = Locpot.from_file(bl_path) self.dl = Locpot.from_file(dl_path) self.bs = self.bl.structure self.ds = self.dl.structure self.kbi = KumagaiBulkInit(self.bs, self.bl.dim, 15, optgamma=3.49423226983) self.kc = KumagaiCorrection(15, -3, 3.49423226983, self.kbi.g_sum, self.bs, self.ds, bulk_locpot=self.bl, defect_locpot=self.dl)
def get_band_edges(): """ Calculate the band edge locations relative to the vacuum level for a semiconductor. Returns: edges (dict): {'up_cbm': , 'up_vbm': , 'dn_cbm': , 'dn_vbm': , 'efermi'} """ # Vacuum level energy from LOCPOT. locpot = Locpot.from_file('LOCPOT') evac = max(locpot.get_average_along_axis(2)) vasprun = Vasprun('vasprun.xml') bs = vasprun.get_band_structure() eigenvals = vasprun.eigenvalues efermi = vasprun.efermi - evac if bs.is_spin_polarized: print(eigenvals[Spin.up]) print([e[0] - evac for e in eigenvals[Spin.up][0]]) up_cbm = min([ min([e[0] for e in eigenvals[Spin.up][i] if not e[1]]) for i in range(len(eigenvals[Spin.up])) ]) - evac up_vbm = max([ max([e[0] for e in eigenvals[Spin.up][i] if e[1]]) for i in range(len(eigenvals[Spin.up])) ]) - evac dn_cbm = min([ min([e[0] for e in eigenvals[Spin.down][i] if not e[1]]) for i in range(len(eigenvals[Spin.down])) ]) - evac dn_vbm = max([ max([e[0] for e in eigenvals[Spin.down][i] if e[1]]) for i in range(len(eigenvals[Spin.down])) ]) - evac edges = { 'up_cbm': up_cbm, 'up_vbm': up_vbm, 'dn_cbm': dn_cbm, 'dn_vbm': dn_vbm, 'efermi': efermi } else: cbm = bs.get_cbm()['energy'] - evac vbm = bs.get_vbm()['energy'] - evac edges = { 'up_cbm': cbm, 'up_vbm': vbm, 'dn_cbm': cbm, 'dn_vbm': vbm, 'efermi': efermi } return edges
def plot_hartree_pot_diff(self): #only plot the difference in planar averaged potentials import matplotlib.pyplot as plt fig = plt.figure() ax = fig.add_subplot(3,1,1) ax.set_title('Locpot planar averaged potential difference') bulkloc=Locpot.from_file(self._locpot_bulk) defloc=Locpot.from_file(self._locpot_defect) for axis in [0,1,2]: ax = fig.add_subplot(3, 1, axis+1) defect_axis = defloc.get_axis_grid(axis) defect_pot = defloc.get_average_along_axis(axis) pure_pot = bulkloc.get_average_along_axis(axis) latt_len = self._lengths[axis] ax.plot(defect_axis,defect_pot-pure_pot,'b', label='Defect-Bulk difference') ax.plot([self._frac_coords[axis] * latt_len], [0], 'or', markersize=4.0, label='Defect site') ax.set_ylabel("axis "+str(axis+1)) if not axis: ax.legend() ax.set_xlabel("distance (Angstrom)") #plt.savefig('locpotdiffplot.png') plt.show()
def process_LOCPOT(self, locpot_path=None, dir_to_save=None): """ This function process LOCPOT file obtained by VASP :param file_path: path to LOCPOT file :param dir_to_save: path to directory to save vacuum_lvl :return: nothing """ if locpot_path == None: locpot_path = self.locpot_path if dir_to_save == None: dir_to_save = self.dir_to_save locpot = Locpot.from_file(locpot_path) avr = locpot.get_average_along_axis(2) self.vacuum_lvl = np.max(avr) self.save('vacuum_lvl', dir_to_save)
def get_band_edges(): """ Calculate the band edge locations relative to the vacuum level for a semiconductor. For a metal, returns the fermi level. Returns: edges (dict): {'up_cbm': , 'up_vbm': , 'dn_cbm': , 'dn_vbm': , 'efermi'} """ # Vacuum level energy from LOCPOT. locpot = Locpot.from_file('LOCPOT') evac = max(locpot.get_average_along_axis(2)) vasprun = Vasprun('vasprun.xml') bs = vasprun.get_band_structure() eigenvals = vasprun.eigenvalues efermi = vasprun.efermi - evac if bs.is_metal(): edges = {'up_cbm': None, 'up_vbm': None, 'dn_cbm': None, 'dn_vbm': None, 'efermi': efermi} elif bs.is_spin_polarized: up_cbm = min( [min([e[0] for e in eigenvals[Spin.up][i] if not e[1]]) for i in range(len(eigenvals[Spin.up]))]) - evac up_vbm = max( [max([e[0] for e in eigenvals[Spin.up][i] if e[1]]) for i in range(len(eigenvals[Spin.up]))]) - evac dn_cbm = min( [min([e[0] for e in eigenvals[Spin.down][i] if not e[1]]) for i in range(len(eigenvals[Spin.down]))]) - evac dn_vbm = max( [max([e[0] for e in eigenvals[Spin.down][i] if e[1]]) for i in range(len(eigenvals[Spin.down]))]) - evac edges = {'up_cbm': up_cbm, 'up_vbm': up_vbm, 'dn_cbm': dn_cbm, 'dn_vbm': dn_vbm, 'efermi': efermi} else: cbm = bs.get_cbm()['energy'] - evac vbm = bs.get_vbm()['energy'] - evac edges = {'up_cbm': cbm, 'up_vbm': vbm, 'dn_cbm': cbm, 'dn_vbm': vbm, 'efermi': efermi} return edges
def get_band_edges(): """ Calculate the band edge locations relative to the vacuum level for a semiconductor. If spin-polarized, returns all 4 band edges. """ # Vacuum level energy from LOCPOT. locpot = Locpot.from_file('LOCPOT') evac = max(locpot.get_average_along_axis(2)) vasprun = Vasprun('vasprun.xml') efermi = vasprun.efermi - evac if vasprun.get_band_structure().is_spin_polarized: eigenvals = {Spin.up: [], Spin.down: []} for band in vasprun.eigenvalues: for eigenvalue in vasprun.eigenvalues[band]: eigenvals[band[0]].append(eigenvalue) up_cbm = min([e[0] for e in eigenvals[Spin.up] if not e[1]]) - evac up_vbm = max([e[0] for e in eigenvals[Spin.up] if e[1]]) - evac dn_cbm = min([e[0] for e in eigenvals[Spin.down] if not e[1]]) - evac dn_vbm = max([e[0] for e in eigenvals[Spin.down] if e[1]]) - evac edges = { 'up_cbm': up_cbm, 'up_vbm': up_vbm, 'dn_cbm': dn_cbm, 'dn_vbm': dn_vbm, 'efermi': efermi } else: bs = vasprun.get_band_structure() cbm = bs.get_cbm()['energy'] - evac vbm = bs.get_vbm()['energy'] - evac edges = {'cbm': cbm, 'vbm': vbm, 'efermi': efermi} return edges
def load_data_for_ecstm(self): """ This inner function load necessary data for ECSTM calculations :param outcar_path: str path to OUTCAR vasp file :param locpot_path: str path to LOCPOT vasp file :return: """ try: self.E = np.load(self.path_to_data + '/E.npy') self.DOS = np.load(self.path_to_data + '/DOS.npy') except: import preprocessing_v2 as preprocessing p = preprocessing.Preprocessing(working_folder=self.working_folder) p.process_OUTCAR() self.E, self.DOS, dE_new = p.get_DOS(self.energy_range, self.dE) if dE_new != self.dE: print( "WARNING! Something wrong with dE during DOS calculations") np.save(self.path_to_data + '/DOS.npy', self.DOS) np.save(self.path_to_data + '/E.npy', self.E) try: self.efermi = np.load(self.path_to_data + '/efermi.npy') except: print( f"ERROR! {self.path_to_data}/efermi.npy does not exist. Try to preprocess data" ) try: self.vacuum_lvl = np.load(self.path_to_data + 'vacuum_lvl.npy') except: from pymatgen.io.vasp.outputs import Locpot locpot = Locpot.from_file(self.working_folder + '/LOCPOT') avr = locpot.get_average_along_axis(2) self.vacuum_lvl = np.max(avr) np.save(self.path_to_data + '/vacuum_lvl.npy', self.vacuum_lvl)
def setUp(self): self.bl = Locpot.from_file(bl_path) self.dl = Locpot.from_file(dl_path) self.bs = self.bl.structure self.ds = self.dl.structure
corrections = {} include_freysoldt_corrections = True ############################################################################### ############################################################################### # Paths have to end with backslash. I would update everything with os.path.join but I'm lazy print('Path of pure calculation: "%s"\n' % pure_path) print('Path of pure LOCPOT calculation: "%s"\n' % path_to_pure_locpot) print('Path of pure DOS calculation: "%s"\n' % dos_path) print('Path of defects calculation: "%s"\n' % input_path) system_name = os.path.basename(os.path.dirname(input_path)) path_to_pure_locpot = os.path.join(path_to_pure_locpot, 'LOCPOT') structure_pure = Locpot.from_file(path_to_pure_locpot).structure natoms_sup = len(structure_pure.sites) vasprun_pure = Vasprun(pure_path + 'vasprun.xml') vasprun_dos = Vasprun(dos_path + 'vasprun.xml') (band_gap, cbm, vbm, is_band_gap_direct) = vasprun_dos.eigenvalue_band_properties natoms_pure = len(Locpot.from_file(path_to_pure_locpot).structure.sites) Epure = ((vasprun_pure.final_energy) / natoms_pure) * natoms_sup list_dir_vacancies = glob(input_path + '*/') # saving initial corrections dictionary corrections_init = corrections.copy() # list with defects defect_list = []
def electrostatic_potential(lattice_vector, filename='./LOCPOT', axis=2, make_csv=True, csv_fname='potential.csv', plt_fname='potential.png', dpi=300, **kwargs): """ Reads LOCPOT to get the planar and macroscopic potential in specified direction Args: lattice_vector (float): the periodicity of the slab filename (str): path to your locpot file, default='./LOCPOT' axis (int): direction in which the potential is investigated; a=0, b=1, c=2; default=2 make_csv (bool): makes a csv file with planar and macroscopic potential, default=True csv_fname (str): filename of the csv file, default='potential.csv' plt_fname (str): filename of the plot of potentials, controls the format, default='potential.png' dpi (int): dots per inch; default=300 Returns: csv file and plot of planar and macroscopic potential """ # Read potential and structure data lpt = Locpot.from_file(filename) struc = Structure.from_file(filename) # Planar potential planar = lpt.get_average_along_axis(axis) # Divide lattice parameter by no. of grid points in the direction resolution = struc.lattice.abc[axis] / lpt.dim[axis] # Get number of points over which the rolling average is evaluated points = int(lattice_vector / resolution) # Need extra points at the start and end of planar potential to evaluate the # macroscopic potential this makes use of the PBC where the end of one unit # cell coincides with start of the next one add_to_start = planar[(len(planar) - points):] add_to_end = planar[0:points] pfm_data = np.concatenate((add_to_start, planar, add_to_end)) pfm = pd.DataFrame(data=pfm_data, columns=['y']) # Macroscopic potential m_data = pfm.y.rolling(window=points, center=True).mean() macroscopic = m_data.iloc[points:(len(planar) + points)] macroscopic.reset_index(drop=True, inplace=True) # Make csv if make_csv: data = pd.DataFrame(data=planar, columns=['planar']) data['macroscopic'] = macroscopic data.to_csv(csv_fname, header=True, index=False) # Plot both planar and macroscopic, save figure fig, ax = plt.subplots() ax.plot(planar, label='planar') ax.plot(macroscopic, label='macroscopic') ax.legend() plt.ylabel('Potential / eV') plt.savefig(plt_fname, dpi=dpi, **kwargs)
def __init__(self, dielectric_tensor, q, gamma, g_sum, bulk_structure, defect_structure, energy_cutoff=520, madetol=0.0001, lengths=None, **kw): """ Args: dielectric_tensor: Macroscopic dielectric tensor Include ionic also if defect is relaxed, othewise ion clamped. Can be a matrix array or scalar. q: Charge associated with the defect. Typically integer gamma: Convergence parameter. Obtained from KumagaiBulkPart g_sum: value that is dependent on the Bulk only. Obtained from KumagaiBulkPart bulk_structure: bulk Pymatgen structure object. Need to specify this if using Outcar method for atomic site avg. (If you specify outcar files for bulk_file_path but dont specify structure then code will break) (TO DO: resolve this dumb dependency by being smarter about where structure comes from?) defect_structure: defect structure. Needed if using Outcar method energy_cutoff: Energy for plane wave cutoff (in eV). If not given, Materials Project default 520 eV is used. madetol: Tolerance for convergence of energy terms in eV lengths: Lengths of axes, for speeding up plotting slightly keywords: 1) bulk_locpot: Bulk Locpot file path OR Bulk Locpot defect_locpot: Defect Locpot file path or defect Locpot 2) (Or) bulk_outcar: Bulk Outcar file path defect_outcar: Defect outcar file path 3) defect_position: Defect position as a pymatgen Site object in the bulk supercell structure NOTE: this is optional but recommended, if not provided then analysis is done to find the defect position; this analysis has been rigorously tested, but has broken in an example with severe long range relaxation (at which point you probably should not be including the defect in your analysis...) """ if isinstance(dielectric_tensor, int) or \ isinstance(dielectric_tensor, float): self.dieltens = np.identity(3) * dielectric_tensor else: self.dieltens = np.array(dielectric_tensor) if 'bulk_locpot' in kw: if isinstance(kw['bulk_locpot'], Locpot): self.locpot_blk = kw['bulk_locpot'] else: self.locpot_blk = Locpot.from_file(kw['bulk_locpot']) if isinstance(kw['defect_locpot'], Locpot): self.locpot_def = kw['defect_locpot'] else: self.locpot_def = Locpot.from_file(kw['defect_locpot']) self.dim = self.locpot_blk.dim self.outcar_blk = None self.outcar_def = None self.do_outcar_method = False if 'bulk_outcar' in kw: self.outcar_blk = Outcar(str(kw['bulk_outcar'])) self.outcar_def = Outcar(str(kw['defect_outcar'])) self.do_outcar_method = True self.locpot_blk = None self.locpot_def = None self.dim = self.outcar_blk.ngf if 'defect_position' in kw: self._defpos = kw['defect_position'] else: self._defpos = None self.madetol = madetol self.q = q self.encut = energy_cutoff self.structure = bulk_structure self.defstructure = defect_structure self.gamma = gamma self.g_sum = g_sum self.lengths = lengths
def electrostatic_potential(locpot='./LOCPOT', lattice_vector=None, save_csv=True, csv_fname='potential.csv', save_plt=True, plt_fname='potential.png', **kwargs): """ Reads LOCPOT to get the planar and optionally macroscopic potential in c direction. Args: locpot (`str`, optional): The path to the LOCPOT file. Defaults to ``'./LOCPOT'`` lattice_vector (`float`, optional): The periodicity of the slab, calculates macroscopic potential with that periodicity save_csv (`bool`, optional): Saves to csv. Defaults to ``True``. csv_fname (`str`, optional): Filename of the csv file. Defaults to ``'potential.csv'``. save_plt (`bool`, optional): Make and save the plot of electrostatic potential. Defaults to ``True``. plt_fname (`str`, optional): Filename of the plot. Defaults to ``'potential.png'``. Returns: DataFrame """ # Read potential and structure data lpt = Locpot.from_file(locpot) struc = Structure.from_file(locpot) # Planar potential planar = lpt.get_average_along_axis(2) df = pd.DataFrame(data=planar, columns=['planar']) # Calculate macroscopic potential if lattice_vector is not None: # Divide lattice parameter by no. of grid points in the direction resolution = struc.lattice.abc[2] / lpt.dim[2] # Get number of points over which the rolling average is evaluated points = int(lattice_vector / resolution) # Need extra points at the start and end of planar potential to evaluate the # macroscopic potential this makes use of the PBC where the end of one unit # cell coincides with start of the next one add_to_start = planar[(len(planar) - points):] add_to_end = planar[0:points] pfm_data = np.concatenate((add_to_start, planar, add_to_end)) pfm = pd.DataFrame(data=pfm_data, columns=['y']) # Macroscopic potential m_data = pfm.y.rolling(window=points, center=True).mean() macroscopic = m_data.iloc[points:(len(planar) + points)] macroscopic.reset_index(drop=True, inplace=True) df['macroscopic'] = macroscopic # Get gradient of the plot - this is used for convergence testing, to make # sure the potential is actually flat df['gradient'] = np.gradient(df['planar']) # Plot and save the graph, save the csv or return the dataframe if save_plt: plot_electrostatic_potential(df=df, plt_fname=plt_fname, **kwargs) if save_csv: if not csv_fname.endswith('.csv'): csv_fname += '.csv' df.to_csv(csv_fname, header=True, index=False) else: return df
def pc(self, struct=None): """ Peform Electrostatic Correction note this ony needs structural info so struct input object speeds this calculation up equivalently fast if input Locpot is a locpot object """ logger = logging.getLogger(__name__) if type(struct) is Structure: s1 = struct else: if not type(self._purelocpot) is Locpot: logging.info('load Pure locpot') self._purelocpot = Locpot.from_file(self._purelocpot) s1 = self._purelocpot.structure ap = s1.lattice.get_cartesian_coords(1) logger.info('Running Freysoldt 2011 PC calculation (should be '\ 'equivalent to sxdefectalign)') logger.debug('defect lattice constants are (in angstroms)' \ + str(cleanlat(ap))) [a1, a2, a3] = ang_to_bohr * ap logging.debug( 'In atomic units, lat consts are (in bohr):' \ + str(cleanlat([a1, a2, a3]))) vol = np.dot(a1, np.cross(a2, a3)) #vol in bohr^3 #compute isolated energy step = 1e-4 encut1 = 20 #converge to some smaller encut first [eV] flag = 0 converge = [] while (flag != 1): eiso = 1. gcut = eV_to_k(encut1) #gcut is in units of 1/A g = step #initalize while g < (gcut + step): #simpson integration eiso += 4 * (self._q_model.rho_rec(g * g)**2) eiso += 2 * (self._q_model.rho_rec((g + step)**2)**2) g += 2 * step eiso -= self._q_model.rho_rec(gcut**2)**2 eiso *= (self._q**2) * step / (3 * round(np.pi, 6)) converge.append(eiso) if len(converge) > 2: if abs(converge[-1] - converge[-2]) < self._madetol: flag = 1 elif encut1 > self._encut: logger.error('Eiso did not converge before ' \ + str(self._encut) + ' eV') raise encut1 += 20 eiso = converge[-1] logger.debug('Eisolated : %f, converged at encut: %d', round(eiso, 5), encut1 - 20) #compute periodic energy; encut1 = 20 #converge to some smaller encut flag = 0 converge = [] while flag != 1: eper = 0.0 for g2 in generate_reciprocal_vectors_squared(a1, a2, a3, encut1): eper += (self._q_model.rho_rec(g2)**2) / g2 eper *= (self._q**2) * 2 * round(np.pi, 6) / vol eper += (self._q**2) *4* round(np.pi, 6) \ * self._q_model.rho_rec_limit0() / vol converge.append(eper) if len(converge) > 2: if abs(converge[-1] - converge[-2]) < self._madetol: flag = 1 elif encut1 > self._encut: logger.error('Eper did not converge before %d eV', self._encut) return encut1 += 20 eper = converge[-1] logger.info('Eperiodic : %f hartree, converged at encut %d eV', round(eper, 5), encut1 - 20) logger.info('difference (periodic-iso) is %f hartree', round(eper - eiso, 6)) logger.info('difference in (eV) is %f', round((eper - eiso) * hart_to_ev, 4)) PCfreycorr = round((eiso - eper) / self._dielectricconst * hart_to_ev, 6) logger.info('Defect Correction without alignment %f (eV): ', PCfreycorr) return PCfreycorr
CONTACR and LOCPOT files.") (options, args) = parser.parse_args() v = Vasprun('vasprun.xml') cdos = v.complete_dos element_dos = cdos.get_element_dos() plotter = DosPlotter() efermi = v.efermi if options.verbose: from pymatgen.core import Element from pymatgen.io.vasp.outputs import Locpot, Poscar from pymatgen.analysis.surface_analysis import WorkFunctionAnalyzer l = Locpot.from_file('LOCPOT') s = Poscar.from_file('CONTCAR') wf = WorkFunctionAnalyzer(s.structure, l.get_average_along_axis(1), efermi, shift=0) loc_vac = wf.vacuum_locpot for i in element_dos: element_dos[i].efermi = loc_vac plotter.add_dos_dict(element_dos) plt = plotter.get_plot(xlim=[-9, 1]) plt.plot([efermi - loc_vac, efermi - loc_vac], plt.ylim(),
def potalign(self, title=None, widthsample=1.0, axis=None, output_sr=False): """ For performing planar averaging potential alignment Accounts for defects in arbitrary positions title is for name of plot, if you dont want a plot then leave it as None widthsample is the width of the region in between defects where the potential alignment correction is averaged axis allows you to override the axis setting of class (good for quickly plotting multiple axes without having to reload Locpot) output_sr allows for output of the short range potential in the middle (sampled) region. (Good for delocalization analysis) """ logger = logging.getLogger(__name__) if axis is None: axis = self._axis else: axis = axis if not type(self._purelocpot) is Locpot: logger.debug('load pure locpot object') self._purelocpot = Locpot.from_file(self._purelocpot) if not type(self._deflocpot) is Locpot: logger.debug('load defect locpot object') self._deflocpot = Locpot.from_file(self._deflocpot) #determine location of defects blksite, defsite = find_defect_pos(self._purelocpot.structure, self._deflocpot.structure, defpos=self._defpos) if blksite is None and defsite is None: logger.error('Not able to determine defect site') return if blksite is None: logger.debug('Found defect to be Interstitial type at %s', repr(defsite)) elif defsite is None: logger.debug('Found defect to be Vacancy type at %s', repr(blksite)) else: logger.debug( 'Found defect to be antisite/substitution type at ' '%s in bulk, and %s in defect cell', repr(blksite), repr(defsite)) #It is important to do planar averaging at same position, otherwise #you can get rigid shifts due to atomic changes at far away from defect #note these are cartesian co-ordinate sites... if defsite is None: #vacancies self._pos = blksite else: #all else, do w.r.t defect site self._pos = defsite x = np.array(self._purelocpot.get_axis_grid(axis)) #angstrom nx = len(x) logging.debug('run Freysoldt potential alignment method') #perform potential alignment part pureavg = self._purelocpot.get_average_along_axis(axis) #eV defavg = self._deflocpot.get_average_along_axis(axis) #eV #now shift these planar averages to have defect at origin blklat = self._purelocpot.structure.lattice axfracval = blklat.get_fractional_coords(self._pos)[axis] axbulkval = axfracval * blklat.abc[axis] if axbulkval < 0: axbulkval += blklat.abc[axis] elif axbulkval > blklat.abc[axis]: axbulkval -= blklat.abc[axis] if axbulkval: for i in range(len(x)): if axbulkval < x[i]: break rollind = len(x) - i pureavg = np.roll(pureavg, rollind) defavg = np.roll(defavg, rollind) #if not self._silence: logger.debug('calculating lr part along planar avg axis') latt = self._purelocpot.structure.lattice reci_latt = latt.reciprocal_lattice dg = reci_latt.abc[axis] dg /= ang_to_bohr #convert to bohr to do calculation in atomic units v_G = np.empty(len(x), np.dtype('c16')) epsilon = self._dielectricconst # q needs to be that of the back ground v_G[0] = 4 * np.pi * -self._q / epsilon * self._q_model.rho_rec_limit0( ) for i in range(1, nx): if (2 * i < nx): g = i * dg else: g = (i - nx) * dg g2 = g * g v_G[i] = 4 * np.pi / (epsilon * g2) * -self._q * self._q_model.rho_rec(g2) if not (nx % 2): v_G[nx // 2] = 0 v_R = np.fft.fft(v_G) v_R_imag = np.imag(v_R) v_R /= (latt.volume * ang_to_bohr**3) v_R = np.real(v_R) * hart_to_ev max_imag_vr = v_R_imag.max() if abs(max_imag_vr) > self._madetol: logging.error('imaginary part found to be %s', repr(max_imag_vr)) sys.exit() #now get correction and do plots short = (defavg - pureavg - v_R) checkdis = int((widthsample / 2) / (x[1] - x[0])) mid = int(len(short) / 2) tmppot = [short[i] for i in range(mid - checkdis, mid + checkdis)] logger.debug('shifted defect position on axis (%s) to origin', repr(axbulkval)) logger.debug('means sampling region is (%f,%f)', x[mid - checkdis], x[mid + checkdis]) C = -np.mean(tmppot) logger.debug('C = %f', C) final_shift = [short[j] + C for j in range(len(v_R))] v_R = [elmnt - C for elmnt in v_R] logger.info('C value is averaged to be %f eV ', C) logger.info('Potentital alignment (-q*delta V): %f (eV)', -self._q * C) if title: # TODO: Make title optional and use a flag for plotting plotter = FreysoldtCorrPlotter( x, v_R, defavg - pureavg, final_shift, np.array([mid - checkdis, mid + checkdis])) if title != 'written': plotter.plot(title=title) else: # TODO: Make this default fname more defect specific so it doesnt # over write previous defect data written fname = 'FreyAxisData' # Extension is npz plotter.to_datafile(fname) if output_sr: return ((-self._q * C), tmppot) #pot align energy correction (eV) else: return (-self._q * C) #pot align energy correction (eV)
def plot_band_alignments(directories, run_type="PBE", fmt="pdf"): """ Plot CBM's and VBM's of all compounds together, relative to the band edges of H2O. Args: directories (list): list of the directory paths for materials to include in the plot. run_type (str): 'PBE' or 'HSE', so that the function knows which subdirectory to go into (pbe_bands or hse_bands). fmt (str): matplotlib format style. Check the matplotlib docs for options. """ if run_type == "HSE": subdirectory = "hse_bands" else: subdirectory = "pbe_bands" band_gaps = {} for directory in directories: if is_converged("{}/{}".format(directory, subdirectory)): os.chdir("{}/{}".format(directory, subdirectory)) band_structure = Vasprun("vasprun.xml").get_band_structure() band_gap = band_structure.get_band_gap() # Vacuum level energy from LOCPOT. locpot = Locpot.from_file("LOCPOT") evac = max(locpot.get_average_along_axis(2)) try: is_metal = False is_direct = band_gap["direct"] cbm = band_structure.get_cbm() vbm = band_structure.get_vbm() except AttributeError: cbm = None vbm = None is_metal = True is_direct = False band_gaps[directory] = {"CBM": cbm, "VBM": vbm, "Direct": is_direct, "Metal": is_metal, "E_vac": evac} os.chdir("../../") ax = plt.figure(figsize=(16, 10)).gca() x_max = len(band_gaps) * 1.315 ax.set_xlim(0, x_max) # Rectangle representing band edges of water. ax.add_patch(plt.Rectangle((0, -5.67), height=1.23, width=len(band_gaps), facecolor="#00cc99", linewidth=0)) ax.text(len(band_gaps) * 1.01, -4.44, r"$\mathrm{H+/H_2}$", size=20, verticalalignment="center") ax.text(len(band_gaps) * 1.01, -5.67, r"$\mathrm{O_2/H_2O}$", size=20, verticalalignment="center") x_ticklabels = [] y_min = -8 i = 0 # Nothing but lies. are_directs, are_indirects, are_metals = False, False, False for compound in [cpd for cpd in directories if cpd in band_gaps]: x_ticklabels.append(compound) # Plot all energies relative to their vacuum level. evac = band_gaps[compound]["E_vac"] if band_gaps[compound]["Metal"]: cbm = -8 vbm = -2 else: cbm = band_gaps[compound]["CBM"]["energy"] - evac vbm = band_gaps[compound]["VBM"]["energy"] - evac # Add a box around direct gap compounds to distinguish them. if band_gaps[compound]["Direct"]: are_directs = True linewidth = 5 elif not band_gaps[compound]["Metal"]: are_indirects = True linewidth = 0 # Metals are grey. if band_gaps[compound]["Metal"]: are_metals = True linewidth = 0 color_code = "#404040" else: color_code = "#002b80" # CBM ax.add_patch( plt.Rectangle( (i, cbm), height=-cbm, width=0.8, facecolor=color_code, linewidth=linewidth, edgecolor="#e68a00" ) ) # VBM ax.add_patch( plt.Rectangle( (i, y_min), height=(vbm - y_min), width=0.8, facecolor=color_code, linewidth=linewidth, edgecolor="#e68a00", ) ) i += 1 ax.set_ylim(y_min, -2) # Set tick labels ax.set_xticks([n + 0.4 for n in range(i)]) ax.set_xticklabels(x_ticklabels, family="serif", size=20, rotation=60) ax.set_yticklabels(ax.get_yticks(), family="serif", size=20) # Add a legend height = y_min if are_directs: ax.add_patch( plt.Rectangle( (i * 1.165, height), width=i * 0.15, height=(-y_min * 0.1), facecolor="#002b80", edgecolor="#e68a00", linewidth=5, ) ) ax.text( i * 1.24, height - y_min * 0.05, "Direct", family="serif", color="w", size=20, horizontalalignment="center", verticalalignment="center", ) height -= y_min * 0.15 if are_indirects: ax.add_patch( plt.Rectangle((i * 1.165, height), width=i * 0.15, height=(-y_min * 0.1), facecolor="#002b80", linewidth=0) ) ax.text( i * 1.24, height - y_min * 0.05, "Indirect", family="serif", size=20, color="w", horizontalalignment="center", verticalalignment="center", ) height -= y_min * 0.15 if are_metals: ax.add_patch( plt.Rectangle((i * 1.165, height), width=i * 0.15, height=(-y_min * 0.1), facecolor="#404040", linewidth=0) ) ax.text( i * 1.24, height - y_min * 0.05, "Metal", family="serif", size=20, color="w", horizontalalignment="center", verticalalignment="center", ) # Who needs axes? ax.spines["top"].set_visible(False) ax.spines["right"].set_visible(False) ax.spines["bottom"].set_visible(False) ax.spines["left"].set_visible(False) ax.yaxis.set_ticks_position("left") ax.xaxis.set_ticks_position("bottom") ax.set_ylabel("eV", family="serif", size=24) plt.savefig("band_alignments.{}".format(fmt), transparent=True) plt.close()
def plot_band_alignments(directories, run_type='PBE', fmt='pdf'): """ Plot CBM's and VBM's of all compounds together, relative to the band edges of H2O. Args: directories (list): list of the directory paths for materials to include in the plot. run_type (str): 'PBE' or 'HSE', so that the function knows which subdirectory to go into (pbe_bands or hse_bands). fmt (str): matplotlib format style. Check the matplotlib docs for options. """ if run_type == 'HSE': subdirectory = 'hse_bands' else: subdirectory = 'pbe_bands' band_gaps = {} for directory in directories: sub_dir = os.path.join(directory, subdirectory) if is_converged(sub_dir): os.chdir(sub_dir) band_structure = Vasprun('vasprun.xml').get_band_structure() band_gap = band_structure.get_band_gap() # Vacuum level energy from LOCPOT. locpot = Locpot.from_file('LOCPOT') evac = max(locpot.get_average_along_axis(2)) if not band_structure.is_metal(): is_direct = band_gap['direct'] cbm = band_structure.get_cbm() vbm = band_structure.get_vbm() else: cbm = None vbm = None is_direct = False band_gaps[directory] = {'CBM': cbm, 'VBM': vbm, 'Direct': is_direct, 'Metal': band_structure.is_metal(), 'E_vac': evac} os.chdir('../../') ax = plt.figure(figsize=(16, 10)).gca() x_max = len(band_gaps) * 1.315 ax.set_xlim(0, x_max) # Rectangle representing band edges of water. ax.add_patch(plt.Rectangle((0, -5.67), height=1.23, width=len(band_gaps), facecolor='#00cc99', linewidth=0)) ax.text(len(band_gaps) * 1.01, -4.44, r'$\mathrm{H+/H_2}$', size=20, verticalalignment='center') ax.text(len(band_gaps) * 1.01, -5.67, r'$\mathrm{O_2/H_2O}$', size=20, verticalalignment='center') x_ticklabels = [] y_min = -8 i = 0 # Nothing but lies. are_directs, are_indirects, are_metals = False, False, False for compound in [cpd for cpd in directories if cpd in band_gaps]: x_ticklabels.append(compound) # Plot all energies relative to their vacuum level. evac = band_gaps[compound]['E_vac'] if band_gaps[compound]['Metal']: cbm = -8 vbm = -2 else: cbm = band_gaps[compound]['CBM']['energy'] - evac vbm = band_gaps[compound]['VBM']['energy'] - evac # Add a box around direct gap compounds to distinguish them. if band_gaps[compound]['Direct']: are_directs = True linewidth = 5 elif not band_gaps[compound]['Metal']: are_indirects = True linewidth = 0 # Metals are grey. if band_gaps[compound]['Metal']: are_metals = True linewidth = 0 color_code = '#404040' else: color_code = '#002b80' # CBM ax.add_patch(plt.Rectangle((i, cbm), height=-cbm, width=0.8, facecolor=color_code, linewidth=linewidth, edgecolor="#e68a00")) # VBM ax.add_patch(plt.Rectangle((i, y_min), height=(vbm - y_min), width=0.8, facecolor=color_code, linewidth=linewidth, edgecolor="#e68a00")) i += 1 ax.set_ylim(y_min, 0) # Set tick labels ax.set_xticks([n + 0.4 for n in range(i)]) ax.set_xticklabels(x_ticklabels, family='serif', size=20, rotation=60) ax.set_yticklabels(ax.get_yticks(), family='serif', size=20) # Add a legend height = y_min if are_directs: ax.add_patch(plt.Rectangle((i*1.165, height), width=i*0.15, height=(-y_min*0.1), facecolor='#002b80', edgecolor='#e68a00', linewidth=5)) ax.text(i*1.24, height - y_min * 0.05, 'Direct', family='serif', color='w', size=20, horizontalalignment='center', verticalalignment='center') height -= y_min * 0.15 if are_indirects: ax.add_patch(plt.Rectangle((i*1.165, height), width=i*0.15, height=(-y_min*0.1), facecolor='#002b80', linewidth=0)) ax.text(i*1.24, height - y_min * 0.05, 'Indirect', family='serif', size=20, color='w', horizontalalignment='center', verticalalignment='center') height -= y_min * 0.15 if are_metals: ax.add_patch(plt.Rectangle((i*1.165, height), width=i*0.15, height=(-y_min*0.1), facecolor='#404040', linewidth=0)) ax.text(i*1.24, height - y_min * 0.05, 'Metal', family='serif', size=20, color='w', horizontalalignment='center', verticalalignment='center') # Who needs axes? ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) ax.spines['bottom'].set_visible(False) ax.spines['left'].set_visible(False) ax.yaxis.set_ticks_position('left') ax.xaxis.set_ticks_position('bottom') ax.set_ylabel('eV', family='serif', size=24) if fmt == "None": return ax else: plt.savefig('band_alignments.{}'.format(fmt), transparent=True) plt.close()
def plot_band_alignments(directories, run_type='PBE', fmt='pdf'): """ Plot CBM's and VBM's of all compounds together, relative to the band edges of H2O. Args: directories (list): list of the directory paths for materials to include in the plot. run_type (str): 'PBE' or 'HSE', so that the function knows which subdirectory to go into (pbe_bands or hse_bands). fmt (str): matplotlib format style. Check the matplotlib docs for options. """ if run_type == 'HSE': subdirectory = 'hse_bands' else: subdirectory = 'pbe_bands' band_gaps = {} for directory in directories: sub_dir = os.path.join(directory, subdirectory) if is_converged(sub_dir): os.chdir(sub_dir) band_structure = Vasprun('vasprun.xml').get_band_structure() band_gap = band_structure.get_band_gap() # Vacuum level energy from LOCPOT. locpot = Locpot.from_file('LOCPOT') evac = max(locpot.get_average_along_axis(2)) if not band_structure.is_metal(): is_direct = band_gap['direct'] cbm = band_structure.get_cbm() vbm = band_structure.get_vbm() else: cbm = None vbm = None is_direct = False band_gaps[directory] = { 'CBM': cbm, 'VBM': vbm, 'Direct': is_direct, 'Metal': band_structure.is_metal(), 'E_vac': evac } os.chdir('../../') ax = plt.figure(figsize=(16, 10)).gca() x_max = len(band_gaps) * 1.315 ax.set_xlim(0, x_max) # Rectangle representing band edges of water. ax.add_patch( plt.Rectangle((0, -5.67), height=1.23, width=len(band_gaps), facecolor='#00cc99', linewidth=0)) ax.text(len(band_gaps) * 1.01, -4.44, r'$\mathrm{H+/H_2}$', size=20, verticalalignment='center') ax.text(len(band_gaps) * 1.01, -5.67, r'$\mathrm{O_2/H_2O}$', size=20, verticalalignment='center') x_ticklabels = [] y_min = -8 i = 0 # Nothing but lies. are_directs, are_indirects, are_metals = False, False, False for compound in [cpd for cpd in directories if cpd in band_gaps]: x_ticklabels.append(compound) # Plot all energies relative to their vacuum level. evac = band_gaps[compound]['E_vac'] if band_gaps[compound]['Metal']: cbm = -8 vbm = -2 else: cbm = band_gaps[compound]['CBM']['energy'] - evac vbm = band_gaps[compound]['VBM']['energy'] - evac # Add a box around direct gap compounds to distinguish them. if band_gaps[compound]['Direct']: are_directs = True linewidth = 5 elif not band_gaps[compound]['Metal']: are_indirects = True linewidth = 0 # Metals are grey. if band_gaps[compound]['Metal']: are_metals = True linewidth = 0 color_code = '#404040' else: color_code = '#002b80' # CBM ax.add_patch( plt.Rectangle((i, cbm), height=-cbm, width=0.8, facecolor=color_code, linewidth=linewidth, edgecolor="#e68a00")) # VBM ax.add_patch( plt.Rectangle((i, y_min), height=(vbm - y_min), width=0.8, facecolor=color_code, linewidth=linewidth, edgecolor="#e68a00")) i += 1 ax.set_ylim(y_min, 0) # Set tick labels ax.set_xticks([n + 0.4 for n in range(i)]) ax.set_xticklabels(x_ticklabels, family='serif', size=20, rotation=60) ax.set_yticklabels(ax.get_yticks(), family='serif', size=20) # Add a legend height = y_min if are_directs: ax.add_patch( plt.Rectangle((i * 1.165, height), width=i * 0.15, height=(-y_min * 0.1), facecolor='#002b80', edgecolor='#e68a00', linewidth=5)) ax.text(i * 1.24, height - y_min * 0.05, 'Direct', family='serif', color='w', size=20, horizontalalignment='center', verticalalignment='center') height -= y_min * 0.15 if are_indirects: ax.add_patch( plt.Rectangle((i * 1.165, height), width=i * 0.15, height=(-y_min * 0.1), facecolor='#002b80', linewidth=0)) ax.text(i * 1.24, height - y_min * 0.05, 'Indirect', family='serif', size=20, color='w', horizontalalignment='center', verticalalignment='center') height -= y_min * 0.15 if are_metals: ax.add_patch( plt.Rectangle((i * 1.165, height), width=i * 0.15, height=(-y_min * 0.1), facecolor='#404040', linewidth=0)) ax.text(i * 1.24, height - y_min * 0.05, 'Metal', family='serif', size=20, color='w', horizontalalignment='center', verticalalignment='center') # Who needs axes? ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) ax.spines['bottom'].set_visible(False) ax.spines['left'].set_visible(False) ax.yaxis.set_ticks_position('left') ax.xaxis.set_ticks_position('bottom') ax.set_ylabel('eV', family='serif', size=24) if fmt == "None": return ax else: plt.savefig('band_alignments.{}'.format(fmt), transparent=True) plt.close()