Exemplo n.º 1
0
def get_phonon_band_structure_from_fc(
    structure: Structure,
    supercell_matrix: np.ndarray,
    force_constants: np.ndarray,
    mesh_density: float = 100.0,
    **kwargs,
) -> PhononBandStructure:
    """
    Get a uniform phonon band structure from phonopy force constants.

    Args:
        structure: A structure.
        supercell_matrix: The supercell matrix used to generate the force
            constants.
        force_constants: The force constants in phonopy format.
        mesh_density: The density of the q-point mesh. See the docstring
            for the ``mesh`` argument in Phonopy.init_mesh() for more details.
        **kwargs: Additional kwargs passed to the Phonopy constructor.

    Returns:
        The uniform phonon band structure.
    """
    structure_phonopy = get_phonopy_structure(structure)
    phonon = Phonopy(structure_phonopy,
                     supercell_matrix=supercell_matrix,
                     **kwargs)
    phonon.set_force_constants(force_constants)
    phonon.run_mesh(mesh_density, is_mesh_symmetry=False, is_gamma_center=True)
    mesh = phonon.get_mesh_dict()

    return PhononBandStructure(mesh["qpoints"], mesh["frequencies"],
                               structure.lattice)
Exemplo n.º 2
0
def get_f_vib_phonopy(structure, supercell_matrix, vasprun_path,
                     qpoint_mesh=(50, 50, 50), t_min=5, t_step=5, t_max=2000.0,):
    """
    Return F_vib(T) for the unitcell in eV/atom

    Parameters
    ----------
    structure : pymatgen.Structure
        Unitcell (not supercell) of interest.
    supercell_matrix : numpy.ndarray
        3x3 matrix of the supercell deformation, e.g. [[3, 0, 0], [0, 3, 0], [0, 0, 3]].
    vasprun_path : str
        String pointing to a vasprun.xml file from a force constants run
    qpoint_mesh : list
        Mesh of q-points to calculate thermal properties on.
    t_min : float
        Minimum temperature
    t_step : float
        Temperature step size
    t_max : float
        Maximum temperature (inclusive)

    Returns
    -------
    tuple
        Tuple of (temperature, F_vib, S_vib, Cv_vib, force_constants)

    """
    # get codename and version from vasprun.xml file
    code_name, code_version = get_code_version(xml=vasprun_path)
    force_constant_factor = 1.0
    if code_version[0:1] >= '6':
        force_constant_factor = 0.004091649655126895

    # get the force constants from a vasprun.xml file
    vasprun = PhonopyVasprun(vasprun_path)
    force_constants, elements = vasprun.read_force_constants()
    force_constants *= force_constant_factor

    ph_unitcell = get_phonopy_structure(structure)
    ph = Phonopy(ph_unitcell, supercell_matrix)
    # set the force constants we found
    ph.set_force_constants(force_constants)
    # calculate the thermal properties
    ph.run_mesh(qpoint_mesh)
    ph.run_thermal_properties(t_min=t_min, t_max=t_max, t_step=t_step)
    # the thermal properties are for the unit cell
    tp_dict = ph.get_thermal_properties_dict()
    temperatures = tp_dict['temperatures']
    # convert the units into our expected eV/atom-form (and per K)
    f_vib = tp_dict['free_energy'] * J_per_mol_to_eV_per_atom*1000
    s_vib = tp_dict['entropy'] * J_per_mol_to_eV_per_atom
    cv_vib = tp_dict['heat_capacity'] * J_per_mol_to_eV_per_atom
    return temperatures, f_vib, s_vib, cv_vib, ph.force_constants, code_version
