Exemple #1
0
    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
Exemple #2
0
 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
Exemple #3
0
    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
Exemple #4
0
 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
Exemple #5
0
    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
Exemple #6
0
    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
Exemple #7
0
 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
Exemple #8
0
 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
Exemple #10
0
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
Exemple #11
0
    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