def heat_capacity_2d(self): """Calculate the generalized 2d heat capacity for each k point in k_points and each mode. If classical, it returns the Boltzmann constant in W/m/K. Returns ------- heat_capacity_2d : np.array(n_k_points, n_modes, n_modes) heat capacity in W/m/K for each k point and each modes couple. """ q_points = self._reciprocal_grid.unitary_grid(is_wrapping=False) shape = (self.n_k_points, self.n_modes, self.n_modes) log_size(shape, name='heat_capacity_2d', type=np.float) heat_capacity_2d = np.zeros(shape) for ik in range(len(q_points)): q_point = q_points[ik] phonon = HarmonicWithQTemp( q_point=q_point, second=self.forceconstants.second, distance_threshold=self.forceconstants.distance_threshold, folder=self.folder, storage=self.storage, temperature=self.temperature, is_classic=self.is_classic, is_nw=self.is_nw, is_unfolding=self.is_unfolding) heat_capacity_2d[ik] = phonon.heat_capacity_2d return heat_capacity_2d
def calculate_scattering_matrix(self, is_including_diagonal, is_rescaling_omega, is_rescaling_population): physical_mode = self.phonons.physical_mode.reshape((self.n_phonons)) frequency = self.phonons.frequency.reshape( (self.n_phonons))[physical_mode] gamma_tensor = -1 * self.phonons._ps_gamma_and_gamma_tensor[:, 2:] index = np.outer(physical_mode, physical_mode) n_physical = physical_mode.sum() log_size((n_physical, n_physical), np.float, name='_scattering_matrix') gamma_tensor = gamma_tensor[index].reshape((n_physical, n_physical)) if is_rescaling_population: n = self.phonons.population.reshape( (self.n_phonons))[physical_mode] gamma_tensor = np.einsum('a,ab,b->ab', ((n * (n + 1)))**(1 / 2), gamma_tensor, 1 / ((n * (n + 1))**(1 / 2))) logging.info('Asymmetry of gamma_tensor: ' + str(np.abs(gamma_tensor - gamma_tensor.T).sum())) if is_including_diagonal: gamma = self.phonons.bandwidth.reshape( (self.n_phonons))[physical_mode] gamma_tensor = gamma_tensor + np.diag(gamma) if is_rescaling_omega: gamma_tensor = 1 / (frequency.reshape( -1, 1)) * gamma_tensor * (frequency.reshape(1, -1)) return gamma_tensor
def _eigensystem(self): """Calculate the eigensystems, for each k point in k_points. Returns ------- _eigensystem : np.array(n_k_points, n_unit_cell * 3, n_unit_cell * 3 + 1) eigensystem is calculated for each k point, the three dimensional array records the eigenvalues in the last column of the last dimension. If the system is not amorphous, these values are stored as complex numbers. """ q_points = self._reciprocal_grid.unitary_grid(is_wrapping=False) shape = (self.n_k_points, self.n_modes + 1, self.n_modes) log_size(shape, name='eigensystem', type=np.complex) eigensystem = np.zeros(shape, dtype=np.complex) for ik in range(len(q_points)): q_point = q_points[ik] phonon = HarmonicWithQ( q_point=q_point, second=self.forceconstants.second, distance_threshold=self.forceconstants.distance_threshold, folder=self.folder, storage=self.storage, is_nw=self.is_nw, is_unfolding=self.is_unfolding) eigensystem[ik] = phonon._eigensystem return eigensystem
def calculate_sij(self, direction): q_point = self.q_point is_amorphous = self.is_amorphous shape = (3 * self.atoms.positions.shape[0], 3 * self.atoms.positions.shape[0]) if is_amorphous and (self.q_point == np.array([0, 0, 0])).all(): type = np.float else: type = np.complex eigenvects = self._eigensystem[1:, :] if direction == 0: dynmat_derivatives = self._dynmat_derivatives_x if direction == 1: dynmat_derivatives = self._dynmat_derivatives_y if direction == 2: dynmat_derivatives = self._dynmat_derivatives_z if self.atoms.positions.shape[0] > 500: # We want to print only for big systems logging.info('Flux operators for q = ' + str(q_point) + ', direction = ' + str(direction)) dir = ['_x', '_y', '_z'] log_size(shape, type, name='sij' + dir[direction]) if is_amorphous and (self.q_point == np.array([0, 0, 0])).all(): sij = tf.tensordot(eigenvects, dynmat_derivatives, (0, 1)) sij = tf.tensordot(eigenvects, sij, (0, 1)) else: eigenvects = tf.cast(eigenvects, tf.complex128) dynmat_derivatives = tf.cast(dynmat_derivatives, tf.complex128) sij = tf.tensordot(eigenvects, dynmat_derivatives, (0, 1)) sij = tf.tensordot(tf.math.conj(eigenvects), sij, (0, 1)) return sij
def calculate_mfp_evect(self): """This calculates the mean free path of evect. In materials where most scattering events conserve momentum :ref:'Relaxon Theory Section' (e.g. in two dimensional materials or three dimensional materials at extremely low temparatures), this quantity can be used to calculate thermal conductivity. Returns ------- lambda : np array (n_k_points, n_modes, 3) """ phonons = self.phonons physical_mode = self.phonons.physical_mode.reshape(self.n_phonons) velocity = phonons.velocity.real.reshape((phonons.n_phonons, 3))[physical_mode, :] _scattering_matrix = -1 * self.calculate_scattering_matrix(with_diagonal=True) physical_mode = self.phonons.physical_mode.reshape(self.n_phonons) evals, evects = np.linalg.eig(_scattering_matrix) neg_diag = (_scattering_matrix.diagonal() < 0).sum() logging.info('negative on diagonal : ' + str(neg_diag)) logging.info('negative eigenvals : ' + str((evals < 0).sum())) # TODO: find a better way to filter states new_physical_states = np.argwhere(evals >= 0)[0, 0] reduced_evects = evects[new_physical_states:, new_physical_states:] reduced_evals = evals[new_physical_states:] log_size(_scattering_matrix.shape, name='reduced_scattering') reduced_scattering_inverse = np.zeros_like(_scattering_matrix) reduced_scattering_inverse[new_physical_states:, new_physical_states:] = reduced_evects.dot(np.diag(1/reduced_evals)).dot(np.linalg.inv(reduced_evects)) scattering_inverse = reduced_scattering_inverse # e, v = np.linalg.eig(a) # a = v.dot(np.diag(e)).dot(np.linalg.inv(v)) lambd = np.zeros((phonons.n_phonons, 3)) lambd[physical_mode] = scattering_inverse.dot(velocity[:, :]) return lambd
def calculate_dynmat_derivatives(self, direction): q_point = self.q_point is_amorphous = self.is_amorphous distance_threshold = self.distance_threshold atoms = self.atoms list_of_replicas = self.second.list_of_replicas replicated_cell = self.second.replicated_atoms.cell replicated_cell_inv = self.second._replicated_cell_inv cell_inv = self.second.cell_inv dynmat = self.second.dynmat positions = self.atoms.positions n_unit_cell = atoms.positions.shape[0] n_modes = n_unit_cell * 3 n_replicas = np.prod(self.supercell) shape = (1, n_unit_cell * 3, n_unit_cell * 3) if is_amorphous: type = np.float else: type = np.complex dir = ['_x', '_y', '_z'] log_size(shape, type, name='dynamical_matrix_derivative_' + dir[direction]) if is_amorphous: distance = positions[:, np.newaxis, :] - positions[np.newaxis, :, :] distance = wrap_coordinates(distance, replicated_cell, replicated_cell_inv) dynmat_derivatives = contract('ij,ibjc->ibjc', tf.convert_to_tensor(distance[..., direction]), dynmat[0, :, :, 0, :, :], backend='tensorflow') else: distance = positions[:, np.newaxis, np.newaxis, :] - ( positions[np.newaxis, np.newaxis, :, :] + list_of_replicas[np.newaxis, :, np.newaxis, :]) if distance_threshold is not None: distance_to_wrap = positions[:, np.newaxis, np.newaxis, :] - ( self.second.replicated_atoms.positions.reshape(n_replicas, n_unit_cell, 3)[ np.newaxis, :, :, :]) shape = (n_unit_cell, 3, n_unit_cell, 3) type = np.complex dynmat_derivatives = np.zeros(shape, dtype=type) for l in range(n_replicas): wrapped_distance = wrap_coordinates(distance_to_wrap[:, l, :, :], replicated_cell, replicated_cell_inv) mask = (np.linalg.norm(wrapped_distance, axis=-1) < distance_threshold) id_i, id_j = np.argwhere(mask).T dynmat_derivatives[id_i, :, id_j, :] += contract('f,fbc->fbc', distance[id_i, l, id_j, direction], \ dynmat.numpy()[0, id_i, :, 0, id_j, :] * chi(q_point, list_of_replicas, cell_inv)[l]) else: dynmat_derivatives = contract('ilj,ibljc,l->ibjc', tf.convert_to_tensor(distance.astype(np.complex)[..., direction]), tf.cast(dynmat[0], tf.complex128), tf.convert_to_tensor(chi(q_point, list_of_replicas, cell_inv).flatten().astype(np.complex)), backend='tensorflow') dynmat_derivatives = tf.reshape(dynmat_derivatives, (n_modes, n_modes)) return dynmat_derivatives
def calculate_eigensystem(self, only_eigenvals): dyn_s = self._dynmat_fourier if only_eigenvals: esystem = tf.linalg.eigvalsh(dyn_s) else: log_size(self._dynmat_fourier.shape, type=np.complex, name='eigensystem') esystem = tf.linalg.eigh(dyn_s) esystem = tf.concat(axis=0, values=(esystem[0][tf.newaxis, :], esystem[1])) return esystem
def calculate_dynmat(self): mass = self.atoms.get_masses() shape = self.value.shape log_size(shape, np.float, name='dynmat') dynmat = self.value * 1 / np.sqrt( mass[np.newaxis, :, np.newaxis, np.newaxis, np.newaxis, np.newaxis]) dynmat = dynmat * 1 / np.sqrt(mass[np.newaxis, np.newaxis, np.newaxis, np.newaxis, :, np.newaxis]) evtotenjovermol = units.mol / (10 * units.J) return tf.convert_to_tensor(dynmat * evtotenjovermol)
def calculate_dynmat_fourier(self): q_point = self.q_point distance_threshold = self.distance_threshold atoms = self.atoms n_unit_cell = atoms.positions.shape[0] n_replicas = np.prod(self.supercell) dynmat = self.second.dynmat cell_inv = self.second.cell_inv replicated_cell_inv = self.second._replicated_cell_inv is_at_gamma = (q_point == (0, 0, 0)).all() is_amorphous = (n_replicas == 1) list_of_replicas = self.second.list_of_replicas log_size((self.n_modes, self.n_modes), np.complex, name='dynmat_fourier') if distance_threshold is not None: shape = (n_unit_cell, 3, n_unit_cell, 3) type = np.complex dyn_s = np.zeros(shape, dtype=type) replicated_cell = self.second.replicated_atoms.cell for l in range(n_replicas): distance_to_wrap = atoms.positions[:, np.newaxis, :] - ( self.second.replicated_atoms.positions.reshape( n_replicas, n_unit_cell, 3)[np.newaxis, l, :, :]) distance_to_wrap = wrap_coordinates(distance_to_wrap, replicated_cell, replicated_cell_inv) mask = np.linalg.norm(distance_to_wrap, axis=-1) < distance_threshold id_i, id_j = np.argwhere(mask).T dyn_s[id_i, :, id_j, :] += dynmat.numpy()[0, id_i, :, 0, id_j, :] * chi( q_point, list_of_replicas, cell_inv)[l] else: if is_at_gamma: if is_amorphous: dyn_s = dynmat[0] else: dyn_s = contract('ialjb->iajb', dynmat[0], backend='tensorflow') else: dyn_s = contract('ialjb,l->iajb', tf.cast(dynmat[0], tf.complex128), tf.convert_to_tensor( chi(q_point, list_of_replicas, cell_inv).flatten()), backend='tensorflow') dyn_s = tf.reshape(dyn_s, (self.n_modes, self.n_modes)) return dyn_s
def project_crystal(phonons): is_balanced = phonons.is_balanced is_gamma_tensor_enabled = phonons.is_gamma_tensor_enabled n_replicas = phonons.forceconstants.third.n_replicas try: sparse_third = phonons.forceconstants.third.value.reshape( (phonons.n_modes, -1)) # transpose sparse_coords = tf.stack( [sparse_third.coords[1], sparse_third.coords[0]], -1) third_tf = tf.SparseTensor( sparse_coords, sparse_third.data, ((phonons.n_modes * n_replicas)**2, phonons.n_modes)) is_sparse = True except AttributeError: third_tf = tf.convert_to_tensor(phonons.forceconstants.third.value) is_sparse = False third_tf = tf.cast(third_tf, dtype=tf.complex64) k_mesh = phonons._reciprocal_grid.unitary_grid(is_wrapping=False) n_k_points = k_mesh.shape[0] _chi_k = tf.convert_to_tensor(phonons.forceconstants.third._chi_k(k_mesh)) _chi_k = tf.cast(_chi_k, dtype=tf.complex64) evect_tf = tf.convert_to_tensor(phonons._rescaled_eigenvectors) evect_tf = tf.cast(evect_tf, dtype=tf.complex64) # The ps and gamma matrix stores ps, gamma and then the scattering matrix if is_gamma_tensor_enabled: shape = (phonons.n_phonons, 2 + phonons.n_phonons) log_size(shape, name='scattering_tensor') ps_and_gamma = np.zeros(shape) else: ps_and_gamma = np.zeros((phonons.n_phonons, 2)) second_minus = tf.math.conj(evect_tf) second_minus_chi = tf.math.conj(_chi_k) logging.info('Projection started') population = phonons.population broadening_shape = phonons.broadening_shape physical_mode = phonons.physical_mode.reshape( (phonons.n_k_points, phonons.n_modes)) omega = phonons.omega if not phonons.third_bandwidth: velocity_tf = tf.convert_to_tensor(phonons.velocity) gamma_to_thz = 1e11 * units.mol * (units.mol / (10 * units.J))**2 for nu_single in range(phonons.n_phonons): if nu_single % 200 == 0: logging.info('Calculating third order projection ' + str(nu_single) + ', ' + \ str(np.round(nu_single / phonons.n_phonons, 2) * 100) + '%') index_k, mu = np.unravel_index(nu_single, (n_k_points, phonons.n_modes)) if is_sparse: third_nu_tf = tf.sparse.sparse_dense_matmul( third_tf, evect_tf[index_k, :, mu, tf.newaxis]) else: third_nu_tf = contract('ijk,i->jk', third_tf, evect_tf[index_k, :, mu], backend='tensorflow') third_nu_tf = tf.reshape( third_nu_tf, (n_replicas * n_replicas, phonons.n_modes, phonons.n_modes)) third_nu_tf = tf.cast(tf.reshape( third_nu_tf, (n_replicas, phonons.n_modes, n_replicas, phonons.n_modes)), dtype=tf.complex64) third_nu_tf = tf.transpose(third_nu_tf, (0, 2, 1, 3)) third_nu_tf = tf.reshape( third_nu_tf, (n_replicas * n_replicas, phonons.n_modes, phonons.n_modes)) for is_plus in (0, 1): index_kpp_full = phonons._allowed_third_phonons_index( index_k, is_plus) index_kpp_full = tf.cast(index_kpp_full, dtype=tf.int32) if phonons.third_bandwidth: sigma_tf = tf.constant(phonons.third_bandwidth, dtype=tf.float64) else: cellinv = phonons.forceconstants.cell_inv k_size = phonons.kpts sigma_tf = calculate_broadening(velocity_tf, cellinv, k_size, index_kpp_full) out = calculate_dirac_delta_crystal(omega, population, physical_mode, sigma_tf, broadening_shape, index_kpp_full, index_k, mu, is_plus, is_balanced) if not out: continue if is_plus: second = evect_tf second_chi = _chi_k else: second = second_minus second_chi = second_minus_chi third = tf.math.conj(tf.gather(evect_tf, index_kpp_full)) third_chi = tf.math.conj(tf.gather(_chi_k, index_kpp_full)) dirac_delta, index_kp_vec, mup_vec, index_kpp_vec, mupp_vec = out # The ps and gamma array stores first ps then gamma then the scattering array chi_prod = tf.einsum('kt,kl->ktl', second_chi, third_chi) chi_prod = tf.reshape(chi_prod, (n_k_points, n_replicas**2)) scaled_potential = tf.tensordot(chi_prod, third_nu_tf, (1, 0)) scaled_potential = tf.einsum('kij,kim->kjm', scaled_potential, second) scaled_potential = tf.einsum('kjm,kjn->kmn', scaled_potential, third) scaled_potential = tf.gather_nd( scaled_potential, tf.stack([index_kp_vec, mup_vec, mupp_vec], axis=-1)) pot_times_dirac = tf.abs(scaled_potential)**2 * dirac_delta nup_vec = index_kp_vec * phonons.n_modes + mup_vec nupp_vec = index_kpp_vec * phonons.n_modes + mupp_vec pot_times_dirac = tf.cast(pot_times_dirac, dtype=tf.float64) pot_times_dirac = pot_times_dirac / tf.gather( omega.flatten(), nup_vec) / tf.gather(omega.flatten(), nupp_vec) if is_gamma_tensor_enabled: # We need to use bincount together with fancy indexing here. See: # https://stackoverflow.com/questions/15973827/handling-of-duplicate-indices-in-numpy-assignments nup_vec = index_kp_vec * phonons.n_modes + mup_vec nupp_vec = index_kpp_vec * phonons.n_modes + mupp_vec result = tf.math.bincount(nup_vec, pot_times_dirac, phonons.n_phonons) if is_plus: ps_and_gamma[nu_single, 2:] -= result else: ps_and_gamma[nu_single, 2:] += result result = tf.math.bincount(nupp_vec, pot_times_dirac, phonons.n_phonons) ps_and_gamma[nu_single, 2:] += result ps_and_gamma[nu_single, 0] += tf.reduce_sum(dirac_delta) / phonons.n_k_points ps_and_gamma[nu_single, 1] += tf.reduce_sum(pot_times_dirac) ps_and_gamma[nu_single, 1:] /= omega.flatten()[nu_single] ps_and_gamma[ nu_single, 1:] *= np.pi * phonons.hbar / 4 / n_k_points * gamma_to_thz return ps_and_gamma
def calculate_conductivity_full(self, is_using_gamma_tensor_evects=False): """This calculates the conductivity using the full solution of the space-dependent Boltzmann Transport Equation. Returns ------- conductivity_per_mode : np array (n_k_points, n_modes, 3) """ length = self.length n_phonons = self.n_phonons n_k_points = self.n_k_points volume = np.linalg.det(self.phonons.atoms.cell) physical_mode = self.phonons.physical_mode.reshape(n_phonons) velocity = self.phonons.velocity.real.reshape( (n_phonons, 3))[physical_mode, :] heat_capacity = self.phonons.heat_capacity.flatten()[physical_mode] gamma_tensor = self.calculate_scattering_matrix( is_including_diagonal=True, is_rescaling_omega=False, is_rescaling_population=True) neg_diag = (gamma_tensor.diagonal() < 0).sum() logging.info('negative on diagonal : ' + str(neg_diag)) log_size(gamma_tensor.shape, name='scattering_inverse') if is_using_gamma_tensor_evects: evals, evects = np.linalg.eigh(gamma_tensor) logging.info('negative eigenvals : ' + str((evals < 0).sum())) new_physical_states = np.argwhere(evals >= 0)[0, 0] reduced_evects = evects[new_physical_states:, new_physical_states:] reduced_evals = evals[new_physical_states:] scattering_inverse = np.zeros_like(gamma_tensor) scattering_inverse[new_physical_states:, new_physical_states:] = reduced_evects.dot( np.diag(1 / reduced_evals)).dot( (reduced_evects.T)) else: scattering_inverse = np.linalg.inv(gamma_tensor) full_cond = np.zeros((n_phonons, 3, 3)) for alpha in range(3): self.calculate_lambda_tensor(alpha, scattering_inverse) forward_states = self._lambd > 0 lambd_p = self._lambd[forward_states] only_lambd_plus = self._lambd.copy() only_lambd_plus[self._lambd < 0] = 0 lambd_tilde = only_lambd_plus new_lambd = np.zeros_like(lambd_tilde) # using average # exp_tilde[self._lambd>0] = (length[alpha] + lambd_p * (-1 + np.exp(-length[alpha] / (lambd_p)))) * lambd_p/length[alpha] if length is not None: if length[alpha]: leng = np.zeros_like(self._lambd) leng[:] = length[alpha] leng[self._lambd < 0] = 0 new_lambd[self._lambd > 0] = (1 - np.exp(-length[alpha] / (lambd_p))) * lambd_p # exp_tilde[lambd<0] = (1 - np.exp(-length[0] / (-lambd_m))) * lambd_m else: new_lambd[self._lambd > 0] = lambd_p else: new_lambd[self._lambd > 0] = lambd_p lambd_tilde = new_lambd for beta in range(3): cond = 2 * contract( 'nl,l,lk,k,k->n', self._psi, lambd_tilde, self._psi_inv, heat_capacity, velocity[:, beta], ) full_cond[physical_mode, alpha, beta] = cond return full_cond / (volume * n_k_points) * 1e22