def __init__(self, data, lpfac=10, energy_range=1.5, curvature=True): """ Args: data: A loader lpfac: the number of interpolation points in the real space. By default 10 gives 10 time more points in the real space than the number of kpoints given in reciprocal space. energy_range: usually the interpolation is not needed on the entire energy range but on a specific range around the fermi level. This energy in eV fix the range around the fermi level (E_fermi-energy_range,E_fermi+energy_range) of bands that will be interpolated and taken into account to calculate the transport properties. curvature: boolean value to enable/disable the calculation of second derivative related trasport properties (Hall coefficient). Example: data = VasprunLoader().from_file('vasprun.xml') bztInterp = BztInterpolator(data) """ self.data = data num_kpts = self.data.kpoints.shape[0] self.efermi = self.data.fermi self.nemin, self.nemax = self.data.bandana( emin=self.efermi - (energy_range * units.eV), emax=self.efermi + (energy_range * units.eV)) self.equivalences = sphere.get_equivalences(self.data.atoms, self.data.magmom, num_kpts * lpfac) self.coeffs = fite.fitde3D(self.data, self.equivalences) self.eband, self.vvband, self.cband = fite.getBTPbands( self.equivalences, self.coeffs, self.data.lattvec, curvature=curvature)
def __init__(self, data, lpfac=10, energy_range=1.5, curvature=True, save_bztInterp=False, load_bztInterp=False, save_bands=False, fname='bztInterp.json.gz'): """ Args: data: A loader lpfac: the number of interpolation points in the real space. By default 10 gives 10 time more points in the real space than the number of kpoints given in reciprocal space. energy_range: usually the interpolation is not needed on the entire energy range but on a specific range around the fermi level. This energy in eV fix the range around the fermi level (E_fermi-energy_range,E_fermi+energy_range) of bands that will be interpolated and taken into account to calculate the transport properties. curvature: boolean value to enable/disable the calculation of second derivative related trasport properties (Hall coefficient). save_bztInterp: Default False. If True coefficients and equivalences are saved in fname file. load_bztInterp: Default False. If True the coefficients and equivalences are loaded from fname file, not calculated. It can be faster than re-calculate them in some cases. save_bands: Default False. If True interpolated bands are also stored. It can be slower than interpolate them. Not recommended. fname: File path where to store/load from the coefficients and equivalences. Example: data = VasprunLoader().from_file('vasprun.xml') bztInterp = BztInterpolator(data) """ bands_loaded = False self.data = data num_kpts = self.data.kpoints.shape[0] self.efermi = self.data.fermi middle_gap_en = (self.data.cbm + self.data.vbm) / 2 self.accepted = self.data.bandana( emin=(middle_gap_en - energy_range) * units.eV, emax=(middle_gap_en + energy_range) * units.eV) if load_bztInterp: bands_loaded = self.load(fname) else: self.equivalences = sphere.get_equivalences( self.data.atoms, self.data.magmom, num_kpts * lpfac) self.coeffs = fite.fitde3D(self.data, self.equivalences) if not bands_loaded: self.eband, self.vvband, self.cband = fite.getBTPbands( self.equivalences, self.coeffs, self.data.lattvec, curvature=curvature) if save_bztInterp: self.save(fname, save_bands)
def get_partial_doses(self, tdos, eband_ud, spins, enr, npts_mu, T, progress): """ Return a CompleteDos object interpolating the projections tdos: total dos previously calculated npts_mu: number of energy points of the Dos T: parameter used to smooth the Dos progress: Default False, If True a progress bar is shown. """ if not self.data.proj: raise BoltztrapError("No projections loaded.") bkp_data_ebands = np.copy(self.data.ebands) pdoss = {} if progress: n_iter = np.prod( np.sum( [np.array(i.shape)[2:] for i in self.data.proj.values()])) t = tqdm(total=n_iter * 2) for spin, eb in zip(spins, eband_ud): for isite, site in enumerate(self.data.structure.sites): if site not in pdoss: pdoss[site] = {} for iorb, orb in enumerate(Orbital): if progress: t.update() if iorb == self.data.proj[spin].shape[-1]: break if orb not in pdoss[site]: pdoss[site][orb] = {} self.data.ebands = self.data.proj[spin][:, :, isite, iorb].T coeffs = fite.fitde3D(self.data, self.equivalences) proj, vvproj, cproj = fite.getBTPbands( self.equivalences, coeffs, self.data.lattvec) edos, pdos = BL.DOS(eb, npts=npts_mu, weights=np.abs(proj.real), erange=enr) if T: pdos = BL.smoothen_DOS(edos, pdos, T) pdoss[site][orb][spin] = pdos self.data.ebands = bkp_data_ebands return CompleteDos(self.data.structure, total_dos=tdos, pdoss=pdoss)
def __init__(self, data, lpfac=10, energy_range=1.5,curvature=True): self.data = data num_kpts = self.data.kpoints.shape[0] self.efermi = self.data.fermi self.nemin, self.nemax = self.data.bandana(emin=self.efermi - (energy_range * units.eV), emax=self.efermi + (energy_range * units.eV)) self.equivalences = sphere.get_equivalences(self.data.atoms, self.data.magmom, num_kpts * lpfac) self.coeffs = fite.fitde3D(self.data, self.equivalences) self.eband, self.vvband, self.cband = fite.getBTPbands(self.equivalences, self.coeffs, self.data.lattvec, curvature=curvature)
def __init__(self, data, lpfac=10, energy_range=1.5, curvature=True): self.data = data num_kpts = self.data.kpoints.shape[0] self.efermi = self.data.fermi self.nemin, self.nemax = self.data.bandana(emin=self.efermi - (energy_range * units.eV), emax=self.efermi + (energy_range * units.eV)) self.equivalences = sphere.get_equivalences(self.data.atoms, self.data.magmom, num_kpts * lpfac) self.coeffs = fite.fitde3D(self.data, self.equivalences) self.eband, self.vvband, self.cband = fite.getBTPbands(self.equivalences, self.coeffs, self.data.lattvec, curvature=curvature)
def get_partial_doses(self, tdos, npts_mu, T): """ Return a CompleteDos object interpolating the projections tdos: total dos previously calculated npts_mu: number of energy points of the Dos T: parameter used to smooth the Dos """ spin = self.data.spin if isinstance(self.data.spin, int) else 1 if not isinstance(self.data.proj, np.ndarray): raise BoltztrapError("No projections loaded.") bkp_data_ebands = np.copy(self.data.ebands) pdoss = {} # for spin in self.data.proj: for isite, site in enumerate(self.data.structure.sites): if site not in pdoss: pdoss[site] = {} for iorb, orb in enumerate(Orbital): if iorb == self.data.proj.shape[-1]: break if orb not in pdoss[site]: pdoss[site][orb] = {} self.data.ebands = self.data.proj[:, :, isite, iorb].T coeffs = fite.fitde3D(self.data, self.equivalences) proj, vvproj, cproj = fite.getBTPbands(self.equivalences, coeffs, self.data.lattvec) edos, pdos = BL.DOS(self.eband, npts=npts_mu, weights=np.abs(proj.real)) if T is not None: pdos = BL.smoothen_DOS(edos, pdos, T) pdoss[site][orb][Spin(spin)] = pdos self.data.ebands = bkp_data_ebands return CompleteDos(self.data.structure, total_dos=tdos, pdoss=pdoss)
def get_partial_doses(self, tdos, npts_mu, T): """ Return a CompleteDos object interpolating the projections tdos: total dos previously calculated npts_mu: number of energy points of the Dos T: parameter used to smooth the Dos """ spin = self.data.spin if isinstance(self.data.spin,int) else 1 if not isinstance(self.data.proj,np.ndarray): raise BoltztrapError("No projections loaded.") bkp_data_ebands = np.copy(self.data.ebands) pdoss = {} # for spin in self.data.proj: for isite, site in enumerate(self.data.structure.sites): if site not in pdoss: pdoss[site] = {} for iorb, orb in enumerate(Orbital): if iorb == self.data.proj.shape[-1]: break if orb not in pdoss[site]: pdoss[site][orb] = {} self.data.ebands = self.data.proj[:, :, isite, iorb].T coeffs = fite.fitde3D(self.data, self.equivalences) proj, vvproj, cproj = fite.getBTPbands(self.equivalences, coeffs, self.data.lattvec) edos, pdos = BL.DOS(self.eband, npts=npts_mu, weights=np.abs(proj.real)) if T is not None: pdos = BL.smoothen_DOS(edos, pdos, T) pdoss[site][orb][Spin(spin)] = pdos self.data.ebands = bkp_data_ebands return CompleteDos(self.data.structure, total_dos=tdos, pdoss=pdoss)
def boltztrap(dirname, bt2file, title, T): print(("\n\nWorking in %s for %s at %i K" % (dirname, title, T))) # If a ready-made file with the interpolation results is available, use it # Otherwise, create the file. if not os.path.exists(bt2file): # Load the input data = BTP.DFTData(dirname) # Select the interesting bands nemin, nemax = data.bandana(emin=data.fermi - .2, emax=data.fermi + .2) # Set up a k point grid with roughly five times the density of the input equivalences = sphere.get_equivalences(data.atoms, len(data.kpoints) * 5) # Perform the interpolation coeffs = fite.fitde3D(data, equivalences) # Save the result serialization.save_calculation( bt2file, data, equivalences, coeffs, serialization.gen_bt2_metadata(data, data.mommat is not None)) # Load the interpolation results print("Load the interpolation results") data, equivalences, coeffs, metadata = serialization.load_calculation( bt2file) # Reconstruct the bands print("Reconstruct the bands") lattvec = data.get_lattvec() eband, vvband, cband = fite.getBTPbands(equivalences, coeffs, lattvec) # Obtain the Fermi integrals for different chemical potentials at # room temperature. TEMP = np.array([T]) epsilon, dos, vvdos, cdos = BL.BTPDOS(eband, vvband, npts=4000) margin = 9. * units.BOLTZMANN * TEMP.max() mur_indices = np.logical_and(epsilon > epsilon.min() + margin, epsilon < epsilon.max() - margin) mur = epsilon[mur_indices] N, L0, L1, L2, Lm11 = BL.fermiintegrals(epsilon, dos, vvdos, mur=mur, Tr=TEMP, dosweight=data.dosweight) # Compute the Onsager coefficients from those Fermi integrals print("Compute the Onsager coefficients") UCvol = data.get_volume() sigma, seebeck, kappa, Hall = BL.calc_Onsager_coefficients( L0, L1, L2, mur, TEMP, UCvol) fermi = BL.solve_for_mu(epsilon, dos, data.nelect, T, data.dosweight) savedata[title + '-%s' % T] = { "sigma": sigma, "seebeck": seebeck, "kappa": kappa, "Hall": Hall, "mu": (mur - fermi) / BL.eV, "temp": T, "n": N[0] + data.nelect }
def interpolate_bands(self, interpolation_factor: float = 5, energy_cutoff: Optional[float] = None, nworkers: int = -1): """Gets a pymatgen band structure. Note, the interpolation mesh is determined using by ``interpolate_factor`` option in the ``Inteprolater`` constructor. The degree of parallelization is controlled by the ``nworkers`` option. Args: energy_cutoff: The energy cut-off to determine which bands are included in the interpolation. If the energy of a band falls within the cut-off at any k-point it will be included. For metals the range is defined as the Fermi level ± energy_cutoff. For gapped materials, the energy range is from the VBM - energy_cutoff to the CBM + energy_cutoff. nworkers: The number of processors used to perform the interpolation. If set to ``-1``, the number of workers will be set to the number of CPU cores. Returns: The interpolated electronic structure. """ coefficients = {} equivalences = sphere.get_equivalences(atoms=self._atoms, nkpt=self._kpoints.shape[0] * interpolation_factor, magmom=self._magmom) # get the interpolation mesh used by BoltzTraP2 interpolation_mesh = 2 * np.max(np.abs(np.vstack(equivalences)), axis=0) + 1 for spin in self._spins: energies = self._band_structure.bands[spin] * units.eV data = DFTData(self._kpoints, energies, self._lattice_matrix, mommat=self._mommat) coefficients[spin] = fite.fitde3D(data, equivalences) is_metal = self._band_structure.is_metal() nworkers = multiprocessing.cpu_count() if nworkers == -1 else nworkers # determine energy cutoffs if energy_cutoff and is_metal: min_e = self._band_structure.efermi - energy_cutoff max_e = self._band_structure.efermi + energy_cutoff elif energy_cutoff: min_e = self._band_structure.get_vbm()['energy'] - energy_cutoff max_e = self._band_structure.get_cbm()['energy'] + energy_cutoff else: min_e = min([ self._band_structure.bands[spin].min() for spin in self._spins ]) max_e = max([ self._band_structure.bands[spin].max() for spin in self._spins ]) energies = {} new_vb_idx = {} for spin in self._spins: ibands = np.any((self._band_structure.bands[spin] > min_e) & (self._band_structure.bands[spin] < max_e), axis=1) energies[spin] = fite.getBTPbands(equivalences, coefficients[spin][ibands], self._lattice_matrix, nworkers=nworkers)[0] # boltztrap2 gives energies in Rydberg, convert to eV energies[spin] /= units.eV if not is_metal: vb_idx = max( self._band_structure.get_vbm()["band_index"][spin]) # need to know the index of the valence band after discounting # bands during the interpolation. As ibands is just a list of # True/False, we can count the number of Trues up to # and including the VBM to get the new number of valence bands new_vb_idx[spin] = sum(ibands[:vb_idx + 1]) - 1 if is_metal: efermi = self._band_structure.efermi else: # if material is semiconducting, set Fermi level to middle of gap e_vbm = max( [np.max(energies[s][:new_vb_idx[s] + 1]) for s in self._spins]) e_cbm = min( [np.min(energies[s][new_vb_idx[s] + 1:]) for s in self._spins]) efermi = (e_vbm + e_cbm) / 2 atoms = AseAtomsAdaptor().get_atoms(self._band_structure.structure) mapping, grid = spglib.get_ir_reciprocal_mesh(interpolation_mesh, atoms, symprec=0.1) full_kpoints = grid / interpolation_mesh sort_idx = np.lexsort( (full_kpoints[:, 2], full_kpoints[:, 2] < 0, full_kpoints[:, 1], full_kpoints[:, 1] < 0, full_kpoints[:, 0], full_kpoints[:, 0] < 0)) reordered_kpoints = full_kpoints[sort_idx] return BandStructure(reordered_kpoints, energies, self._band_structure.structure.lattice, efermi, structure=self._structure), np.max(np.abs( np.vstack(equivalences)), axis=0)
doping_level = 0.01 tau = 1.0e-14 ecut, efcut, deltae, tmax, deltat, lpfac = 1.0 * RYDBERG, 0.3 * RYDBERG, 0.0005 * RYDBERG, 1200.0, 10.0, 5 # Load the input data = dft.DFTData(dirname) # Select the interesting bands nemin, nemax = data.bandana(emin=data.fermi - ecut, emax=data.fermi + ecut) # Set up a k point grid with roughly five times the density of the input equivalences = sphere.get_equivalences(data.atoms, len(data.kpoints) * lpfac) # Perform the interpolation coeffs = fite.fitde3D(data, equivalences) lattvec = data.get_lattvec() eband, vvband, cband = fite.getBTPbands(equivalences, coeffs, lattvec) epsilon, dos, vvdos, cdos = BL.BTPDOS( eband, vvband, erange=[data.fermi - ecut, data.fermi + ecut], npts=round(2 * ecut / deltae), scattering_model='uniform_tau') # Define the temperatures and chemical potentials we are interested in #Tr = np.arange(deltat, tmax + deltat / 2, deltat) #mur_indices = np.logical_and(epsilon > data.fermi - efcut, epsilon < data.fermi + efcut) #mur = epsilon[mur_indices] Tr = np.arange(deltat, tmax + deltat / 2, deltat) mur = np.empty_like(Tr) _nelect = data.nelect
def get_amset_data( self, energy_cutoff: Optional[float] = None, scissor: float = None, bandgap: float = None, symprec: float = 0.01, nworkers: int = -1, ) -> AmsetData: """Gets an AmsetData object using the interpolated bands. Note, the interpolation mesh is determined using by ``interpolate_factor`` option in the ``Inteprolater`` constructor. This method is much faster than the ``get_energies`` function but doesn't provide as much flexibility. The degree of parallelization is controlled by the ``nworkers`` option. Args: energy_cutoff: The energy cut-off to determine which bands are included in the interpolation. If the energy of a band falls within the cut-off at any k-point it will be included. For metals the range is defined as the Fermi level ± energy_cutoff. For gapped materials, the energy range is from the VBM - energy_cutoff to the CBM + energy_cutoff. scissor: The amount by which the band gap is scissored. Cannot be used in conjunction with the ``bandgap`` option. Has no effect for metallic systems. bandgap: Automatically adjust the band gap to this value. Cannot be used in conjunction with the ``scissor`` option. Has no effect for metallic systems. symprec: The symmetry tolerance used when determining the symmetry inequivalent k-points on which to interpolate. nworkers: The number of processors used to perform the interpolation. If set to ``-1``, the number of workers will be set to the number of CPU cores. Returns: The electronic structure (including energies, velocities, density of states and k-point information) as an AmsetData object. """ is_metal = self._band_structure.is_metal() if is_metal and (bandgap or scissor): raise ValueError("{} option set but system is metallic".format( "bandgap" if bandgap else "scissor")) if not self._interpolate_projections: raise ValueError( "Band structure projections needed to obtain full " "electronic structure. Reinitialise the " "interpolater with interpolate_projections=True") nworkers = multiprocessing.cpu_count() if nworkers == -1 else nworkers str_kmesh = "x".join(map(str, self.interpolation_mesh)) logger.info("Interpolation parameters:") log_list([ "k-point mesh: {}".format(str_kmesh), "energy cutoff: {} eV".format(energy_cutoff), ]) # only calculate the energies for the bands within the energy cutoff min_e, max_e = _get_energy_cutoffs(energy_cutoff, self._band_structure) energies = {} vvelocities = {} curvature = {} projections = defaultdict(dict) new_vb_idx = {} for spin in self._spins: bands = self._band_structure.bands[spin] ibands = np.any((bands > min_e) & (bands < max_e), axis=1) logger.info("Interpolating {} bands {}-{}".format( spin_name[spin], np.where(ibands)[0].min() + 1, np.where(ibands)[0].max() + 1, )) t0 = time.perf_counter() energies[spin], vvelocities[spin], curvature[ spin] = fite.getBTPbands( self._equivalences, self._coefficients[spin][ibands], self._lattice_matrix, curvature=True, nworkers=nworkers, ) log_time_taken(t0) if not is_metal: # Need the largest VB index. Sometimes the index of the band # containing the VBM is not the largest index of all VBs, so # find all bands with energies less than the VBM and count that # number vbm_energy = self._band_structure.get_vbm()["energy"] vb_idx = np.any(bands <= vbm_energy, axis=1).sum() - 1 # need to know the index of the valence band after discounting # bands during the interpolation (i.e., if energy_cutoff is # set). As ibands is just a list of True/False, we can count the # number of Trues up to and including the VBM to get the new # number of valence bands new_vb_idx[spin] = sum(ibands[:vb_idx + 1]) - 1 logger.info("Interpolating {} projections".format(spin_name[spin])) t0 = time.perf_counter() for label, proj_coeffs in self._projection_coefficients[ spin].items(): projections[spin][label] = fite.getBTPbands( self._equivalences, proj_coeffs[ibands], self._lattice_matrix, nworkers=nworkers, )[0] log_time_taken(t0) if is_metal: efermi = self._band_structure.efermi * units.eV scissor = 0.0 else: energies, scissor = _shift_energies( energies, new_vb_idx, scissor=scissor, bandgap=bandgap, return_scissor=True, ) # if material is semiconducting, set Fermi level to middle of gap efermi = _get_efermi(energies, new_vb_idx) # get the actual k-points used in the BoltzTraP2 interpolation # unfortunately, BoltzTraP2 doesn't expose this information so we # have to get it ourselves ir_kpts, _, full_kpts, ir_kpts_idx, ir_to_full_idx = get_kpoints( self.interpolation_mesh, self._band_structure.structure, symprec=symprec, return_full_kpoints=True, ) return AmsetData( self._band_structure.structure, energies, vvelocities, projections, self.interpolation_mesh, full_kpts, ir_kpts, ir_kpts_idx, ir_to_full_idx, efermi, is_metal, self._soc, vb_idx=new_vb_idx, scissor=scissor, # curvature=curvature, )
def interpolate_bands( self, interpolation_factor: float = 5, energy_cutoff: Optional[float] = None, nworkers: int = -1, ): """Gets a pymatgen band structure. Note, the interpolation mesh is determined using by ``interpolate_factor`` option in the ``Inteprolater`` constructor. The degree of parallelization is controlled by the ``nworkers`` option. Args: interpolation_factor: The factor by which the band structure will be interpolated. energy_cutoff: The energy cut-off to determine which bands are included in the interpolation. If the energy of a band falls within the cut-off at any k-point it will be included. For metals the range is defined as the Fermi level ± energy_cutoff. For gapped materials, the energy range is from the VBM - energy_cutoff to the CBM + energy_cutoff. nworkers: The number of processors used to perform the interpolation. If set to ``-1``, the number of workers will be set to the number of CPU cores. Returns: The interpolated electronic structure. """ coefficients = {} equivalences = sphere.get_equivalences( atoms=self._atoms, nkpt=self._kpoints.shape[0] * interpolation_factor, magmom=self._magmom, ) # get the interpolation mesh used by BoltzTraP2 interpolation_mesh = 2 * np.max(np.abs(np.vstack(equivalences)), axis=0) + 1 for spin in self._spins: energies = self._band_structure.bands[spin] * eV data = DFTData(self._kpoints, energies, self._lattice_matrix, mommat=self._mommat) coefficients[spin] = fite.fitde3D(data, equivalences) is_metal = self._band_structure.is_metal() nworkers = multiprocessing.cpu_count() if nworkers == -1 else nworkers # determine energy cutoffs if energy_cutoff and is_metal: min_e = self._band_structure.efermi - energy_cutoff max_e = self._band_structure.efermi + energy_cutoff elif energy_cutoff: min_e = self._band_structure.get_vbm()["energy"] - energy_cutoff max_e = self._band_structure.get_cbm()["energy"] + energy_cutoff else: min_e = min([ self._band_structure.bands[spin].min() for spin in self._spins ]) max_e = max([ self._band_structure.bands[spin].max() for spin in self._spins ]) energies = {} new_vb_idx = {} for spin in self._spins: ibands = np.any( (self._band_structure.bands[spin] > min_e) & (self._band_structure.bands[spin] < max_e), axis=1, ) energies[spin] = fite.getBTPbands( equivalences, coefficients[spin][ibands], self._lattice_matrix, nworkers=nworkers, )[0] # boltztrap2 gives energies in Rydberg, convert to eV energies[spin] /= eV if not is_metal: vb_energy = self._band_structure.get_vbm()["energy"] spin_bands = self._band_structure.bands[spin] below_vbm = np.any(spin_bands < vb_energy, axis=1) spin_vb_idx = np.max(np.where(below_vbm)[0]) # need to know the index of the valence band after discounting # bands during the interpolation. As ibands is just a list of # True/False, we can count the number of Trues up to # and including the VBM to get the new number of valence bands new_vb_idx[spin] = sum(ibands[:spin_vb_idx + 1]) - 1 if is_metal: efermi = self._band_structure.efermi else: # if material is semiconducting, set Fermi level to middle of gap warnings.warn( "The Fermi energy may be different to that in the vasprun.xml file," " due to the material being a semiconductor. The Fermi level has been " "set to midway between the top of the valence band and the bottom of " "the conduction band.", category=None, stacklevel=1, source=None, ) e_vbm = max( [np.max(energies[s][:new_vb_idx[s] + 1]) for s in self._spins]) e_cbm = min( [np.min(energies[s][new_vb_idx[s] + 1:]) for s in self._spins]) efermi = (e_vbm + e_cbm) / 2 atoms = AseAtomsAdaptor().get_atoms(self._band_structure.structure) mapping, grid = spglib.get_ir_reciprocal_mesh(interpolation_mesh, atoms, symprec=0.1) kpoints = grid / interpolation_mesh # sort energies so they have the same order as the k-points generated by spglib sort_idx = sort_boltztrap_to_spglib(kpoints) energies = {s: ener[:, sort_idx] for s, ener in energies.items()} rlat = self._band_structure.structure.lattice.reciprocal_lattice interp_band_structure = BandStructure(kpoints, energies, rlat, efermi, structure=self._structure) return interp_band_structure, interpolation_mesh