def setup(self): phonon = Phonopy(self._bulk, supercell_matrix=[[6, 0, 0], [0, 6, 0], [0, 0, 6]]) self._phonon = phonon self._symmetry = self._phonon.get_symmetry() self._force_sets = parse_FORCE_SETS() phonon.get_displacement_dataset()
def get_force_sets_inline(**kwargs): from phonopy.structure.atoms import Atoms as PhonopyAtoms from phonopy import Phonopy structure = kwargs.pop('structure') phonopy_input = kwargs.pop('phonopy_input').get_dict() # Generate phonopy phonon object bulk = PhonopyAtoms(symbols=[site.kind_name for site in structure.sites], positions=[site.position for site in structure.sites], cell=structure.cell) phonon = Phonopy(bulk, phonopy_input['supercell'], primitive_matrix=phonopy_input['primitive'], symprec=phonopy_input['symmetry_precision']) phonon.generate_displacements(distance=phonopy_input['distance']) # Build data_sets from forces of supercells with displacments data_sets = phonon.get_displacement_dataset() for i, first_atoms in enumerate(data_sets['first_atoms']): first_atoms['forces'] = kwargs.pop( 'force_{}'.format(i)).get_array('forces')[-1] data = ArrayData() data.set_array('force_sets', np.array(data_sets)) return {'phonopy_output': data}
def create_supercells_with_displacements_using_phonopy(structure, ph_settings): """ Use phonopy to create the supercells with displacements to calculate the force constants by using finite displacements methodology :param structure: StructureData object :param phonopy_input: ParametersData object containing a dictionary with the data needed for phonopy :return: A set of StructureData Objects containing the supercells with displacements """ from phonopy import Phonopy # Generate phonopy phonon object phonon = Phonopy(phonopy_bulk_from_structure(structure), supercell_matrix=ph_settings.dict.supercell, primitive_matrix=ph_settings.dict.primitive, symprec=ph_settings.dict.symmetry_precision) phonon.generate_displacements(distance=ph_settings.dict.distance) cells_with_disp = phonon.get_supercells_with_displacements() # Transform cells to StructureData and set them ready to return data_sets = phonon.get_displacement_dataset() data_sets_object = ForceSetsData(data_sets=data_sets) disp_cells = {'data_sets': data_sets_object} for i, phonopy_supercell in enumerate(cells_with_disp): supercell = StructureData(cell=phonopy_supercell.get_cell()) for symbol, position in zip(phonopy_supercell.get_chemical_symbols(), phonopy_supercell.get_positions()): supercell.append_atom(position=position, symbols=symbol) disp_cells["structure_{}".format(i)] = supercell return disp_cells
def create_supercells_with_displacements_using_phonopy(structure, phonopy_input): """ Create the supercells with the displacements to use the finite displacements methodology to calculate the force constants :param structure: Aiida StructureData Object :param phonopy_input: Aiida Parametersdata object containing a dictionary with the data needed to run phonopy: supercells matrix, primitive matrix and displacement distance. :return: dictionary of Aiida StructureData Objects containing the cells with displacements """ from phonopy.structure.atoms import Atoms as PhonopyAtoms from phonopy import Phonopy import numpy as np # Generate phonopy phonon object bulk = PhonopyAtoms(symbols=[site.kind_name for site in structure.sites], positions=[site.position for site in structure.sites], cell=structure.cell) phonopy_input = phonopy_input.get_dict() phonon = Phonopy(bulk, phonopy_input['supercell'], primitive_matrix=phonopy_input['primitive']) phonon.generate_displacements(distance=phonopy_input['distance']) cells_with_disp = phonon.get_supercells_with_displacements() # Transform cells to StructureData and set them ready to return data_sets = phonon.get_displacement_dataset() data_sets_object = ArrayData() for i, first_atoms in enumerate(data_sets['first_atoms']): data_sets_array = np.array([ first_atoms['direction'], first_atoms['number'], first_atoms['displacement'] ]) data_sets_object.set_array('data_sets_{}'.format(i), data_sets_array) disp_cells = {'data_sets': data_sets_object} for i, phonopy_supercell in enumerate(cells_with_disp): supercell = StructureData(cell=phonopy_supercell.get_cell()) for symbol, position in zip(phonopy_supercell.get_chemical_symbols(), phonopy_supercell.get_positions()): supercell.append_atom(position=position, symbols=symbol) disp_cells["structure_{}".format(i)] = supercell return disp_cells
def get_force_constants_from_phonopy(**kwargs): """ Calculate the force constants using phonopy :param kwargs: :return: """ from phonopy.structure.atoms import Atoms as PhonopyAtoms from phonopy import Phonopy import numpy as np # print 'function',kwargs structure = kwargs.pop('structure') phonopy_input = kwargs.pop('phonopy_input').get_dict() # Generate phonopy phonon object bulk = PhonopyAtoms(symbols=[site.kind_name for site in structure.sites], positions=[site.position for site in structure.sites], cell=structure.cell) phonon = Phonopy(bulk, phonopy_input['supercell'], primitive_matrix=phonopy_input['primitive'], distance=phonopy_input['distance']) phonon.generate_displacements(distance=phonopy_input['distance']) # Build data_sets from forces of supercells with displacments data_sets = phonon.get_displacement_dataset() for i, first_atoms in enumerate(data_sets['first_atoms']): forces = kwargs.pop('forces_{}'.format(i)).get_array('forces')[0] first_atoms['forces'] = np.array(forces, dtype='double', order='c') # LOCAL calculation # Calculate and get force constants phonon.set_displacement_dataset(data_sets) phonon.produce_force_constants() force_constants = phonon.get_force_constants() array_data = ArrayData() array_data.set_array('force_constants', force_constants) return {'array_data': array_data}
def get_force_constants_inline(**kwargs): from phonopy.structure.atoms import Atoms as PhonopyAtoms from phonopy import Phonopy structure = kwargs.pop('structure') phonopy_input = kwargs.pop('phonopy_input').get_dict() # Generate phonopy phonon object bulk = PhonopyAtoms(symbols=[site.kind_name for site in structure.sites], positions=[site.position for site in structure.sites], cell=structure.cell) phonon = Phonopy(bulk, phonopy_input['supercell'], primitive_matrix=phonopy_input['primitive'], distance=phonopy_input['distance']) # Build data_sets from forces of supercells with displacments data_sets = phonon.get_displacement_dataset() for i, first_atoms in enumerate(data_sets['first_atoms']): force = kwargs.pop('force_{}'.format(i)).get_array('forces')[0] first_atoms['forces'] = np.array(force, dtype='double', order='c') # Calculate and get force constants phonon.set_displacement_dataset(data_sets) phonon.produce_force_constants() force_constants = phonon.get_force_constants().tolist() # Set force constants ready to return force_constants = phonon.get_force_constants() data = ArrayData() data.set_array('force_constants', force_constants) data.set_array('force_sets', np.array(data_sets)) return {'phonopy_output': data}
def phonopy_run(phonon, single=True, filename='FORCE_SETS'): """Run the phonon calculations, using PhonoPy. The force constants are then stored in the Phonon ASE object. input: phonon: ASE Phonon object with Atoms and Calculator single: when True, the forces are computed only for a single step, and then exit. This allows to split the loop in independent iterations. When calling again the 'run' method, already computed steps are ignored, missing steps are completed, until no more are needed. When set to False, all steps are done in a row. output: True when a calculation step was performed, False otherwise or no more is needed. nb_of_iterations: the number of steps remaining """ from phonopy import Phonopy from phonopy.structure.atoms import Atoms as PAtoms from phonopy.structure.atoms import PhonopyAtoms import phonopy.file_IO as file_IO # we first look if an existing phonon pickle exists. This is the case if we # are running with iterative calls while return value is True. The first call # will then create the objects, which are subsequently updated until False. # Set calculator if provided # assert phonon.calc is not None, "Provide calculator in Phonon __init__ method" # Atoms in the supercell -- repeated in the lattice vector directions # beginning with the last supercell = phonon.atoms * phonon.N_c # create a PhonopyAtoms object cell = PhonopyAtoms(phonon.atoms.get_chemical_symbols(), positions=phonon.atoms.get_positions(), cell=phonon.atoms.get_cell(), magmoms=None) # is there an existing PhonoPy calculation ? # using factor=6.46541380e-2=VaspToeV if os.path.exists('FORCE_SETS'): phonpy = Phonopy(cell, numpy.diag(phonon.N_c), primitive_matrix=None, dynamical_matrix_decimals=None, force_constants_decimals=None, symprec=1e-05, is_symmetry=True, use_lapack_solver=False, log_level=1) force_sets = file_IO.parse_FORCE_SETS(filename='FORCE_SETS') phonpy.set_displacement_dataset(force_sets) # inactivate magmoms in supercell as some calculators do not provide that phonpy._supercell.magmoms = None phonpy.produce_force_constants(calculate_full_force_constants=False) else: # create a PhonoPy Phonon object. phonpy = Phonopy(cell, numpy.diag(phonon.N_c)) # generate displacements (minimal set) phonpy.generate_displacements(distance=0.01) # iterative call for all displacements set_of_forces, flag, nb_of_iterations = phonopy_run_calculate( phonon, phonpy, supercell, single) if flag is True: return nb_of_iterations # some more work is required sys.stdout.write('[ASE/Phonopy] Computing force constants\n') # use symmetry to derive forces in equivalent displacements phonpy.produce_force_constants(forces=set_of_forces) # generate disp.yaml and FORCE_SETS (for later use) displacements = phonpy.get_displacements() directions = phonpy.get_displacement_directions() file_IO.write_disp_yaml(displacements, phonpy.get_supercell(), directions=directions) file_IO.write_FORCE_SETS(phonpy.get_displacement_dataset()) # store as additional data in atoms 'info' phonon.atoms.info["phonopy"] = phonpy # save the PhonoPy object fid = opencew("phonopy.pkl") if fid is not None and rank == 0: print("[ASE/Phonopy] Writing %s" % "phonopy.pkl") pickle.dump(phonpy, fid, protocol=2) fid.close() # transfer results to the ASE phonon object # Number of atoms (primitive cell) natoms = len(phonon.indices) # Number of unit cells (supercell) N = numpy.prod(phonon.N_c) # Phonopy: force_constants size is [N*natoms,N*natoms,3,3] # Phi[i,j,a,b] with [i,j = atom in supercell] and [a,b=xyz] force_constants = phonpy.get_force_constants() # the atoms [i] which are moved are in the first cell of the supercell, i.e.Ni=0 # the forces are then stored for all atoms [Nj,j] as [3,3] matrices # we compute the sum on all supercells, which all contain n atoms. C_N = numpy.zeros((N, 3 * natoms, 3 * natoms), dtype=complex) Ni = 0 for Nj in range(N): for ni in range(natoms): Nni = ni for nj in range(natoms): # compute Nn indices Nnj = Nj * natoms + nj # get fc 3x3 matrix C_N[Nj, (3 * ni):(3 * ni + 3), (3 * nj):(3 * nj + 3)] += force_constants[Nni][Nnj] # convert to ASE storage # ASE: phonon.C_N size is be [N, 3*natoms, 3*natoms] # Phi[i,j] = Phi[j,i] phonon.C_N = C_N # fill dynamical matrix (mass prefactor) phonon.D_N = phonon.C_N.copy() # Add mass prefactor m_a = phonon.atoms.get_masses() phonon.m_inv_x = numpy.repeat(m_a[phonon.indices]**-0.5, 3) M_inv = numpy.outer(phonon.m_inv_x, phonon.m_inv_x) for D in phonon.D_N: D *= M_inv return 0 # nothing left to do
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
phonon_scell.generate_displacements(distance=0.03) # vasp Scells_phonopy = phonon_scell.get_supercells_with_displacements( ) # This returns a list of Phononpy atoms object # convert phonopy atoms objects to quippy atom objects Scells_quippy = [] for scell in Scells_phonopy: scell_qp = api_ph.phonopyAtoms_to_aseAtoms(scell) Scells_quippy.append(scell_qp) # calculate forces and convert to phonopy force_sets force_gap_scells = api_q.calc_force_sets_GAP(gp_xml_file, Scells_quippy) #parse force set and calc force constants phonon_scell.set_forces(force_gap_scells) PhonIO.write_FORCE_SETS(phonon_scell.get_displacement_dataset() ) # write forces & displacements to FORCE_SET force_set = PhonIO.parse_FORCE_SETS() # parse force_sets phonon_scell.set_displacement_dataset( force_set) # force_set is a list of forces and displacements if NAC == True: nac_params = PhonIO.get_born_parameters( open("BORN"), phonon_scell.get_primitive(), phonon_scell.get_primitive_symmetry()) if nac_params['factor'] == None: physical_units = get_default_physical_units(interface_mode) nac_params['factor'] = physical_units['nac_factor'] phonon_scell._nac_params = nac_params phonon_scell.produce_force_constants()
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., '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) @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: 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.server.run_mode.interactive: forces_lst = self.project_hdf5.inspect(self.child_ids[0])["output/generic/forces"] else: forces_lst = [self.project_hdf5.inspect(job_id)["output/generic/forces"][-1] for job_id in self.child_ids] self.phonopy.set_forces(forces_lst) self.phonopy.produce_force_constants() self.phonopy.set_mesh(mesh=[self.input['dos_mesh']] * 3) qpoints, weights, frequencies, eigvecs = self.phonopy.get_mesh() self.phonopy.set_total_DOS() erg, dos = self.phonopy.get_total_DOS() self.to_hdf() with self.project_hdf5.open("output") as hdf5_out: hdf5_out["dos_total"] = dos hdf5_out["dos_energies"] = erg hdf5_out["qpoints"] = 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.set_thermal_properties(t_step=t_step, t_max=t_max, t_min=t_min, temperatures=temperatures) return thermal(*self.phonopy.get_thermal_properties()) @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
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()