def read_dos(bands_file, pdos_file=None, cell_file=None, bin_width=0.01, gaussian=None, padding=None, emin=None, emax=None, efermi_to_vbm=True, lm_orbitals=None, elements=None, atoms=None, total_only=False): """Convert DOS data from CASTEP .bands file to Pymatgen/Sumo format The data is binned into a regular series using np.histogram Args: bands_file (:obj:`str`): Path to CASTEP prefix.bands output file. The k-point positions, weights and eigenvalues are read from this file. pdos_bin (:obj:`str`): Path to CASTEP prefix.pdos_bin output file. The weights of projected density of states are read from this file. bin_width (:obj:`float`, optional): Spacing for DOS energy axis gaussian (:obj:`float` or None, optional): Width of Gaussian broadening function padding (:obj:`float`, optional): Energy range above and below occupied region. (This is not used if xmin and xmax are set.) emin (:obj:`float`, optional): Minimum energy value for output DOS) emax (:obj:`float`, optional): Maximum energy value for output DOS efermi_to_vbm (:obj:`bool`, optional): If a bandgap is detected, modify the stored Fermi energy so that it lies at the VBM. Returns: (:obj:`pymatgen.electronic_structure.dos.Dos`, dict) where the dict is either empty or contains a PDOS arranged:: {species: {orbital: Dos}} """ header = _read_bands_header_verbose(bands_file) logging.info("Reading band eigenvalues...") _, weights, eigenvalues = read_bands_eigenvalues(bands_file, header) calc_efermi = header['e_fermi'][0] * _ry_to_ev * 2 if efermi_to_vbm and not _is_metal(eigenvalues, calc_efermi): logging.info("Setting energy zero to VBM") efermi = _get_vbm(eigenvalues, calc_efermi) else: logging.info("Setting energy zero to Fermi energy") efermi = calc_efermi emin_data = min(eigenvalues[Spin.up].flatten()) emax_data = max(eigenvalues[Spin.up].flatten()) if Spin.down in eigenvalues: emin_data = min(emin_data, min(eigenvalues[Spin.down].flatten())) emax_data = max(emax_data, max(eigenvalues[Spin.down].flatten())) if padding is None and gaussian: padding = gaussian * 3 elif padding is None: padding = 0.5 if emin is None: emin = emin_data - padding if emax is None: emax = emax_data + padding # Shift sampling window to account for zeroing at VBM/EFermi emin += efermi emax += efermi bins = np.arange(emin, emax + bin_width, bin_width) energies = (bins[1:] + bins[:-1]) / 2 # Add rows to weights for each band so they are aligned with eigenval data weights = weights * np.ones([eigenvalues[Spin.up].shape[0], 1]) dos_data = { spin: np.histogram(eigenvalue_set, bins=bins, weights=weights)[0] for spin, eigenvalue_set in eigenvalues.items() } dos = Dos(efermi, energies, dos_data) if pdos_file is not None and not total_only: if cell_file is None: raise OSError(f'Cell file {cell_file} not found: this must be ' 'provided for PDOS.') pdos_raw = compute_pdos(pdos_file, eigenvalues, weights, bins) # Also we, need to read the structure, but have it sorted with increasing # atomic numbers structure = CastepCell.from_file( cell_file).structure.get_sorted_structure( key=lambda x: x.species.elements[0].Z) pdoss = {} for isite, site in enumerate(structure.sites): pdoss[site] = pdos_raw[isite] # Get the pdos dictionary for potting pdos = get_pdos(CompleteDos(structure, dos, pdoss), lm_orbitals=lm_orbitals, elements=elements, atoms=atoms) # Smear the PDOS for orbs in pdos.values(): for dtmp in orbs.values(): if gaussian: dtmp.densities = dtmp.get_smeared_densities(gaussian) else: pdos = {} if gaussian: dos.densities = dos.get_smeared_densities(gaussian) return dos, pdos
def read_tdos(bands_file, bin_width=0.01, gaussian=None, padding=None, emin=None, emax=None, efermi_to_vbm=True): """Convert DOS data from CASTEP .bands file to Pymatgen/Sumo format The data is binned into a regular series using np.histogram Args: bands_file (:obj:`str`): Path to CASTEP prefix.bands output file. The k-point positions, weights and eigenvalues are read from this file. bin_width (:obj:`float`, optional): Spacing for DOS energy axis gaussian (:obj:`float` or None, optional): Width of Gaussian broadening function padding (:obj:`float`, optional): Energy range above and below occupied region. (This is not used if xmin and xmax are set.) emin (:obj:`float`, optional): Minimum energy value for output DOS) emax (:obj:`float`, optional): Maximum energy value for output DOS efermi_to_vbm (:obj:`bool`, optional): If a bandgap is detected, modify the stored Fermi energy so that it lies at the VBM. Returns: :obj:`pymatgen.electronic_structure.dos.Dos` """ header = _read_bands_header_verbose(bands_file) logging.info("Reading band eigenvalues...") _, weights, eigenvalues = read_bands_eigenvalues(bands_file, header) calc_efermi = header['e_fermi'][0] * _ry_to_ev * 2 if efermi_to_vbm and not _is_metal(eigenvalues, calc_efermi): logging.info("Setting energy zero to VBM") efermi = _get_vbm(eigenvalues, calc_efermi) else: logging.info("Setting energy zero to Fermi energy") efermi = calc_efermi emin_data = min(eigenvalues[Spin.up].flatten()) emax_data = max(eigenvalues[Spin.up].flatten()) if Spin.down in eigenvalues: emin_data = min(emin_data, min(eigenvalues[Spin.down].flatten())) emax_data = max(emax_data, max(eigenvalues[Spin.down].flatten())) if padding is None and gaussian: padding = gaussian * 3 elif padding is None: padding = 0.5 if emin is None: emin = emin_data - padding if emax is None: emax = emax_data + padding # Shift sampling window to account for zeroing at VBM/EFermi emin += efermi emax += efermi bins = np.arange(emin, emax + bin_width, bin_width) energies = (bins[1:] + bins[:-1]) / 2 # Add rows to weights for each band so they are aligned with eigenval data weights = weights * np.ones([eigenvalues[Spin.up].shape[0], 1]) dos_data = { spin: np.histogram(eigenvalue_set, bins=bins, weights=weights)[0] for spin, eigenvalue_set in eigenvalues.items() } dos = Dos(efermi, energies, dos_data) if gaussian: dos.densities = dos.get_smeared_densities(gaussian) return dos
def read_dos( bands_file, pdos_file=None, cell_file=None, bin_width=0.01, gaussian=None, padding=None, emin=None, emax=None, efermi_to_vbm=True, lm_orbitals=None, elements=None, atoms=None, total_only=False, ): """Convert DOS data from CASTEP .bands file to Pymatgen/Sumo format The data is binned into a regular series using np.histogram Args: bands_file (:obj:`str`): Path to CASTEP prefix.bands output file. The k-point positions, weights and eigenvalues are read from this file. bin_width (:obj:`float`, optional): Spacing for DOS energy axis gaussian (:obj:`float` or None, optional): Width of Gaussian broadening function padding (:obj:`float`, optional): Energy range above and below occupied region. (This is not used if xmin and xmax are set.) emin (:obj:`float`, optional): Minimum energy value for output DOS) emax (:obj:`float`, optional): Maximum energy value for output DOS efermi_to_vbm (:obj:`bool`, optional): If a bandgap is detected, modify the stored Fermi energy so that it lies at the VBM. elements (:obj:`dict`, optional): The elements and orbitals to extract from the projected density of states. Should be provided as a :obj:`dict` with the keys as the element names and corresponding values as a :obj:`tuple` of orbitals. For example, the following would extract the Bi s, px, py and d orbitals:: {'Bi': ('s', 'px', 'py', 'd')} If an element is included with an empty :obj:`tuple`, all orbitals for that species will be extracted. If ``elements`` is not set or set to ``None``, all elements for all species will be extracted. lm_orbitals (:obj:`dict`, optional): The orbitals to decompose into their lm contributions (e.g. p -> px, py, pz). Should be provided as a :obj:`dict`, with the elements names as keys and a :obj:`tuple` of orbitals as the corresponding values. For example, the following would be used to decompose the oxygen p and d orbitals:: {'O': ('p', 'd')} atoms (:obj:`dict`, optional): Which atomic sites to use when calculating the projected density of states. Should be provided as a :obj:`dict`, with the element names as keys and a :obj:`tuple` of :obj:`int` specifying the atomic indices as the corresponding values. The elemental projected density of states will be summed only over the atom indices specified. If an element is included with an empty :obj:`tuple`, then all sites for that element will be included. The indices are 0 based for each element specified in the POSCAR. For example, the following will calculate the density of states for the first 4 Sn atoms and all O atoms in the structure:: {'Sn': (1, 2, 3, 4), 'O': (, )} If ``atoms`` is not set or set to ``None`` then all atomic sites for all elements will be considered. Returns: (:obj:`pymatgen.electronic_structure.dos.Dos`, dict) where the dict is either empty or contains a PDOS arranged:: {species: {orbital: Dos}} """ header = _read_bands_header_verbose(bands_file) logging.info("Reading band eigenvalues...") _, weights, eigenvalues = read_bands_eigenvalues(bands_file, header) calc_efermi = header["e_fermi"][0] * _ry_to_ev * 2 if efermi_to_vbm and not _is_metal(eigenvalues, calc_efermi): logging.info("Setting energy zero to VBM") efermi = _get_vbm(eigenvalues, calc_efermi) else: logging.info("Setting energy zero to Fermi energy") efermi = calc_efermi emin_data = min(eigenvalues[Spin.up].flatten()) emax_data = max(eigenvalues[Spin.up].flatten()) if Spin.down in eigenvalues: emin_data = min(emin_data, min(eigenvalues[Spin.down].flatten())) emax_data = max(emax_data, max(eigenvalues[Spin.down].flatten())) if padding is None and gaussian: padding = gaussian * 3 elif padding is None: padding = 0.5 if emin is None: emin = emin_data - padding if emax is None: emax = emax_data + padding # Shift sampling window to account for zeroing at VBM/EFermi emin += efermi emax += efermi bins = np.arange(emin, emax + bin_width, bin_width) energies = (bins[1:] + bins[:-1]) / 2 # Add rows to weights for each band so they are aligned with eigenval data weights = weights * np.ones([eigenvalues[Spin.up].shape[0], 1]) dos_data = { spin: np.histogram(eigenvalue_set, bins=bins, weights=weights)[0] for spin, eigenvalue_set in eigenvalues.items() } dos = Dos(efermi, energies, dos_data) if pdos_file is not None and not total_only: if cell_file is None: raise OSError(f"Cell file {cell_file} not found: this must be " "provided for PDOS.") pdos_raw = compute_pdos(pdos_file, eigenvalues, weights, bins) # Also we, need to read the structure, but have it sorted with increasing # atomic numbers structure = CastepCell.from_file( cell_file).structure.get_sorted_structure( key=lambda x: x.species.elements[0].Z) pdoss = {} for isite, site in enumerate(structure.sites): pdoss[site] = pdos_raw[isite] # Get the pdos dictionary for potting pdos = get_pdos( CompleteDos(structure, dos, pdoss), lm_orbitals=lm_orbitals, elements=elements, atoms=atoms, ) # Smear the PDOS for orbs in pdos.values(): for dtmp in orbs.values(): if gaussian: dtmp.densities = dtmp.get_smeared_densities(gaussian) else: pdos = {} if gaussian: dos.densities = dos.get_smeared_densities(gaussian) return dos, pdos