def __init__(self, fc2, fc3, supercell, primitive, supercell_extra=None, nac_params=None, nac_q_direction=None, ion_clamped=False, factor=VaspToTHz, symprec=1e-5): self._fc2 = fc2 self._fc3 = fc3 self._scell = supercell if supercell_extra is not None: self._scell_extra = supercell_extra else: self._scell_extra = supercell self._pcell = primitive self._ion_clamped = ion_clamped self._factor = factor self._symprec = symprec if nac_params is None: self._dm = DynamicalMatrix(self._scell_extra, self._pcell, self._fc2, symprec=self._symprec) else: self._dm = DynamicalMatrixNAC(self._scell_extra, self._pcell, self._fc2, symprec=self._symprec) self._dm.set_nac_params(nac_params) self._nac_q_direction = nac_q_direction self._shortest_vectors, self._multiplicity = get_smallest_vectors( self._scell, self._pcell, self._symprec) self._shortest_vectors_extra, self._multiplicity_extra = get_smallest_vectors( self._scell_extra, self._pcell, self._symprec) if self._ion_clamped: num_atom_prim = self._pcell.get_number_of_atoms() self._X = np.zeros((num_atom_prim, 3, 3, 3), dtype=float) else: self._X = self._get_X() self._dPhidu = self._get_dPhidu() self._gruneisen_parameters = None self._frequencies = None self._qpoints = None self._mesh = None self._band_paths = None self._band_distances = None self._run_mode = None self._weights = None
def _set_dynamical_matrix(self): if self._nac_params==None: self._dm = DynamicalMatrix(self._supercell, self._primitive, self._fc2, frequency_scale_factor=self._frequency_scale_factor, symprec=self._symprec) else: self._dm = DynamicalMatrixNAC(self._supercell, self._primitive, self._fc2, frequency_scale_factor=self._frequency_scale_factor, symprec=self._symprec) self._dm.set_nac_params(self._nac_params)
def _set_dynamical_matrix(self): self._dynamical_matrix = None if (self._supercell is None or self._primitive is None): print("Bug: Supercell or primitive is not created.") return False elif self._force_constants is None: print("Warning: Force constants are not prepared.") return False elif self._primitive.get_masses() is None: print("Warning: Atomic masses are not correctly set.") return False else: if self._nac_params is None: self._dynamical_matrix = DynamicalMatrix( self._supercell, self._primitive, self._force_constants, decimals=self._dynamical_matrix_decimals, symprec=self._symprec) else: self._dynamical_matrix = DynamicalMatrixNAC( self._supercell, self._primitive, self._force_constants, nac_params=self._nac_params, decimals=self._dynamical_matrix_decimals, symprec=self._symprec) return True
def get_dynamical_matrix(fc2, supercell, primitive, nac_params=None, frequency_scale_factor=None, decimals=None, symprec=1e-5): if nac_params is None: dm = DynamicalMatrix( supercell, primitive, fc2, frequency_scale_factor=frequency_scale_factor, decimals=decimals, symprec=symprec) else: dm = DynamicalMatrixNAC( supercell, primitive, fc2, frequency_scale_factor=frequency_scale_factor, decimals=decimals, symprec=symprec) dm.set_nac_params(nac_params) return dm
def set_dynamical_matrix(self, decimals=None): if self._is_nac: self._dynamical_matrix = \ DynamicalMatrixNAC(self._supercell, self._primitive, self._force_constants, decimals=decimals, symprec=self._symprec) else: self._dynamical_matrix = \ DynamicalMatrix(self._supercell, self._primitive, self._force_constants, decimals=decimals, symprec=self._symprec)
def _set_dynamical_matrix(self): if self._nac_params is None: self._dynamical_matrix = DynamicalMatrix( self._supercell, self._primitive, self._force_constants, decimals=self._dynamical_matrix_decimals, symprec=self._symprec) else: self._dynamical_matrix = DynamicalMatrixNAC( self._supercell, self._primitive, self._force_constants, nac_params=self._nac_params, decimals=self._dynamical_matrix_decimals, symprec=self._symprec)
class Gruneisen: def __init__(self, fc2, fc3, supercell, primitive, nac_params=None, nac_q_direction=None, ion_clamped=False, factor=VaspToTHz, symprec=1e-5): self._fc2 = fc2 self._fc3 = fc3 self._scell = supercell self._pcell = primitive self._ion_clamped = ion_clamped self._factor = factor self._symprec = symprec if nac_params is None: self._dm = DynamicalMatrix(self._scell, self._pcell, self._fc2, symprec=self._symprec) else: self._dm = DynamicalMatrixNAC(self._scell, self._pcell, self._fc2, symprec=self._symprec) self._dm.set_nac_params(nac_params) self._nac_q_direction = nac_q_direction self._shortest_vectors, self._multiplicity = get_smallest_vectors( self._scell, self._pcell, self._symprec) if self._ion_clamped: num_atom_prim = self._pcell.get_number_of_atoms() self._X = np.zeros((num_atom_prim, 3, 3, 3), dtype=float) else: self._X = self._get_X() self._dPhidu = self._get_dPhidu() self._gruneisen_parameters = None self._frequencies = None self._qpoints = None self._mesh = None self._band_paths = None self._band_distances = None self._run_mode = None self._weights = None def run(self): if self._run_mode == 'band': (self._gruneisen_parameters, self._frequencies) = self._calculate_band_paths() elif self._run_mode == 'qpoints' or self._run_mode == 'mesh': (self._gruneisen_parameters, self._frequencies) = self._calculate_at_qpoints(self._qpoints) else: sys.stderr.write('Q-points are not specified.\n') def get_gruneisen_parameters(self): return self._gruneisen_parameters def set_qpoints(self, qpoints): self._run_mode = 'qpoints' self._qpoints = qpoints def set_sampling_mesh(self, mesh, shift=None, is_gamma_center=False): self._run_mode = 'mesh' self._mesh = mesh self._qpoints, self._weights = get_qpoints( self._mesh, np.linalg.inv(self._pcell.get_cell()), q_mesh_shift=shift, is_gamma_center=is_gamma_center) def set_band_structure(self, paths): self._run_mode = 'band' self._band_paths = paths rec_lattice = np.linalg.inv(self._pcell.get_cell()) self._band_distances = [] for path in paths: distances_at_path = [0.] for i in range(len(path) - 1): distances_at_path.append( np.linalg.norm(np.dot(rec_lattice, path[i + 1] - path[i])) + distances_at_path[-1]) self._band_distances.append(distances_at_path) def write_yaml(self, filename="gruneisen3.yaml"): if self._gruneisen_parameters is not None: f = open(filename, 'w') if self._run_mode == 'band': self._write_band_yaml(f) elif self._run_mode == 'qpoints' or self._run_mode == 'mesh': self._write_yaml(f) f.close() def _write_yaml(self, f): if self._run_mode == 'mesh': f.write("mesh: [ %5d, %5d, %5d ]\n" % tuple(self._mesh)) f.write("nqpoint: %d\n" % len(self._qpoints)) f.write("phonon:\n") for i, (q, g_at_q, freqs_at_q) in enumerate( zip(self._qpoints, self._gruneisen_parameters, self._frequencies)): f.write("- q-position: [ %10.7f, %10.7f, %10.7f ]\n" % tuple(q)) if self._weights is not None: f.write(" multiplicity: %d\n" % self._weights[i]) f.write(" band:\n") for j, (g, freq) in enumerate(zip(g_at_q, freqs_at_q)): f.write(" - # %d\n" % (j + 1)) f.write(" frequency: %15.10f\n" % freq) f.write(" gruneisen: %15.10f\n" % (g.trace() / 3)) f.write(" gruneisen_tensor:\n") for g_xyz in g: f.write(" - [ %10.7f, %10.7f, %10.7f ]\n" % tuple(g_xyz)) def _write_band_yaml(self, f): f.write("path:\n\n") for path, distances, gs, fs in zip(self._band_paths, self._band_distances, self._gruneisen_parameters, self._frequencies): f.write("- nqpoint: %d\n" % len(path)) f.write(" phonon:\n") for i, (q, d, g_at_q, freqs_at_q) in enumerate(zip(path, distances, gs, fs)): f.write(" - q-position: [ %10.7f, %10.7f, %10.7f ]\n" % tuple(q)) f.write(" distance: %10.7f\n" % d) f.write(" band:\n") for j, (g, freq) in enumerate(zip(g_at_q, freqs_at_q)): f.write(" - # %d\n" % (j + 1)) f.write(" frequency: %15.10f\n" % freq) f.write(" gruneisen: %15.10f\n" % (g.trace() / 3)) f.write(" gruneisen_tensor:\n") for g_xyz in g: f.write(" - [ %10.7f, %10.7f, %10.7f ]\n" % tuple(g_xyz)) f.write("\n") def _calculate_at_qpoints(self, qpoints): gruneisen_parameters = [] frequencies = [] for i, q in enumerate(qpoints): if self._dm.is_nac(): if (np.abs(q) < 1e-5).all(): # If q is almost at Gamma if self._run_mode == 'band': # Direction estimated from neighboring point if i > 0: q_dir = qpoints[i] - qpoints[i - 1] elif i == 0 and len(qpoints) > 1: q_dir = qpoints[i + 1] - qpoints[i] else: q_dir = None g, omega2 = self._get_gruneisen_tensor( q, nac_q_direction=q_dir) else: # Specified q-vector g, omega2 = self._get_gruneisen_tensor( q, nac_q_direction=self._nac_q_direction) else: # If q is away from Gamma-point, then q-vector g, omega2 = self._get_gruneisen_tensor(q, nac_q_direction=q) else: # Without NAC g, omega2 = self._get_gruneisen_tensor(q) gruneisen_parameters.append(g) frequencies.append( np.sqrt(abs(omega2)) * np.sign(omega2) * self._factor) return gruneisen_parameters, frequencies def _calculate_band_paths(self): gruneisen_parameters = [] frequencies = [] for path in self._band_paths: (gruneisen_at_path, frequencies_at_path) = self._calculate_at_qpoints(path) gruneisen_parameters.append(gruneisen_at_path) frequencies.append(frequencies_at_path) return gruneisen_parameters, frequencies def _get_gruneisen_tensor(self, q, nac_q_direction=None): if nac_q_direction is None: self._dm.set_dynamical_matrix(q) else: self._dm.set_dynamical_matrix(q, nac_q_direction) omega2, w = np.linalg.eigh(self._dm.get_dynamical_matrix()) g = np.zeros((len(omega2), 3, 3), dtype=float) num_atom_prim = self._pcell.get_number_of_atoms() dDdu = self._get_dDdu(q) for s in range(len(omega2)): if (np.abs(q) < 1e-5).all() and s < 3: continue for i in range(3): for j in range(3): for nu in range(num_atom_prim): for pi in range(num_atom_prim): g[s] += (w[nu * 3 + i, s].conjugate() * dDdu[nu, pi, i, j] * w[pi * 3 + j, s]).real g[s] *= -1.0 / 2 / omega2[s] return g, omega2 def _get_dDdu(self, q): num_atom_prim = self._pcell.get_number_of_atoms() num_atom_super = self._scell.get_number_of_atoms() p2s = self._pcell.get_primitive_to_supercell_map() s2p = self._pcell.get_supercell_to_primitive_map() vecs = self._shortest_vectors multi = self._multiplicity m = self._pcell.get_masses() dPhidu = self._dPhidu dDdu = np.zeros((num_atom_prim, num_atom_prim, 3, 3, 3, 3), dtype=complex) for nu in range(num_atom_prim): for pi, p in enumerate(p2s): for Ppi, s in enumerate(s2p): if not s == p: continue phase = np.exp( 2j * np.pi * np.dot(vecs[Ppi, nu, :multi[Ppi, nu], :], q)).sum() / multi[Ppi, nu] dDdu[nu, pi] += phase * dPhidu[nu, Ppi] dDdu[nu, pi] /= np.sqrt(m[nu] * m[pi]) return dDdu def _get_dPhidu(self): fc3 = self._fc3 num_atom_prim = self._pcell.get_number_of_atoms() num_atom_super = self._scell.get_number_of_atoms() p2s = self._pcell.get_primitive_to_supercell_map() dPhidu = np.zeros((num_atom_prim, num_atom_super, 3, 3, 3, 3), dtype=float) for nu in range(num_atom_prim): Y = self._get_Y(nu) for pi in range(num_atom_super): for i in range(3): for j in range(3): for k in range(3): for l in range(3): for m in range(3): dPhidu[nu, pi, i, j, k, l] = (fc3[p2s[nu], pi, :, i, j, :] * Y[:, :, k, l]).sum() # (Y[:,:,k,l] + Y[:,:,l,k]) / 2).sum() # Symmetrization? return dPhidu def _get_Y(self, nu): P = self._fc2 X = self._X vecs = self._shortest_vectors multi = self._multiplicity lat = self._pcell.get_cell() num_atom_super = self._scell.get_number_of_atoms() R = np.array([ np.dot( vecs[Npi, nu, :multi[Npi, nu], :].sum(axis=0) / multi[Npi, nu], lat) for Npi in range(num_atom_super) ]) p2s = self._pcell.get_primitive_to_supercell_map() s2p = self._pcell.get_supercell_to_primitive_map() p2p = self._pcell.get_primitive_to_primitive_map() Y = np.zeros((num_atom_super, 3, 3, 3), dtype=float) for Mmu in range(num_atom_super): for i in range(3): Y[Mmu, i, i, :] = R[Mmu, :] Y[Mmu] += X[p2p[s2p[Mmu]]] return Y def _get_X(self): num_atom_super = self._scell.get_number_of_atoms() num_atom_prim = self._pcell.get_number_of_atoms() p2s = self._pcell.get_primitive_to_supercell_map() lat = self._pcell.get_cell() vecs = self._shortest_vectors multi = self._multiplicity X = np.zeros((num_atom_prim, 3, 3, 3), dtype=float) G = self._get_Gamma() P = self._fc2 for mu in range(num_atom_prim): for nu in range(num_atom_prim): R = np.array([ np.dot( vecs[Npi, nu, :multi[Npi, nu], :].sum(axis=0) / multi[Npi, nu], lat) for Npi in range(num_atom_super) ]) for i in range(3): for j in range(3): for k in range(3): for l in range(3): X[mu, i, j, k] -= G[mu, nu, i, l] * \ np.dot(P[p2s[nu], :, l, j], R[:, k]) return X def _get_Gamma(self): num_atom_prim = self._pcell.get_number_of_atoms() m = self._pcell.get_masses() self._dm.set_dynamical_matrix([0, 0, 0]) vals, vecs = np.linalg.eigh(self._dm.get_dynamical_matrix().real) G = np.zeros((num_atom_prim, num_atom_prim, 3, 3), dtype=float) for pi in range(num_atom_prim): for mu in range(num_atom_prim): for k in range(3): for i in range(3): # Eigenvectors are real. # 3: means optical modes G[pi, mu, k, i] = 1.0 / np.sqrt(m[pi] * m[mu]) * \ (vecs[pi * 3 + k, 3:] * vecs[mu * 3 + i, 3:] / vals[3:]).sum() return G
class JointDos: def __init__(self, mesh, primitive, supercell, fc2, nac_params=None, nac_q_direction=None, sigma=None, cutoff_frequency=None, frequency_step=None, num_frequency_points=None, temperatures=None, frequency_factor_to_THz=VaspToTHz, frequency_scale_factor=1.0, is_nosym=False, symprec=1e-5, filename=None, log_level=False, lapack_zheev_uplo='L'): self._grid_point = None self._mesh = np.array(mesh, dtype='intc') self._primitive = primitive self._supercell = supercell self._fc2 = fc2 self._nac_params = nac_params self._nac_q_direction = None self.set_nac_q_direction(nac_q_direction) self._sigma = None self.set_sigma(sigma) if cutoff_frequency is None: self._cutoff_frequency = 0 else: self._cutoff_frequency = cutoff_frequency self._frequency_step = frequency_step self._num_frequency_points = num_frequency_points self._temperatures = temperatures self._frequency_factor_to_THz = frequency_factor_to_THz self._frequency_scale_factor = frequency_scale_factor self._is_nosym = is_nosym self._symprec = symprec self._filename = filename self._log_level = log_level self._lapack_zheev_uplo = lapack_zheev_uplo self._num_band = self._primitive.get_number_of_atoms() * 3 self._reciprocal_lattice = np.linalg.inv(self._primitive.get_cell()) self._set_dynamical_matrix() self._symmetry = Symmetry(primitive, symprec) self._tetrahedron_method = None self._phonon_done = None self._frequencies = None self._eigenvectors = None self._joint_dos = None self._frequency_points = None def set_grid_points(self): if self._is_nosym: # All grid points self._grid_points = np.arange(np.prod(self._mesh), dtype='intc') self._grid_weights = np.ones(len(self.grid_points), dtype='intc') else: # Automatic sampling (grid_points, grid_weights, grid_address) = get_ir_grid_points( self._mesh, self._primitive, mesh_shifts=[False, False, False]) self._grid_points = grid_points self._grid_weights = grid_weights def run(self, is_band_freq=False): try: import anharmonic._phono3py as phono3c self._run_c(is_band_freq) except ImportError: print "Joint density of states in python is not implemented." return None, None def get_joint_dos(self): return self._joint_dos def get_frequency_points(self): return self._frequency_points def get_phonons(self): return self._frequencies, self._eigenvectors, self._phonon_done def get_primitive(self): return self._primitive def get_mesh_numbers(self): return self._mesh def set_nac_q_direction(self, nac_q_direction=None): if nac_q_direction is not None: self._nac_q_direction = np.array(nac_q_direction, dtype='double') def set_sigma(self, sigma): if sigma is None: self._sigma = None else: self._sigma = float(sigma) def set_grid_point(self, grid_point): self._grid_point = grid_point self._set_triplets() num_grid = np.prod(len(self._grid_address)) num_band = self._num_band if self._phonon_done is None: self._phonon_done = np.zeros(num_grid, dtype='byte') self._frequencies = np.zeros((num_grid, num_band), dtype='double') self._eigenvectors = np.zeros((num_grid, num_band, num_band), dtype='complex128') self._joint_dos = None self._frequency_points = None self.set_phonons(np.array([grid_point], dtype='intc')) def get_triplets_at_q(self): return self._triplets_at_q, self._weights_at_q def get_grid_address(self): return self._grid_address def get_bz_map(self): return self._bz_map def _run_c(self, is_band_freq = False, lang='C'): if self._sigma is None: if lang == 'C': self._run_c_with_g(is_band_freq) else: if self._temperatures is not None: print "JDOS with phonon occupation numbers doesn't work", print "in this option." self._run_py_tetrahedron_method(is_band_freq) else: self._run_c_with_g(is_band_freq) def _run_c_with_g(self, is_band_freq=False): self.set_phonons(self._triplets_at_q.ravel()) if is_band_freq == True: self._frequency_points = self._frequencies[self._grid_point] else: if self._sigma is None: f_max = np.max(self._frequencies) * 2 else: f_max = np.max(self._frequencies) * 2 + self._sigma * 4 f_max *= 1.005 f_min = 0 self._set_frequency_points(f_min, f_max) num_freq_points = len(self._frequency_points) num_mesh = np.prod(self._mesh) if self._temperatures is None: jdos = np.zeros((num_freq_points, 2), dtype='double') else: num_temps = len(self._temperatures) jdos = np.zeros((num_temps, num_freq_points, 2), dtype='double') occ_phonons = [] for t in self._temperatures: freqs = self._frequencies[self._triplets_at_q[:, 1:]] occ_phonons.append(np.where(freqs > self._cutoff_frequency, occupation(freqs, t), 0)) for i, freq_point in enumerate(self._frequency_points): g = get_triplets_integration_weights( self, np.array([freq_point], dtype='double'), self._sigma, neighboring_phonons=(i == 0), is_triplet_symmetry=False) if self._temperatures is None: jdos[i, 1] = np.sum( np.tensordot(g[0, :, 0], self._weights_at_q, axes=(0, 0))) # gx = g[2] - g[0] gx = g[1] + g[2] jdos[i, 0] = - np.sum( np.tensordot(gx[:, 0], self._weights_at_q, axes=(0, 0)))# the negative sign is for a clearer plot else: # g1 = g[1] g1 = g[2] - g[1] for j, n in enumerate(occ_phonons): # loop over temperature for k, l in list(np.ndindex(g.shape[3:])): # double loop over other two phonon branches jdos[j, i, 1] += np.dot( (n[:, 0, k] + n[:, 1, l] + 1) * g[0, :, 0, k, l], self._weights_at_q) jdos[j, i, 0] += - np.dot((n[:, 0, k] - n[:, 1, l]) * g1[:, 0, k, l], self._weights_at_q) # the negative sign is for a clearer plot self._joint_dos = jdos / num_mesh def _run_py_tetrahedron_method(self, is_band_freq=False): thm = TetrahedronMethod(self._reciprocal_lattice, mesh=self._mesh) self._vertices = get_tetrahedra_vertices( thm.get_tetrahedra(), self._mesh, self._triplets_at_q, self._grid_address) self.set_phonons(self._vertices.ravel()) if is_band_freq: self._frequency_points = self._frequencies[self._grid_point] else: f_max = np.max(self._frequencies) * 2 f_max *= 1.005 f_min = 0 self._set_frequency_points(f_min, f_max) num_freq_points = len(self._frequency_points) jdos = np.zeros((num_freq_points, 2), dtype='double') for vertices, w in zip(self._vertices, self._weights_at_q): for i, j in list(np.ndindex(self._num_band, self._num_band)): f1 = self._frequencies[vertices[0], i] f2 = self._frequencies[vertices[1], j] thm.set_tetrahedra_omegas(f1 + f2) thm.run(self._frequency_points) iw = thm.get_integration_weight() jdos[:, 1] += iw * w thm.set_tetrahedra_omegas(f1 - f2) thm.run(self._frequency_points) iw = thm.get_integration_weight() jdos[:, 0] += iw * w thm.set_tetrahedra_omegas(-f1 + f2) thm.run(self._frequency_points) iw = thm.get_integration_weight() jdos[:, 0] += iw * w self._joint_dos = jdos / np.prod(self._mesh) def _set_dynamical_matrix(self): if self._nac_params==None: self._dm = DynamicalMatrix(self._supercell, self._primitive, self._fc2, frequency_scale_factor=self._frequency_scale_factor, symprec=self._symprec) else: self._dm = DynamicalMatrixNAC(self._supercell, self._primitive, self._fc2, frequency_scale_factor=self._frequency_scale_factor, symprec=self._symprec) self._dm.set_nac_params(self._nac_params) def _set_triplets(self): primitive_lattice = self._primitive.get_cell() if self._is_nosym: if self._log_level: print "Triplets at q without considering symmetry" sys.stdout.flush() (self._triplets_at_q, self._weights_at_q, self._grid_address, self._bz_map, map_triplets, map_q) = get_nosym_triplets_at_q( self._grid_point, self._mesh, primitive_lattice) else: (self._triplets_at_q, self._weights_at_q, self._grid_address, self._bz_map, map_q) = get_triplets_at_q( self._grid_point, self._mesh, self._symmetry.get_pointgroup_operations(), primitive_lattice) def set_phonons(self, grid_points): set_phonon_c(self._dm, self._frequencies, self._eigenvectors, None, self._phonon_done, grid_points, self._grid_address, self._mesh, self._frequency_factor_to_THz, self._nac_q_direction, self._lapack_zheev_uplo) def _set_frequency_points(self, f_min, f_max): if self._num_frequency_points is None: if self._frequency_step is not None: self._frequency_points = np.arange( f_min, f_max, self._frequency_step, dtype='double') else: self._frequency_points = np.array(np.linspace( f_min, f_max, 201), dtype='double') else: self._frequency_points = np.array(np.linspace( f_min, f_max, self._num_frequency_points), dtype='double')
class Gruneisen: def __init__(self, fc2, fc3, supercell, primitive, supercell_extra=None, nac_params=None, nac_q_direction=None, ion_clamped=False, factor=VaspToTHz, symprec=1e-5): self._fc2 = fc2 self._fc3 = fc3 self._scell = supercell if supercell_extra is not None: self._scell_extra = supercell_extra else: self._scell_extra = supercell self._pcell = primitive self._ion_clamped = ion_clamped self._factor = factor self._symprec = symprec if nac_params is None: self._dm = DynamicalMatrix(self._scell_extra, self._pcell, self._fc2, symprec=self._symprec) else: self._dm = DynamicalMatrixNAC(self._scell_extra, self._pcell, self._fc2, symprec=self._symprec) self._dm.set_nac_params(nac_params) self._nac_q_direction = nac_q_direction self._shortest_vectors, self._multiplicity = get_smallest_vectors( self._scell, self._pcell, self._symprec) self._shortest_vectors_extra, self._multiplicity_extra = get_smallest_vectors( self._scell_extra, self._pcell, self._symprec) if self._ion_clamped: num_atom_prim = self._pcell.get_number_of_atoms() self._X = np.zeros((num_atom_prim, 3, 3, 3), dtype=float) else: self._X = self._get_X() self._dPhidu = self._get_dPhidu() self._gruneisen_parameters = None self._frequencies = None self._qpoints = None self._mesh = None self._band_paths = None self._band_distances = None self._run_mode = None self._weights = None def run(self): if self._run_mode == 'band': (self._gruneisen_parameters, self._frequencies) = self._calculate_band_paths() elif self._run_mode == 'qpoints' or self._run_mode == 'mesh': (self._gruneisen_parameters, self._frequencies) = self._calculate_at_qpoints(self._qpoints) else: sys.stderr.write('Q-points are not specified.\n') def get_gruneisen_parameters(self): return self._gruneisen_parameters def set_qpoints(self, qpoints): self._run_mode = 'qpoints' self._qpoints = qpoints def set_sampling_mesh(self, mesh, grid_shift=None, is_gamma_center=False): self._run_mode = 'mesh' self._mesh = mesh self._qpoints, self._weights = get_qpoints(self._mesh, self._pcell, grid_shift, is_gamma_center) def set_band_structure(self, paths): self._run_mode = 'band' self._band_paths = paths rec_lattice = np.linalg.inv(self._pcell.get_cell()) self._band_distances = [] for path in paths: distances_at_path = [0.] for i in range(len(path) - 1): distances_at_path.append(np.linalg.norm( np.dot(rec_lattice, path[i + 1] - path[i])) + distances_at_path[-1]) self._band_distances.append(distances_at_path) def write_yaml(self, filename="gruneisen3.yaml"): if self._gruneisen_parameters is not None: f = open(filename, 'w') if self._run_mode == 'band': self._write_band_yaml(f) elif self._run_mode == 'qpoints' or self._run_mode == 'mesh': self._write_yaml(f) f.close() def _write_yaml(self, f): if self._run_mode == 'mesh': f.write("mesh: [ %5d, %5d, %5d ]\n" % tuple(self._mesh)) f.write("nqpoint: %d\n" % len(self._qpoints)) f.write("phonon:\n") for i, (q, g_at_q, freqs_at_q) in enumerate( zip(self._qpoints, self._gruneisen_parameters, self._frequencies)): f.write("- q-position: [ %10.7f, %10.7f, %10.7f ]\n" % tuple(q)) if self._weights is not None: f.write(" multiplicity: %d\n" % self._weights[i]) f.write(" band:\n") for j, (g, freq) in enumerate(zip(g_at_q, freqs_at_q)): f.write(" - # %d\n" % (j + 1)) f.write(" frequency: %15.10f\n" % freq) f.write(" gruneisen: %15.10f\n" % (g.trace() / 3)) f.write(" gruneisen_tensor:\n") for g_xyz in g: f.write(" - [ %10.7f, %10.7f, %10.7f ]\n" % tuple(g_xyz)) def _write_band_yaml(self, f): f.write("path:\n\n") for path, distances, gs, fs in zip(self._band_paths, self._band_distances, self._gruneisen_parameters, self._frequencies): f.write("- nqpoint: %d\n" % len(path)) f.write(" phonon:\n") for i, (q, d, g_at_q, freqs_at_q) in enumerate( zip(path, distances, gs, fs)): f.write(" - q-position: [ %10.7f, %10.7f, %10.7f ]\n" % tuple(q)) f.write(" distance: %10.7f\n" % d) f.write(" band:\n") for j, (g, freq) in enumerate(zip(g_at_q, freqs_at_q)): f.write(" - # %d\n" % (j + 1)) f.write(" frequency: %15.10f\n" % freq) f.write(" gruneisen: %15.10f\n" % (g.trace() / 3)) f.write(" gruneisen_tensor:\n") for g_xyz in g: f.write(" - [ %10.7f, %10.7f, %10.7f ]\n" % tuple(g_xyz)) f.write("\n") def _calculate_at_qpoints(self, qpoints): gruneisen_parameters = [] frequencies = [] for i, q in enumerate(qpoints): if self._dm.is_nac(): if (np.abs(q) < 1e-5).all(): # If q is almost at Gamma if self._run_mode == 'band': # Direction estimated from neighboring point if i > 0: q_dir = qpoints[i] - qpoints[i - 1] elif i == 0 and len(qpoints) > 1: q_dir = qpoints[i + 1] - qpoints[i] else: q_dir = None g, omega2 = self._get_gruneisen_tensor( q, nac_q_direction=q_dir) else: # Specified q-vector g, omega2 = self._get_gruneisen_tensor( q, nac_q_direction=self._nac_q_direction) else: # If q is away from Gamma-point, then q-vector g, omega2 = self._get_gruneisen_tensor(q, nac_q_direction=q) else: # Without NAC g, omega2 = self._get_gruneisen_tensor(q) gruneisen_parameters.append(g) frequencies.append( np.sqrt(abs(omega2)) * np.sign(omega2) * self._factor) return gruneisen_parameters, frequencies def _calculate_band_paths(self): gruneisen_parameters = [] frequencies = [] for path in self._band_paths: (gruneisen_at_path, frequencies_at_path) = self._calculate_at_qpoints(path) gruneisen_parameters.append(gruneisen_at_path) frequencies.append(frequencies_at_path) return gruneisen_parameters, frequencies def _get_gruneisen_tensor(self, q, nac_q_direction=None): if nac_q_direction is None: self._dm.set_dynamical_matrix(q) else: self._dm.set_dynamical_matrix(q, nac_q_direction) omega2, w = np.linalg.eigh(self._dm.get_dynamical_matrix()) g = np.zeros((len(omega2), 3, 3), dtype=float) num_atom_prim = self._pcell.get_number_of_atoms() dDdu = self._get_dDdu(q) for s in range(len(omega2)): if (np.abs(q) < 1e-5).all() and s < 3: continue for i in range(3): for j in range(3): for nu in range(num_atom_prim): for pi in range(num_atom_prim): g[s] += (w[nu * 3 + i, s].conjugate() * dDdu[nu, pi, i, j] * w[pi * 3 + j, s]).real g[s] *= -1.0/2/omega2[s] return g, omega2 def _get_dDdu(self, q): num_atom_prim = self._pcell.get_number_of_atoms() num_atom_super = self._scell.get_number_of_atoms() p2s = self._pcell.get_primitive_to_supercell_map() s2p = self._pcell.get_supercell_to_primitive_map() vecs = self._shortest_vectors multi = self._multiplicity m = self._pcell.get_masses() dPhidu = self._dPhidu dDdu = np.zeros((num_atom_prim, num_atom_prim, 3, 3, 3, 3), dtype=complex) for nu in range(num_atom_prim): for pi, p in enumerate(p2s): for Ppi, s in enumerate(s2p): if not s==p: continue phase = np.exp(2j * np.pi * np.dot( vecs[Ppi,nu,:multi[Ppi, nu], :], q) ).sum() / multi[Ppi, nu] dDdu[nu, pi] += phase * dPhidu[nu, Ppi] dDdu[nu, pi] /= np.sqrt(m[nu] * m[pi]) return dDdu def _get_dPhidu(self): fc3 = self._fc3 num_atom_prim = self._pcell.get_number_of_atoms() num_atom_super = self._scell.get_number_of_atoms() p2s = self._pcell.get_primitive_to_supercell_map() dPhidu = np.zeros((num_atom_prim, num_atom_super, 3, 3, 3, 3), dtype=float) for nu in range(num_atom_prim): Y = self._get_Y(nu) for pi in range(num_atom_super): for i in range(3): for j in range(3): for k in range(3): for l in range(3): for m in range(3): dPhidu[nu, pi, i, j, k, l] = ( fc3[p2s[nu], pi, :, i, j, :] * Y[:, :, k, l]).sum() # (Y[:,:,k,l] + Y[:,:,l,k]) / 2).sum() # Symmetrization? return dPhidu def _get_Y(self, nu): P = self._fc2 X = self._X vecs = self._shortest_vectors multi = self._multiplicity lat = self._pcell.get_cell() num_atom_super = self._scell.get_number_of_atoms() R = np.array( [np.dot(vecs[Npi, nu, :multi[Npi,nu], :].sum(axis=0) / multi[Npi,nu], lat) for Npi in range(num_atom_super)]) p2s = self._pcell.get_primitive_to_supercell_map() s2p = self._pcell.get_supercell_to_primitive_map() p2p = self._pcell.get_primitive_to_primitive_map() # s2p = self._scell_extra.get_supercell_to_unitcell_map() # p2p = {0:0, 32:1} Y = np.zeros((num_atom_super, 3, 3, 3), dtype=float) for Mmu in range(num_atom_super): for i in range(3): Y[Mmu, i, i, :] = R[Mmu, :] Y[Mmu] += X[p2p[s2p[Mmu]]] return Y def _get_X(self): num_atom_super = self._scell_extra.get_number_of_atoms() num_atom_prim = self._pcell.get_number_of_atoms() p2s = self._pcell.get_primitive_to_supercell_map() lat = self._pcell.get_cell() vecs = self._shortest_vectors_extra multi = self._multiplicity_extra X = np.zeros((num_atom_prim, 3, 3, 3), dtype=float) G = self._get_Gamma() P = self._fc2 for mu in range(num_atom_prim): for nu in range(num_atom_prim): R = np.array( [np.dot(vecs[Npi, nu, :multi[Npi, nu], :].sum(axis=0) / multi[Npi, nu], lat) for Npi in range(num_atom_super)]) for i in range(3): for j in range(3): for k in range(3): for l in range(3): X[mu, i, j, k] -= G[mu, nu, i, l] * \ np.dot(P[p2s[nu], :, l, j], R[:, k]) return X def _get_Gamma(self): num_atom_prim = self._pcell.get_number_of_atoms() m = self._pcell.get_masses() self._dm.set_dynamical_matrix([0, 0, 0]) vals, vecs = np.linalg.eigh(self._dm.get_dynamical_matrix().real) G = np.zeros((num_atom_prim, num_atom_prim, 3, 3), dtype=float) for pi in range(num_atom_prim): for mu in range(num_atom_prim): for k in range(3): for i in range(3): # Eigenvectors are real. # 3: means optical modes G[pi, mu, k, i] = 1.0 / np.sqrt(m[pi] * m[mu]) * \ (vecs[pi * 3 + k, 3:] * vecs[mu * 3 + i, 3:] / vals[3:]).sum() return G