Exemplo n.º 3
0
def get_phonon_dos(structure,
                   supercell_matrix,
                   force_constants,
                   qpoint_mesh=(50, 50, 50),
                   phonon_pdos=False,
                   save_data=False,
                   save_fig=False):
    '''
    Return the phonon dos

    Parameters
    ----------
    structure : pymatgen.Structure
        Unitcell (not supercell) of interest.
    supercell_matrix : numpy.ndarray
        3x3 matrix of the supercell deformation, e.g. [[3, 0, 0], [0, 3, 0], [0, 0, 3]].
    force_constants: list
        force constants
    qpoint_mesh : list
        Mesh of q-points to calculate thermal properties on.
    phonon_pdos: bool
        Determine if calculate phonon pdos or not
    save_data/save_fig: bool
        Determine if save the data/figure or not
    '''
    volume = structure.volume
    formula = structure.composition.reduced_formula
    filename = "{}-phonon-Vol{:.2f}".format(formula, volume)

    unitcell = get_phonopy_structure(structure)
    ph_dos_obj = Phonopy(unitcell, supercell_matrix)
    ph_dos_obj.set_force_constants(force_constants)

    ph_dos_obj.run_mesh(qpoint_mesh)
    ph_dos_obj.run_total_dos()
    if save_fig:
        fig_dos = ph_dos_obj.plot_total_dos()
        fig_dos.savefig(fname='{}-dos.png'.format(filename))
        fig_dos.close()
    if save_data:
        ph_dos_obj.write_total_dos(filename='{}-dos.dat'.format(filename))

    if phonon_pdos:
        ph_dos_obj.run_mesh(qpoint_mesh,
                            with_eigenvectors=True,
                            is_mesh_symmetry=False)
        ph_dos_obj.run_projected_dos()
        if save_fig:
            ph_dos_obj.plot_projected_dos().savefig(
                fname='{}-pdos.png'.format(filename))
        if save_data:
            ph_dos_obj.write_projected_dos(
                filename='{}-pdos.dat'.format(filename))
    return ph_dos_obj
Exemplo n.º 4
0
def get_phonon_dos_from_fc(
    structure: Structure,
    supercell_matrix: np.ndarray,
    force_constants: np.ndarray,
    mesh_density: float = 100.0,
    num_dos_steps: int = 200,
    **kwargs,
) -> CompletePhononDos:
    """
    Get a projected phonon density of states from phonopy force constants.

    Args:
        structure: A structure.
        supercell_matrix: The supercell matrix used to generate the force
            constants.
        force_constants: The force constants in phonopy format.
        mesh_density: The density of the q-point mesh. See the docstring
            for the ``mesh`` argument in Phonopy.init_mesh() for more details.
        num_dos_steps: Number of frequency steps in the energy grid.
        **kwargs: Additional kwargs passed to the Phonopy constructor.

    Returns:
        The density of states.
    """
    structure_phonopy = get_phonopy_structure(structure)
    phonon = Phonopy(structure_phonopy,
                     supercell_matrix=supercell_matrix,
                     **kwargs)
    phonon.set_force_constants(force_constants)
    phonon.run_mesh(
        mesh_density,
        is_mesh_symmetry=False,
        with_eigenvectors=True,
        is_gamma_center=True,
    )

    # get min, max, step frequency
    frequencies = phonon.get_mesh_dict()["frequencies"]
    freq_min = frequencies.min()
    freq_max = frequencies.max()
    freq_pitch = (freq_max - freq_min) / num_dos_steps

    phonon.run_projected_dos(freq_min=freq_min,
                             freq_max=freq_max,
                             freq_pitch=freq_pitch)

    dos_raw = phonon.projected_dos.get_partial_dos()
    pdoss = dict(zip(structure, dos_raw[1]))

    total_dos = PhononDos(dos_raw[0], dos_raw[1].sum(axis=0))
    return CompletePhononDos(structure, total_dos, pdoss)
