Exemplo n.º 1
0
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)
Exemplo n.º 2
0
    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),
        )
Exemplo n.º 3
0
    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,
        )
Exemplo n.º 4
0
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)
Exemplo n.º 5
0
    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)
Exemplo n.º 6
0
    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,
        )
Exemplo n.º 7
0
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))
Exemplo n.º 8
0
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))