def _get_interpolater(self, n_idx, t_idx): # interpolater expects energies in eV and structure in angstrom energies = {s: e * hartree_to_ev for s, e in self.energies.items()} structure = get_angstrom_structure(self.structure) bs = BandStructure( self.ir_kpoints, energies, structure.lattice, self.efermi * hartree_to_ev, structure=structure, ) nelect = sum([idx for idx in self.vb_idx.values()]) props = defaultdict(dict) for spin in self.spins: # easier to interpolate the log props[spin]["rates"] = np.log10( np.sum(self.scattering_rates[spin][:, n_idx, t_idx], axis=0)) return Interpolater( bs, nelect, interpolation_factor=self.interpolation_factor, soc=self.soc, other_properties=props, )
def get_BandStructure(self): eigenvalues = self.eigenvalues eigendict = {Spin.up: eigenvalues} return BandStructure(efermi=self.fermi_energy, eigenvals=eigendict, kpoints=self.k_points, structure=self.get_Structure(), lattice=self.get_reciprocal_Lattice())
def expand_bandstructure( bandstructure, symprec=defaults["symprec"], time_reversal=True ): kpoints = get_kpoints_from_bandstructure(bandstructure) full_kpoints, _, _, _, _, kp_mapping = expand_kpoints( bandstructure.structure, kpoints, symprec=symprec, time_reversal=time_reversal, return_mapping=True, ) return BandStructure( full_kpoints, {s: b[:, kp_mapping] for s, b in bandstructure.bands.items()}, bandstructure.structure.lattice.reciprocal_lattice, bandstructure.efermi, structure=bandstructure.structure, )
def _get_interpolater(self, n_idx, t_idx, mode="linear"): props = defaultdict(dict) for spin in self.spins: # calculate total rate spin_rates = np.sum(self.scattering_rates[spin][:, n_idx, t_idx], axis=0) # easier to interpolate the log log_rates = np.log10(spin_rates) # # handle rates that close to numerical noise log_rates[log_rates > 18] = 15 log_rates[np.isnan(log_rates)] = 15 # map to full k-point mesh props[spin]["rates"] = log_rates if mode == "linear": return _LinearBandStructureInterpolator( self.kpoints, self.ir_to_full_kpoint_mapping, self.energies, self.structure, self.efermi, props, ) elif mode == "fourier": bs = BandStructure( self.ir_kpoints, self.energies, self.structure.lattice, self.efermi, structure=self.structure, ) return Interpolator( bs, self.num_electrons, interpolation_factor=self.interpolation_factor, soc=self.soc, other_properties=props, ) raise ValueError("Unknown interpolation mode; should be 'linear' or 'fourier'.")
def rotate_bandstructure(bandstructure: BandStructure, frac_symop: SymmOp): """Won't rotate projections...""" kpoints = get_kpoints_from_bandstructure(bandstructure) recip_rot = frac_symop.rotation_matrix.T rot_kpoints = np.dot(recip_rot, kpoints.T).T # map to first BZ, use VASP zone boundary convention rot_kpoints = kpoints_to_first_bz(rot_kpoints, negative_zone_boundary=False) # rotate structure structure = bandstructure.structure.copy() structure.apply_operation(frac_symop, fractional=True) return BandStructure( rot_kpoints, bandstructure.bands, structure.lattice.reciprocal_lattice, bandstructure.efermi, structure=structure, )
def get_reconstructed_band_structure(list_bs, efermi=None): """Combine a list of band structures into a single band structure. This is typically very useful when you split non self consistent band structure runs in several independent jobs and want to merge back the results. This method will also ensure that any BandStructure objects will contain branches. Args: list_bs (:obj:`list` of \ :obj:`~pymatgen.electronic_structure.bandstructure.BandStructure` \ or :obj:`~pymatgen.electronic_structure.bandstructure.BandStructureSymmLine`): The band structures. efermi (:obj:`float`, optional): The Fermi energy of the reconstructed band structure. If `None`, an average of all the Fermi energies across all band structures is used. Returns: :obj:`pymatgen.electronic_structure.bandstructure.BandStructure` or \ :obj:`pymatgen.electronic_structure.bandstructureBandStructureSymmLine`: A band structure object. The type depends on the type of the band structures in ``list_bs``. """ if efermi is None: efermi = sum([b.efermi for b in list_bs]) / len(list_bs) kpoints = [] labels_dict = {} rec_lattice = list_bs[0].lattice_rec nb_bands = min([list_bs[i].nb_bands for i in range(len(list_bs))]) kpoints = np.concatenate([[k.frac_coords for k in bs.kpoints] for bs in list_bs]) dicts = [bs.labels_dict for bs in list_bs] labels_dict = {k: v.frac_coords for d in dicts for k, v in d.items()} # pymatgen band structure objects support branches. These are formed when # two kpoints with the same label are next to each other. This bit of code # will ensure that the band structure will contain branches, if it doesn't # already. dup_ids = [] for i, k in enumerate(kpoints): dup_ids.append(i) if (tuple(k) in tuple(map(tuple, labels_dict.values())) and i != 0 and i != len(kpoints) - 1 and (not np.array_equal(kpoints[i + 1], k) or not np.array_equal(kpoints[i - 1], k))): dup_ids.append(i) kpoints = kpoints[dup_ids] eigenvals = {} eigenvals[Spin.up] = np.concatenate( [bs.bands[Spin.up][:nb_bands] for bs in list_bs], axis=1) eigenvals[Spin.up] = eigenvals[Spin.up][:, dup_ids] if list_bs[0].is_spin_polarized: eigenvals[Spin.down] = np.concatenate( [bs.bands[Spin.down][:nb_bands] for bs in list_bs], axis=1) eigenvals[Spin.down] = eigenvals[Spin.up][:, dup_ids] projections = {} if len(list_bs[0].projections) != 0: projs = [bs.projections[Spin.up][:nb_bands][dup_ids] for bs in list_bs] projections[Spin.up] = np.concatenate(projs, axis=1)[:, dup_ids] if list_bs[0].is_spin_polarized: projs = [ bs.projections[Spin.down][:nb_bands][dup_ids] for bs in list_bs ] projections[Spin.down] = np.concatenate(projs, axis=1)[:, dup_ids] if isinstance(list_bs[0], BandStructureSymmLine): return BandStructureSymmLine(kpoints, eigenvals, rec_lattice, efermi, labels_dict, structure=list_bs[0].structure, projections=projections) else: return BandStructure(kpoints, eigenvals, rec_lattice, efermi, labels_dict, structure=list_bs[0].structure, projections=projections)
def get_band_structure( vasprun: Vasprun, zero_weighted: str = defaults["zero_weighted_kpoints"] ) -> BandStructure: """ Get a band structure from a Vasprun object. This can ensure that if the calculation contains zero-weighted k-points then the weighted k-points will be discarded (helps with hybrid calculations). Also ensures that the Fermi level is set correctly. Args: vasprun: A vasprun object. zero_weighted: How to handle zero-weighted k-points if they are present in the calculation. Options are: - "keep": Keep zero-weighted k-points in the band structure. - "drop": Drop zero-weighted k-points, keeping only the weighted k-points. - "prefer": Drop weighted-kpoints if zero-weighted k-points are present in the calculation (useful for cheap hybrid calculations). Returns: A band structure. """ # first check if Fermi level crosses a band k_idx = get_zero_weighted_kpoint_indices(vasprun, mode=zero_weighted) kpoints = np.array(vasprun.actual_kpoints)[k_idx] projections = {} eigenvalues = {} for spin, spin_eigenvalues in vasprun.eigenvalues.items(): # discard weight and set shape nbands, nkpoints eigenvalues[spin] = spin_eigenvalues[k_idx, :, 0].transpose(1, 0) if vasprun.projected_eigenvalues: # is nkpoints, nbands, nion, norb; we need nbands, nkpoints, norb, nion spin_projections = vasprun.projected_eigenvalues[spin] projections[spin] = spin_projections[k_idx].transpose(1, 0, 3, 2) # finding the Fermi level is quite painful, as VASP can sometimes put it slightly # inside a band fermi_crosses_band = False for spin_eigenvalues in eigenvalues.values(): eigs_below = np.any(spin_eigenvalues < vasprun.efermi, axis=1) eigs_above = np.any(spin_eigenvalues > vasprun.efermi, axis=1) if np.any(eigs_above & eigs_below): fermi_crosses_band = True # if the Fermi level crosses a band, the eigenvalue band properties is a more # reliable way to check whether this is a real effect bandgap, cbm, vbm, _ = vasprun.eigenvalue_band_properties if not fermi_crosses_band: # safe to use VASP fermi level efermi = vasprun.efermi elif fermi_crosses_band and bandgap == 0: # it is actually a metal efermi = vasprun.efermi else: # Set Fermi level half way between valence and conduction bands efermi = (cbm + vbm) / 2 return BandStructure( kpoints, eigenvalues, vasprun.final_structure.lattice.reciprocal_lattice, efermi, structure=vasprun.final_structure, projections=projections, )
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)
) #,ionic_step_skip=1,ionic_step_offset=1,parse_dos=True,parse_eigen=True,occu_tol=1e-8) efermi = vasp.efermi eigenvals = vasp.eigenvalues bs = vasp.get_band_structure(kpoints_filename='KPOINTS', efermi=efermi, line_mode=True) #kpoints_modes = Kpoints_support_modes(3) #Line mode kpoints = Kpoints.from_file('KPOINTS') poscar = Poscar.from_file('POSCAR') incar = Incar.from_file(('INCAR')) struc = IStructure.from_file('POSCAR') lattice = struc.lattice labels = kpoints.labels space_group = struc.get_space_group_info() #coords = struc.frac_coords() BS = BandStructure(kpoints.kpts, eigenvals, lattice, efermi, structure=struc) #kpoints.kpts labels_dict = BS.labels_dict BSSL = BandStructureSymmLine(kpoints.kpts, eigenvals, lattice, efermi, labels_dict, structure=struc) b = vasp.eigenvalue_band_properties vbm = BS.get_vbm() cbm = BS.get_cbm() print(b, efermi, 'cbm:', bs.get_cbm(), 'vbm:', bs.get_vbm()) #,cbm,vbm#BSSL.get_branch(90)#,kpoints.kpts #print a.eigenvalue_band_properties,fermi #vbm = a.eigenvalue_band_properties
def get_band_structure( self, kpoint_mesh: Union[float, int, List[int]], energy_cutoff: Optional[float] = None, scissor: Optional[float] = None, bandgap: Optional[float] = None, symprec: float = 0.01, ) -> BandStructure: """Calculates the density of states using the interpolated bands. Args: kpoint_mesh: The k-point mesh as a 1x3 array. E.g.,``[6, 6, 6]``. Alternatively, if a single value is provided this will be treated as a reciprocal density and the k-point mesh dimensions generated automatically. 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. Returns: The density of states. """ ir_kpoints, weights, full_kpoints, ir_kpoints_idx, ir_to_full_idx = get_kpoints( kpoint_mesh, self._band_structure.structure, symprec=symprec, return_full_kpoints=True, ) energies = self.get_energies( ir_kpoints, scissor=scissor, bandgap=bandgap, energy_cutoff=energy_cutoff, atomic_units=True, ) energies = { s: bands[:, ir_to_full_idx] / units.eV for s, bands in energies.items() } return BandStructure( full_kpoints, energies, self._band_structure.structure.lattice, self._band_structure.efermi, coords_are_cartesian=True, structure=self._band_structure.structure, )
def bs_graph(rawdatadir, savedir, e_fermi, soc=False): run = BSVasprun("{}/vasprun.xml".format(rawdatadir), parse_projected_eigen=True) bs = run.get_band_structure(efermi=e_fermi, line_mode=True, force_hybrid_mode=True) bsplot = BSPlotter(bs) # Get the plot bsplot.get_plot(vbm_cbm_marker=True, ylim=(-1.5, 1.5), zero_to_efermi=True) bs_graph.e_fermi = float(bs.efermi) bs_graph.band_gap = float(bs.get_band_gap()["energy"]) ax = plt.gca() xlim = ax.get_xlim() ylim = ax.get_ylim() ax.hlines(0, xlim[0], xlim[1], linestyle="--", color="black") ax.tick_params(labelsize=20) if not soc: ax.plot((), (), "r-", label="spin up") ax.plot((), (), "b-", label="spin down") ax.legend(fontsize=16, loc="upper left") plt.savefig("{}/BSGraph".format(savedir)) plt.close() if not soc: # Print quick info about band gap (source: vasprun.xml) #print(bs_graph.e_fermi) #print(bs_graph.band_gap) # Get quick info about band gap (source: EIGENVAL) eigenval = Eigenval("{}/EIGENVAL".format(rawdatadir)) bs_graph.band_properties = eigenval.eigenvalue_band_properties # Get detailed info about band gap and CB/VB in each spin channel # (source: EIGENVAL) bs_graph.eigenvalues = eigenval.eigenvalues bs_graph.kpoints = eigenval.kpoints poscar = Poscar.from_file("{}/POSCAR".format(rawdatadir)) bs_graph.lattice = poscar.structure.lattice.reciprocal_lattice bs_graph.eigenvalues[Spin.up] = bs_graph.eigenvalues[ Spin.up][:, :, :-1] bs_graph.eigenvalues[Spin.down] = bs_graph.eigenvalues[ Spin.down][:, :, :-1] bs_graph.eigenvalues[Spin.up] = bs_graph.eigenvalues[Spin.up][:, :, 0] bs_graph.eigenvalues[Spin.down] = bs_graph.eigenvalues[Spin.down][:, :, 0] bs_graph.eigenvalues[Spin.up] = \ np.transpose(bs_graph.eigenvalues[Spin.up]) bs_graph.eigenvalues[Spin.down] = \ np.transpose(bs_graph.eigenvalues[Spin.down]) bs = BandStructure(bs_graph.kpoints, bs_graph.eigenvalues, bs_graph.lattice, bs_graph.e_fermi) bs_graph.vbm = bs.get_vbm()["energy"] bs_graph.cbm = bs.get_cbm()["energy"] bs_graph.electronic_gap = bs.get_band_gap()["energy"] bs_graph.direct = bs.get_band_gap()["direct"] if bs_graph.vbm and bs_graph.cbm and bs_graph.electronic_gap: bs_graph.gap_by_spin = bs.get_direct_band_gap_dict() return
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