def wave(**kwargs): """Extract wavefunction coefficients from a WAVECAR""" from pymatgen.io.vasp import BSVasprun from amset.constants import defaults from amset.electronic_structure.common import ( get_band_structure, get_ibands, get_zero_weighted_kpoint_indices, ) from amset.tools.common import echo_ibands from amset.wavefunction.io import write_coefficients output = kwargs.pop("output") planewave_cutoff = kwargs.pop("planewave_cutoff") pawpyseed = kwargs.pop("pawpyseed") energy_cutoff = kwargs.pop("energy_cutoff") if not energy_cutoff: energy_cutoff = defaults["energy_cutoff"] if kwargs["directory"]: vasprun_file = Path(kwargs["directory"]) / "vasprun.xml" else: vasprun_file = kwargs["vasprun"] try: vr = BSVasprun(vasprun_file) except FileNotFoundError: vr = BSVasprun(str(vasprun_file) + ".gz") zwk_mode = kwargs.pop("zero_weighted_kpoints") if not zwk_mode: zwk_mode = defaults["zero_weighted_kpoints"] bs = get_band_structure(vr, zero_weighted=zwk_mode) if "bands" in kwargs and kwargs["bands"] is not None: ibands = parse_ibands(kwargs["bands"]) else: ibands = get_ibands(energy_cutoff, bs) ikpoints = get_zero_weighted_kpoint_indices(vr, zwk_mode) click.echo("******* Getting wavefunction coefficients *******\n") echo_ibands(ibands, bs.is_spin_polarized) click.echo("") if pawpyseed: coeffs, gpoints, kpoints = _wavefunction_pawpy(bs, ibands, planewave_cutoff, ikpoints, **kwargs) else: coeffs, gpoints = _wavefunction_vasp(ibands, planewave_cutoff, ikpoints, **kwargs) kpoints = np.array([k.frac_coords for k in bs.kpoints]) structure = vr.final_structure click.echo("Writing coefficients to {}".format(output)) write_coefficients(coeffs, gpoints, kpoints, structure, filename=output)
def from_band_structure( cls, band_structure: BandStructure, energy_cutoff=defaults["energy_cutoff"], symprec=defaults["symprec"], ): kpoints = np.array([k.frac_coords for k in band_structure.kpoints]) efermi = band_structure.efermi structure = band_structure.structure full_kpoints, ir_to_full_idx, rot_mapping = expand_kpoints( structure, kpoints, symprec=symprec ) ibands = get_ibands(energy_cutoff, band_structure) vb_idx = get_vb_idx(energy_cutoff, band_structure) # energies = { # s: e[ibands[s], ir_to_full_idx] for s, e in band_structure.bands.items() # } energies = {s: e[ibands[s]] for s, e in band_structure.bands.items()} energies = {s: e[:, ir_to_full_idx] for s, e in energies.items()} projections = {s: p[ibands[s]] for s, p in band_structure.projections.items()} band_centers = get_band_centers(full_kpoints, energies, vb_idx, efermi) return cls( structure, kpoints, projections, band_centers, kpoint_symmetry_mapping=(full_kpoints, ir_to_full_idx, rot_mapping), )
def from_band_structure( cls, band_structure: BandStructure, energy_cutoff=defaults["energy_cutoff"], symprec=defaults["symprec"], ): kpoints = np.array([k.frac_coords for k in band_structure.kpoints]) efermi = band_structure.efermi structure = band_structure.structure full_kpoints, _, _, _, _, ir_to_full_idx = expand_kpoints( structure, kpoints, symprec=symprec, return_mapping=True, time_reversal=True) ibands = get_ibands(energy_cutoff, band_structure) vb_idx = get_vb_idx(energy_cutoff, band_structure) energies = {s: e[ibands[s]] for s, e in band_structure.bands.items()} energies = {s: e[:, ir_to_full_idx] for s, e in energies.items()} projections = { s: p[ibands[s]] for s, p in band_structure.projections.items() } band_centers = get_band_centers(full_kpoints, energies, vb_idx, efermi) rotation_mask = get_rotation_mask(projections) full_projections = {} for spin, spin_projections in projections.items(): nbands = spin_projections.shape[0] nkpoints = len(full_kpoints) spin_projections = spin_projections[:, ir_to_full_idx].reshape( (nbands, nkpoints, -1), order="F") spin_projections /= np.linalg.norm(spin_projections, axis=2)[..., None] spin_projections[np.isnan(spin_projections)] = 0 full_projections[spin] = spin_projections return cls.from_data( full_kpoints, full_projections, rotation_mask=rotation_mask, band_centers=band_centers, )
def dump_wavefunction(**kwargs): """Extract wavefunction coefficients from a WAVECAR""" from amset.electronic_structure.wavefunction import ( get_wavefunction, get_wavefunction_coefficients, dump_coefficients, ) from amset.electronic_structure.common import get_ibands from amset.constants import defaults from pymatgen.io.vasp import BSVasprun output = kwargs.pop("output") planewave_cutoff = kwargs.pop("planewave_cutoff") energy_cutoff = kwargs.pop("energy_cutoff") if not energy_cutoff: energy_cutoff = defaults["energy_cutoff"] wf = get_wavefunction(**kwargs) if kwargs["directory"]: kwargs["vasprun"] = Path(kwargs["directory"]) / "vasprun.xml" vr = BSVasprun(kwargs["vasprun"]) bs = vr.get_band_structure() ibands = get_ibands(energy_cutoff, bs) click.echo("******* Getting wavefunction coefficients *******") click.echo("\nIncluding:") for spin, spin_bands in ibands.items(): min_b = spin_bands.min() + 1 max_b = spin_bands.max() + 1 click.echo(" Spin-{} bands {}—{}".format(spin.name, min_b, max_b)) click.echo("") coeffs = get_wavefunction_coefficients(wf, bs, iband=ibands, encut=planewave_cutoff) click.echo("Writing coefficients to {}".format(output)) dump_coefficients(coeffs, wf.kpts, wf.structure, filename=output)
def get_energies( self, kpoints: Union[np.ndarray, List], energy_cutoff: Optional[float] = None, scissor: float = None, bandgap: float = None, return_velocity: bool = False, return_curvature: bool = False, return_other_properties: bool = False, coords_are_cartesian: bool = False, atomic_units: bool = False, symprec: Optional[float] = defaults["symprec"], return_efermi: bool = False, return_vb_idx: bool = False, ) -> Union[Dict[Spin, np.ndarray], Tuple[Dict[Spin, np.ndarray], ...]]: """Gets the interpolated energies for multiple k-points in a band. Note, the accuracy of the interpolation is dependant on the ``interpolate_factor`` used to initialize the Interpolater. Args: kpoints: The k-point coordinates. 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. return_velocity: Whether to return the band velocities. return_curvature: Whether to return the band curvature (inverse effective mass). return_other_properties: Whether to return the interpolated results for the ``other_properties`` data. coords_are_cartesian: Whether the kpoints are in cartesian or fractional coordinates. atomic_units: Return the energies, velocities, and effective_massses in atomic units. If False, energies will be in eV, velocities in cm/s, and curvature in units of 1 / electron rest mass (1/m0). symprec: Symmetry precision. If set, symmetry will be used to reduce the nummber of calculated k-points and velocities. return_efermi: Whether to return the Fermi level with the unit determined by ``atomic_units``. If the system is semiconducting the Fermi level will be given in the middle of the band gap. return_vb_idx: Whether to return the index of the highest valence band in the interpolated bands. Will be returned as a dictionary of ``{spin: vb_idx}``. Returns: The band energies as dictionary of:: {spin: energies} If ``return_velocity``, ``curvature`` or ``return_other_properties`` a tuple is returned, formatted as:: (energies, Optional[velocities], Optional[curvature], Optional[other_properties]) The velocities and effective masses are given as the 1x3 trace and full 3x3 tensor, respectively (along cartesian directions). The projections are summed for each orbital type (s, p, d) across all atoms, and are given as:: {spin: {orbital: projections}} """ if self._band_structure.is_metal() and (bandgap or scissor): raise ValueError("{} option set but system is metallic".format( "bandgap" if bandgap else "scissor")) # only calculate the energies for the bands within the energy cutoff lattice = self._band_structure.structure.lattice kpoints = np.asarray(kpoints) nkpoints = len(kpoints) if coords_are_cartesian: kpoints = lattice.reciprocal_lattice.get_fractional_coords(kpoints) if symprec: logger.info("Reducing # k-points using symmetry") ( kpoints, weights, ir_kpoints_idx, ir_to_full_idx, _, rot_mapping, ) = get_symmetry_equivalent_kpoints( self._band_structure.structure, kpoints, symprec=symprec, return_inverse=True, time_reversal_symmetry=not self._soc, ) k_info = [ "# original k-points: {}".format(nkpoints), "# reduced k-points {}".format(len(kpoints)), ] log_list(k_info) ibands = get_ibands(energy_cutoff, self._band_structure) new_vb_idx = get_vb_idx(energy_cutoff, self._band_structure) energies = {} velocities = {} curvature = {} other_properties = defaultdict(dict) for spin in self._spins: spin_ibands = ibands[spin] min_b = spin_ibands.min() + 1 max_b = spin_ibands.max() + 1 info = "Interpolating {} bands {}-{}".format( spin_name[spin], min_b, max_b) logger.info(info) t0 = time.perf_counter() fitted = fite.getBands( kpoints, self._equivalences, self._lattice_matrix, self._coefficients[spin][spin_ibands], curvature=return_curvature, ) log_time_taken(t0) energies[spin] = fitted[0] velocities[spin] = fitted[1] if return_curvature: # make curvature have the shape ((nbands, nkpoints, 3, 3) curvature[spin] = fitted[2] curvature[spin] = curvature[spin].transpose((2, 3, 0, 1)) if return_other_properties: logger.info("Interpolating {} properties".format( spin_name[spin])) t0 = time.perf_counter() for label, coeffs in self._other_coefficients[spin].items(): other_properties[spin][label], _ = fite.getBands( kpoints, self._equivalences, self._lattice_matrix, coeffs[spin_ibands], curvature=False, ) log_time_taken(t0) if not atomic_units: energies[spin] = energies[spin] * hartree_to_ev velocities[spin] = _convert_velocities(velocities[spin], lattice.matrix) if symprec: energies, velocities, curvature, other_properties = symmetrize_results( energies, velocities, curvature, other_properties, ir_to_full_idx, rot_mapping, self._band_structure.structure.lattice.reciprocal_lattice. matrix, ) if not self._band_structure.is_metal(): energies = _shift_energies(energies, new_vb_idx, scissor=scissor, bandgap=bandgap) to_return = [energies] if return_velocity: to_return.append(velocities) if return_curvature: to_return.append(curvature) if return_other_properties: to_return.append(other_properties) if return_efermi: if self._band_structure.is_metal(): efermi = self._band_structure.efermi if atomic_units: efermi *= ev_to_hartree else: # if semiconducting, set Fermi level to middle of gap efermi = _get_efermi(energies, new_vb_idx) to_return.append(efermi) if return_vb_idx: to_return.append(new_vb_idx) if len(to_return) == 1: return to_return[0] else: return tuple(to_return)
def get_amset_data( self, energy_cutoff: Optional[float] = None, scissor: float = None, bandgap: float = None, symprec: float = defaults["symprec"], nworkers: int = defaults["nworkers"], ) -> 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")) nworkers = multiprocessing.cpu_count() if nworkers == -1 else nworkers logger.info("Interpolation parameters:") iinfo = [ "k-point mesh: {}".format("x".join( map(str, self.interpolation_mesh))), "energy cutoff: {} eV".format(energy_cutoff), ] log_list(iinfo) ibands = get_ibands(energy_cutoff, self._band_structure) new_vb_idx = get_vb_idx(energy_cutoff, self._band_structure) energies = {} vvelocities = {} velocities = {} forgotten_electrons = 0 for spin in self._spins: spin_ibands = ibands[spin] min_b = spin_ibands.min() + 1 max_b = spin_ibands.max() + 1 info = "Interpolating {} bands {}-{}".format( spin_name[spin], min_b, max_b) logger.info(info) # these are bands beneath the Fermi level that are dropped forgotten_electrons += min_b - 1 t0 = time.perf_counter() energies[spin], vvelocities[spin], _, velocities[ spin] = get_bands_fft( self._equivalences, self._coefficients[spin][spin_ibands], self._lattice_matrix, return_effective_mass=False, nworkers=nworkers, ) log_time_taken(t0) if not self._soc and len(self._spins) == 1: forgotten_electrons *= 2 nelectrons = self._num_electrons - forgotten_electrons if is_metal: efermi = self._band_structure.efermi * ev_to_hartree else: energies = _shift_energies(energies, new_vb_idx, scissor=scissor, bandgap=bandgap) # 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, tetrahedra, *ir_tetrahedra_info, ) = get_kpoints_tetrahedral( self.interpolation_mesh, self._band_structure.structure, symprec=symprec, time_reversal_symmetry=not self._soc, ) energies, vvelocities, velocities = sort_amset_results( full_kpts, energies, vvelocities, velocities) atomic_structure = get_atomic_structure(self._band_structure.structure) return AmsetData( atomic_structure, energies, vvelocities, velocities, self.interpolation_mesh, full_kpts, ir_kpts, ir_kpts_idx, ir_to_full_idx, tetrahedra, ir_tetrahedra_info, efermi, nelectrons, is_metal, self._soc, vb_idx=new_vb_idx, )
def read(bulk_folder, deformation_folders, **kwargs): """ Read deformation calculations and extract deformation potentials. """ from pymatgen.symmetry.analyzer import SpacegroupAnalyzer from pymatgen.util.string import unicodeify_spacegroup from amset.constants import defaults from amset.deformation.common import get_formatted_tensors from amset.deformation.io import parse_calculation, write_deformation_potentials from amset.deformation.potentials import ( calculate_deformation_potentials, extract_bands, get_strain_mapping, get_symmetrized_strain_mapping, strain_coverage_ok, ) from amset.electronic_structure.common import get_ibands from amset.electronic_structure.kpoints import get_kpoints_from_bandstructure from amset.electronic_structure.symmetry import expand_bandstructure energy_cutoff = kwargs.pop("energy_cutoff") if not energy_cutoff: energy_cutoff = defaults["energy_cutoff"] zwk_mode = kwargs.pop("zero_weighted_kpoints") if not zwk_mode: zwk_mode = defaults["zero_weighted_kpoints"] symprec = _parse_symprec(kwargs["symprec"]) symprec_deformation = kwargs["symprec_deformation"] click.echo("Reading bulk (undeformed) calculation") bulk_calculation = parse_calculation(bulk_folder, zero_weighted_kpoints=zwk_mode) bulk_structure = bulk_calculation["bandstructure"].structure deformation_calculations = [] for deformation_folder in deformation_folders: click.echo("Reading deformation calculation in {}".format(deformation_folder)) deformation_calculation = parse_calculation( deformation_folder, zero_weighted_kpoints=zwk_mode ) if check_calculation(bulk_calculation, deformation_calculation): deformation_calculations.append(deformation_calculation) sga = SpacegroupAnalyzer(bulk_structure, symprec=symprec) spg_symbol = unicodeify_spacegroup(sga.get_space_group_symbol()) spg_number = sga.get_space_group_number() click.echo("\nSpacegroup: {} ({})".format(spg_symbol, spg_number)) lattice_match = reciprocal_lattice_match( bulk_calculation["bandstructure"], symprec=symprec ) if not lattice_match: click.echo( "\nWARNING: Reciprocal lattice and k-lattice belong to different\n" " class of lattices. Often results are still useful but\n" " it is recommended to regenerate deformations without\n" " symmetry using: amset deform create --symprec N" ) strain_mapping = get_strain_mapping(bulk_structure, deformation_calculations) click.echo("\nFound {} strains:".format(len(strain_mapping))) fmt_strain = get_formatted_tensors(strain_mapping.keys()) click.echo(" - " + "\n - ".join(fmt_strain)) strain_mapping = get_symmetrized_strain_mapping( bulk_structure, strain_mapping, symprec=symprec, symprec_deformation=symprec_deformation, ) click.echo("\nAfter symmetrization found {} strains:".format(len(strain_mapping))) fmt_strain = get_formatted_tensors(strain_mapping.keys()) click.echo(" - " + "\n - ".join(fmt_strain)) if not strain_coverage_ok(list(strain_mapping.keys())): click.echo("\nERROR: Strains do not cover full tensor, check calculations") sys.exit() click.echo("\nCalculating deformation potentials") bulk_calculation["bandstructure"] = expand_bandstructure( bulk_calculation["bandstructure"], symprec=symprec ) deformation_potentials = calculate_deformation_potentials( bulk_calculation, strain_mapping ) print_deformation_summary(bulk_calculation["bandstructure"], deformation_potentials) if "bands" in kwargs and kwargs["bands"] is not None: ibands = parse_ibands(kwargs["bands"]) else: ibands = get_ibands(energy_cutoff, bulk_calculation["bandstructure"]) echo_ibands(ibands, bulk_calculation["bandstructure"].is_spin_polarized) deformation_potentials = extract_bands(deformation_potentials, ibands) kpoints = get_kpoints_from_bandstructure(bulk_calculation["bandstructure"]) filename = write_deformation_potentials( deformation_potentials, kpoints, bulk_structure, filename=kwargs["output"] ) click.echo("\nDeformation potentials written to {}".format(filename))
def read(bulk_folder, deformation_folders, **kwargs): """ Read deformation calculations and extract deformation potentials. """ from pymatgen.symmetry.analyzer import SpacegroupAnalyzer from pymatgen.util.string import unicodeify_spacegroup from amset.constants import defaults from amset.deformation.common import get_formatted_tensors from amset.deformation.io import parse_calculation, write_deformation_potentials from amset.electronic_structure.symmetry import expand_bandstructure from amset.deformation.potentials import ( calculate_deformation_potentials, extract_bands, get_strain_mapping, get_symmetrized_strain_mapping, strain_coverage_ok, ) from amset.electronic_structure.common import get_ibands from amset.electronic_structure.kpoints import get_kpoints_from_bandstructure energy_cutoff = kwargs.pop("energy_cutoff") if not energy_cutoff: energy_cutoff = defaults["energy_cutoff"] symprec = _parse_symprec(kwargs["symprec"]) click.echo("Reading bulk (undeformed) calculation") bulk_calculation = parse_calculation(bulk_folder) bulk_structure = bulk_calculation["bandstructure"].structure deformation_calculations = [] for deformation_folder in deformation_folders: click.echo("Reading deformation calculation in {}".format(deformation_folder)) deformation_calculation = parse_calculation(deformation_folder) if check_calculation(bulk_calculation, deformation_calculation): deformation_calculations.append(deformation_calculation) sga = SpacegroupAnalyzer(bulk_structure, symprec=symprec) spg_symbol = unicodeify_spacegroup(sga.get_space_group_symbol()) spg_number = sga.get_space_group_number() click.echo("\nSpacegroup: {} ({})".format(spg_symbol, spg_number)) strain_mapping = get_strain_mapping(bulk_structure, deformation_calculations) click.echo("\nFound {} strains:".format(len(strain_mapping))) fmt_strain = get_formatted_tensors(strain_mapping.keys()) click.echo(" - " + "\n - ".join(fmt_strain)) strain_mapping = get_symmetrized_strain_mapping( bulk_structure, strain_mapping, symprec=symprec ) click.echo("\nAfter symmetrization found {} strains:".format(len(strain_mapping))) fmt_strain = get_formatted_tensors(strain_mapping.keys()) click.echo(" - " + "\n - ".join(fmt_strain)) if not strain_coverage_ok(list(strain_mapping.keys())): click.echo("\nERROR: Strains do not cover full tensor, check calculations") sys.exit() click.echo("\nCalculating deformation potentials") bulk_calculation["bandstructure"] = expand_bandstructure( bulk_calculation["bandstructure"], symprec=symprec ) deformation_potentials = calculate_deformation_potentials( bulk_calculation, strain_mapping ) print_deformation_summary(bulk_calculation["bandstructure"], deformation_potentials) ibands = get_ibands(energy_cutoff, bulk_calculation["bandstructure"]) echo_ibands(ibands, bulk_calculation["bandstructure"].is_spin_polarized) click.echo("") deformation_potentials = extract_bands(deformation_potentials, ibands) kpoints = get_kpoints_from_bandstructure(bulk_calculation["bandstructure"]) filename = write_deformation_potentials( deformation_potentials, kpoints, bulk_structure, filename=kwargs["output"] ) click.echo("\nDeformation potentials written to {}".format(filename))