Exemplo n.º 5
0
    def get_phonon(self, phonon, **kwargs):
        flag_savefig = kwargs.get('savefig', False)
        flag_savedata = kwargs.get('savedata', False)
        flag_band = kwargs.get('band', False)
        flag_dos = kwargs.get('phonon_dos', False)
        flag_pdos = kwargs.get('phonon_pdos', False)

        mesh = kwargs.get('mesh', [50, 50, 50])
        labels = kwargs.get('labels', None)

        if 'unitcell' not in phonon:
            raise FileNotFoundError(
                'There is no phonon result. Please run phonon first.')
        self.head = ['Temperature', 'F_vib', 'CV_vib', 'S_vib']
        self.unit = ['K', 'eV', 'eV/K', 'eV/K']
        self.data = np.vstack((phonon.pop('temperatures'), phonon.pop('F_vib'),
                               phonon.pop('CV_vib'), phonon.pop('S_vib'))).T
        filename = '{}-phonon-Vol{:.2f}'.format(self.formula, self.volume)

        unitcell = get_phonopy_structure(self.structure)
        supercell_matrix = phonon['supercell_matrix']
        force_constants = phonon['force_constants']
        ph = Phonopy(unitcell, supercell_matrix)
        ph.set_force_constants(force_constants)

        #for band structure
        if flag_band:
            if 'path' in kwargs:
                qpoints, connections = get_band_qpoints_and_path_connections(
                    path, npoints=51)
                ph.run_band_structure(qpoints,
                                      path_connections=connections,
                                      labels=labels)
            else:
                ph.auto_band_structure()
            if flag_savefig:
                fig_band = ph.plot_band_structure()
                fig_band.savefig(fname='{}-band.png'.format(filename))
                fig_band.close()
            if flag_savedata:
                ph.write_yaml_band_structure(
                    filename='{}-band.yaml'.format(filename))

        #for dos
        if flag_dos:
            ph.run_mesh(mesh)
            ph.run_total_dos()
            #phonon_dos_tmp = np.vstack((ph._total_dos._frequency_points, ph._total_dos._dos))
            #print(phonon_dos_tmp)
            #print(type(phonon_dos_tmp))
            if flag_savefig:
                fig_dos = ph.plot_total_dos()
                fig_dos.savefig(fname='{}-dos.png'.format(filename))
                fig_dos.close()
            if flag_savedata:
                ph.write_total_dos(filename='{}-dos.dat'.format(filename))
        #for pdos.
        if flag_pdos:
            ph.run_mesh(mesh, with_eigenvectors=True, is_mesh_symmetry=False)
            ph.run_projected_dos()
            if flag_savefig:
                ph.plot_projected_dos().savefig(
                    fname='{}-pdos.png'.format(filename))
            if flag_savedata:
                ph.write_projected_dos(filename='{}-pdos.dat'.format(filename))

        phonon.pop('_id')
        self.parameter = phonon
Exemplo n.º 6
0
append_band(bands, [0.0, 0.0, 0.0], [0.5, 0.5, 0.5])
phonon.set_band_structure(bands)
band_dict = phonon.get_band_structure_dict()
q_points = band_dict['qpoints']
distances = band_dict['distances']
frequencies = band_dict['frequencies']
eigvecs = band_dict['eigenvectors']
for q_path, d_path, freq_path in zip(q_points, distances, frequencies):
    for q, d, freq in zip(q_path, d_path, freq_path):
        print(("%10.5f  %5.2f %5.2f %5.2f " + (" %7.3f" * len(freq))) %
              ((d, q[0], q[1], q[2]) + tuple(freq)))

phonon.plot_band_structure().show()

# Mesh sampling 20x20x20
phonon.run_mesh(mesh=[20, 20, 20])
phonon.run_thermal_properties(t_step=10, t_max=1000, t_min=0)

# DOS
phonon.run_total_dos(sigma=0.1)
dos_dict = phonon.get_total_dos_dict()
for omega, dos in zip(dos_dict['frequency_points'], dos_dict['total_dos']):
    print("%15.7f%15.7f" % (omega, dos))
phonon.plot_total_dos().show()

# Thermal properties
tprop_dict = phonon.get_thermal_properties_dict()

