def get_phonon(structure, NAC=False, setup_forces=True, custom_supercell=None): super_cell_phonon = structure.get_supercell_phonon() if not(isinstance(custom_supercell, type(None))): super_cell_phonon = custom_supercell #Preparing the bulk type object bulk = PhonopyAtoms(symbols=structure.get_atomic_types(), scaled_positions=structure.get_scaled_positions(), cell=structure.get_cell().T) phonon = Phonopy(bulk, super_cell_phonon, primitive_matrix=structure.get_primitive_matrix()) # Non Analytical Corrections (NAC) from Phonopy [Frequencies only, eigenvectors no affected by this option] if NAC: print("Phonopy warning: Using Non Analytical Corrections") get_is_symmetry = True #from phonopy: settings.get_is_symmetry() primitive = phonon.get_primitive() nac_params = parse_BORN(primitive, get_is_symmetry) phonon.set_nac_params(nac_params=nac_params) if setup_forces: if not structure.forces_available(): # if not np.array(structure.get_force_constants()).any() and not np.array(structure.get_force_sets()).any(): print('No force sets/constants available!') exit() if np.array(structure.get_force_constants()).any(): phonon.set_force_constants(structure.get_force_constants()) else: phonon.set_displacement_dataset(structure.get_force_sets()) phonon.produce_force_constants(computation_algorithm="svd") return phonon
def get_phonon(structure, NAC=False, setup_forces=True, super_cell_phonon=np.identity(3), primitive_axis=np.identity(3)): phonon = Phonopy(structure, super_cell_phonon, primitive_matrix=primitive_axis, symprec=1e-5) # Non Analytical Corrections (NAC) from Phonopy [Frequencies only, eigenvectors no affected by this option] if setup_forces: if structure.get_force_constants() is not None: phonon.set_force_constants(structure.get_force_constants().get_array()) elif structure.get_force_sets() is not None: phonon.set_displacement_dataset(structure.get_force_sets().get_dict()) phonon.produce_force_constants(computation_algorithm="svd") structure.set_force_constants(ForceConstants(phonon.get_force_constants(), supercell=structure.get_force_sets().get_supercell())) else: print('No force sets/constants available!') exit() if NAC: print("Using non-analytical corrections") primitive = phonon.get_primitive() nac_params = parse_BORN(primitive, is_symmetry=True) phonon.set_nac_params(nac_params=nac_params) return phonon
def load(supercell_matrix, primitive_matrix=None, nac_params=None, unitcell=None, calculator="vasp", unitcell_filename=None, born_filename=None, force_sets_filename=None, force_constants_filename=None, factor=VaspToTHz, frequency_scale_factor=None, symprec=1e-5, is_symmetry=True, log_level=0): if unitcell is None: _unitcell, _ = read_crystal_structure(filename=unitcell_filename, interface_mode=calculator) else: _unitcell = unitcell # units keywords: factor, nac_factor, distance_to_A units = get_default_physical_units(calculator) phonon = Phonopy(_unitcell, supercell_matrix, primitive_matrix=primitive_matrix, factor=units['factor']) if nac_params is None: if born_filename is None: _nac_params = None else: _nac_params = parse_BORN(phonon.primitive, filename=born_filename) else: _nac_params = nac_params if _nac_params is not None: if _nac_params['factor'] is None: _nac_params['factor'] = units['nac_factor'] phonon.set_nac_params(_nac_params) if force_constants_filename is not None: dot_split = force_constants_filename.split('.') p2s_map = phonon.primitive.get_primitive_to_supercell_map() if len(dot_split) > 1 and dot_split[-1] == 'hdf5': fc = read_force_constants_hdf5(filename=force_constants_filename, p2s_map=p2s_map) else: fc = parse_FORCE_CONSTANTS(filename=force_constants_filename, p2s_map=p2s_map) phonon.set_force_constants(fc) elif force_sets_filename is not None: force_sets = parse_FORCE_SETS(filename=force_sets_filename) phonon.set_displacement_dataset(force_sets) phonon.produce_force_constants() return phonon
def get_phonon(structure, NAC=False, setup_forces=True, super_cell_phonon=np.identity(3), primitive_matrix=np.identity(3), symmetrize=True): """ Return a phonopy phonon object (instance of the class Phonon) :param structure: unit cell matrix (lattice vectors in rows) :param NAC: (Bool) activate/deactivate Non-analytic corrections :param setup_forces: (Bool) decide if pre-calculate harmonic forces in phonon object :param super_cell_phonon: 3x3 array containing the supercell to be used to calculate the force constants :param primitive_matrix: 3x3 array containing the primitive axis (in rows) which define the primitive cell :param symmetrize: decide if symmetrize the force constants :return: phonopy phonon object """ phonon = Phonopy(structure, super_cell_phonon, primitive_matrix=primitive_matrix, symprec=1e-5, is_symmetry=symmetrize) # Non Analytical Corrections (NAC) from Phonopy [Frequencies only, eigenvectors no affected by this option] if setup_forces: if structure.get_force_constants() is not None: phonon.set_force_constants( structure.get_force_constants().get_array()) elif structure.get_force_sets() is not None: phonon.set_displacement_dataset( structure.get_force_sets().get_dict()) phonon.produce_force_constants(computation_algorithm="svd") structure.set_force_constants( ForceConstants( phonon.get_force_constants(), supercell=structure.get_force_sets().get_supercell())) else: print('No force sets/constants available!') exit() if NAC: print("Using non-analytical corrections") primitive = phonon.get_primitive() try: nac_params = parse_BORN(primitive, is_symmetry=True) phonon.set_nac_params(nac_params=nac_params) except OSError: print('Required BORN file not found!') exit() return phonon
def get_phonon(structure, NAC=False, setup_forces=True, custom_supercell=None, symprec=1e-5): if custom_supercell is None: super_cell_phonon = structure.get_supercell_phonon() else: super_cell_phonon = custom_supercell # Preparing the bulk type object bulk = PhonopyAtoms(symbols=structure.get_atomic_elements(), scaled_positions=structure.get_scaled_positions(), cell=structure.get_cell()) phonon = Phonopy(bulk, super_cell_phonon, primitive_matrix=structure.get_primitive_matrix(), symprec=symprec) # Non Analytical Corrections (NAC) from Phonopy [Frequencies only, eigenvectors no affected by this option] if setup_forces: if structure.get_force_constants() is not None: phonon.set_force_constants( structure.get_force_constants().get_array()) elif structure.get_force_sets() is not None: phonon.set_displacement_dataset( structure.get_force_sets().get_dict()) phonon.produce_force_constants() structure.set_force_constants( ForceConstants( phonon.get_force_constants(), supercell=structure.get_force_sets().get_supercell())) else: print('No force sets/constants available!') exit() if NAC: print("Warning: Using Non Analytical Corrections") primitive = phonon.get_primitive() nac_params = parse_BORN(primitive, is_symmetry=True) phonon.set_nac_params(nac_params=nac_params) return phonon
def BZ_sample(self): """Returns a full sampling of the BZ by calculating frequencies at every unique k-point as sampled on the :attr:`dosmesh` grid. Returns: tuple: `(q-points, weights, eigenvalues)` where the `q-points` are the unique points after symmetry reduction; `weights` are the corresponding weights for each point; `eigenvalues` is a :class:`numpy.ndarray` of frequencies (in THz) for each point. """ #This is a little convoluted because of how the phonopy API works. We #want to get a full sampling of the BZ, but with symmetry, and then #compare the eigenvalues at every point. if self._bzsample is None: with chdir(self.phonodir): atoms = matdb_to_phonopy(self.atoms) phonpy = Phonopy(atoms, np.array(self.supercell).reshape(3, 3)) phonpy.set_force_constants(roll_fc(self.H)) phonpy._set_dynamical_matrix() #Phonopy requires full settings to compute the unique grid and #eigenvalues. We spoof the command-line parser. parser = get_parser() (options, args) = parser.parse_args() option_list = parser.option_list options.mesh_numbers = ' '.join(map(str, self.dosmesh)) phonopy_conf = PhonopyConfParser(options=options, option_list=option_list) settings = phonopy_conf.get_settings() #Next, set the mesh on the phonopy object and ask it to reduce and #calculate frequencies. mesh = settings.get_mesh() phonpy.set_mesh(*mesh) self._bzsample = phonpy.get_mesh()[0:3] return self._bzsample
def get_phonon(structure, NAC=False, setup_forces=True, custom_supercell=None): if custom_supercell is None: super_cell_phonon = structure.get_supercell_phonon() else: super_cell_phonon = custom_supercell # Preparing the bulk type object bulk = PhonopyAtoms(symbols=structure.get_atomic_elements(), scaled_positions=structure.get_scaled_positions(), cell=structure.get_cell()) phonon = Phonopy(bulk, super_cell_phonon, primitive_matrix=structure.get_primitive_matrix(), symprec=1e-5) # Non Analytical Corrections (NAC) from Phonopy [Frequencies only, eigenvectors no affected by this option] if setup_forces: if structure.get_force_constants() is not None: phonon.set_force_constants(structure.get_force_constants().get_array()) elif structure.get_force_sets() is not None: phonon.set_displacement_dataset(structure.get_force_sets().get_dict()) phonon.produce_force_constants() structure.set_force_constants(ForceConstants(phonon.get_force_constants(), supercell=structure.get_force_sets().get_supercell())) else: print('No force sets/constants available!') exit() if NAC: print("Warning: Using Non Analytical Corrections") primitive = phonon.get_primitive() nac_params = parse_BORN(primitive, is_symmetry=True) phonon.set_nac_params(nac_params=nac_params) return phonon
class HessianSupercell(object): """Represents a supercell configuration generator that has a set of eigenvalues and eigenvectors compatible with Hessian fitting. Args: primitive (ase.Atoms): primitive configuration to create the supercell from. supercell (np.array): array of `int` supercell matrix. folder (str): path to the `phonopy` folder where `FORCE_SETS` and displacements are kept; or where `vasprun.xml` is located when the full Hessian is calculated using VASP. Attributes: phonopy (phonopy.Phonopy): phonopy class for generating dynamical matrix, eigenvalues and eigenvectors. """ def __init__(self, primitive, supercell, folder, vasp=False): self.atoms = PhonopyAtoms(symbols=primitive.get_chemical_symbols(), positions=primitive.positions, masses=primitive.get_masses(), cell=primitive.cell, pbc=primitive.pbc) self.supercell = supercell self.folder = folder if not vasp: self.phonopy = Phonopy(self.atoms, supercell) self._get_dynmatrix() self.primitive = self.phonopy._dynamical_matrix.get_primitive() else: from matdb.io import vasp_to_xyz self.phonopy = Phonopy(self.atoms, supercell) if path.isfile("FORCE_CONSTANTS"): fc = file_IO.parse_FORCE_CONSTANTS(filename="FORCE_CONSTANTS") self.phonopy.set_force_constants(fc) self.phonopy._set_dynamical_matrix() self.primitive = primitive vasp_to_xyz(folder) self.parent = quippy.Atoms(path.join(folder, "output.xyz")) def diagonalize(self, q): """Diagonalizes the dynamical matrix at `q`. Args: q (numpy.array): q-point to diagonalize at. Returns: tuple: `(eigvals, eigvecs)`, where the eigenvalues are in units of `THz` and both eigenvalues and eigenvectors are in the *primitive* cell. """ return self.phonopy.get_frequencies_with_eigenvectors(q) def _get_phase_factor(self, modulation, argument): """Returns a phase factor corresponding to the given modulation. Args: modulation (numpy.ndarray): list of displacements, one for each atom. argument (float): phase angle (in degrees) to manipulate the displacement by. """ u = np.ravel(modulation) index_max_elem = np.argmax(abs(u)) max_elem = u[index_max_elem] phase_for_zero = max_elem / abs(max_elem) phase_factor = np.exp(1j * np.pi * argument / 180) / phase_for_zero return phase_factor def _map_eigvec_supercell(self, supercell, eigvec): """Maps the specified eigenvector to the target supercell. Args: supercell (phonopy.structure.cells.Supercell): the target supercell that the atoms will be displaced in. eigvec (numpy.ndarray): target eigenvector from the *primitive* cell. """ s2u_map = supercell.get_supercell_to_unitcell_map() u2u_map = supercell.get_unitcell_to_unitcell_map() s2uu_map = [u2u_map[x] for x in s2u_map] result = [] for i in range(supercell.get_number_of_atoms()): eig_index = s2uu_map[i] * 3 ej = eigvec[eig_index:eig_index + 3] result.append(ej) return np.array(result) def _get_displacements(self, supercell, eigvec, q, amplitude, argument): """Returns the vector of displacements for a single eigenvector. .. warning:: The supercell *must* be comensurate with the selected q-vector. Args: supercell (phonopy.structure.cells.Supercell): the target supercell that the atoms will be displaced in. eigvec (numpy.ndarray): target eigenvector from the *primitive* cell. """ m = supercell.get_masses() spos = supercell.get_scaled_positions() dim = supercell.get_supercell_matrix() coefs = np.exp( 2j * np.pi * np.dot(np.dot(spos, dim.T), q)) / np.sqrt(m) meigvec = self._map_eigvec_supercell(supercell, eigvec) u = (meigvec.T * coefs).T u = np.array(u) / np.sqrt(len(m)) phase_factor = self._get_phase_factor(u, argument) u *= phase_factor * amplitude return u def iterate(self, method="hessian", supercell=None, q=None, nrattle=0): """Returns a list of possible configurations, one for each eigenmode in the system, that has `supercell` is compatible with the specified `q` vector. Args: method (str): desired method for computing the eigenvalues and eigenvectors. Possible choices are :meth:`hessian` or :meth:`dynmat`. supercell (numpy.ndarray): supercell matrix to use in generating the configs. q (numpy.ndarray): q-vector that the resulting supercell should be compatible with. nrattle (int): number of additional, empty configs to include via :meth:`quippy.Atoms.rattle`. """ if method == "hessian": dmd = self.hessian() elif method == "vasp_hessian": dmd = self.vasp_hessian() else: dmd = self.dynmat(supercell, q) hname = "hessian1" seed = quippy.Atoms() seed.copy_from(dmd["template"]) result = quippy.AtomsList() result.append(dmd["template"]) #Delete the force, energy and virial information from the copy, so that #we don't duplicate it all over. del seed.params["dft_energy"] del seed.params["dft_virial"] del seed.properties["dft_force"] for l, v in zip(dmd["eigvals"], dmd["eigvecs"]): atc = seed.copy() #Add this eigenvector to its own configuration. atc.add_property(hname, 0.0, n_cols=3) H = np.reshape(v.real, (atc.n, 3)).T setattr(atc, hname, H) #Same thing for the eigenvalue. Then save it to the group folder #structure. atc.params.set_value(hname, l) atc.params.set_value("n_hessian", 1) #atc.add_property("force", 0.0, n_cols=3) result.append(atc) for i in range(nrattle): atRand = seed.copy() quippy.randomise(atRand.pos, 0.2) result.append(atRand) # atz = seed.copy() # atz.add_property("dft_force", 0.0, n_cols=3) #atz.params.set_value("dft_energy", return result def vasp_hessian(self): """Extracts the hessian from `vasprun.xml`. """ import xml.etree.ElementTree as ET tree = ET.parse('vasprun.xml') root = tree.getroot() calc = root.getchildren()[-2] dynm = calc.find("dynmat") hessian = dynm.getchildren()[0] H = [] for v in hessian.getchildren(): H.append(map(float, v.text.split())) H = -np.array(H) eigvals, eigvecs = np.linalg.eigh(H) Na = self.primitive.n * int(np.linalg.det(self.supercell)) result = { "template": self.parent, "eigvals": [], "eigvecs": [], "hessian": H } for i, l in enumerate(eigvals): if np.abs(l) > 1e-3: result["eigvals"].append(l) result["eigvecs"].append(eigvecs[:, i].reshape(Na, 3)) return result def hessian(self): """Returns the non-zero eigenvalues and their corresponding eigenvectors. """ supercell = self.phonopy.get_supercell() Na = supercell.get_number_of_atoms() Nf = Na * 3 H = self.phonopy._dynamical_matrix._force_constants.reshape((Nf, Nf)) eigvals, eigvecs = np.linalg.eigh(H) result = { "template": phonopy_to_ase(supercell), "eigvals": [], "eigvecs": [] } for i, l in enumerate(eigvals): if np.abs(l) > 0.1: result["eigvals"].append(l) result["eigvecs"].append(eigvecs[:, i].reshape(Na, 3)) return result def dynmat(self, supercell, q=None, cutoff=0.1): """Returns the non-zero eigenvalues and their corresponding eigenvectors for the specified supercell. Args: supercell (numpy.ndarray): supercell matrix to use in generating the configs. q (numpy.ndarray): q-vector that the resulting supercell should be compatible with. cutoff (float): minimum value an eigenvalue should have before it is included in the set. """ #We need to determine the supercell matrix that is compatible with the #given `q` and has `N` atoms. scell = get_supercell(self.primitive, supercell) eigvals, eigvecs = self.diagonalize(q) result = { "template": phonopy_to_ase(scell), "eigvals": [], "eigvecs": [] } for i, l in enumerate(eigvals): if np.abs(l) > 0.1: meigvec = self._map_eigvec_supercell(scell, eigvecs[:, i]) result["eigvals"].append(l) result["eigvecs"].append(meigvec) return result def _get_dynmatrix(self): """Extracts the force constants from `FORCE_SETS` and constructs the dynamical matrix for the calculation. """ with chdir(self.folder): force_sets = file_IO.parse_FORCE_SETS() self.phonopy.set_displacement_dataset(force_sets) self.phonopy.produce_force_constants( calculate_full_force_constants=True, computation_algorithm="svd") self.phonopy._set_dynamical_matrix()