def test_gv_nacl(ph_nacl: Phonopy): """Test of GroupVelocity by NaCl. This test should pass _get_dD_FD. """ gv_ref = [ 14.90162220, 14.90162220, 14.90162220, 14.90162220, 14.90162220, 14.90162220, 24.77046520, 24.77046520, 24.77046520, -2.17239664, -2.17239664, -2.17239664, -2.17239664, -2.17239664, -2.17239664, -3.05277585, -3.05277585, -3.05277585, ] gv = GroupVelocity(ph_nacl.dynamical_matrix, symmetry=ph_nacl.primitive_symmetry) gv.run([[0.1, 0.1, 0.1]]) np.testing.assert_allclose(gv.group_velocities[0].ravel(), gv_ref, atol=1e-5)
def test_gv_si(ph_si: Phonopy): """Test of GroupVelocity by Si. This test should pass _get_dD_analytical. """ gv_ref = [ 17.06443768, 17.06443768, 17.06443768, 17.06443768, 17.06443768, 17.06443768, 46.95145125, 46.95145125, 46.95145125, -3.59278449, -3.59278449, -3.59278449, -2.39847202, -2.39847202, -2.39847202, -2.39847202, -2.39847202, -2.39847202, ] gv = GroupVelocity(ph_si.dynamical_matrix, symmetry=ph_si.primitive_symmetry) gv.run([[0.1, 0.1, 0.1]]) np.testing.assert_allclose(gv.group_velocities[0].ravel(), gv_ref, atol=1e-5)
class Conductivity(object): def __init__( self, interaction, symmetry, grid_points=None, temperatures=None, sigmas=None, sigma_cutoff=None, is_isotope=False, mass_variances=None, mesh_divisors=None, coarse_mesh_shifts=None, boundary_mfp=None, # in micrometre is_kappa_star=True, gv_delta_q=None, # finite difference for group veolocity is_full_pp=False, log_level=0): if sigmas is None: self._sigmas = [] else: self._sigmas = sigmas self._sigma_cutoff = sigma_cutoff self._pp = interaction self._is_full_pp = is_full_pp self._collision = None # has to be set derived class if temperatures is None: self._temperatures = None else: self._temperatures = np.array(temperatures, dtype='double') self._is_kappa_star = is_kappa_star self._gv_delta_q = gv_delta_q self._log_level = log_level self._primitive = self._pp.get_primitive() self._dm = self._pp.get_dynamical_matrix() self._frequency_factor_to_THz = self._pp.get_frequency_factor_to_THz() self._cutoff_frequency = self._pp.get_cutoff_frequency() self._boundary_mfp = boundary_mfp self._symmetry = symmetry if not self._is_kappa_star: self._point_operations = np.array([np.eye(3, dtype='intc')], dtype='intc') else: self._point_operations = symmetry.get_reciprocal_operations() rec_lat = np.linalg.inv(self._primitive.get_cell()) self._rotations_cartesian = np.array([ similarity_transformation(rec_lat, r) for r in self._point_operations ], dtype='double') self._grid_points = None self._grid_weights = None self._grid_address = None self._ir_grid_points = None self._ir_grid_weights = None self._read_gamma = False self._read_gamma_iso = False self._kappa = None self._mode_kappa = None self._frequencies = None self._cv = None self._gv = None self._gv_sum2 = None self._gamma = None self._gamma_iso = None self._num_sampling_grid_points = 0 self._mesh = None self._mesh_divisors = None self._coarse_mesh = None self._coarse_mesh_shifts = None self._set_mesh_numbers(mesh_divisors=mesh_divisors, coarse_mesh_shifts=coarse_mesh_shifts) volume = self._primitive.get_volume() self._conversion_factor = unit_to_WmK / volume self._isotope = None self._mass_variances = None self._is_isotope = is_isotope if mass_variances is not None: self._is_isotope = True if self._is_isotope: self._set_isotope(mass_variances) self._grid_point_count = None self._set_grid_properties(grid_points) if (self._dm.is_nac() and self._dm.get_nac_method() == 'gonze' and self._gv_delta_q is None): self._gv_delta_q = 1e-5 if self._log_level: msg = "Group velocity calculation:\n" text = ("Analytical derivative of dynamical matrix is not " "implemented for NAC by Gonze et al. Instead " "numerical derivative of it is used with dq=1e-5 " "for group velocity calculation.") msg += textwrap.fill(text, initial_indent=" ", subsequent_indent=" ", width=70) print(msg) self._gv_obj = GroupVelocity( self._dm, q_length=self._gv_delta_q, symmetry=self._symmetry, frequency_factor_to_THz=self._frequency_factor_to_THz) # gv_delta_q may be changed. self._gv_delta_q = self._gv_obj.get_q_length() def __iter__(self): return self def __next__(self): if self._grid_point_count == len(self._grid_points): if self._log_level: print("=================== End of collection of collisions " "===================") raise StopIteration else: self._run_at_grid_point() self._grid_point_count += 1 return self._grid_point_count - 1 def next(self): return self.__next__() def get_mesh_divisors(self): return self._mesh_divisors @property def mesh_numbers(self): return self._mesh def get_mesh_numbers(self): return self.mesh_numbers def get_mode_heat_capacities(self): return self._cv def get_group_velocities(self): return self._gv def get_gv_by_gv(self): return self._gv_sum2 def get_frequencies(self): return self._frequencies[self._grid_points] def get_qpoints(self): return self._qpoints def get_grid_points(self): return self._grid_points def get_grid_weights(self): return self._grid_weights @property def temperatures(self): return self._temperatures def get_temperatures(self): return self.temperatures def set_temperatures(self, temperatures): self._temperatures = temperatures self._allocate_values() def set_gamma(self, gamma): self._gamma = gamma self._read_gamma = True def set_gamma_isotope(self, gamma_iso): self._gamma_iso = gamma_iso self._read_gamma_iso = True @property def gamma(self): return self._gamma def get_gamma(self): return self.gamma @property def gamma_isotope(self): return self._gamma_iso def get_gamma_isotope(self): return self.gamma_isotope @property def kappa(self): return self._kappa def get_kappa(self): return self.kappa @property def mode_kappa(self): return self._mode_kappa def get_mode_kappa(self): return self.mode_kappa def get_sigmas(self): return self._sigmas def get_sigma_cutoff_width(self): return self._sigma_cutoff def get_grid_point_count(self): return self._grid_point_count def get_averaged_pp_interaction(self): return self._averaged_pp_interaction def _run_at_grid_point(self): """This has to be implementated in the derived class""" pass def _allocate_values(self): """This has to be implementated in the derived class""" pass def _set_grid_properties(self, grid_points): self._grid_address = self._pp.get_grid_address() self._pp.set_nac_q_direction(nac_q_direction=None) if grid_points is not None: # Specify grid points self._grid_points = reduce_grid_points( self._mesh_divisors, self._grid_address, grid_points, coarse_mesh_shifts=self._coarse_mesh_shifts) (self._ir_grid_points, self._ir_grid_weights) = self._get_ir_grid_points() elif not self._is_kappa_star: # All grid points coarse_grid_address = get_grid_address(self._coarse_mesh) coarse_grid_points = np.arange(np.prod(self._coarse_mesh), dtype='uintp') self._grid_points = from_coarse_to_dense_grid_points( self._mesh, self._mesh_divisors, coarse_grid_points, coarse_grid_address, coarse_mesh_shifts=self._coarse_mesh_shifts) self._grid_weights = np.ones(len(self._grid_points), dtype='intc') self._ir_grid_points = self._grid_points self._ir_grid_weights = self._grid_weights else: # Automatic sampling self._grid_points, self._grid_weights = self._get_ir_grid_points() self._ir_grid_points = self._grid_points self._ir_grid_weights = self._grid_weights self._qpoints = np.array(self._grid_address[self._grid_points] / self._mesh.astype('double'), dtype='double', order='C') self._grid_point_count = 0 self._frequencies, self._eigenvectors, _ = self._pp.get_phonons() def _get_gamma_isotope_at_sigmas(self, i): gamma_iso = [] bz_map = self._pp.get_bz_map() pp_freqs, pp_eigvecs, pp_phonon_done = self._pp.get_phonons() for j, sigma in enumerate(self._sigmas): if self._log_level: text = "Calculating Gamma of ph-isotope with " if sigma is None: text += "tetrahedron method" else: text += "sigma=%s" % sigma print(text) self._isotope.set_sigma(sigma) self._isotope.set_phonons(self._grid_address, bz_map, pp_freqs, pp_eigvecs, pp_phonon_done, dm=self._dm) gp = self._grid_points[i] self._isotope.set_grid_point(gp) self._isotope.run() gamma_iso.append(self._isotope.get_gamma()) return np.array(gamma_iso, dtype='double', order='C') def _set_mesh_numbers(self, mesh_divisors=None, coarse_mesh_shifts=None): self._mesh = self._pp.get_mesh_numbers() if mesh_divisors is None: self._mesh_divisors = np.array([1, 1, 1], dtype='intc') else: self._mesh_divisors = [] for i, (m, n) in enumerate(zip(self._mesh, mesh_divisors)): if m % n == 0: self._mesh_divisors.append(n) else: self._mesh_divisors.append(1) print(("Mesh number %d for the " + ["first", "second", "third"][i] + " axis is not dividable by divisor %d.") % (m, n)) self._mesh_divisors = np.array(self._mesh_divisors, dtype='intc') if coarse_mesh_shifts is None: self._coarse_mesh_shifts = [False, False, False] else: self._coarse_mesh_shifts = coarse_mesh_shifts for i in range(3): if (self._coarse_mesh_shifts[i] and (self._mesh_divisors[i] % 2 != 0)): print("Coarse grid along " + ["first", "second", "third"][i] + " axis can not be shifted. Set False.") self._coarse_mesh_shifts[i] = False self._coarse_mesh = self._mesh // self._mesh_divisors if self._log_level: print("Lifetime sampling mesh: [ %d %d %d ]" % tuple(self._mesh // self._mesh_divisors)) def _get_ir_grid_points(self): if self._coarse_mesh_shifts is None: mesh_shifts = [False, False, False] else: mesh_shifts = self._coarse_mesh_shifts (coarse_grid_points, coarse_grid_weights, coarse_grid_address, _) = get_ir_grid_points(self._coarse_mesh, self._symmetry.get_pointgroup_operations(), mesh_shifts=mesh_shifts) grid_points = from_coarse_to_dense_grid_points( self._mesh, self._mesh_divisors, coarse_grid_points, coarse_grid_address, coarse_mesh_shifts=self._coarse_mesh_shifts) grid_weights = coarse_grid_weights assert grid_weights.sum() == np.prod(self._mesh // self._mesh_divisors) return grid_points, grid_weights def _set_isotope(self, mass_variances): if mass_variances is True: mv = None else: mv = mass_variances self._isotope = Isotope( self._mesh, self._primitive, mass_variances=mv, frequency_factor_to_THz=self._frequency_factor_to_THz, symprec=self._symmetry.get_symmetry_tolerance(), cutoff_frequency=self._cutoff_frequency, lapack_zheev_uplo=self._pp.get_lapack_zheev_uplo()) self._mass_variances = self._isotope.get_mass_variances() def _set_harmonic_properties(self, i_irgp, i_data): grid_point = self._grid_points[i_irgp] freqs = self._frequencies[grid_point][self._pp.get_band_indices()] self._cv[:, i_data, :] = self._get_cv(freqs) gv = self._get_gv(self._qpoints[i_irgp]) self._gv[i_data] = gv[self._pp.get_band_indices(), :] # Outer product of group velocities (v x v) [num_k*, num_freqs, 3, 3] gv_by_gv_tensor, order_kstar = self._get_gv_by_gv(i_irgp, i_data) self._num_sampling_grid_points += order_kstar # Sum all vxv at k* for j, vxv in enumerate(([0, 0], [1, 1], [2, 2], [1, 2], [0, 2], [0, 1])): self._gv_sum2[i_data, :, j] = gv_by_gv_tensor[:, vxv[0], vxv[1]] def _get_gv(self, q): self._gv_obj.run([q]) return self._gv_obj.get_group_velocity()[0] def _get_gv_by_gv(self, i_irgp, i_data): rotation_map = get_grid_points_by_rotations( self._grid_address[self._grid_points[i_irgp]], self._point_operations, self._mesh) gv = self._gv[i_data] gv_by_gv = np.zeros((len(gv), 3, 3), dtype='double') for r in self._rotations_cartesian: gvs_rot = np.dot(gv, r.T) gv_by_gv += [np.outer(r_gv, r_gv) for r_gv in gvs_rot] gv_by_gv /= len(rotation_map) // len(np.unique(rotation_map)) order_kstar = len(np.unique(rotation_map)) if self._grid_weights is not None: if order_kstar != self._grid_weights[i_irgp]: if self._log_level: text = ("Number of elements in k* is unequal " "to number of equivalent grid-points. " "This means that the mesh sampling grids break " "symmetry. Please check carefully " "the convergence over grid point densities.") msg = textwrap.fill(text, initial_indent=" ", subsequent_indent=" ", width=70) print("*" * 30 + "Warning" + "*" * 30) print(msg) print("*" * 67) return gv_by_gv, order_kstar def _get_cv(self, freqs): cv = np.zeros((len(self._temperatures), len(freqs)), dtype='double') # T/freq has to be large enough to avoid divergence. # Otherwise just set 0. for i, f in enumerate(freqs): finite_t = (self._temperatures > f / 100) if f > self._cutoff_frequency: cv[:, i] = np.where( finite_t, get_mode_cv(np.where(finite_t, self._temperatures, 10000), f * THzToEv), 0) return cv def _get_main_diagonal(self, i, j, k): num_band = self._primitive.get_number_of_atoms() * 3 main_diagonal = self._gamma[j, k, i].copy() if self._gamma_iso is not None: main_diagonal += self._gamma_iso[j, i] if self._boundary_mfp is not None: main_diagonal += self._get_boundary_scattering(i) # if self._boundary_mfp is not None: # for l in range(num_band): # # Acoustic modes at Gamma are avoided. # if i == 0 and l < 3: # continue # gv_norm = np.linalg.norm(self._gv[i, l]) # mean_free_path = (gv_norm * Angstrom * 1e6 / # (4 * np.pi * main_diagonal[l])) # if mean_free_path > self._boundary_mfp: # main_diagonal[l] = ( # gv_norm / (4 * np.pi * self._boundary_mfp)) return main_diagonal def _get_boundary_scattering(self, i): num_band = self._primitive.get_number_of_atoms() * 3 g_boundary = np.zeros(num_band, dtype='double') for l in range(num_band): g_boundary[l] = (np.linalg.norm(self._gv[i, l]) * Angstrom * 1e6 / (4 * np.pi * self._boundary_mfp)) return g_boundary def _show_log_header(self, i): if self._log_level: gp = self._grid_points[i] print("======================= Grid point %d (%d/%d) " "=======================" % (gp, i + 1, len(self._grid_points))) print("q-point: (%5.2f %5.2f %5.2f)" % tuple(self._qpoints[i])) if self._boundary_mfp is not None: if self._boundary_mfp > 1000: print("Boundary mean free path (millimetre): %.3f" % (self._boundary_mfp / 1000.0)) else: print("Boundary mean free path (micrometre): %.5f" % self._boundary_mfp) if self._is_isotope: print(("Mass variance parameters: " + "%5.2e " * len(self._mass_variances)) % tuple(self._mass_variances))