for t, free_energy, entropy, cv in zip(tprop_dict['temperatures'],
                                       tprop_dict['free_energy'],
                                       tprop_dict['entropy'],
Exemplo n.º 7
0
# 'L':[1/2., 1/2., 1/2.],
# }
# G = points['Gamma']
# X = points['X']
# U = points['U']
# K = points['K']
# L = points['L']
# path = [[G, X], [X, U], [K, G], [G, L]]
# labels = ['$\Gamma$', 'X', 'U|K', '$\Gamma$', 'L']

# DOS grid
# from kpoints_gen import get_grid_num
# k_grids = get_grid_num(phonon.get_primitive().cell, precision=pdos_precision)
phonon.run_mesh(
    pdos_mesh,
    # is_mesh_symmetry=False,
    with_eigenvectors=True,
    is_gamma_center=True,
)

######### eigenvectors #########
# freq, eigvec = phonon.get_frequencies_with_eigenvectors([0.00000333,0.00000333,0.])
# freq, eigvec = phonon.get_frequencies_with_eigenvectors([0.,0.000001,0.])
freq, eigvec = phonon.get_frequencies_with_eigenvectors(G)
eigvec = eigvec.T
np.savez('freqNeigvec', freq=freq, eigvec=eigvec)

########## Plot ################
from subprocess import call
#### Band plot
if run_mode == 'phonon':
    #
Exemplo n.º 8
0
class PhonopyJob(AtomisticParallelMaster):
    """

    Args:
        project:
        job_name:
    """
    def __init__(self, project, job_name):
        super(PhonopyJob, self).__init__(project, job_name)
        self.__name__ = "PhonopyJob"
        self.__version__ = "0.0.1"
        self.input["interaction_range"] = (10.0,
                                           "Minimal size of supercell, Ang")
        self.input["factor"] = (
            VaspToTHz,
            "Frequency unit conversion factor (default for VASP)",
        )
        self.input["displacement"] = (0.01, "atoms displacement, Ang")
        self.input["dos_mesh"] = (20, "mesh size for DOS calculation")

        self.phonopy = None
        self._job_generator = PhonopyJobGenerator(self)
        self._disable_phonopy_pickle = False
        s.publication_add(phonopy_publication())

    @property
    def phonopy_pickling_disabled(self):
        return self._disable_phonopy_pickle

    @phonopy_pickling_disabled.setter
    def phonopy_pickling_disabled(self, disable):
        self._disable_phonopy_pickle = disable

    @property
    def _phonopy_unit_cell(self):
        if self.structure is not None:
            return atoms_to_phonopy(self.structure)
        else:
            return None

    def _enable_phonopy(self):
        if self.phonopy is None:
            if self.structure is not None:
                self.phonopy = Phonopy(
                    unitcell=self._phonopy_unit_cell,
                    supercell_matrix=self._phonopy_supercell_matrix(),
                    factor=self.input["factor"],
                )
                self.phonopy.generate_displacements(
                    distance=self.input["displacement"])
                self.to_hdf()
            else:
                raise ValueError(
                    "No reference job/ No reference structure found.")

    def list_structures(self):
        if self.structure is not None:
            self._enable_phonopy()
            return [struct for _, struct in self._job_generator.parameter_list]
        else:
            return []

    def _phonopy_supercell_matrix(self):
        if self.structure is not None:
            supercell_range = np.ceil(
                self.input["interaction_range"] / np.array([
                    np.linalg.norm(vec)
                    for vec in self._phonopy_unit_cell.get_cell()
                ]))
            return np.eye(3) * supercell_range
        else:
            return np.eye(3)

    def run_static(self):
        # Initialise the phonopy object before starting the first calculation.
        self._enable_phonopy()
        super(PhonopyJob, self).run_static()

    def run_if_interactive(self):
        self._enable_phonopy()
        super(PhonopyJob, self).run_if_interactive()

    def to_hdf(self, hdf=None, group_name=None):
        """
        Store the PhonopyJob in an HDF5 file

        Args:
            hdf (ProjectHDFio): HDF5 group object - optional
            group_name (str): HDF5 subgroup name - optional
        """
        super(PhonopyJob, self).to_hdf(hdf=hdf, group_name=group_name)
        if self.phonopy is not None and not self._disable_phonopy_pickle:
            with self.project_hdf5.open("output") as hdf5_output:
                hdf5_output["phonopy_pickeled"] = codecs.encode(
                    pickle.dumps(self.phonopy), "base64").decode()

    def from_hdf(self, hdf=None, group_name=None):
        """
        Restore the PhonopyJob from an HDF5 file

        Args:
            hdf (ProjectHDFio): HDF5 group object - optional
            group_name (str): HDF5 subgroup name - optional
        """
        super(PhonopyJob, self).from_hdf(hdf=hdf, group_name=group_name)
        with self.project_hdf5.open("output") as hdf5_output:
            if "phonopy_pickeled" in hdf5_output.list_nodes():
                self.phonopy = pickle.loads(
                    codecs.decode(hdf5_output["phonopy_pickeled"].encode(),
                                  "base64"))
                if "dos_total" in hdf5_output.list_nodes():
                    self._dos_total = hdf5_output["dos_total"]
                if "dos_energies" in hdf5_output.list_nodes():
                    self._dos_energies = hdf5_output["dos_energies"]

    def collect_output(self):
        """

        Returns:

        """
        if self.ref_job.server.run_mode.interactive:
            forces_lst = self.project_hdf5.inspect(
                self.child_ids[0])["output/generic/forces"]
        else:
            pr_job = self.project_hdf5.project.open(self.job_name + "_hdf5")
            forces_lst = [
                pr_job.inspect(job_name)["output/generic/forces"][-1]
                for job_name in self._get_jobs_sorted()
            ]
        self.phonopy.set_forces(forces_lst)
        self.phonopy.produce_force_constants()
        self.phonopy.run_mesh(mesh=[self.input["dos_mesh"]] * 3)
        mesh_dict = self.phonopy.get_mesh_dict()
        self.phonopy.run_total_dos()
        dos_dict = self.phonopy.get_total_dos_dict()

        self.to_hdf()

        with self.project_hdf5.open("output") as hdf5_out:
            hdf5_out["dos_total"] = dos_dict['total_dos']
            hdf5_out["dos_energies"] = dos_dict['frequency_points']
            hdf5_out["qpoints"] = mesh_dict['qpoints']
            hdf5_out["supercell_matrix"] = self._phonopy_supercell_matrix()
            hdf5_out[
                "displacement_dataset"] = self.phonopy.get_displacement_dataset(
                )
            hdf5_out[
                "dynamical_matrix"] = self.phonopy.dynamical_matrix.get_dynamical_matrix(
                )
            hdf5_out["force_constants"] = self.phonopy.force_constants

    def write_phonopy_force_constants(self,
                                      file_name="FORCE_CONSTANTS",
                                      cwd=None):
        """

        Args:
            file_name:
            cwd:

        Returns:

        """
        if cwd is not None:
            file_name = posixpath.join(cwd, file_name)
        write_FORCE_CONSTANTS(force_constants=self.phonopy.force_constants,
                              filename=file_name)

    def get_hesse_matrix(self):
        """

        Returns:

        """
        unit_conversion = (
            scipy.constants.physical_constants["Hartree energy in eV"][0] /
            scipy.constants.physical_constants["Bohr radius"][0]**2 *
            scipy.constants.angstrom**2)
        force_shape = np.shape(self.phonopy.force_constants)
        force_reshape = force_shape[0] * force_shape[2]
        return (np.transpose(self.phonopy.force_constants,
                             (0, 2, 1, 3)).reshape(
                                 (force_reshape, force_reshape)) /
                unit_conversion)

    def get_thermal_properties(self,
                               t_min=1,
                               t_max=1500,
                               t_step=50,
                               temperatures=None):
        """

        Args:
            t_min:
            t_max:
            t_step:
            temperatures:

        Returns:

        """
        self.phonopy.run_thermal_properties(t_step=t_step,
                                            t_max=t_max,
                                            t_min=t_min,
                                            temperatures=temperatures)
        tp_dict = self.phonopy.get_thermal_properties_dict()
        return thermal(tp_dict['temperatures'], tp_dict['free_energy'],
                       tp_dict['entropy'], tp_dict['heat_capacity'])

    @property
    def dos_total(self):
        """

        Returns:

        """
        return self["output/dos_total"]

    @property
    def dos_energies(self):
        """

        Returns:

        """
        return self["output/dos_energies"]

    @property
    def dynamical_matrix(self):
        """

        Returns:

        """
        return np.real_if_close(
            self.phonopy.get_dynamical_matrix().get_dynamical_matrix())

    def dynamical_matrix_at_q(self, q):
        """

        Args:
            q:

        Returns:

        """
        return np.real_if_close(self.phonopy.get_dynamical_matrix_at_q(q))

    def plot_dos(self, ax=None, *args, **qwargs):
        """

        Args:
            *args:
            ax:
            **qwargs:

        Returns:

        """
        try:
            import pylab as plt
        except ImportError:
            import matplotlib.pyplot as plt
        if ax is None:
            fig, ax = plt.subplots(1, 1)
        ax.plot(self["output/dos_energies"], self["output/dos_total"], *args,
                **qwargs)
        ax.set_xlabel("Frequency [THz]")
        ax.set_ylabel("DOS")
        ax.set_title("Phonon DOS vs Energy")
        return ax
Exemplo n.º 9
0
class PhonopyJob(AtomisticParallelMaster):
    """
    Phonopy wrapper for the calculation of free energy in the framework of quasi harmonic approximation.

    Example:

    >>> from pyiron_atomistics import Project
    >>> pr = Project('my_project')
    >>> lmp = pr.create_job('Lammps', 'lammps')
    >>> lmp.structure = pr.create_structure('Fe', 'bcc', 2.832)
    >>> phono = lmp.create_job('PhonopyJob', 'phonopy')
    >>> phono.run()

    Get output via `get_thermal_properties()`.

    Note:

    - This class does not consider the thermal expansion. For this, use `QuasiHarmonicJob` (find more in its
        docstring)
    - Depending on the value given in `job.input['interaction_range']`, this class automatically changes the number of
        atoms. The input parameters of the reference job might have to be set appropriately (e.g. use `k_mesh_density`
        for DFT instead of setting k-points directly).
    - The structure used in the reference job should be a relaxed structure.
    - Theory behind it: https://en.wikipedia.org/wiki/Quasi-harmonic_approximation
    """
    def __init__(self, project, job_name):
        super(PhonopyJob, self).__init__(project, job_name)
        self.__name__ = "PhonopyJob"
        self.__version__ = "0.0.1"
        self.input["interaction_range"] = (10.0,
                                           "Minimal size of supercell, Ang")
        self.input["factor"] = (
            VaspToTHz,
            "Frequency unit conversion factor (default for VASP)",
        )
        self.input["displacement"] = (0.01, "atoms displacement, Ang")
        self.input["dos_mesh"] = (20, "mesh size for DOS calculation")
        self.input["primitive_matrix"] = None

        self.phonopy = None
        self._job_generator = PhonopyJobGenerator(self)
        self._disable_phonopy_pickle = False
        s.publication_add(phonopy_publication())

    @property
    def phonopy_pickling_disabled(self):
        return self._disable_phonopy_pickle

    @phonopy_pickling_disabled.setter
    def phonopy_pickling_disabled(self, disable):
        self._disable_phonopy_pickle = disable

    @property
    def _phonopy_unit_cell(self):
        if self.structure is not None:
            return atoms_to_phonopy(self.structure)
        else:
            return None

    def _enable_phonopy(self):
        if self.phonopy is None:
            if self.structure is not None:
                self.phonopy = Phonopy(
                    unitcell=self._phonopy_unit_cell,
                    supercell_matrix=self._phonopy_supercell_matrix(),
                    primitive_matrix=self.input["primitive_matrix"],
                    factor=self.input["factor"],
                )
                self.phonopy.generate_displacements(
                    distance=self.input["displacement"])
                self.to_hdf()
            else:
                raise ValueError(
                    "No reference job/ No reference structure found.")

    def list_structures(self):
        if self.structure is not None:
            self._enable_phonopy()
            return [struct for _, struct in self._job_generator.parameter_list]
        else:
            return []

    def _phonopy_supercell_matrix(self):
        if self.structure is not None:
            supercell_range = np.ceil(
                self.input["interaction_range"] / np.array([
                    np.linalg.norm(vec)
                    for vec in self._phonopy_unit_cell.get_cell()
                ]))
            return np.eye(3) * supercell_range
        else:
            return np.eye(3)

    def run_static(self):
        # Initialise the phonopy object before starting the first calculation.
        self._enable_phonopy()
        super(PhonopyJob, self).run_static()

    def run_if_interactive(self):
        self._enable_phonopy()
        super(PhonopyJob, self).run_if_interactive()

    def to_hdf(self, hdf=None, group_name=None):
        """
        Store the PhonopyJob in an HDF5 file

        Args:
            hdf (ProjectHDFio): HDF5 group object - optional
            group_name (str): HDF5 subgroup name - optional
        """
        super(PhonopyJob, self).to_hdf(hdf=hdf, group_name=group_name)
        if self.phonopy is not None and not self._disable_phonopy_pickle:
            with self.project_hdf5.open("output") as hdf5_output:
                hdf5_output["phonopy_pickeled"] = codecs.encode(
                    pickle.dumps(self.phonopy), "base64").decode()

    def from_hdf(self, hdf=None, group_name=None):
        """
        Restore the PhonopyJob from an HDF5 file

        Args:
            hdf (ProjectHDFio): HDF5 group object - optional
            group_name (str): HDF5 subgroup name - optional
        """
        super(PhonopyJob, self).from_hdf(hdf=hdf, group_name=group_name)
        with self.project_hdf5.open("output") as hdf5_output:
            if "phonopy_pickeled" in hdf5_output.list_nodes():
                self.phonopy = pickle.loads(
                    codecs.decode(hdf5_output["phonopy_pickeled"].encode(),
                                  "base64"))
                if "dos_total" in hdf5_output.list_nodes():
                    self._dos_total = hdf5_output["dos_total"]
                if "dos_energies" in hdf5_output.list_nodes():
                    self._dos_energies = hdf5_output["dos_energies"]

    def collect_output(self):
        """

        Returns:

        """
        if self.ref_job.server.run_mode.interactive:
            forces_lst = self.project_hdf5.inspect(
                self.child_ids[0])["output/generic/forces"]
        else:
            pr_job = self.project_hdf5.project.open(self.job_name + "_hdf5")
            forces_lst = [
                pr_job.inspect(job_name)["output/generic/forces"][-1]
                for job_name in self._get_jobs_sorted()
            ]
        self.phonopy.set_forces(forces_lst)
        self.phonopy.produce_force_constants()
        self.phonopy.run_mesh(mesh=[self.input["dos_mesh"]] * 3)
        mesh_dict = self.phonopy.get_mesh_dict()
        self.phonopy.run_total_dos()
        dos_dict = self.phonopy.get_total_dos_dict()

        self.to_hdf()

        with self.project_hdf5.open("output") as hdf5_out:
            hdf5_out["dos_total"] = dos_dict['total_dos']
            hdf5_out["dos_energies"] = dos_dict['frequency_points']
            hdf5_out["qpoints"] = mesh_dict['qpoints']
            hdf5_out["supercell_matrix"] = self._phonopy_supercell_matrix()
            hdf5_out[
                "displacement_dataset"] = self.phonopy.get_displacement_dataset(
                )
            hdf5_out[
                "dynamical_matrix"] = self.phonopy.dynamical_matrix.get_dynamical_matrix(
                )
            hdf5_out["force_constants"] = self.phonopy.force_constants

    def write_phonopy_force_constants(self,
                                      file_name="FORCE_CONSTANTS",
                                      cwd=None):
        """

        Args:
            file_name:
            cwd:

        Returns:

        """
        if cwd is not None:
            file_name = posixpath.join(cwd, file_name)
        write_FORCE_CONSTANTS(force_constants=self.phonopy.force_constants,
                              filename=file_name)

    def get_hesse_matrix(self):
        """

        Returns:

        """
        unit_conversion = (
            scipy.constants.physical_constants["Hartree energy in eV"][0] /
            scipy.constants.physical_constants["Bohr radius"][0]**2 *
            scipy.constants.angstrom**2)
        force_shape = np.shape(self.phonopy.force_constants)
        force_reshape = force_shape[0] * force_shape[2]
        return (np.transpose(self.phonopy.force_constants,
                             (0, 2, 1, 3)).reshape(
                                 (force_reshape, force_reshape)) /
                unit_conversion)

    def get_thermal_properties(self,
                               t_min=1,
                               t_max=1500,
                               t_step=50,
                               temperatures=None):
        """
        Returns thermal properties at constant volume in the given temperature range.  Can only be called after job
        successfully ran.

        Args:
            t_min (float): minimum sample temperature
            t_max (float): maximum sample temperature
            t_step (int):  tempeature sample interval
            temperatures (array_like, float):  custom array of temperature samples, if given t_min, t_max, t_step are
                                               ignored.

        Returns:
            :class:`Thermal`: thermal properties as returned by Phonopy
        """
        self.phonopy.run_thermal_properties(t_step=t_step,
                                            t_max=t_max,
                                            t_min=t_min,
                                            temperatures=temperatures)
        tp_dict = self.phonopy.get_thermal_properties_dict()
        kJ_mol_to_eV = 1000 / scipy.constants.Avogadro / scipy.constants.electron_volt
        return Thermal(tp_dict['temperatures'],
                       tp_dict['free_energy'] * kJ_mol_to_eV,
                       tp_dict['entropy'], tp_dict['heat_capacity'])

    @property
    def dos_total(self):
        """

        Returns:

        """
        return self["output/dos_total"]

    @property
    def dos_energies(self):
        """

        Returns:

        """
        return self["output/dos_energies"]

    @property
    def dynamical_matrix(self):
        """

        Returns:

        """
        return np.real_if_close(
            self.phonopy.get_dynamical_matrix().get_dynamical_matrix())

    def dynamical_matrix_at_q(self, q):
        """

        Args:
            q:

        Returns:

        """
        return np.real_if_close(self.phonopy.get_dynamical_matrix_at_q(q))

    def plot_dos(self, ax=None, *args, **qwargs):
        """

        Args:
            *args:
            ax:
            **qwargs:

        Returns:

        """
        try:
            import pylab as plt
        except ImportError:
            import matplotlib.pyplot as plt
        if ax is None:
            fig, ax = plt.subplots(1, 1)
        ax.plot(self["output/dos_energies"], self["output/dos_total"], *args,
                **qwargs)
        ax.set_xlabel("Frequency [THz]")
        ax.set_ylabel("DOS")
        ax.set_title("Phonon DOS vs Energy")
        return ax

    def get_band_structure(self,
                           npoints=101,
                           with_eigenvectors=False,
                           with_group_velocities=False):
        """
        Calculate band structure with automatic path through reciprocal space.

        Can only be called after job is finished.

        Args:
            npoints (int, optional):  Number of sample points between high symmetry points.
            with_eigenvectors (boolean, optional):  Calculate eigenvectors, too
            with_group_velocities (boolean, optional):  Calculate group velocities, too

        Returns:
            :class:`dict` of the results from phonopy under the following keys
                - 'qpoints':  list of (npoints, 3), samples paths in reciprocal space
                - 'distances':  list of (npoints,), distance along the paths in reciprocal space
                - 'frequencies':  list of (npoints, band), phonon frequencies
                - 'eigenvectors':  list of (npoints, band, band//3, 3), phonon eigenvectors
                - 'group_velocities': list of (npoints, band), group velocities
            where band is the number of bands (number of atoms * 3).  Each entry is a list of arrays, and each array
            corresponds to one path between two high-symmetry points automatically picked by Phonopy and may be of
            different length than other paths.  As compared to the phonopy output this method also reshapes the
            eigenvectors so that they directly have the same shape as the underlying structure.

        Raises:
            :exception:`ValueError`: method is called on a job that is not finished or aborted
        """
        if not self.status.finished:
            raise ValueError(
                "Job must be successfully run, before calling this method.")

        self.phonopy.auto_band_structure(
            npoints,
            with_eigenvectors=with_eigenvectors,
            with_group_velocities=with_group_velocities)
        results = self.phonopy.get_band_structure_dict()
        if results["eigenvectors"] is not None:
            # see https://phonopy.github.io/phonopy/phonopy-module.html#eigenvectors for the way phonopy stores the
            # eigenvectors
            results["eigenvectors"] = [
                e.transpose(0, 2, 1).reshape(*e.shape[:2], -1, 3)
                for e in results["eigenvectors"]
            ]
        return results

    def plot_band_structure(self, axis=None):
        """
        Plot bandstructure calculated with :method:`.get_bandstructure`.

        If :method:`.get_bandstructure` hasn't been called before, it is automatically called with the default arguments.

        Args:
            axis (matplotlib axis, optional): plot to this axis, if not given a new one is created.

        Returns:
            matplib axis: the axis the figure has been drawn to, if axis is given the same object is returned
        """
        try:
            import pylab as plt
        except ImportError:
            import matplotlib.pyplot as plt

        if axis is None:
            _, axis = plt.subplots(1, 1)

        try:
            results = self.phonopy.get_band_structure_dict()
        except RuntimeError:
            results = self.get_band_structure()

        distances = results["distances"]
        frequencies = results["frequencies"]

        # HACK: strictly speaking this breaks phonopy API and could bite us
        path_connections = self.phonopy._band_structure.path_connections
        labels = self.phonopy._band_structure.labels

        offset = 0
        tick_positions = [distances[0][0]]
        for di, fi, ci in zip(distances, frequencies, path_connections):
            axis.axvline(tick_positions[-1], color="black", linestyle="--")
            axis.plot(offset + di, fi, color="black")
            tick_positions.append(di[-1] + offset)
            if not ci:
                offset += .05
                plt.axvline(tick_positions[-1], color="black", linestyle="--")
                tick_positions.append(di[-1] + offset)
        axis.set_xticks(tick_positions[:-1])
        axis.set_xticklabels(labels)
        axis.set_xlabel("Bandpath")
        axis.set_ylabel("Frequency [THz]")
        axis.set_title("Bandstructure")
        return axis

    def validate_ready_to_run(self):
        if self.ref_job._generic_input["calc_mode"] != "static":
            raise ValueError(
                "Phonopy reference jobs should be static calculations, but got {}"
                .format(self.ref_job._generic_input["calc_mode"]))
        super().validate_ready_to_run()