diff --git a/README.md b/README.md
index 5393d414..570d9968 100644
--- a/README.md
+++ b/README.md
@@ -15,10 +15,12 @@ Briefly summarized, the package provides all the tools you need to build accurat
- `phonon_dispersion_relations`: Calculate phonon dispersion relations and related harmonic thermodynamic properties from the second-order force constants.
-- `thermal_conductivity`: Compute thermal transport by solving the phonon Boltzmann transport equation with perturbative treatment of third-order anharmonicity.
+- `thermal_conductivity`: Compute thermal transport in the mode-coupling formalism including third- and fourth-order anharmonicity.
- `lineshape`: Compute phonon spectral functions including lifetime broadening and shifts for single q-points, q-point meshes, or q-point paths in the Brillouin zone. The grid mode computes _spectral_ thermal transport properties as well.
+- `thermal_conductivity_2023`: Compute thermal transport by solving the phonon Boltzmann transport equation with perturbative treatment of third-order anharmonicity. Legacy implementation, the significantly improved program thermal_conductivity should be used!
+
More details, examples, and theoretical background can be found in the [online documentation](https://tdep-developers.github.io/tdep/program). See [below](#how-to-cite) which references should be cited for which program.
## Tutorials
diff --git a/build_things.sh b/build_things.sh
index 5679420b..0b6d4e01 100755
--- a/build_things.sh
+++ b/build_things.sh
@@ -144,6 +144,7 @@ atomic_distribution
pack_simulation
refine_structure
thermal_conductivity
+thermal_conductivity_2023
anharmonic_free_energy
"
@@ -213,7 +214,6 @@ cd src/libflap
cd ../../
-
# go through them and compile
for code in ${listofcodes}
do
diff --git a/docs/media/monte_carlo_grid.png b/docs/media/monte_carlo_grid.png
new file mode 100644
index 00000000..bb6a8619
Binary files /dev/null and b/docs/media/monte_carlo_grid.png differ
diff --git a/docs/program/README.md b/docs/program/README.md
index 93ff6676..fff2eb91 100644
--- a/docs/program/README.md
+++ b/docs/program/README.md
@@ -26,4 +26,6 @@
[`refine_structure`: Clean up input structures with imprecise symmetry.](refine_structure.md)
-[`phasespace_surface`: Compute the phonon scattering phase space.](phasespace_surface.md)
\ No newline at end of file
+[`phasespace_surface`: Compute the phonon scattering phase space.](phasespace_surface.md)
+
+[`thermal_conductivity_2023`: Compute thermal transport by solving the phonon Boltzmann transport equation with perturbative treatment of third-order anharmonicity. Older implementation, the new program should be used.](thermal_conductivity_2023.md)
diff --git a/docs/program/thermal_conductivity.md b/docs/program/thermal_conductivity.md
index 1f350d73..c3022909 100644
--- a/docs/program/thermal_conductivity.md
+++ b/docs/program/thermal_conductivity.md
@@ -1,7 +1,7 @@
### Short description
-Calculates the lattice thermal conductivity from the iterative solution of the phonon Boltzmann equation. In addition, cumulative plots and raw data dumps of intermediate values are available.
+Calculates the lattice thermal conductivity in the mode-coupling formalism, including collective and off-diagonal contributions up to fourth-order interactions.
### Command line options:
@@ -18,46 +18,58 @@ Optional switches:
default value 26 26 26
Density of q-point mesh for Brillouin zone integrations.
-* `--integrationtype value`, `-it value`, value in: `1,2,3`
+* `--qpoint_grid3ph value#1 value#2 value#3`, `-qg3ph value#1 value#2 value#3`
+ default value -1 -1 -1
+ Dimension of the grid for the threephonon integration.
+
+* `--qpoint_grid4ph value#1 value#2 value#3`, `-qg4ph value#1 value#2 value#3`
+ default value -1 -1 -1
+ Dimension of the grid for the fourphonon integration.
+
+* `--integrationtype value`, `-it value`, value in: `1,2`
default value 2
- Type of integration for the phonon DOS. 1 is Gaussian, 2 adaptive Gaussian and 3 Tetrahedron.
+ Type of integration for the phonon DOS. 1 is Gaussian, 2 adaptive Gaussian.
* `--sigma value`
default value 1.0
- Global scaling factor for adaptive Gaussian smearing.
-
-* `--threshold value`
- default value 4.0
- Consider a Gaussian distribution to be 0 after this many standard deviations.
+ Global scaling factor for Gaussian/adaptive Gaussian smearing. The default is determined procedurally, and scaled by this number.
* `--readqmesh`
default value .false.
Read the q-point mesh from file. To generate a q-mesh file, see the genkpoints utility.
-* `--temperature value`
- default value -1
- Evaluate thermal conductivity at a single temperature.
+* `--fourthorder`
+ default value .false.
+ Consider four-phonon contributions to the scattering.
-* `--temperature_range value#1 value#2 value#3`
- default value 100 300 5
- Series of temperatures for thermal conductivity. Specify min, max and the number of points.
+* `--classical`
+ default value .false.
+ Use the classical limit for phonon occupation and heat capacity.
-* `--logtempaxis`
- default value .false.
- Space the temperature points logarithmically instead of linearly.
+* `--temperature value`
+ default value 300
+ Evaluate thermal conductivity at a single temperature.
* `--max_mfp value`
default value -1
Add a limit on the mean free path as an approximation of domain size (in m).
-* `--dumpgrid`
- default value .false.
- Write files with q-vectors, frequencies, eigenvectors and group velocities for a grid.
-
* `--noisotope`
default value .false.
Do not consider isotope scattering.
+* `--iterative_tolerance`
+ default value 1e-5
+ Tolerance for the iterative solution.
+
+* `--iterative_maxsteps`
+ default 200
+ Max number of iterations for the iterative solution.
+
+* `--seed`
+ default -1
+ Positive integer to seed the random number generator for the Monte-Carlo grids.
+
* `--help`, `-h`
Print this help message
@@ -67,684 +79,260 @@ Optional switches:
`mpirun thermal_conductivity --temperature 300`
-`mpirun thermal_conductivity -qg 15 15 15 --temperature_range 200 600 50`
+`mpirun thermal_conductivity -qg 30 30 30 --temperature 300 -qg3ph 15 15 15`
-`mpirun thermal_conductivity --integrationtype 2 -qg 30 30 30 --max_mfp 1E-6`
+`mpirun thermal_conductivity -qg 30 30 30 --fourthorder -qg4ph 4 4 4 `
### Longer summary
-Heat transport can be determined by solving the inelastic phonon Boltzmann equation. By applying a temperature gradient $\nabla T_\alpha$ in direction $\alpha$, the heat current is given by the group velocities of phonon mode $\lambda$ and non-equilibrium phonon distribution function $\tilde{n}_\lambda$:[^peierls1955quantum]
+The thermal conductivity tensor can be computed from the Green-Kubo formula
$$
\begin{equation}
-J_{\alpha}=\frac{1}{V}\sum_\lambda
-\hbar \omega_\lambda v_{\lambda\alpha} \tilde{n}_{\lambda\alpha}.
+\kappa^{\alpha\beta} = \frac{\beta}{V T} \int_0^\infty dt \int_0^\beta d\lambda \langle J_{\alpha}(i\hbar\lambda) J_\beta(t) \rangle
\end{equation}
$$
-Assuming the thermal gradient is small, the non-equilibrium distribution function can be linearised as,
-
-$$
-\tilde{n}_{\lambda\alpha} \approx n_{\lambda}-
-v_{\lambda\alpha}
-\tau_{\lambda\alpha}
-\frac{d n_{\lambda}}{d T}
-\frac{d T}{d \alpha} \, ,
-$$
-
-That is a linear deviation from the equilibrium distribution function $n_{\lambda}$. Inserting this into the equation 1, and exploiting the fact that the equilibrium occupation carries no heat, we arrive at,
-
-$$
-J_{\alpha}=\frac{1}{V}\sum_{\lambda}
-\hbar \omega_{\lambda}
-\frac{d n_{\lambda}}{d T}
-v_{\lambda\alpha}
-v_{\lambda\alpha}
-\tau_{\lambda\alpha}
-\frac{d T}{d \alpha}.
-$$
-
-Utilizing Fourier's law, $J=\kappa \nabla T$, and identifying the phonon heat capacity,
-
-$$
-c_{\lambda}=
-\hbar \omega_\lambda
-\frac{d n_{\lambda}}{d T},
-$$
-
-we arrive at,
-
-$$
-\kappa_{\alpha\beta}=\frac{1}{V} \sum_{\lambda}
-c_{\lambda}
-v_{\alpha \lambda}v_{\beta \lambda} \tau_{\beta \lambda},
-$$
-
-which can be interpreted as follows: the heat transported by each phonon will depend on how much heat it carries, how fast it travels, and how long it lives. The phonon-phonon induced lifetime can be determined from the self-energy $\Gamma_{\lambda}$. In addition, one must consider the scattering with mass impurities (isotopes), and the boundaries of the sample.
-
-### Lifetimes
-
-With the third order force constants we can calculate the phonon lifetimes needed as input to the thermal conductivity calculations. The lifetime due to phonon-phonon scattering is related to the imaginary part of the phonon self energy ( $\Sigma=\Delta+i\Gamma$ ).
-
-$$
-\frac{1}{\tau_{\lambda}}=2 \Gamma_{\lambda},
-$$
-
-where $\tau_{\lambda}$ is the lifetime phonon mode $\lambda$, and
-
-$$
-\begin{split}
-\Gamma_{\lambda}=& \frac{\hbar \pi}{16} % _{\lambda'}
-\sum_{\lambda'\lambda''}
-\left|\Phi_{\lambda\lambda'\lambda''}\right|^2
-\bigl[(n_{\lambda'}+n_{\lambda''}+1)
-\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''}) \\
-+ & 2(n_{\lambda'}-n_{\lambda''})
-\delta(\omega_{\lambda}-\omega_{\lambda'}+\omega_{\lambda''}) \bigr]
-\end{split}
-$$
-
-$n_{\lambda}$ is the equilibrium occupation number. The sum is over momentum conserving three-phonon processes, $\textbf{q}+\textbf{q}'+\textbf{q}''=\textbf{G}$, and the deltafunctions in frequency ensure energy conservation. The three-phonon matrix elements are given by
-
-$$
-\Phi_{\lambda\lambda'\lambda''} =
-\sum_{ijk}
-\sum_{\alpha\beta\gamma}
-\frac{
-\epsilon_{\lambda}^{i \alpha}
-\epsilon_{\lambda'}^{j \beta}
-\epsilon_{\lambda''}^{k \gamma}
-}{
-\sqrt{m_{i}m_{j}m_{j}}
-\sqrt{
- \omega_{\lambda}
- \omega_{\lambda'}
- \omega_{\lambda''}}
-}
-\Phi^{\alpha\beta\gamma}_{ijk}
-e^{i \mathbf{q}\cdot\mathbf{r}_i + i \mathbf{q}'\cdot\mathbf{r}_j+i \mathbf{q}''\cdot\mathbf{r}_k}
-$$
-
-where $m_i$ is the mass of atom $i$, $\epsilon_{\lambda}^{\alpha i}$ is component $\alpha$ of the eigenvector for mode $\lambda$ and atom $i$ and $\textbf{r}_i$ is the lattice vector associated with atom $i$.
-
-Mass disorder, in the form of natural isotope distributions also cause thermal resistance. According to Tamura[^Tamura1983], if the isotopes are randomly distributed on the lattice sites then the strength of the isotope scattering can be given by a mass variance parameter $g$:
-
-$$
-g_i=\sum_j c_{i}^j \left(\frac{m_i^j-\bar{m_i}}{\bar{m_i}}\right)^2
-$$
-
-where $\bar{m_i}$ is the average isotopic mass( $\bar{m_i}=\sum_j c_i^j m_i^j$ ), $m^j_i$ is the mass of isotope $j$ of atom $i$ and $c^j_i$ is its concentration. The contribution to the imaginary part of the self-energy is
-
-$$
-\Gamma^{\textrm{iso}}_{\lambda}=
-\frac{\pi}{4} \sum_{\lambda'}
-\underbrace{\omega_{\lambda}\omega_{\lambda'} \sum_i g_i \left| \epsilon_{\lambda}^{i \dagger} \epsilon_{\lambda'}^{i} \right|^2}_{\Lambda_{\lambda\lambda'}}
-\delta(\omega_{\lambda}-\omega_{\lambda'})
-$$
-
-Per default, the isotope distribution will be the natural distribution. In case some other distribution is desired, this can be specified.
-
-Scattering by domain boundaries is implemented as
-
-$$
-\Gamma^{\textrm{boundary}}_{\lambda} = \frac{ v_{\lambda} }{2d}
-$$
-
-Where $d$ is a characteristic domain size.
-
-### Beyond the relaxation time approximation
-
-So far we have have considered the phonon heat conduction as an elastic process, whereas it is inelastic. This can be treated by iteratively solving the phonon boltzmann equation, formulated in terms of the (linear) deviations from equilibrium occupation numbers.[^peierls1929],[^Omini1996],[^Omini],[^Broido2007],[^Broido2005]
-
-### Phonon scattering rates and the phonon Boltzmann equation
-
-I always found it confusing how you arrived at most of these things. This is something I put together for myself, to clear it up a bit. Please bear in mind that this is not an attempt at a formal derivation whatsoever, just to make it a bit easier to interpret the different terms. There might be an arbitrary number of plusses and minuses and other things missing. Recall the transformation we introduced [earlier](phonon_dispersion_relations.md):
-
-$$
-\begin{equation}\label{eq:normalmodetransformation}
-\hat{u}_{i\alpha} = \sqrt{ \frac{\hbar}{2N m_\alpha} }
-\sum_\lambda \frac{\epsilon_\lambda^{i\alpha}}{ \sqrt{ \omega_\lambda} }
-e^{i\mathbf{q}\cdot\mathbf{r}_i}
-\left( \hat{a}^{\mathstrut}_\lambda + \hat{a}^\dagger_\lambda \right)
-\end{equation}
-$$
-
-and consider the three-phonon process where two phonons combine into one:
-
-$$
-\begin{equation*}
-\begin{split}
-\mathbf{q} + \mathbf{q}' + \mathbf{q}'' & = \mathbf{G} \\
-\omega + \omega' & = \omega''
-\end{split}
-\end{equation*}
-$$
-
-This process changes the state of the system:
+where $J_{\alpha}$ is the heat current operator.
+In a crystal, the heat current operator can be approximated as
$$
\begin{equation}
-\underbrace{\left| \ldots , n_{\lambda},n_{\lambda'},n_{\lambda''} , \ldots \right\rangle}_{\left\vert i \right\rangle}
-\rightarrow
-\underbrace{\left| \ldots , n_{\lambda}-1,n_{\lambda'}-1,n_{\lambda''}+1, \ldots \right\rangle}_{\left\vert f \right\rangle}
-\end{equation}
-$$
-
-that is, we lost one phonon at $\lambda$ and one at $\lambda'$, and created a phonon at $\lambda''$.
-Mostly out of habit, we sandwich the Hamiltonian between the initial and final states:
-
-$$
-\begin{equation}\label{eq:sandwich}
-{\left\langle f \middle\vert \hat{H} \middle\vert i \right\rangle} =
-{\left\langle f \middle\vert \sum_i \frac{p^2_i}{2m} +
-\frac{1}{2!}\sum_{ij} \sum_{\alpha\beta}\Phi_{ij}^{\alpha\beta}
-u_i^\alpha u_j^\beta +\frac{1}{3!}
-\sum_{ijk} \sum_{\alpha\beta\gamma}\Phi_{ijk}^{\alpha\beta\gamma}
-u_i^\alpha u_j^\beta u_k^\gamma \ldots
-\middle\vert i \right\rangle}
-\end{equation}
-$$
-
-and remember the rules for ladder operators, and that the eigenstates to the quantum harmonic oscillator are orthogonal:
-
-$$
-\begin{equation*}
-\begin{split}
-\hat{a}^\dagger \left\vert n \right\rangle & = \sqrt{n+1} \left\vert n + 1 \right\rangle \\
-\hat{a} \left\vert n \right\rangle & = \sqrt{n} \left\vert n -1 \right\rangle \\
-\left\langle i \middle\vert j \right\rangle & = \delta_{ij}
-\end{split}
-\end{equation*}
-$$
-
-Inserting eq \ref{eq:normalmodetransformation} into \ref{eq:sandwich} (and realising that the kinetic energy part and the second order part disappears), we end up with a pretty large expression, that we will deal with in steps, first identify
-
-$$
-\begin{equation}\label{eq:uprod}
-\begin{split}
-u^\alpha_{i}u^\beta_{j}u^\gamma_{k} & =
-%
-\left(\frac{\hbar}{2N}\right)^{3/2} \frac{1}{\sqrt{m_{i}m_{j}m_{k}}}
-\sum_{\lambda\lambda'\lambda''}
-\frac{
-\epsilon_{\lambda}^{i \alpha}
-\epsilon_{\lambda'}^{j \beta}
-\epsilon_{\lambda''}^{k \gamma}
-}{
-\sqrt{
- \omega_{\lambda}
- \omega_{\lambda'}
- \omega_{\lambda''}}
-}
-e^{i \mathbf{q}\cdot\mathbf{r}_i + i \mathbf{q}'\cdot\mathbf{r}_j+i \mathbf{q}''\cdot\mathbf{r}_k}
- \left(a_{\lambda}+a_{\lambda}^\dagger \right)
-\left(a_{\lambda'}+a_{\lambda'}^\dagger \right)
-\left(a_{\lambda''}+a_{\lambda''}^\dagger \right)
-\end{split}
+J_{\alpha} = - \sum_{ij} \sum_{\beta\gamma} \big( \langle R_i^\alpha \rangle - \langle R_j^\alpha \rangle ) \Phi_{ij}^{\beta\gamma} u_i^\beta v_j^\gamma
\end{equation}
$$
-as well as
+which can be projected on phonons to give
$$
\begin{equation}
-\begin{split}
-& \sum_{\lambda\lambda'\lambda''}
-\left\langle f \middle\vert
-\left(a_{\lambda}+a_{\lambda}^\dagger \right)
-\left(a_{\lambda'}+a_{\lambda'}^\dagger \right)
-\left(a_{\lambda''}+a_{\lambda''}^\dagger \right)
-\middle\vert i \right\rangle = \\
-= & \sum_{\lambda\lambda'\lambda''} \left\langle f \middle\vert
-\hat{a}_{\lambda} \hat{a}_{\lambda'} \hat{a}_{\lambda''} + \hat{a}_{\lambda} \hat{a}_{\lambda'} \hat{a}^{\dagger}_{\lambda''} + \hat{a}_{\lambda} \hat{a}^{\dagger}_{\lambda'} \hat{a}_{\lambda''} + \hat{a}_{\lambda} \hat{a}^{\dagger}_{\lambda'} \hat{a}^{\dagger}_{\lambda''} + \hat{a}^{\dagger}_{\lambda} \hat{a}_{\lambda'} \hat{a}_{\lambda''} + \hat{a}^{\dagger}_{\lambda} \hat{a}_{\lambda'} \hat{a}^{\dagger}_{\lambda''} + \hat{a}^{\dagger}_{\lambda} \hat{a}^{\dagger}_{\lambda'} \hat{a}_{\lambda''} + \hat{a}^{\dagger}_{\lambda} \hat{a}^{\dagger}_{\lambda'} \hat{a}^{\dagger}_{\lambda''}
-\middle\vert i \right\rangle = \\
-= & \sum_{\lambda\lambda'\lambda''} \left\langle f \middle\vert
-a_{\lambda}a_{\lambda'}a^\dagger_{\lambda''}
-\middle\vert i \right\rangle
- = 3 \sqrt{n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)}
-\end{split}
+J_{\alpha} = \frac{1}{2}\sum_{\lambda \lambda'} \hbar \omega_{\lambda'} v_{\lambda\lambda'}^{\alpha} A_\lambda B_{\lambda'}
\end{equation}
$$
-where the factor 3 comes from the multiplicity, to get at
+In this equation, $A_\lambda$ and $B_\lambda$ are respectively the displacements and momentum phonon operators and $v_{\lambda\lambda'}^{\alpha}$ is the generalized off-diagonal phonon group-velocity [^Dangic2021], written
$$
\begin{equation}
-{\left\langle f \middle\vert \hat{H}_3 \middle\vert i \right\rangle} =
-\frac{1}{2}
-\sum_{ijk} \sum_{\alpha\beta\gamma}\Phi_{ijk}^{\alpha\beta\gamma}
-\sqrt{n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)}
-%
-\left(\frac{\hbar}{2N}\right)^{3/2}
-\frac{
-\epsilon_{\lambda}^{i \alpha}
-\epsilon_{\lambda'}^{j \beta}
-\epsilon_{\lambda''}^{k \gamma}
-}{
-\sqrt{m_{i}m_{j}m_{j}}
-\sqrt{
- \omega_{\lambda}
- \omega_{\lambda'}
- \omega_{\lambda''}}
-}
-e^{i \mathbf{q}\cdot\mathbf{r}_i + i \mathbf{q}'\cdot\mathbf{r}_j+i \mathbf{q}''\cdot\mathbf{r}_k}
+v_{\lambda\lambda'}^\alpha = \frac{i}{2 \sqrt{\omega_\lambda \omega_{\lambda'}}} \sum_{ij \beta\gamma} \epsilon_\lambda^{i\beta} \sum_{\mathbf{R}} \big( \langle R_i^\alpha \rangle - \langle R_j^\alpha \rangle \big) \frac{\Phi_{ij}^{\beta\gamma}}{\sqrt{m_i m_j}} \epsilon_{\lambda'}^{j\gamma}
\end{equation}
$$
-The initial factor 1/2 is the multiplicity cancelled by the 3! from the Hamiltonian. Here, as it happens, we can identify the three-phonon matrix elements and simplify a little bit more
+and whose diagonal contributions are equal to the usual phonon group velocities $\mathbf{v}_{\lambda\lambda} = \mathbf{v}_{\lambda}$.
+Now, the heat current can be separated in a diagonal and a non diagonal contribution as
$$
\begin{equation}
-{\left\langle f \middle\vert \hat{H}_3 \middle\vert i \right\rangle} =
-\frac{1}{2}
-\sqrt{n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)}
-\left(\frac{\hbar}{2N}\right)^{3/2}
-\Phi_{\lambda\lambda'\lambda''}
+J_{\alpha} = J_{\alpha}^{\mathrm{d}} + J_{\alpha}^{\mathrm{nd}}
\end{equation}
$$
-The probability of this particular three-phonon process can be estimated via the Fermi golden rule:
+Here, we will only provide a sketch of the derivation.
+For more informations, we refer reader to the article describing the implementation [^Castellano2024] and the references at the bottom of the page.
-$$
-\begin{equation}
-\begin{split}
-P_{\lambda\lambda'\rightarrow\lambda''} & =\frac{2\pi}{\hbar}
-\left|{\left\langle f \middle\vert \hat{H}_3 \middle\vert i \right\rangle}\right|^2
-\delta(E_f-E_i) =
-\frac{\hbar^2\pi}{16N}
-n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)
-\left| \Phi_{\lambda\lambda'\lambda''} \right|^2
-\delta(E_f-E_i)
-\end{split}
-\end{equation}
-$$
-With near identical reasoning, we can also arrive at
+### Scattering rates
-$$
-\begin{equation}\label{pplus}
-P_{\lambda\rightarrow\lambda'\lambda''} =
-\frac{\hbar^2\pi}{16N}
-n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1)
-\left| \Phi_{\lambda\lambda'\lambda''} \right|^2
-\delta(E_f-E_i)
-\end{equation}
-$$
+Before handling the thermal conductivity tensor, we will discuss the scattering rates of the phonons.
+Due to interaction with other phonons or quasiparticles, isotopic disorder, boundaries, ..., the phonons scatters.
+This scattering is encoded in the self-energy (or memory kernel).
-for the other kind of three-phonon processes, and
+Here, we will make the approximation that these interactions are weak enough so that we can work in the Markovian approximation (or equivalently apply Fermi's golden rule).
+In this case, the self-energy can be simplified to a single number $\Gamma_\lambda$, which allows to define the phonon lifetime
$$
-\begin{equation}\label{pminus}
-P_{\lambda\rightarrow\lambda'} =\frac{2\pi}{\hbar}\left|\langle f | H^{\textrm{iso}} | i \rangle \right|^2\delta(E_f-E_i) =
-\frac{\pi\hbar}{2N} n_{\lambda}(n_{\lambda'}+1) \Lambda_{\lambda\lambda'}\delta(E_f-E_i)
+\begin{equation}
+\tau_\lambda = \frac{1}{2 \Gamma_\lambda}
\end{equation}
$$
-for the isotope scattering. I leave those derivations as an exercise. The phonon Boltzmann equation is stated as:
-
-$$
-\begin{equation}\label{eq:pbe}
-\frac{\partial \tilde{n}_\lambda}{\partial T} \mathbf{v}_\lambda \cdot \nabla T =
-\left. \frac{\partial \tilde{n}_\lambda }{\partial t} \right|_{\mathrm{coll}}
-\end{equation}
-$$
+Within this approximation, the phonon spectral function $\chi''(\Omega)$ reduces to a Lorentzian centered on $\omega_\lambda$ with a width of $\Gamma_\lambda$.
-Where $\tilde{n}$ is the non-equilibrium occupation number. This is ridiculously complicated. To make life easier, we only consider the terms we outlined above as possible collisions. Gathering all possible events that involve mode $\lambda$ we get
+The contribution to $\Gamma_\lambda$ given by third order interaction is written
$$
-\begin{equation}\label{manyprob}
+\begin{equation}
\begin{split}
-\left. \frac{\partial n_{\lambda}}{ \partial t} \right|_{\mathrm{coll}}
-= & \sum_{\lambda'}
-\left( P_{\lambda\rightarrow\lambda'}-P_{\lambda'\rightarrow\lambda } \right) +
-\sum_{\lambda'\lambda''}
-- P_{\lambda \rightarrow \lambda' \lambda'' }
-- P_{\lambda \rightarrow \lambda''\lambda' }
-+ P_{\lambda' \rightarrow \lambda \lambda'' }
-+ P_{\lambda' \rightarrow \lambda''\lambda }
-+ P_{\lambda''\rightarrow \lambda \lambda' }
-+ P_{\lambda''\rightarrow \lambda' \lambda } \\
-& - P_{\lambda \lambda' \rightarrow \lambda'' }
-- P_{\lambda \lambda'' \rightarrow \lambda' }
-- P_{\lambda' \lambda \rightarrow \lambda'' }
-+ P_{\lambda' \lambda'' \rightarrow \lambda }
-- P_{\lambda'' \lambda \rightarrow \lambda' }
-+ P_{\lambda'' \lambda' \rightarrow \lambda }
+\Gamma_\lambda^{3\mathrm{ph}} = \frac{\pi}{16} \sum_{\lambda' \lambda''} \vert \Psi_{\lambda\lambda'\lambda''} \vert^2 &\big[(n_{\lambda'} + n_{\lambda''} + 1) (\delta(\omega_\lambda - \omega_{\lambda'} - \omega_{\lambda''}) - (\delta(\omega_\lambda + \omega_{\lambda'} + \omega_{\lambda''})) \\
+&+ (n_{\lambda'} - n_{\lambda''}) (\delta(\omega_\lambda + \omega_{\lambda'} - \omega_{\lambda''}) - (\delta(\omega_\lambda - \omega_{\lambda'} + \omega_{\lambda''})) \big]
\end{split}
\end{equation}
$$
-Which does not seem to make life easier. To make it slightly worse, we insert \ref{pplus} and \ref{pminus} into this, and at the same time say that the non-equilibrium distribution functions are the equilibrium distributions, plus a (small) deviation:
+with $n_\lambda = (e^{\hbar\omega_\lambda / k_{\mathrm{B}}T} - 1)^{-1}$ the Bose-Einstein distribution of phonon $\lambda$.
+In this equation, the sum is over momentum conserving processes, $\mathbf{q} + \mathbf{q}' + \mathbf{q}'' = \mathbf{G}$ and the three-phonon matrix elements are given by
$$
\begin{equation}
-\tilde{n}_{\lambda}\approx n_{\lambda}+\epsilon_{\lambda}
+\Psi_{\lambda\lambda'\lambda''} = \sum_{ijk} \sum_{\alpha\beta\gamma} \frac{\epsilon_{\lambda}^{i \alpha}\epsilon_{\lambda'}^{j \beta}\epsilon_{\lambda''}^{k \gamma}}
+{\sqrt{m_{i}m_{j}m_{k}}\sqrt{\omega_{\lambda}\omega_{\lambda'}\omega_{\lambda''}}}\Phi^{\alpha\beta\gamma}_{ijk}e^{i \mathbf{q}\cdot\mathbf{r}_i + i \mathbf{q}'\cdot\mathbf{r}_j+i \mathbf{q}''\cdot\mathbf{r}_k}
\end{equation}
$$
-After some [hard work](https://reference.wolfram.com/language/ref/FullSimplify.html), and discarding terms of $\epsilon^2$ and higher, we get
+At the fourth-order, the contribution is
$$
\begin{equation}
\begin{split}
-\left. \frac{\partial n_{\lambda}}{ \partial t} \right|_{\mathrm{coll}}
-= & \sum_{\lambda'\lambda''}
-\frac{\hbar\pi}{8N}
-\left| \Phi_{\lambda\lambda'\lambda''} \right|^2 \Big(
-\left[
--n_{\lambda} \epsilon_{\lambda'} + n_{\lambda''} (\epsilon_{\lambda} + \epsilon_{\lambda'}) + \epsilon_{\lambda''} + n_{\lambda} \epsilon_{\lambda''} + n_{\lambda'} (-\epsilon_{\lambda} + \epsilon_{\lambda''})
-\right]\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''}) + \\
-& \left[
-\epsilon_{\lambda'} + n_{\lambda} \epsilon_{\lambda'} + n_{\lambda''} (-\epsilon_{\lambda} + \epsilon_{\lambda'}) - n_{\lambda} \epsilon_{\lambda''} +
- n_{\lambda'} (\epsilon_{\lambda} + \epsilon_{\lambda''} )
-\right]\delta(\omega_{\lambda}-\omega_{\lambda'}+\omega_{\lambda''}) - \\
-& \left[(1 + n_{\lambda'} + n_{\lambda''})\epsilon_{\lambda} - n_{\lambda''}\epsilon_{\lambda''} - n_{\lambda'} \epsilon_{\lambda''} + n_{\lambda} (\epsilon_{\lambda'} + \epsilon_{\lambda''} )\right]
-\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''}) \Big)
+\Gamma_\lambda^{4\mathrm{ph}} = \frac{\pi}{96} \sum_{\lambda'\lambda''\lambda'''} \vert \Psi_{\lambda\lambda'\lambda''\lambda'''} \vert^2
+&\big[ (n_{\lambda'} + 1)(n_{\lambda''} + 1)(n_{\lambda'''} + 1) - n_{\lambda'}n_{\lambda''}n_{\lambda'''} (\delta(\omega_{\lambda} - \omega_{\lambda'} - \omega_{\lambda''} - \omega_{\lambda'''}) - \delta(\omega_{\lambda} + \omega_{\lambda'} + \omega_{\lambda''} + \omega_{\lambda'''})) \\
+&+ 3 n_{\lambda'}(n_{\lambda''} + 1)(n_{\lambda'''} + 1) - (n_{\lambda'} + 1) n_{\lambda''}n_{\lambda'''} (\delta(\omega_{\lambda} + \omega_{\lambda'} - \omega_{\lambda''} - \omega_{\lambda'''}) - \delta(\omega_{\lambda} - \omega_{\lambda'} + \omega_{\lambda''} + \omega_{\lambda'''}))]
\end{split}
\end{equation}
$$
-Which does not seem like a lot of help. If we make another substitution, and say that the deviation from equilibrium behaves sort of like the equilibrium (with no loss of generality, just to make life easier):
+where the sum is also over momentum conserving processes, $\mathbf{q} + \mathbf{q}' + \mathbf{q}'' + \mathbf{q}''' = \mathbf{G}$ and the four-phonon matrix elements are given by
$$
\begin{equation}
-\epsilon_{\lambda} =
-\frac{\partial n_{\lambda} }{\partial \omega_\lambda}
-\frac{k_B T}{\hbar} \zeta_{\lambda}=-n_{\lambda}(n_{\lambda}+1) \zeta_{\lambda}
+\Psi_{\lambda\lambda'\lambda''\lambda'''} = \sum_{ijkl} \sum_{\alpha\beta\gamma\delta} \frac{\epsilon_{\lambda}^{i \alpha}\epsilon_{\lambda'}^{j \beta}\epsilon_{\lambda''}^{k \gamma}\epsilon_{\lambda'''}^{l \delta}}
+{\sqrt{m_{i}m_{j}m_{k}m_{l}}\sqrt{\omega_{\lambda}\omega_{\lambda'}\omega_{\lambda''}\omega_{\lambda'''}}}\Phi^{\alpha\beta\gamma\delta}_{ijkl}e^{i \mathbf{q}\cdot\mathbf{r}_i + i \mathbf{q}'\cdot\mathbf{r}_j+i \mathbf{q}''\cdot\mathbf{r}_k+i\mathbf{q}'''\cdot\mathbf{r}_l}
\end{equation}
$$
-Inserting this, and more tedious algebra, we get
+The contribution to the scattering rate by isotopic disorder can be computed to Tamura's model[^Tamura1983], written
$$
\begin{equation}
-\begin{split}
-\left. \frac{\partial n_{\lambda}}{ \partial t} \right|_{\mathrm{coll}}
-=& \frac{\hbar\pi}{4N}
-\sum_{\lambda'\lambda''}
-\left| \Phi_{\lambda\lambda'\lambda''} \right|^2 \Big(
-n_{\lambda} n_{\lambda'} (n_{\lambda''}+1) \delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''} )
-\left( \zeta_{\lambda} + \zeta_{\lambda'} - \zeta_{\lambda''} \right) + \\
-& \frac{1}{2} n_{\lambda} (n_{\lambda'}+1) (n_{\lambda''}+1) \delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})
-\left( \zeta_{\lambda} - \zeta_{\lambda'} -\zeta_{\lambda''} \right) \Big)
-\end{split}
+\Gamma_{\lambda}^{\mathrm{iso}} = \frac{\pi}{4} \sum_{\lambda'} \omega_{\lambda} \omega_{\lambda'} \sum_i g_i \vert \epsilon_\lambda^{i\dagger} \epsilon_{\lambda'}^{i} \vert \delta(\omega_{\lambda} - \omega_{\lambda'})
\end{equation}
$$
-If we add the isotope term again, that I forgot at some point between the beginning and here, we can rearrange this in terms of scattering rates that should look familiar (using strange relations for occupation numbers that only hold when the deltafunctions in energy are satisfied):
+where the mass variance parameter $g$ is written
$$
\begin{equation}
-\left. \frac{\partial n_{\lambda}}{ \partial t} \right|_{\mathrm{coll}} =
-\sum_{\lambda'\lambda''}
-\tilde{P}^{+}_{\lambda\lambda'\lambda''}
-\left( \zeta_{\lambda}+\zeta_{\lambda'}-\zeta_{\lambda''} \right)
-+\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
-\left( \zeta_{\lambda}-\zeta_{\lambda'}-\zeta_{\lambda''} \right)+
-\sum_{\lambda'}
-\tilde{P}^\textrm{iso}_{\lambda\lambda'} \left( \zeta_{\lambda}-\zeta_{\lambda'} \right)
+g_i=\sum_j c_{i}^j \left(\frac{m_i^j-\bar{m_i}}{\bar{m_i}}\right)^2
\end{equation}
$$
-where
-
-$$
-\begin{align}
-\tilde{P}^{+}_{\lambda\lambda'\lambda''}&=
-\frac{\hbar \pi}{4 N}
-n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)\left|\Phi_{\lambda\lambda'\lambda''}\right|^2
-\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''})
-\\
-\tilde{P}^{-}_{\lambda\lambda'\lambda''}&=
-\frac{\hbar \pi}{4 N}
-n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1)\left|\Phi_{\lambda\lambda'\lambda''}\right|^2
-\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})
-\\
-\tilde{P}^\textrm{iso}_{\lambda\lambda'} &=
-\frac{\pi}{2N} n_{\lambda}(n_{\lambda'}+1) \Lambda_{\lambda\lambda'}
-\delta(\omega_{\lambda}-\omega_{\lambda})
-\end{align}
-$$
+In this equation, $\bar{m_i}$ is the average isotopic mass( $\bar{m_i}=\sum_j c_i^j m_i^j$ ), $m^j_i$ is the mass of isotope $j$ of atom $i$ and $c^j_i$ is its concentration.
-What we have done here is to rearrange the transition propabilities to scattering rates. If we let
+Finally, scattering by domain boundaries is implemented as
$$
\begin{equation}
-\zeta_{\lambda}=\frac{\hbar}{k_B T} \mathbf{F}_{\lambda} \cdot \nabla T
+\Gamma_{\lambda}^{\mathrm{boundary}} = \frac{v_{\lambda}}{2 L}
\end{equation}
$$
-and combine everything we end up with
+where $L$ is a characteristic domain size.
-$$
-\begin{equation}
-\begin{split}
--\frac{\omega_{\lambda}}{T}n_{\lambda}(n_{\lambda}+1)\mathbf{v}_{\lambda} \cdot \nabla T = &
- \sum_{\lambda'}
-\tilde{P}^\textrm{iso}_{\lambda\lambda'}
-\left(\mathbf{F}_{\lambda}-\mathbf{F}_{\lambda'}\right)\cdot\nabla T +
-\sum_{\lambda'\lambda''}
-\tilde{P}^{+}_{\lambda\lambda'\lambda''}
-\left(\mathbf{F}_{\lambda}+\mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''}\right)\cdot\nabla T+
-\tilde{P}^{-}_{\lambda\lambda'\lambda''}
-\left(\mathbf{F}_{\lambda}-\mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''}\right)\cdot\nabla T =
-\\ = &
-\mathbf{F}_{\lambda}\cdot\nabla T
-\left(
-\sum_{\lambda'}
-\tilde{P}^\textrm{iso}_{\lambda\lambda'}
-+
-\sum_{\lambda'\lambda''}
-\tilde{P}^{+}_{\lambda\lambda'\lambda''}+
-\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
-\right)- \\
-& - \sum_{\lambda'}
-\tilde{P}^\textrm{iso}_{\lambda\lambda'}\mathbf{F}_{\lambda'}\cdot\nabla T +
-\sum_{\lambda'\lambda''}
-\tilde{P}^{+}_{\lambda\lambda'\lambda''}
-\left(\mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''}\right)\cdot\nabla T-
-\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
-\left(\mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''}\right)\cdot\nabla T
-\end{split}
-\end{equation}
-$$
-Where we can identify
+### The diagonal contribution
+
+The diagonal contribution to the heat current is written
$$
\begin{equation}
-Q_{\lambda}=\sum_{\lambda'}
-\tilde{P}^\textrm{iso}_{\lambda\lambda'}
-+
-\sum_{\lambda'\lambda''}
-\tilde{P}^{+}_{\lambda\lambda'\lambda''}+
-\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
+J_{\alpha}^{\mathrm{d}} = \sum_\lambda \hbar \omega_\lambda v_{\lambda}^{\alpha} A_\lambda B_\lambda
\end{equation}
$$
-And rearrange terms
+Injecting it into the Green-Kubo formula, we obtain that the thermal conductivity tensor is proportional to a four-point correlation
$$
\begin{equation}
-\mathbf{F}_{\lambda}=
-\frac{\omega_{\lambda} \bar{n}_{\lambda}(\bar{n}_{\lambda}+1)\mathbf{v}_{\lambda} }{T Q_{\lambda}}
-+
-\frac{1}{Q_{\lambda}}\left[
-\sum_{\mathbf{q}'\mathbf{q}''}\sum_{s's''}
-\tilde{P}^{+}_{\lambda\lambda'\lambda''}
-\left( \mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''} \right)-
-\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
-\left( \mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''} \right)
-\right]
+\kappa_{\alpha\beta}^\mathrm{d} \propto \int_0^\infty dt \int_0^\beta d\lambda \langle A_\lambda(i\hbar\lambda) B_\lambda(i\hbar\lambda) A_{\lambda'}(t) B_{\lambda'}(t) \rangle
\end{equation}
$$
-And we have a set of equations for $F$ that we can solve self-consistently. Previously, we used the imaginary part of the self-energy to get a phonon lifetime. What we got here, from Fermi golden rule, is related:
-
-$$
-\sum_{\lambda'} \tilde{P}^\textrm{iso}_{\lambda\lambda'} =
-\frac{\pi}{2N} n_{\lambda}(n_{\lambda}+1) \sum_{\lambda'} \Lambda_{\lambda\lambda'}
-\delta(\omega_{\lambda}-\omega_{\lambda}) = 2 n_{\lambda}(n_{\lambda}+1) \Gamma^{\textrm{iso}}_{\lambda}
-$$
-
-This can also be done for the three-phonon terms:
+Solving the integral of this four-point correlation is a cumbersome task, and we refer the reader to references [^Fiorentino2023],[^Castellano2024] for the detailed derivation.
+In a nutshell, an equation of motion is formulated for the four-point correlation.
+This equation of motion is then solved using a Laplace transform and injected in the thermal conductivity tensor to give
$$
\begin{equation}
\begin{split}
-\sum_{\lambda'\lambda''} \tilde{P}^{+}_{\lambda\lambda'\lambda''}+
-\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''} & =
-\frac{\hbar \pi}{8 N}
-\sum_{\lambda'\lambda''} \left|\Phi_{\lambda\lambda'\lambda''}\right|^2
-\left[
-n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1) \delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})+
-2n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''})
-\right] \\
-& = n_{\lambda}(n_{\lambda}+1) \frac{\hbar \pi}{8 N}
-\sum_{\lambda'\lambda''} \left|\Phi_{\lambda\lambda'\lambda''}\right|^2
-\left[
-\frac{n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1)}{n_{\lambda}(n_{\lambda}+1)}
-\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})
-+
-\frac{2n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)}{n_{\lambda}(n_{\lambda}+1)}
-\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''})
-\right] \\
-& =
-n_{\lambda}(n_{\lambda}+1) \frac{\hbar \pi}{8 N}
-\sum_{\lambda'\lambda''} \left|\Phi_{\lambda\lambda'\lambda''}\right|^2
-\left[
-(n_{\lambda'}+n_{\lambda''}+1)
-\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})
-+
-(n_{\lambda'}-n_{\lambda''})
-\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''})
-\right] \\
-& = 2 n_{\lambda}(n_{\lambda}+1) \Gamma_{\lambda}
+\kappa_{\alpha\beta}^{\mathrm{d}} =& \frac{1}{V} \sum_{\lambda\lambda'} v_{\lambda}^{\alpha} v_{\lambda}^{\beta} c_\lambda \Xi^{-1}({\lambda\lambda'}) \\
+=& \frac{1}{V} \sum_{\lambda} c_\lambda v_{\lambda}^{\alpha} F_{\lambda\beta}
\end{split}
\end{equation}
$$
-Where the second to last step seems a little impossible, but with $\hbar\omega/k_BT = x$, you get
+with $c_\lambda = n_\lambda (n_\lambda + 1) \omega_\lambda^2 / k_{\mathrm{B}}T^2$ and where the vector $F_{\lambda}^{\beta}$, defined as
$$
\begin{equation}
-\frac{ n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1) }{ n_{\lambda}(n_{\lambda}+1) } -
-\left( n_{\lambda'} + n_{\lambda''} + 1 \right)
-=
-\frac{
-1-\exp[x'+x''-x]
-}{
-\left( \exp[x'] -1 \right) \left( \exp[x''] -1 \right)
-}
+F_{\lambda\alpha} = \Xi^{-1} v_{\lambda}^{\alpha}
\end{equation}
$$
-which comes out to 0 when $x=x'+x''$, which the deltafunction ensures. In the same way
+is simply introduced to ease the computation of the thermal conductivity tensor.
-$$
-\begin{equation}
-\frac{ n_{\lambda}n_{\lambda'}(n_{\lambda''}+1) }{ n_{\lambda}(n_{\lambda}+1) } -
-\left( n_{\lambda'} - n_{\lambda''} \right)
-=
-\frac{
-\exp[-x]\left(\exp[x+x']-\exp[x''] \right)
-}{
-\left( \exp[x'] -1 \right) \left( \exp[x''] -1 \right)
-}
-\end{equation}
-$$
+In the previous equation, $\Xi$ is called the scattering matrix.
+The diagonal component of this matrix is equal to the scattering rates $\Gamma_\lambda$ of phonons while the off-diagonal part describes the coupling between modes, which introduce collective phonon contributions to heat transport.
-comes out to 0 when $x''=x+x'$. We can directly relate the relaxation time lifetime
+Using the Neumann series for matrix inversion, $F_{\lambda}^{\alpha}$ can be computed self-consistently [^Omini],[^Omini1996] as
$$
\begin{equation}
-\tau_{\lambda} = \frac{1}{2\Gamma_{\lambda}} = \frac{ n_{\lambda}(n_{\lambda}+1) }{Q_{\lambda}}
+F_{\lambda\alpha}^{n+1} = F_{\lambda\alpha}^0 - \tau_\lambda \sum_{\lambda'} \Xi_{\lambda\lambda'} F_{\lambda\alpha}^n
\end{equation}
$$
-to an initial guess
-
-$$
-\mathbf{F}^0_{\lambda} =
-\frac{\tau_{\lambda} \omega_{\lambda} \mathbf{v}_{\lambda} }{T}
-$$
-
-and iteratively solve
+where the starting point is given by
$$
\begin{equation}
-\mathbf{F}^{i+1}_{\lambda}=
-\mathbf{F}^0_{\lambda}
-+
-\frac{1}{Q_{\lambda}}\left[
-\sum_{\lambda'\lambda''}
-\tilde{P}^{+}_{\lambda\lambda'\lambda''}
-\left( \mathbf{F}^{i}_{\lambda'}-\mathbf{F}^{i}_{\lambda''} \right)-
-\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
-\left( \mathbf{F}^{i}_{\lambda'}-\mathbf{F}^{i}_{\lambda''} \right)
-\right]
+F_{\lambda\alpha}^0 = v_{\lambda}^{\alpha} \tau_{\lambda\alpha}
\end{equation}
$$
-to arrive at the non-equilibrium distributions. The thermal conductivity tensor is then given as
+If the off-diagonal part of the scattering matrix are neglected, one obtain the single mode approximation, written
$$
\begin{equation}
-\kappa_{\alpha\beta} =
-\frac{1}{V}
-\sum_{\lambda}
-\frac{T c_{\lambda} v_{\lambda}^\alpha F_{\lambda}^\beta}{\omega_{\lambda}}
+\kappa_{\alpha\beta}^{\mathrm{d},\mathrm{SMA}} = \frac{1}{V} \sum_{\lambda\lambda'} v_{\lambda}^{\alpha} v_{\lambda}^{\beta} c_\lambda \tau_\lambda
\end{equation}
$$
+This approximation consists in neglecting the collective phonon contribution to the thermal conductivity tensor and can also be obtain by decoupling the four-point correlation in product of two-point correlation functions[^Caldarelli2022],[^Castellano2024].
+It is important to note that while starting from differents considerations, this formulation and the Boltzmann equation [^peierls1929],[^peierls1955quantum],[^Broido2007],[^Broido2005] are strictly equivalent [^Fiorentino2023].
-### Off-diagonal / coherence's contribution
-The Boltzmann equation only takes into account the relaxation of phonons from perturbed state to the equilibrium.
-However, for systems with complex unit cell, a contribution stemming from coherence tunneling between different phonons ($\lambda \neq \lambda'$) can become important.
-This off-diagonal term can be introduced both by a formulation based on the Hardy current [^Isaeva2019] or a Wigner current [^Simoncelli2019], with very similar results[^Caldarelli2022].
-In TDEP, we use the formulation based on the Hardy heat current [^Isaeva2019]
+### The off-diagonal contribution
+
+The off diagonal heat tensor is written
$$
\begin{equation}
-J_\alpha = \frac{1}{V} \sum_{\lambda\lambda'} \frac{\omega_\lambda + \omega_{\lambda'}}{2} v_{\lambda\lambda'}^\alpha \hat{a}_\lambda^\dagger \hat{a}_{\lambda'} \delta_{q_{\lambda} q_{\lambda'}}
+J_{\alpha}^{\mathrm{nd}} = \sum'_{\lambda \lambda'} \hbar \omega_{\lambda'} v_{\lambda\lambda'}^{\alpha} A_\lambda B_{\lambda'}
\end{equation}
$$
-where $v_{\lambda\lambda'}^\alpha$ are generalized group velocity tensor [^Dangic2021]
+where $\sum'$ indicates that $\lambda = \lambda'$ is excluded from the sum.
+Injecting this contribution into the Green-Kubo formula also ends up in something proportional to a four-point correlation function
$$
\begin{equation}
-v_{\lambda\lambda'}^\alpha = \frac{i}{2 \sqrt{\omega_\lambda \omega_{\lambda'}}} \sum_{ij \beta\gamma} \epsilon_\lambda^{i\beta} \sum_{\mathbf{R}} \big( R^\alpha \frac{\Phi_{ij}^{\beta\gamma}(\mathbf{R})}{\sqrt{m_i m_j}} \big) \epsilon_{\lambda'}^{j\gamma}
+\kappa_{\alpha\beta}^\mathrm{nd} \propto \int_0^\infty dt \int_0^\beta d\lambda \langle A_{\lambda}(i\hbar\lambda) B_{\lambda'}(i\hbar\lambda) A_{\lambda''}(t) B_{\lambda'''}(t) \rangle
\end{equation}
$$
-with $\mathbf{R}$ the distance vector between unit cells and $m_i$ the mass of atom $i$ in the unit cell.
-It should be recognized that for $\lambda = \lambda'$, we recover the heat current used previously to derive the thermal conductivity with the Boltzman equation.
-
-The derivation of this off-diagonal contribution starts from the Green-Kubo formula for the thermal conductivity tensor
+For this contribution, we will directly neglect the collective part (this is called the dressed-bubble approximation[^Caldarelli2022],[^Fiorentino2023]) and decouple the four-point correlation in product of two-point correlations
$$
\begin{equation}
-\kappa_{\alpha\beta} = \frac{1}{V k_BT}\int_0^\infty dt \int_0^\beta d\nu \langle J_\alpha(-i\hbar\nu) J_\beta(t) \rangle
+\langle A_{\lambda} B_{\lambda'} A_{\lambda''} B_{\lambda'''} \rangle \approx \langle A_{\lambda} A_{\lambda''} \rangle \langle B_{\lambda'} B_{\lambda'''} \rangle + ...
\end{equation}
$$
-Injecting the harmonic heat current into this equation allows to rewrite the total thermal conductivity tensor as [^Fiorentino2023]
+Performing some Fourier transform, we can now express the integral in term of spectral function $\chi_{\lambda}''(\Omega)$
$$
\begin{equation}
-\kappa_{\alpha\beta} = \kappa_{\alpha\beta}^{{\rm BTE}} + \kappa_{\alpha\beta}^{{\rm c}}
+\int_0^\infty dt \int_0^\beta d\lambda \langle A_{\lambda}(i\hbar\lambda) B_{\lambda'}(i\hbar\lambda) A_{\lambda''}(t) B_{\lambda'''}(t) \rangle \approx \int_{-\infty}^{\infty} d\Omega \chi_{\lambda}''(\Omega) \chi_{\lambda'}''(\Omega) \Omega^2 n(\Omega) (n(\Omega) + 1)
\end{equation}
$$
-where $\kappa_{\alpha\beta}^{\mathrm{BTE}}$ is the thermal conductivity from the previous section and $\kappa_{\alpha\beta}^{{\rm c}}$ is the off-diagonal (coherences') contribution.
-This term is computed as (cf. Eq. 22-24 in [^Fiorentino2023])
+Recalling that we are working in the Markovian approximation, we can approximate these spectral functions as Lorentzian, and we can make the approximation that these will act as Dirac deltas centered on the harmonic frequencies.
+This allows to perform the integral analytically, and we finally obtain the off diagonal contribution to the thermal conductivity tensor as
$$
\begin{equation}
-\kappa_{\alpha\beta}^{{\rm c}} = \frac{1}{Vk_BT^2} \sum_{\lambda\lambda'\neq \lambda} c_{\lambda\lambda'} v_{\lambda\lambda'}^\alpha v_{\lambda\lambda'}^\beta \tau_{\lambda\lambda'} \delta_{q_\lambda q_{\lambda'}}
+\kappa_{\alpha\beta}^{\mathrm{nd}} = \frac{1}{V} \sum_{\lambda\lambda'} v_{\lambda\lambda'}^{\alpha}v_{\lambda\lambda'}^{\beta} \frac{c_\lambda + c_{\lambda'}}{2} \Gamma_{\lambda\lambda'}
\end{equation}
$$
@@ -752,80 +340,46 @@ with
$$
\begin{equation}
-c_{\lambda\lambda'} = \frac{1}{4} [n_\lambda (n_\lambda + 1) + n_{\lambda'} (n_{\lambda'} + 1) ] (\omega_\lambda + \omega_{\lambda'})^2
-\end{equation}
-$$
-
-$$
-\begin{equation}
-\tau_{\lambda\lambda'} = \frac{\Gamma_\lambda + \Gamma_{\lambda'}}{(\omega_{\lambda} - \omega_{\lambda'})^2 + (\Gamma_{\lambda} + \Gamma_{\lambda'})^2}
+\Gamma_{\lambda\lambda'} = \frac{\Gamma_\lambda + \Gamma_{\lambda'}}{(\omega_\lambda - \omega_{\lambda'})^2 + (\Gamma_\lambda + \Gamma_{\lambda'})^2}
\end{equation}
$$
+This off-diagonal contribution, describing wavelike-interference between phonons of similar frequencies, becomes important for system with complex unitcell.
+While the derivation sketched here is based on the Hardy current[^Isaeva2019], it can also be obtain from a Wigner description of heat transport [^Simoncelli2019], with very similar results[^Caldarelli2022].
-### Cumulative kappa
-
-@todo Check code snippets
-
-@todo Spectral kappa, links to things.
-
-Experimentally, the cumulative thermal conductivity with respect to phonon mean free path,
-
-$$
-l_{\lambda} = \left| v_{\lambda} \right| \tau_{\lambda} \,,
-$$
-
-can be measured.[^Minnich2012] The cumulative thermal conductivity can then be computed as a sum of the fraction of heat that is carried by phonons with mean free paths smaller than $l$:
-
-$$
-\kappa_{\alpha\beta}^{\textrm{acc}}(l)=
-\frac{1}{V} \sum_{\lambda}
-C_{\lambda} v^{\alpha}_{\lambda} v^{\beta}_{\lambda} \tau_{\lambda} \Theta(l- l_{\lambda} ) \,,
-$$
+### Monte-Carlo integration for the scattering rates
-where $\Theta$ is the Heaviside step function.
+To reach the thermodynamic limit, the thermal conductivity has to be computed on a large grid of q-points, which can make the computation quite expensive.
+This cost comes almost entirely from the computation of the scattering.
-One can also define a spectral thermal conductivity as
+However, one can observe that the computation of $\kappa$ actually requires two kind of integrations.
+The first is the sum of the contribution of each q-point to the thermal conductivity, while the second one correspond to the computation of the scattering.
-$$
-\kappa_{\alpha\beta}(\omega)=
-\frac{1}{V} \sum_{\lambda}
-C_{\lambda} v^{\alpha}_{\lambda} v^{\beta}_{\lambda} \tau_{\lambda} \delta(\omega- \omega_{\lambda} )
-$$
+Fortunately for us, these two integrations converges at different rates.
+In particular, the expensive scattering integration converges more quickly than the thermal conductivity integration.
-which is a measure which frequencies contribute most to thermal transport.
+Thus, to improve the computational cost, the code offers the possibility to disassociate these two integrations by using a Monte-Carlo integration of the scattering.
+For this, we generate a full grid, on which the thermal conductivity will be integrated.
+A subset of this full grid can then be selected to perform the scattering integration.
+In order to improve the convergence, these point are not selected entirely at random but using a stratified approached in order to sample more uniformly the Brillouin zone.
-### Thin film scattering
+This is schematically represented in the following picture, where each dot represents a point on a $8\times8$ grid, with the red dot corresponding to point selected for a Monte-Carlo integration equivalent to a $4\times4$ grid and the bar representing the way the grid is stratified.
-Constrained geometries will incur additional scattering from domain boundaries. For a thin film (thin, but thick enough that the interior of the film is accurately described by bulk phonons) one can estimate the suppression due to film thinkness.[^Minnich2015] Assyming the cross-plane direction of the film is in the $y$-direction, and the thermal gradient is applied in the $z$-direction, the in-plane thermal conductivity $\kappa_{zz}$ is supressed as:
+
+
+
-$$
-\kappa_{zz}(d)=A+B+C,
-$$
+The code allows to use different Monte-Carlo grids for third and fourth order, using the variables `--qpoint_grid3ph` and `--qpoint_grid4ph`.
-where
+It is important to note that this method of integration is not deterministic, so that several runs will give different results
+However, the noise reduces as the density of the Monte-Carlo grids increases, to finally vanish if the Monte-Carlo and full grid density are the same (which is the default).
+Similarly to the full grid on which the thermal conductivity is computed, the Monte-Carlo grid densities are parameters to be carefully converged.
-$$
-\begin{split}
-x_{\lambda} = & \frac{\hbar\omega_{\lambda}}{V}
-\frac{\partial n_{\lambda}} {\partial T}
-v_{\lambda}^z l_{\lambda}^z
- \\
-A = & -\frac{1}{d} \sum_{v_y>0}
-x_{\lambda}
-\left( -l^{y}_{\lambda} \exp\left[\frac{d}{l^{y}_{\lambda}}\right]+l^{y}_{\lambda}-d \right) \\
-B = & -\frac{1}{d} \sum_{v_y<0}
-x_{\lambda}
-\left(
-l^{y}_{\lambda}
-\exp\left[ -\frac{d}{l^{y}_{\lambda}} \right]
--l^{y}_{\lambda}-d
-\right) \\
-C = & \sum_{v_y=0} x_{\lambda}
-\end{split}
-$$
+Converging the grids is an important step to ensure accurate results.
+Since the convergence of the Monte-Carlo grids are not related to the convergence of the full grid, their determination can be done independently.
+To reduce the computational cost of the convergence, an approach is to fix the full grid to a moderately large density, and first converge the third order grid and then the fourth-order one.
+Once the Monte-Carlo grid densities are known, then the full grid density can be determined.
-where $v_y$ and $v_z$ are the components of the phonon group velocity along the $y$ and $z$ directions, $\tau_{\lambda}$ is the phonon relaxation time. $l^{y}_{\lambda}$ is the $y$ component of the MFP and $d$ is the thickness of the film in $y$-direction.
### Input files
@@ -838,110 +392,37 @@ These files are necesarry:
and these are optional:
* [infile.isotopes](../files.md#infile.isotopes) (for non-natural isotope distribution)
+* [infile.forceconstant_fourthorder](extract_forceconstants.md#infile.forceconstant_fourthorder)
### Output files
-Depending on options, the set of output files may differ. We start with the basic files that are written after running this code.
-
-#### `outfile.thermal_conductivity`
-
-This file contains components of the thermal conductivity tensor $\kappa_{\alpha \beta}$ for each temperature.
-
-$test$
-
-| **Row** | **Description** |
-| ------- | ------------------------------------------------------------ |
-| 1 | $T_1 \qquad \kappa_{xx} \quad \kappa_{yy} \quad \kappa_{zz} \quad \kappa_{xz} \quad \kappa_{yz} \quad \kappa_{xy} \quad \kappa_{zx} \quad \kappa_{zy} \quad \kappa_{yx}$ |
-| 2 | $T_2 \qquad \kappa_{xx} \quad \kappa_{yy} \quad \kappa_{zz} \quad \kappa_{xz} \quad \kappa_{yz} \quad \kappa_{xy} \quad \kappa_{zx} \quad \kappa_{zy} \quad \kappa_{yx}$ |
-| … | … |
-
-
-#### `outfile.cumulative_kappa.hdf5`
-
-This file is self-explainatory. It contains the different cumulative plots described above, at a series of temperatures. Below is a matlab snippet that plots part of the output.
-
-```matlab
-figure(1); clf; hold on; box on;
-
-% filename
-fn='outfile.cumulative_kappa.hdf5';
-% which temperature?
-t=1;
-
-subplot(1,3,1); hold on; box on;
-
- % read in cumulative kappa vs mean free path from file
- x=h5read(fn,['/temperature_' num2str(t) '/mean_free_path_axis']);
- xunit=h5readatt(fn,['/temperature_' num2str(t) '/mean_free_path_axis'],'unit');
- y=h5read(fn,['/temperature_' num2str(t) '/cumulative_kappa_vs_mean_free_path_total']);
- % projections to modes and/or atoms
- z=h5read(fn,['/temperature_' num2str(t) '/cumulative_kappa_vs_mean_free_path_per_atom']);
- %z=h5read(fn,['/temperature_' num2str(t) '/cumulative_kappa_vs_mean_free_path_per_mode']);
-
- yunit=h5readatt(fn,['/temperature_' num2str(t) '/cumulative_kappa_vs_mean_free_path_total'],'unit');
-
- % plot
- plot(x,y)
- plot(x,z)
-
- % set a legend
- lgd{1}='Total';
- for i=1:size(z,2)
- lgd{i+1}=['Atom ' num2str(i)];
- end
- l=legend(lgd);
- set(l,'edgecolor','none','location','northwest');
-
- % some titles
- title('Cumulative kappa vs mean free path');
- ylabel(['Cumulative \kappa (' yunit ')']);
- xlabel(['Mean free path (' xunit ')']);
-
- % get some reasonable ranges
- minx=x(max(find(ymax(y*0.9999))))*2;
- xlim([minx maxx]);
- set(gca,'xscale','log','yminortick','on');
-
-subplot(1,3,2); hold on; box on;
-
- % read in spectral kappa vs frequency
- x=h5read(fn,['/temperature_' num2str(t) '/frequency_axis']);
- y=h5read(fn,['/temperature_' num2str(t) '/spectral_kappa_vs_frequency_total']);
- z=h5read(fn,['/temperature_' num2str(t) '/spectral_kappa_vs_frequency_per_mode']);
- %z=h5read(fn,['/temperature_' num2str(t) '/spectral_kappa_vs_frequency_per_atom']);
-
- plot(x,y)
- plot(x,z)
-
- set(gca,'xminortick','on','yminortick','on')
- xlabel('Frequency (THz)')
- ylabel('Spectral \kappa (W/m/K/THz)')
- title('Spectral kappa vs frequency')
+### `outfile.thermal_conductivity`
-subplot(1,3,3); hold on; box on;
+This file contains the thermal conductivity tensor, with the decomposition from all contributions, in a format that can be parsed with tools such as numpy.
+It looks like this
- % read in cumulative kappa vs mean free path from file
- x=h5read(fn,['/temperature_' num2str(t) '/boundary_scattering_lengths']);
- y=h5read(fn,['/temperature_' num2str(t) '/boundary_scattering_kappa']);
- % grab only kxx
- y=squeeze(y(1,1,:));
- plot(x,y)
-
- set(gca,'xscale','log','yminortick','on')
- xlabel('Domain size (m)')
- ylabel('Kappa (W/mK)')
- title('Kappa vs boundary scattering')
-
- % get a reasonable range in x
- minx=max(x(find(ymax(y*0.9999))))*2
- xlim([minx maxx])
```
+# Unit: W/m/K
+# Temperature: 0.300000000000E+03
+# Single mode approximation
+# kxx kyy kzz kxy kxz kyz
+ 0.708649123335E+02 0.708649123335E+02 0.708649123335E+02 0.000000000000E+00 0.000000000000E+00 0.000000000000E+00
+# Collective contribution
+# kxx kyy kzz kxy kxz kyz
+ 0.475409189194E+01 0.475409189194E+01 0.475409189194E+01 0.000000000000E+00 0.000000000000E+00 0.000000000000E+00
+# Off diagonal (coherence) contribution
+# kxx kyy kzz kxy kxz kyz
+ 0.854567548533E-03 0.854567548533E-03 0.854567548533E-03 0.000000000000E+00 0.000000000000E+00 0.000000000000E+00
+# Total thermal conductivity
+# kxx kyy kzz kxy kxz kyz
+ 0.756198587929E+02 0.756198587929E+02 0.756198587929E+02 0.000000000000E+00 0.000000000000E+00 0.000000000000E+00
+```
+
-#### `outfile.grid_thermal_conductivity.hdf5`
+#### `outfile.thermal_conductivity_grid.hdf5`
-Option `--dumpgrid` produces this self-explainatory file. It will not get written if you use more than one temperature, the reason is that this file can get uncomfortably large, nearly all quantities on the full q-grid are written. Below is a matlab snippet that plots a subset:
+This file contains nearly all quantities on the full q-grid.
+Below is a matlab snippet that plots a subset:
```matlab
@@ -990,14 +471,12 @@ subplot(1,3,3); hold on; box on;
```
+[^Castellano2024]: [Castellano, A & Batista, J. P. & Verstraete, M. J. (2024). Mode-coupling theory of heat transport in anharmonic materials](https://doi.org/10.48550/arXiv.2411.14949)
+
[^peierls1929]: Peierls, R. E. (1929). Annalen der Physik, 3, 1055
[^peierls1955quantum]: [Peierls, R. E. (1955). Quantum Theory of Solids. Clarendon Press.](https://books.google.com/books?id=WvPcBUsSJBAC)
-[^Minnich2012]: [Minnich, A. J. (2012). Determining phonon mean free paths from observations of quasiballistic thermal transport. Physical Review Letters, 109(20), 1–5.](http://doi.org/10.1103/PhysRevLett.109.205901)
-
-[^Minnich2015]: [Minnich, A. J. (2015). Thermal phonon boundary scattering in anisotropic thin films. Applied Physics Letters, 107(18), 8–11.](http://doi.org/10.1063/1.4935160)
-
[^Tamura1983]: [Tamura, S. (1983). Isotope scattering of dispersive phonons in Ge. Physical Review B, 27(2), 858–866.](http://doi.org/10.1103/PhysRevB.27.858)
[^Omini1996]: [Omini, M., & Sparavigna, A. (1996). Beyond the isotropic-model approximation in the theory of thermal conductivity. Physical Review B, 53(14), 9064–9073.](http://doi.org/10.1103/PhysRevB.53.9064)
diff --git a/docs/program/thermal_conductivity_2023.md b/docs/program/thermal_conductivity_2023.md
new file mode 100644
index 00000000..d95168ad
--- /dev/null
+++ b/docs/program/thermal_conductivity_2023.md
@@ -0,0 +1,1023 @@
+
+### Short description
+
+Calculates the lattice thermal conductivity from the iterative solution of the phonon Boltzmann equation. In addition, cumulative plots and raw data dumps of intermediate values are available.
+
+!!! Note
+ Legacy implementation, the significantly improved program [thermal_conductivity](thermal_conductivity.md) should be used!
+
+### Command line options:
+
+
+
+
+Optional switches:
+
+* `--readiso`
+ default value .false.
+ Read the isotope distribution from `infile.isotopes`. The format is specified [here](../files.md#infile.isotopes).
+
+* `--qpoint_grid value#1 value#2 value#3`, `-qg value#1 value#2 value#3`
+ default value 26 26 26
+ Density of q-point mesh for Brillouin zone integrations.
+
+* `--integrationtype value`, `-it value`, value in: `1,2,3`
+ default value 2
+ Type of integration for the phonon DOS. 1 is Gaussian, 2 adaptive Gaussian and 3 Tetrahedron.
+
+* `--sigma value`
+ default value 1.0
+ Global scaling factor for adaptive Gaussian smearing.
+
+* `--threshold value`
+ default value 4.0
+ Consider a Gaussian distribution to be 0 after this many standard deviations.
+
+* `--readqmesh`
+ default value .false.
+ Read the q-point mesh from file. To generate a q-mesh file, see the genkpoints utility.
+
+* `--temperature value`
+ default value -1
+ Evaluate thermal conductivity at a single temperature.
+
+* `--temperature_range value#1 value#2 value#3`
+ default value 100 300 5
+ Series of temperatures for thermal conductivity. Specify min, max and the number of points.
+
+* `--logtempaxis`
+ default value .false.
+ Space the temperature points logarithmically instead of linearly.
+
+* `--max_mfp value`
+ default value -1
+ Add a limit on the mean free path as an approximation of domain size.
+
+* `--dumpgrid`
+ default value .false.
+ Write files with q-vectors, frequencies, eigenvectors and group velocities for a grid.
+
+* `--noisotope`
+ default value .false.
+ Do not consider isotope scattering.
+
+* `--help`, `-h`
+ Print this help message
+
+* `--version`, `-v`
+ Print version
+### Examples
+
+`mpirun thermal_conductivity --temperature 300`
+
+`mpirun thermal_conductivity -qg 15 15 15 --temperature_range 200 600 50`
+
+`mpirun thermal_conductivity --integrationtype 2 -qg 30 30 30 --max_mfp 1E-6`
+
+### Longer summary
+
+Heat transport can be determined by solving the inelastic phonon Boltzmann equation. By applying a temperature gradient $\nabla T_\alpha$ in direction $\alpha$, the heat current is given by the group velocities of phonon mode $\lambda$ and non-equilibrium phonon distribution function $\tilde{n}_\lambda$:[^peierls1955quantum]
+
+$$
+\begin{equation}
+J_{\alpha}=\frac{1}{V}\sum_\lambda
+\hbar \omega_\lambda v_{\lambda\alpha} \tilde{n}_{\lambda\alpha}.
+\end{equation}
+$$
+
+Assuming the thermal gradient is small, the non-equilibrium distribution function can be linearised as,
+
+$$
+\tilde{n}_{\lambda\alpha} \approx n_{\lambda}-
+v_{\lambda\alpha}
+\tau_{\lambda\alpha}
+\frac{d n_{\lambda}}{d T}
+\frac{d T}{d \alpha} \, ,
+$$
+
+That is a linear deviation from the equilibrium distribution function $n_{\lambda}$. Inserting this into the equation 1, and exploiting the fact that the equilibrium occupation carries no heat, we arrive at,
+
+$$
+J_{\alpha}=\frac{1}{V}\sum_{\lambda}
+\hbar \omega_{\lambda}
+\frac{d n_{\lambda}}{d T}
+v_{\lambda\alpha}
+v_{\lambda\alpha}
+\tau_{\lambda\alpha}
+\frac{d T}{d \alpha}.
+$$
+
+Utilizing Fourier's law, $J=\kappa \nabla T$, and identifying the phonon heat capacity,
+
+$$
+c_{\lambda}=
+\hbar \omega_\lambda
+\frac{d n_{\lambda}}{d T},
+$$
+
+we arrive at,
+
+$$
+\kappa_{\alpha\beta}=\frac{1}{V} \sum_{\lambda}
+c_{\lambda}
+v_{\alpha \lambda}v_{\beta \lambda} \tau_{\beta \lambda},
+$$
+
+which can be interpreted as follows: the heat transported by each phonon will depend on how much heat it carries, how fast it travels, and how long it lives. The phonon-phonon induced lifetime can be determined from the self-energy $\Gamma_{\lambda}$. In addition, one must consider the scattering with mass impurities (isotopes), and the boundaries of the sample.
+
+### Lifetimes
+
+With the third order force constants we can calculate the phonon lifetimes needed as input to the thermal conductivity calculations. The lifetime due to phonon-phonon scattering is related to the imaginary part of the phonon self energy ( $\Sigma=\Delta+i\Gamma$ ).
+
+$$
+\frac{1}{\tau_{\lambda}}=2 \Gamma_{\lambda},
+$$
+
+where $\tau_{\lambda}$ is the lifetime phonon mode $\lambda$, and
+
+$$
+\begin{split}
+\Gamma_{\lambda}=& \frac{\hbar \pi}{16} % _{\lambda'}
+\sum_{\lambda'\lambda''}
+\left|\Phi_{\lambda\lambda'\lambda''}\right|^2
+\bigl[(n_{\lambda'}+n_{\lambda''}+1)
+\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''}) \\
++ & 2(n_{\lambda'}-n_{\lambda''})
+\delta(\omega_{\lambda}-\omega_{\lambda'}+\omega_{\lambda''}) \bigr]
+\end{split}
+$$
+
+$n_{\lambda}$ is the equilibrium occupation number. The sum is over momentum conserving three-phonon processes, $\textbf{q}+\textbf{q}'+\textbf{q}''=\textbf{G}$, and the deltafunctions in frequency ensure energy conservation. The three-phonon matrix elements are given by
+
+$$
+\Phi_{\lambda\lambda'\lambda''} =
+\sum_{ijk}
+\sum_{\alpha\beta\gamma}
+\frac{
+\epsilon_{\lambda}^{i \alpha}
+\epsilon_{\lambda'}^{j \beta}
+\epsilon_{\lambda''}^{k \gamma}
+}{
+\sqrt{m_{i}m_{j}m_{j}}
+\sqrt{
+ \omega_{\lambda}
+ \omega_{\lambda'}
+ \omega_{\lambda''}}
+}
+\Phi^{\alpha\beta\gamma}_{ijk}
+e^{i \mathbf{q}\cdot\mathbf{r}_i + i \mathbf{q}'\cdot\mathbf{r}_j+i \mathbf{q}''\cdot\mathbf{r}_k}
+$$
+
+where $m_i$ is the mass of atom $i$, $\epsilon_{\lambda}^{\alpha i}$ is component $\alpha$ of the eigenvector for mode $\lambda$ and atom $i$ and $\textbf{r}_i$ is the lattice vector associated with atom $i$.
+
+Mass disorder, in the form of natural isotope distributions also cause thermal resistance. According to Tamura[^Tamura1983], if the isotopes are randomly distributed on the lattice sites then the strength of the isotope scattering can be given by a mass variance parameter $g$:
+
+$$
+g_i=\sum_j c_{i}^j \left(\frac{m_i^j-\bar{m_i}}{\bar{m_i}}\right)^2
+$$
+
+where $\bar{m_i}$ is the average isotopic mass( $\bar{m_i}=\sum_j c_i^j m_i^j$ ), $m^j_i$ is the mass of isotope $j$ of atom $i$ and $c^j_i$ is its concentration. The contribution to the imaginary part of the self-energy is
+
+$$
+\Gamma^{\textrm{iso}}_{\lambda}=
+\frac{\pi}{4} \sum_{\lambda'}
+\underbrace{\omega_{\lambda}\omega_{\lambda'} \sum_i g_i \left| \epsilon_{\lambda}^{i \dagger} \epsilon_{\lambda'}^{i} \right|^2}_{\Lambda_{\lambda\lambda'}}
+\delta(\omega_{\lambda}-\omega_{\lambda'})
+$$
+
+Per default, the isotope distribution will be the natural distribution. In case some other distribution is desired, this can be specified.
+
+Scattering by domain boundaries is implemented as
+
+$$
+\Gamma^{\textrm{boundary}}_{\lambda} = \frac{ v_{\lambda} }{2d}
+$$
+
+Where $d$ is a characteristic domain size.
+
+### Beyond the relaxation time approximation
+
+So far we have have considered the phonon heat conduction as an elastic process, whereas it is inelastic. This can be treated by iteratively solving the phonon boltzmann equation, formulated in terms of the (linear) deviations from equilibrium occupation numbers.[^peierls1929],[^Omini1996],[^Omini],[^Broido2007],[^Broido2005]
+
+### Phonon scattering rates and the phonon Boltzmann equation
+
+I always found it confusing how you arrived at most of these things. This is something I put together for myself, to clear it up a bit. Please bear in mind that this is not an attempt at a formal derivation whatsoever, just to make it a bit easier to interpret the different terms. There might be an arbitrary number of plusses and minuses and other things missing. Recall the transformation we introduced [earlier](phonon_dispersion_relations.md):
+
+$$
+\begin{equation}\label{eq:normalmodetransformation}
+\hat{u}_{i\alpha} = \sqrt{ \frac{\hbar}{2N m_\alpha} }
+\sum_\lambda \frac{\epsilon_\lambda^{i\alpha}}{ \sqrt{ \omega_\lambda} }
+e^{i\mathbf{q}\cdot\mathbf{r}_i}
+\left( \hat{a}^{\mathstrut}_\lambda + \hat{a}^\dagger_\lambda \right)
+\end{equation}
+$$
+
+and consider the three-phonon process where two phonons combine into one:
+
+$$
+\begin{equation*}
+\begin{split}
+\mathbf{q} + \mathbf{q}' + \mathbf{q}'' & = \mathbf{G} \\
+\omega + \omega' & = \omega''
+\end{split}
+\end{equation*}
+$$
+
+This process changes the state of the system:
+
+$$
+\begin{equation}
+\underbrace{\left| \ldots , n_{\lambda},n_{\lambda'},n_{\lambda''} , \ldots \right\rangle}_{\left\vert i \right\rangle}
+\rightarrow
+\underbrace{\left| \ldots , n_{\lambda}-1,n_{\lambda'}-1,n_{\lambda''}+1, \ldots \right\rangle}_{\left\vert f \right\rangle}
+\end{equation}
+$$
+
+that is, we lost one phonon at $\lambda$ and one at $\lambda'$, and created a phonon at $\lambda''$.
+Mostly out of habit, we sandwich the Hamiltonian between the initial and final states:
+
+$$
+\begin{equation}\label{eq:sandwich}
+{\left\langle f \middle\vert \hat{H} \middle\vert i \right\rangle} =
+{\left\langle f \middle\vert \sum_i \frac{p^2_i}{2m} +
+\frac{1}{2!}\sum_{ij} \sum_{\alpha\beta}\Phi_{ij}^{\alpha\beta}
+u_i^\alpha u_j^\beta +\frac{1}{3!}
+\sum_{ijk} \sum_{\alpha\beta\gamma}\Phi_{ijk}^{\alpha\beta\gamma}
+u_i^\alpha u_j^\beta u_k^\gamma \ldots
+\middle\vert i \right\rangle}
+\end{equation}
+$$
+
+and remember the rules for ladder operators, and that the eigenstates to the quantum harmonic oscillator are orthogonal:
+
+$$
+\begin{equation*}
+\begin{split}
+\hat{a}^\dagger \left\vert n \right\rangle & = \sqrt{n+1} \left\vert n + 1 \right\rangle \\
+\hat{a} \left\vert n \right\rangle & = \sqrt{n} \left\vert n -1 \right\rangle \\
+\left\langle i \middle\vert j \right\rangle & = \delta_{ij}
+\end{split}
+\end{equation*}
+$$
+
+Inserting eq \ref{eq:normalmodetransformation} into \ref{eq:sandwich} (and realising that the kinetic energy part and the second order part disappears), we end up with a pretty large expression, that we will deal with in steps, first identify
+
+$$
+\begin{equation}\label{eq:uprod}
+\begin{split}
+u^\alpha_{i}u^\beta_{j}u^\gamma_{k} & =
+%
+\left(\frac{\hbar}{2N}\right)^{3/2} \frac{1}{\sqrt{m_{i}m_{j}m_{k}}}
+\sum_{\lambda\lambda'\lambda''}
+\frac{
+\epsilon_{\lambda}^{i \alpha}
+\epsilon_{\lambda'}^{j \beta}
+\epsilon_{\lambda''}^{k \gamma}
+}{
+\sqrt{
+ \omega_{\lambda}
+ \omega_{\lambda'}
+ \omega_{\lambda''}}
+}
+e^{i \mathbf{q}\cdot\mathbf{r}_i + i \mathbf{q}'\cdot\mathbf{r}_j+i \mathbf{q}''\cdot\mathbf{r}_k}
+ \left(a_{\lambda}+a_{\lambda}^\dagger \right)
+\left(a_{\lambda'}+a_{\lambda'}^\dagger \right)
+\left(a_{\lambda''}+a_{\lambda''}^\dagger \right)
+\end{split}
+\end{equation}
+$$
+
+as well as
+
+$$
+\begin{equation}
+\begin{split}
+& \sum_{\lambda\lambda'\lambda''}
+\left\langle f \middle\vert
+\left(a_{\lambda}+a_{\lambda}^\dagger \right)
+\left(a_{\lambda'}+a_{\lambda'}^\dagger \right)
+\left(a_{\lambda''}+a_{\lambda''}^\dagger \right)
+\middle\vert i \right\rangle = \\
+= & \sum_{\lambda\lambda'\lambda''} \left\langle f \middle\vert
+\hat{a}_{\lambda} \hat{a}_{\lambda'} \hat{a}_{\lambda''} + \hat{a}_{\lambda} \hat{a}_{\lambda'} \hat{a}^{\dagger}_{\lambda''} + \hat{a}_{\lambda} \hat{a}^{\dagger}_{\lambda'} \hat{a}_{\lambda''} + \hat{a}_{\lambda} \hat{a}^{\dagger}_{\lambda'} \hat{a}^{\dagger}_{\lambda''} + \hat{a}^{\dagger}_{\lambda} \hat{a}_{\lambda'} \hat{a}_{\lambda''} + \hat{a}^{\dagger}_{\lambda} \hat{a}_{\lambda'} \hat{a}^{\dagger}_{\lambda''} + \hat{a}^{\dagger}_{\lambda} \hat{a}^{\dagger}_{\lambda'} \hat{a}_{\lambda''} + \hat{a}^{\dagger}_{\lambda} \hat{a}^{\dagger}_{\lambda'} \hat{a}^{\dagger}_{\lambda''}
+\middle\vert i \right\rangle = \\
+= & \sum_{\lambda\lambda'\lambda''} \left\langle f \middle\vert
+a_{\lambda}a_{\lambda'}a^\dagger_{\lambda''}
+\middle\vert i \right\rangle
+ = 3 \sqrt{n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)}
+\end{split}
+\end{equation}
+$$
+
+where the factor 3 comes from the multiplicity, to get at
+
+$$
+\begin{equation}
+{\left\langle f \middle\vert \hat{H}_3 \middle\vert i \right\rangle} =
+\frac{1}{2}
+\sum_{ijk} \sum_{\alpha\beta\gamma}\Phi_{ijk}^{\alpha\beta\gamma}
+\sqrt{n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)}
+%
+\left(\frac{\hbar}{2N}\right)^{3/2}
+\frac{
+\epsilon_{\lambda}^{i \alpha}
+\epsilon_{\lambda'}^{j \beta}
+\epsilon_{\lambda''}^{k \gamma}
+}{
+\sqrt{m_{i}m_{j}m_{j}}
+\sqrt{
+ \omega_{\lambda}
+ \omega_{\lambda'}
+ \omega_{\lambda''}}
+}
+e^{i \mathbf{q}\cdot\mathbf{r}_i + i \mathbf{q}'\cdot\mathbf{r}_j+i \mathbf{q}''\cdot\mathbf{r}_k}
+\end{equation}
+$$
+
+The initial factor 1/2 is the multiplicity cancelled by the 3! from the Hamiltonian. Here, as it happens, we can identify the three-phonon matrix elements and simplify a little bit more
+
+$$
+\begin{equation}
+{\left\langle f \middle\vert \hat{H}_3 \middle\vert i \right\rangle} =
+\frac{1}{2}
+\sqrt{n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)}
+\left(\frac{\hbar}{2N}\right)^{3/2}
+\Phi_{\lambda\lambda'\lambda''}
+\end{equation}
+$$
+
+The probability of this particular three-phonon process can be estimated via the Fermi golden rule:
+
+$$
+\begin{equation}
+\begin{split}
+P_{\lambda\lambda'\rightarrow\lambda''} & =\frac{2\pi}{\hbar}
+\left|{\left\langle f \middle\vert \hat{H}_3 \middle\vert i \right\rangle}\right|^2
+\delta(E_f-E_i) =
+\frac{\hbar^2\pi}{16N}
+n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)
+\left| \Phi_{\lambda\lambda'\lambda''} \right|^2
+\delta(E_f-E_i)
+\end{split}
+\end{equation}
+$$
+
+With near identical reasoning, we can also arrive at
+
+$$
+\begin{equation}\label{pplus}
+P_{\lambda\rightarrow\lambda'\lambda''} =
+\frac{\hbar^2\pi}{16N}
+n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1)
+\left| \Phi_{\lambda\lambda'\lambda''} \right|^2
+\delta(E_f-E_i)
+\end{equation}
+$$
+
+for the other kind of three-phonon processes, and
+
+$$
+\begin{equation}\label{pminus}
+P_{\lambda\rightarrow\lambda'} =\frac{2\pi}{\hbar}\left|\langle f | H^{\textrm{iso}} | i \rangle \right|^2\delta(E_f-E_i) =
+\frac{\pi\hbar}{2N} n_{\lambda}(n_{\lambda'}+1) \Lambda_{\lambda\lambda'}\delta(E_f-E_i)
+\end{equation}
+$$
+
+for the isotope scattering. I leave those derivations as an exercise. The phonon Boltzmann equation is stated as:
+
+$$
+\begin{equation}\label{eq:pbe}
+\frac{\partial \tilde{n}_\lambda}{\partial T} \mathbf{v}_\lambda \cdot \nabla T =
+\left. \frac{\partial \tilde{n}_\lambda }{\partial t} \right|_{\mathrm{coll}}
+\end{equation}
+$$
+
+Where $\tilde{n}$ is the non-equilibrium occupation number. This is ridiculously complicated. To make life easier, we only consider the terms we outlined above as possible collisions. Gathering all possible events that involve mode $\lambda$ we get
+
+$$
+\begin{equation}\label{manyprob}
+\begin{split}
+\left. \frac{\partial n_{\lambda}}{ \partial t} \right|_{\mathrm{coll}}
+= & \sum_{\lambda'}
+\left( P_{\lambda\rightarrow\lambda'}-P_{\lambda'\rightarrow\lambda } \right) +
+\sum_{\lambda'\lambda''}
+- P_{\lambda \rightarrow \lambda' \lambda'' }
+- P_{\lambda \rightarrow \lambda''\lambda' }
++ P_{\lambda' \rightarrow \lambda \lambda'' }
++ P_{\lambda' \rightarrow \lambda''\lambda }
++ P_{\lambda''\rightarrow \lambda \lambda' }
++ P_{\lambda''\rightarrow \lambda' \lambda } \\
+& - P_{\lambda \lambda' \rightarrow \lambda'' }
+- P_{\lambda \lambda'' \rightarrow \lambda' }
+- P_{\lambda' \lambda \rightarrow \lambda'' }
++ P_{\lambda' \lambda'' \rightarrow \lambda }
+- P_{\lambda'' \lambda \rightarrow \lambda' }
++ P_{\lambda'' \lambda' \rightarrow \lambda }
+\end{split}
+\end{equation}
+$$
+
+Which does not seem to make life easier. To make it slightly worse, we insert \ref{pplus} and \ref{pminus} into this, and at the same time say that the non-equilibrium distribution functions are the equilibrium distributions, plus a (small) deviation:
+
+$$
+\begin{equation}
+\tilde{n}_{\lambda}\approx n_{\lambda}+\epsilon_{\lambda}
+\end{equation}
+$$
+
+After some [hard work](https://reference.wolfram.com/language/ref/FullSimplify.html), and discarding terms of $\epsilon^2$ and higher, we get
+
+$$
+\begin{equation}
+\begin{split}
+\left. \frac{\partial n_{\lambda}}{ \partial t} \right|_{\mathrm{coll}}
+= & \sum_{\lambda'\lambda''}
+\frac{\hbar\pi}{8N}
+\left| \Phi_{\lambda\lambda'\lambda''} \right|^2 \Big(
+\left[
+-n_{\lambda} \epsilon_{\lambda'} + n_{\lambda''} (\epsilon_{\lambda} + \epsilon_{\lambda'}) + \epsilon_{\lambda''} + n_{\lambda} \epsilon_{\lambda''} + n_{\lambda'} (-\epsilon_{\lambda} + \epsilon_{\lambda''})
+\right]\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''}) + \\
+& \left[
+\epsilon_{\lambda'} + n_{\lambda} \epsilon_{\lambda'} + n_{\lambda''} (-\epsilon_{\lambda} + \epsilon_{\lambda'}) - n_{\lambda} \epsilon_{\lambda''} +
+ n_{\lambda'} (\epsilon_{\lambda} + \epsilon_{\lambda''} )
+\right]\delta(\omega_{\lambda}-\omega_{\lambda'}+\omega_{\lambda''}) - \\
+& \left[(1 + n_{\lambda'} + n_{\lambda''})\epsilon_{\lambda} - n_{\lambda''}\epsilon_{\lambda''} - n_{\lambda'} \epsilon_{\lambda''} + n_{\lambda} (\epsilon_{\lambda'} + \epsilon_{\lambda''} )\right]
+\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''}) \Big)
+\end{split}
+\end{equation}
+$$
+
+Which does not seem like a lot of help. If we make another substitution, and say that the deviation from equilibrium behaves sort of like the equilibrium (with no loss of generality, just to make life easier):
+
+$$
+\begin{equation}
+\epsilon_{\lambda} =
+\frac{\partial n_{\lambda} }{\partial \omega_\lambda}
+\frac{k_B T}{\hbar} \zeta_{\lambda}=-n_{\lambda}(n_{\lambda}+1) \zeta_{\lambda}
+\end{equation}
+$$
+
+Inserting this, and more tedious algebra, we get
+
+$$
+\begin{equation}
+\begin{split}
+\left. \frac{\partial n_{\lambda}}{ \partial t} \right|_{\mathrm{coll}}
+=& \frac{\hbar\pi}{4N}
+\sum_{\lambda'\lambda''}
+\left| \Phi_{\lambda\lambda'\lambda''} \right|^2 \Big(
+n_{\lambda} n_{\lambda'} (n_{\lambda''}+1) \delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''} )
+\left( \zeta_{\lambda} + \zeta_{\lambda'} - \zeta_{\lambda''} \right) + \\
+& \frac{1}{2} n_{\lambda} (n_{\lambda'}+1) (n_{\lambda''}+1) \delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})
+\left( \zeta_{\lambda} - \zeta_{\lambda'} -\zeta_{\lambda''} \right) \Big)
+\end{split}
+\end{equation}
+$$
+
+If we add the isotope term again, that I forgot at some point between the beginning and here, we can rearrange this in terms of scattering rates that should look familiar (using strange relations for occupation numbers that only hold when the deltafunctions in energy are satisfied):
+
+$$
+\begin{equation}
+\left. \frac{\partial n_{\lambda}}{ \partial t} \right|_{\mathrm{coll}} =
+\sum_{\lambda'\lambda''}
+\tilde{P}^{+}_{\lambda\lambda'\lambda''}
+\left( \zeta_{\lambda}+\zeta_{\lambda'}-\zeta_{\lambda''} \right)
++\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
+\left( \zeta_{\lambda}-\zeta_{\lambda'}-\zeta_{\lambda''} \right)+
+\sum_{\lambda'}
+\tilde{P}^\textrm{iso}_{\lambda\lambda'} \left( \zeta_{\lambda}-\zeta_{\lambda'} \right)
+\end{equation}
+$$
+
+where
+
+$$
+\begin{align}
+\tilde{P}^{+}_{\lambda\lambda'\lambda''}&=
+\frac{\hbar \pi}{4 N}
+n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)\left|\Phi_{\lambda\lambda'\lambda''}\right|^2
+\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''})
+\\
+\tilde{P}^{-}_{\lambda\lambda'\lambda''}&=
+\frac{\hbar \pi}{4 N}
+n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1)\left|\Phi_{\lambda\lambda'\lambda''}\right|^2
+\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})
+\\
+\tilde{P}^\textrm{iso}_{\lambda\lambda'} &=
+\frac{\pi}{2N} n_{\lambda}(n_{\lambda'}+1) \Lambda_{\lambda\lambda'}
+\delta(\omega_{\lambda}-\omega_{\lambda})
+\end{align}
+$$
+
+What we have done here is to rearrange the transition propabilities to scattering rates. If we let
+
+$$
+\begin{equation}
+\zeta_{\lambda}=\frac{\hbar}{k_B T} \mathbf{F}_{\lambda} \cdot \nabla T
+\end{equation}
+$$
+
+and combine everything we end up with
+
+$$
+\begin{equation}
+\begin{split}
+-\frac{\omega_{\lambda}}{T}n_{\lambda}(n_{\lambda}+1)\mathbf{v}_{\lambda} \cdot \nabla T = &
+ \sum_{\lambda'}
+\tilde{P}^\textrm{iso}_{\lambda\lambda'}
+\left(\mathbf{F}_{\lambda}-\mathbf{F}_{\lambda'}\right)\cdot\nabla T +
+\sum_{\lambda'\lambda''}
+\tilde{P}^{+}_{\lambda\lambda'\lambda''}
+\left(\mathbf{F}_{\lambda}+\mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''}\right)\cdot\nabla T+
+\tilde{P}^{-}_{\lambda\lambda'\lambda''}
+\left(\mathbf{F}_{\lambda}-\mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''}\right)\cdot\nabla T =
+\\ = &
+\mathbf{F}_{\lambda}\cdot\nabla T
+\left(
+\sum_{\lambda'}
+\tilde{P}^\textrm{iso}_{\lambda\lambda'}
++
+\sum_{\lambda'\lambda''}
+\tilde{P}^{+}_{\lambda\lambda'\lambda''}+
+\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
+\right)- \\
+& - \sum_{\lambda'}
+\tilde{P}^\textrm{iso}_{\lambda\lambda'}\mathbf{F}_{\lambda'}\cdot\nabla T +
+\sum_{\lambda'\lambda''}
+\tilde{P}^{+}_{\lambda\lambda'\lambda''}
+\left(\mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''}\right)\cdot\nabla T-
+\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
+\left(\mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''}\right)\cdot\nabla T
+\end{split}
+\end{equation}
+$$
+
+Where we can identify
+
+$$
+\begin{equation}
+Q_{\lambda}=\sum_{\lambda'}
+\tilde{P}^\textrm{iso}_{\lambda\lambda'}
++
+\sum_{\lambda'\lambda''}
+\tilde{P}^{+}_{\lambda\lambda'\lambda''}+
+\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
+\end{equation}
+$$
+
+And rearrange terms
+
+$$
+\begin{equation}
+\mathbf{F}_{\lambda}=
+\frac{\omega_{\lambda} \bar{n}_{\lambda}(\bar{n}_{\lambda}+1)\mathbf{v}_{\lambda} }{T Q_{\lambda}}
++
+\frac{1}{Q_{\lambda}}\left[
+\sum_{\mathbf{q}'\mathbf{q}''}\sum_{s's''}
+\tilde{P}^{+}_{\lambda\lambda'\lambda''}
+\left( \mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''} \right)-
+\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
+\left( \mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''} \right)
+\right]
+\end{equation}
+$$
+
+And we have a set of equations for $F$ that we can solve self-consistently. Previously, we used the imaginary part of the self-energy to get a phonon lifetime. What we got here, from Fermi golden rule, is related:
+
+$$
+\sum_{\lambda'} \tilde{P}^\textrm{iso}_{\lambda\lambda'} =
+\frac{\pi}{2N} n_{\lambda}(n_{\lambda}+1) \sum_{\lambda'} \Lambda_{\lambda\lambda'}
+\delta(\omega_{\lambda}-\omega_{\lambda}) = 2 n_{\lambda}(n_{\lambda}+1) \Gamma^{\textrm{iso}}_{\lambda}
+$$
+
+This can also be done for the three-phonon terms:
+
+$$
+\begin{equation}
+\begin{split}
+\sum_{\lambda'\lambda''} \tilde{P}^{+}_{\lambda\lambda'\lambda''}+
+\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''} & =
+\frac{\hbar \pi}{8 N}
+\sum_{\lambda'\lambda''} \left|\Phi_{\lambda\lambda'\lambda''}\right|^2
+\left[
+n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1) \delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})+
+2n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''})
+\right] \\
+& = n_{\lambda}(n_{\lambda}+1) \frac{\hbar \pi}{8 N}
+\sum_{\lambda'\lambda''} \left|\Phi_{\lambda\lambda'\lambda''}\right|^2
+\left[
+\frac{n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1)}{n_{\lambda}(n_{\lambda}+1)}
+\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})
++
+\frac{2n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)}{n_{\lambda}(n_{\lambda}+1)}
+\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''})
+\right] \\
+& =
+n_{\lambda}(n_{\lambda}+1) \frac{\hbar \pi}{8 N}
+\sum_{\lambda'\lambda''} \left|\Phi_{\lambda\lambda'\lambda''}\right|^2
+\left[
+(n_{\lambda'}+n_{\lambda''}+1)
+\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})
++
+(n_{\lambda'}-n_{\lambda''})
+\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''})
+\right] \\
+& = 2 n_{\lambda}(n_{\lambda}+1) \Gamma_{\lambda}
+\end{split}
+\end{equation}
+$$
+
+Where the second to last step seems a little impossible, but with $\hbar\omega/k_BT = x$, you get
+
+$$
+\begin{equation}
+\frac{ n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1) }{ n_{\lambda}(n_{\lambda}+1) } -
+\left( n_{\lambda'} + n_{\lambda''} + 1 \right)
+=
+\frac{
+1-\exp[x'+x''-x]
+}{
+\left( \exp[x'] -1 \right) \left( \exp[x''] -1 \right)
+}
+\end{equation}
+$$
+
+which comes out to 0 when $x=x'+x''$, which the deltafunction ensures. In the same way
+
+$$
+\begin{equation}
+\frac{ n_{\lambda}n_{\lambda'}(n_{\lambda''}+1) }{ n_{\lambda}(n_{\lambda}+1) } -
+\left( n_{\lambda'} - n_{\lambda''} \right)
+=
+\frac{
+\exp[-x]\left(\exp[x+x']-\exp[x''] \right)
+}{
+\left( \exp[x'] -1 \right) \left( \exp[x''] -1 \right)
+}
+\end{equation}
+$$
+
+comes out to 0 when $x''=x+x'$. We can directly relate the relaxation time lifetime
+
+$$
+\begin{equation}
+\tau_{\lambda} = \frac{1}{2\Gamma_{\lambda}} = \frac{ n_{\lambda}(n_{\lambda}+1) }{Q_{\lambda}}
+\end{equation}
+$$
+
+to an initial guess
+
+$$
+\mathbf{F}^0_{\lambda} =
+\frac{\tau_{\lambda} \omega_{\lambda} \mathbf{v}_{\lambda} }{T}
+$$
+
+and iteratively solve
+
+$$
+\begin{equation}
+\mathbf{F}^{i+1}_{\lambda}=
+\mathbf{F}^0_{\lambda}
++
+\frac{1}{Q_{\lambda}}\left[
+\sum_{\lambda'\lambda''}
+\tilde{P}^{+}_{\lambda\lambda'\lambda''}
+\left( \mathbf{F}^{i}_{\lambda'}-\mathbf{F}^{i}_{\lambda''} \right)-
+\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
+\left( \mathbf{F}^{i}_{\lambda'}-\mathbf{F}^{i}_{\lambda''} \right)
+\right]
+\end{equation}
+$$
+
+to arrive at the non-equilibrium distributions. The thermal conductivity tensor is then given as
+
+$$
+\begin{equation}
+\kappa_{\alpha\beta} =
+\frac{1}{V}
+\sum_{\lambda}
+\frac{T c_{\lambda} v_{\lambda}^\alpha F_{\lambda}^\beta}{\omega_{\lambda}}
+\end{equation}
+$$
+
+
+
+### Off diagonal coherent contribution
+
+
+The Boltzmann equation only takes into account the relaxation of phonons from perturbed state to the equilibrium.
+However, for systems with complex unit cell, a contribution stemming from coherence tunelling between different phonons can become important.
+This off-diagonal coherent term can be introduced both by a formulation based on the Hardy current [^Isaeva2019] or a Wigner current [^Simoncelli2019], with very similar results[^Caldarelli2022].
+In TDEP, we use the formulation based on the Hardy heat current [^Isaeva2019]
+
+$$
+\begin{equation}
+J_\alpha = \frac{1}{V} \sum_{\lambda\lambda'} \frac{\omega_\lambda + \omega_{\lambda'}}{2} v_{\lambda\lambda'}^\alpha \hat{a}_\lambda^\dagger \hat{a}_{\lambda'} \delta_{q_{\lambda} q_{\lambda'}}
+\end{equation}
+$$
+
+where $v_{\lambda\lambda'}^\alpha$ are generalized group velocity tensor [^Dangic2021]
+
+$$
+\begin{equation}
+v_{\lambda\lambda'}^\alpha = \frac{i}{2 \sqrt{\omega_\lambda \omega_{\lambda'}}} \sum_{ij \beta\gamma} \epsilon_\lambda^{i\beta} \sum_{\mathbf{R}} \big( R^\alpha \frac{\Phi_{ij}^{\beta\gamma}(\mathbf{R})}{\sqrt{m_i m_j}} \big) \epsilon_{\lambda'}^{j\gamma}
+\end{equation}
+$$
+
+with $\mathbf{R}$ the distance vector between unit cells and $m_i$ the mass of atom $i$ in the unit cell.
+It should be recognized that for $\lambda = \lambda'$, we recover the heat current used previously to derive the thermal conductivity with the Boltzman equation.
+
+The derivation of this off-diagonal contribution starts from the Green-Kubo formula for the thermal conductivity tensor
+
+$$
+\begin{equation}
+\kappa_{\alpha\beta} = \frac{1}{V k_BT}\int_0^\infty dt \int_0^\beta d\nu \langle J_\alpha(-i\hbar\nu) J_\beta(t) \rangle
+\end{equation}
+$$
+
+Injecting the harmonic heat current into this equation allows to rewrite the total thermal conductivity tensor as [^Fiorentino2023]
+
+$$
+\begin{equation}
+\kappa_{\alpha\beta} = \kappa_{\alpha\beta}^{BTE} + \kappa_{\alpha\beta}^{c}
+\end{equation}
+$$
+
+where $\kappa_{\alpha\beta}^{\mathrm{BTE}}$ is the thermal conductivity from the previous section and $\kappa_{\alpha\beta}^{c}$ is the off-diagonal coherent contribution.
+This term is computed as
+
+$$
+\begin{equation}
+\kappa_{\alpha\beta}^{c} = \frac{1}{Vk_BT^2} \sum_{\lambda\lambda'\neq \lambda} c_{\lambda\lambda'} v_{\lambda\lambda'}^\alpha v_{\lambda\lambda'}^\beta \tau_{\lambda\lambda'} \delta_{q_\lambda q_{\lambda'}}
+\end{equation}
+$$
+
+with
+
+$$
+\begin{equation}
+c_{\lambda\lambda'} = \frac{1}{4} [n_\lambda (n_\lambda + 1) + n_{\lambda'} (n_{\lambda'} + 1) ] (\omega_\lambda + \omega_{\lambda'})^2
+\end{equation}
+$$
+
+$$
+\begin{equation}
+\tau_{\lambda\lambda'} = \frac{\Gamma_\lambda + \Gamma_{\lambda'}}{(\omega_{\lambda} - \omega_{\lambda'})^2 + (\Gamma_{\lambda} + \Gamma_{\lambda'})^2}
+\end{equation}
+$$
+
+
+### Cumulative kappa
+
+@todo Check code snippets
+
+@todo Spectral kappa, links to things.
+
+Experimentally, the cumulative thermal conductivity with respect to phonon mean free path,
+
+$$
+l_{\lambda} = \left| v_{\lambda} \right| \tau_{\lambda} \,,
+$$
+
+can be measured.[^Minnich2012] The cumulative thermal conductivity can then be computed as a sum of the fraction of heat that is carried by phonons with mean free paths smaller than $l$:
+
+$$
+\kappa_{\alpha\beta}^{\textrm{acc}}(l)=
+\frac{1}{V} \sum_{\lambda}
+C_{\lambda} v^{\alpha}_{\lambda} v^{\beta}_{\lambda} \tau_{\lambda} \Theta(l- l_{\lambda} ) \,,
+$$
+
+where $\Theta$ is the Heaviside step function.
+
+One can also define a spectral thermal conductivity as
+
+$$
+\kappa_{\alpha\beta}(\omega)=
+\frac{1}{V} \sum_{\lambda}
+C_{\lambda} v^{\alpha}_{\lambda} v^{\beta}_{\lambda} \tau_{\lambda} \delta(\omega- \omega_{\lambda} )
+$$
+
+which is a measure which frequencies contribute most to thermal transport.
+
+### Thin film scattering
+
+Constrained geometries will incur additional scattering from domain boundaries. For a thin film (thin, but thick enough that the interior of the film is accurately described by bulk phonons) one can estimate the suppression due to film thinkness.[^Minnich2015] Assyming the cross-plane direction of the film is in the $y$-direction, and the thermal gradient is applied in the $z$-direction, the in-plane thermal conductivity $\kappa_{zz}$ is supressed as:
+
+$$
+\kappa_{zz}(d)=A+B+C,
+$$
+
+where
+
+$$
+\begin{split}
+x_{\lambda} = & \frac{\hbar\omega_{\lambda}}{V}
+\frac{\partial n_{\lambda}} {\partial T}
+v_{\lambda}^z l_{\lambda}^z
+ \\
+A = & -\frac{1}{d} \sum_{v_y>0}
+x_{\lambda}
+\left( -l^{y}_{\lambda} \exp\left[\frac{d}{l^{y}_{\lambda}}\right]+l^{y}_{\lambda}-d \right) \\
+B = & -\frac{1}{d} \sum_{v_y<0}
+x_{\lambda}
+\left(
+l^{y}_{\lambda}
+\exp\left[ -\frac{d}{l^{y}_{\lambda}} \right]
+-l^{y}_{\lambda}-d
+\right) \\
+C = & \sum_{v_y=0} x_{\lambda}
+\end{split}
+$$
+
+where $v_y$ and $v_z$ are the components of the phonon group velocity along the $y$ and $z$ directions, $\tau_{\lambda}$ is the phonon relaxation time. $l^{y}_{\lambda}$ is the $y$ component of the MFP and $d$ is the thickness of the film in $y$-direction.
+
+### Input files
+
+These files are necesarry:
+
+* [infile.ucposcar](../files.md#infile.ucposcar)
+* [infile.forceconstant](extract_forceconstants.md#infile.forceconstant)
+* [infile.forceconstant_thirdorder](extract_forceconstants.md#infile.forceconstant_thirdorder)
+
+and these are optional:
+
+* [infile.isotopes](../files.md#infile.isotopes) (for non-natural isotope distribution)
+
+### Output files
+
+Depending on options, the set of output files may differ. We start with the basic files that are written after running this code.
+
+#### `outfile.thermal_conductivity`
+
+This file contains components of the thermal conductivity tensor $\kappa_{\alpha \beta}$ for each temperature.
+
+$test$
+
+| **Row** | **Description** |
+| ------- | ------------------------------------------------------------ |
+| 1 | $T_1 \qquad \kappa_{xx} \quad \kappa_{yy} \quad \kappa_{zz} \quad \kappa_{xz} \quad \kappa_{yz} \quad \kappa_{xy} \quad \kappa_{zx} \quad \kappa_{zy} \quad \kappa_{yx}$ |
+| 2 | $T_2 \qquad \kappa_{xx} \quad \kappa_{yy} \quad \kappa_{zz} \quad \kappa_{xz} \quad \kappa_{yz} \quad \kappa_{xy} \quad \kappa_{zx} \quad \kappa_{zy} \quad \kappa_{yx}$ |
+| … | … |
+
+
+#### `outfile.cumulative_kappa.hdf5`
+
+This file is self-explainatory. It contains the different cumulative plots described above, at a series of temperatures. Below is a matlab snippet that plots part of the output.
+
+```matlab
+figure(1); clf; hold on; box on;
+
+% filename
+fn='outfile.cumulative_kappa.hdf5';
+% which temperature?
+t=1;
+
+subplot(1,3,1); hold on; box on;
+
+ % read in cumulative kappa vs mean free path from file
+ x=h5read(fn,['/temperature_' num2str(t) '/mean_free_path_axis']);
+ xunit=h5readatt(fn,['/temperature_' num2str(t) '/mean_free_path_axis'],'unit');
+ y=h5read(fn,['/temperature_' num2str(t) '/cumulative_kappa_vs_mean_free_path_total']);
+ % projections to modes and/or atoms
+ z=h5read(fn,['/temperature_' num2str(t) '/cumulative_kappa_vs_mean_free_path_per_atom']);
+ %z=h5read(fn,['/temperature_' num2str(t) '/cumulative_kappa_vs_mean_free_path_per_mode']);
+
+ yunit=h5readatt(fn,['/temperature_' num2str(t) '/cumulative_kappa_vs_mean_free_path_total'],'unit');
+
+ % plot
+ plot(x,y)
+ plot(x,z)
+
+ % set a legend
+ lgd{1}='Total';
+ for i=1:size(z,2)
+ lgd{i+1}=['Atom ' num2str(i)];
+ end
+ l=legend(lgd);
+ set(l,'edgecolor','none','location','northwest');
+
+ % some titles
+ title('Cumulative kappa vs mean free path');
+ ylabel(['Cumulative \kappa (' yunit ')']);
+ xlabel(['Mean free path (' xunit ')']);
+
+ % get some reasonable ranges
+ minx=x(max(find(ymax(y*0.9999))))*2;
+ xlim([minx maxx]);
+ set(gca,'xscale','log','yminortick','on');
+
+subplot(1,3,2); hold on; box on;
+
+ % read in spectral kappa vs frequency
+ x=h5read(fn,['/temperature_' num2str(t) '/frequency_axis']);
+ y=h5read(fn,['/temperature_' num2str(t) '/spectral_kappa_vs_frequency_total']);
+ z=h5read(fn,['/temperature_' num2str(t) '/spectral_kappa_vs_frequency_per_mode']);
+ %z=h5read(fn,['/temperature_' num2str(t) '/spectral_kappa_vs_frequency_per_atom']);
+
+ plot(x,y)
+ plot(x,z)
+
+ set(gca,'xminortick','on','yminortick','on')
+ xlabel('Frequency (THz)')
+ ylabel('Spectral \kappa (W/m/K/THz)')
+ title('Spectral kappa vs frequency')
+
+subplot(1,3,3); hold on; box on;
+
+ % read in cumulative kappa vs mean free path from file
+ x=h5read(fn,['/temperature_' num2str(t) '/boundary_scattering_lengths']);
+ y=h5read(fn,['/temperature_' num2str(t) '/boundary_scattering_kappa']);
+ % grab only kxx
+ y=squeeze(y(1,1,:));
+ plot(x,y)
+
+ set(gca,'xscale','log','yminortick','on')
+ xlabel('Domain size (m)')
+ ylabel('Kappa (W/mK)')
+ title('Kappa vs boundary scattering')
+
+ % get a reasonable range in x
+ minx=max(x(find(ymax(y*0.9999))))*2
+ xlim([minx maxx])
+```
+
+#### `outfile.grid_thermal_conductivity.hdf5`
+
+Option `--dumpgrid` produces this self-explainatory file. It will not get written if you use more than one temperature, the reason is that this file can get uncomfortably large, nearly all quantities on the full q-grid are written. Below is a matlab snippet that plots a subset:
+
+```matlab
+
+% file to read from
+fn='outfile.grid_thermal_conductivity.hdf5';
+% convert units to THz from Hz?
+toTHz=1/1E12/2/pi;
+
+figure(1); clf; hold on;
+
+subplot(1,3,1); hold on; box on;
+
+ x=h5read(fn,'/frequencies');
+ y=h5read(fn,'/linewidths');
+
+ for i=1:size(x,1)
+ plot(x(i,:)*toTHz,y(i,:)*toTHz,'marker','.','linestyle','none','markersize',8)
+ end
+ set(gca,'xminortick','on','yminortick','on')
+ xlabel('Frequency (THz)')
+ ylabel('Linewidth (THz)')
+
+subplot(1,3,2); hold on; box on;
+
+ x=h5read(fn,'/frequencies');
+ y=h5read(fn,'/lifetimes');
+
+ for i=1:size(x,1)
+ plot(x(i,:)*toTHz,y(i,:),'marker','.','linestyle','none','markersize',8)
+ end
+ set(gca,'yscale','log','xminortick','on')
+ xlabel('Frequency (THz)')
+ ylabel('Lifetime (s)')
+
+subplot(1,3,3); hold on; box on;
+
+ x=h5read(fn,'/frequencies');
+ y=h5read(fn,'/mean_free_paths');
+
+ for i=1:size(x,1)
+ plot(x(i,:)*toTHz,y(i,:),'marker','.','linestyle','none','markersize',8)
+ end
+ set(gca,'yscale','log','xminortick','on')
+ xlabel('Frequency (THz)')
+ ylabel('Mean free paths (m)')
+
+```
+
+[^peierls1929]: Peierls, R. E. (1929). Annalen der Physik, 3, 1055
+
+[^peierls1955quantum]: [Peierls, R. E. (1955). Quantum Theory of Solids. Clarendon Press.](https://books.google.com/books?id=WvPcBUsSJBAC)
+
+[^Minnich2012]: [Minnich, A. J. (2012). Determining phonon mean free paths from observations of quasiballistic thermal transport. Physical Review Letters, 109(20), 1–5.](http://doi.org/10.1103/PhysRevLett.109.205901)
+
+[^Minnich2015]: [Minnich, A. J. (2015). Thermal phonon boundary scattering in anisotropic thin films. Applied Physics Letters, 107(18), 8–11.](http://doi.org/10.1063/1.4935160)
+
+[^Tamura1983]: [Tamura, S. (1983). Isotope scattering of dispersive phonons in Ge. Physical Review B, 27(2), 858–866.](http://doi.org/10.1103/PhysRevB.27.858)
+
+[^Omini1996]: [Omini, M., & Sparavigna, A. (1996). Beyond the isotropic-model approximation in the theory of thermal conductivity. Physical Review B, 53(14), 9064–9073.](http://doi.org/10.1103/PhysRevB.53.9064)
+
+[^Omini]: [Omini, M., & Sparavigna, A. (1997). Heat transport in dielectric solids with diamond structure. Nuovo Cimento Della Societa Italiana Di Fisica D, 19D, 1537–63.](http://www.sif.it/riviste/ncd/econtents/1997/019/10/article/5)
+
+[^Broido2007]: [Broido, D. A., Malorny, M., Birner, G., Mingo, N., & Stewart, D. A. (2007). Intrinsic lattice thermal conductivity of semiconductors from first principles. Applied Physics Letters, 91(23), 231922.](http://doi.org/10.1063/1.2822891)
+
+[^Broido2005]: [Broido, D. A., Ward, A., & Mingo, N. (2005). Lattice thermal conductivity of silicon from empirical interatomic potentials. Physical Review B, 72(1), 1–8.](http://doi.org/10.1103/PhysRevB.72.014308)
+
+[^Isaeva2019]: [Isaeva, L & Barbalinardo, G. & Donadio, D. & Baroni, S. (2019). Modeling heat transport in crystals and glasses from a unified lattice-dynamical approach. Nature Communications 10 3853](https://doi.org/10.1038/s41467-019-11572-4)
+
+[^Fiorentino2023]: [Fiorentino, A. & Baroni, S (2023). From Green-Kubo to the full Boltzmann kinetic approach to heat transport in crystals and glasses. Physical Review B, 107, 054311](https://doi.org/10.1103/PhysRevB.107.054311)
+
+[^Simoncelli2019]: [Simoncelli, M. & Marzari, N. & Mauri, F. (2019). Unified theory of thermal transport in crystals and glasses. Nature physics 15 803-819](https://doi.org/10.1038/s41567-019-0520-x)
+
+[^Caldarelli2022]: [Caldarelli, G. & Simoncelli, M. & Marzari, N. & Mauri, F. & Benfatto, L. (2022). Many-body Green's function approach to lattice thermal transport. Physical Review B 106 024312](https://doi.org/10.1103/PhysRevB.106.024312)
+
+[^Dangic2021]: [Dangić, Đ. & Hellman, O. & Fahy, S. and Savić, I. (2021) The origin of the lattice thermal conductivity enhancement at the ferroelectric phase transition in GeTe. Nature Computational Materials 7, 57](https://doi.org/10.1038/s41524-021-00523-7)
diff --git a/mkdocs.yml b/mkdocs.yml
index bd0efba6..c477875c 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -33,6 +33,7 @@ nav:
- Canonical configuration: program/canonical_configuration.md
- Extract forceconstants: program/extract_forceconstants.md
- Phonon dispersion relations: program/phonon_dispersion_relations.md
+ - Thermal conductivity 2023: program/thermal_conductivity_2023.md
- Thermal conductivity: program/thermal_conductivity.md
- Lineshape: program/lineshape.md
- Anharmonic free energy: program/anharmonic_free_energy.md
diff --git a/src/libolle/Makefile b/src/libolle/Makefile
index bc62be77..f6329d1e 100644
--- a/src/libolle/Makefile
+++ b/src/libolle/Makefile
@@ -145,7 +145,8 @@ $(OBJECT_PATH)type_forcemap_coefficient_quartet_12.o \
$(OBJECT_PATH)type_forcemap_coefficient_diel.o \
$(OBJECT_PATH)type_forcemap_returntensors.o \
$(OBJECT_PATH)type_forcemap_io.o \
-$(OBJECT_PATH)type_lassosolvers.o
+$(OBJECT_PATH)type_lassosolvers.o\
+$(OBJECT_PATH)lo_fftgrid_helper.o
# maybe CGAL, maybe not.
ifeq ($(USECGAL),yes)
OBJScg = \
@@ -913,6 +914,10 @@ $(OBJECT_PATH)ifc_solvers_fastugly.o:\
$(OBJECT_PATH)ifc_solvers.o
$(FC) $(FFLAGS) -c ifc_solvers_fastugly.f90 -o $@
+$(OBJECT_PATH)lo_fftgrid_helper.o:\
+ $(OBJECT_PATH)konstanter.o\
+ $(OBJECT_PATH)lo_randomnumbers.o
+ $(FC) $(FFLAGS) -c lo_fftgrid_helper.f90 -o $@
clean:
rm -rf $(OBJECT_PATH)*.o $(MODULE_PATH)*mod
diff --git a/src/libolle/lo_fftgrid_helper.f90 b/src/libolle/lo_fftgrid_helper.f90
new file mode 100644
index 00000000..83212d8b
--- /dev/null
+++ b/src/libolle/lo_fftgrid_helper.f90
@@ -0,0 +1,197 @@
+module lo_fftgrid_helper
+use konstanter, only: r8
+use lo_randomnumbers, only: lo_mersennetwister
+
+implicit none
+private
+public :: lo_montecarlo_grid
+public :: singlet_to_triplet
+public :: triplet_to_singlet
+public :: fft_third_grid_index
+public :: fft_fourth_grid_index
+
+type lo_montecarlo_grid
+ !> The size of the grid
+ integer :: npoints
+ !> The weight of each point on the Monte-Carlo grid
+ real(r8) :: weight
+ !> The dimensions of the Monte-Carlo grid
+ integer, dimension(3) :: mc_dims
+ !> The dimensions of the full grid
+ integer, dimension(3) :: full_dims
+ !> The ratio between the full and mc grids
+ real(r8), dimension(3) :: ratio
+
+contains
+ !> Initialize the grid
+ procedure :: initialize => initialize_montecarlo_grid
+ !> Generate a point from the Monte-Carlo to the full grid
+ procedure :: mc_point_to_full
+ !> Generate an array with the grid
+ procedure :: generate_grid
+end type
+
+contains
+
+subroutine initialize_montecarlo_grid(mcg, full_dims, mc_dims)
+ !> The Monte-Carlo grid
+ class(lo_montecarlo_grid), intent(out) :: mcg
+ !> The dimensions of the full grid
+ integer, dimension(3), intent(in) :: full_dims
+ !> The dimensions of the Monte-Carlo grid
+ integer, dimension(3), intent(in) :: mc_dims
+
+ !> Some integer for the do loop
+ integer :: i
+
+ mcg%full_dims = full_dims
+ do i=1, 3
+ mcg%mc_dims(i) = min(mc_dims(i), mcg%full_dims(i))
+ mcg%ratio(i) = real(mcg%full_dims(i), r8) / real(mcg%mc_dims(i), r8)
+ end do
+ mcg%npoints = mcg%mc_dims(1) * mcg%mc_dims(2) * mcg%mc_dims(3)
+ mcg%weight = 1.0_r8 / real(mcg%npoints, r8)
+end subroutine
+
+function mc_point_to_full(mcg, imc, rng) result(ifull)
+ !> The Monte-Carlo grid
+ class(lo_montecarlo_grid), intent(in) :: mcg
+ !> The index of the point on the Monte-Carlo grid
+ integer, intent(in) :: imc
+ !> The random number generator
+ type(lo_mersennetwister), intent(inout) :: rng
+ !> The index of the point on the full grid
+ integer :: ifull
+
+ !> The triplets of point on the Monte-Carlo and full grids
+ integer, dimension(3) :: gi_mc, gi_full
+
+ gi_mc = singlet_to_triplet(imc, mcg%mc_dims(2), mcg%mc_dims(3))
+ ! This way of generating the number makes it work even if the ratio is not an integer
+ gi_full(1) = ceiling((real(gi_mc(1), r8) - 1.0_r8) * mcg%ratio(1) + rng%rnd_real() * mcg%ratio(1))
+ gi_full(2) = ceiling((real(gi_mc(2), r8) - 1.0_r8) * mcg%ratio(2) + rng%rnd_real() * mcg%ratio(2))
+ gi_full(3) = ceiling((real(gi_mc(3), r8) - 1.0_r8) * mcg%ratio(3) + rng%rnd_real() * mcg%ratio(3))
+ ifull = triplet_to_singlet(gi_full, mcg%full_dims(2), mcg%full_dims(3))
+end function
+
+subroutine generate_grid(mcg, qgrid, rng)
+ !> The Monte-Carlo grid
+ class(lo_montecarlo_grid), intent(in) :: mcg
+ !> The random number generator
+ type(lo_mersennetwister), intent(inout) :: rng
+ !> The grid to be generated
+ integer, dimension(:), intent(out) :: qgrid
+
+ !> Some integers for the do loop
+ integer :: qi, qprev, qtest
+
+ ! To improve convergence, we avoid repeating points in the integration grid
+ qgrid(1) = mcg%mc_point_to_full(1, rng)
+ qprev = qgrid(1)
+ qtest = qgrid(1)
+ do qi=2, mcg%npoints
+ do while(qtest .eq. qprev)
+ qtest = mcg%mc_point_to_full(qi, rng)
+ end do
+ qgrid(qi) = qtest
+ qprev = qtest
+ end do
+end subroutine
+
+!> convert a linear index to a triplet
+pure function singlet_to_triplet(l, ny, nz) result(gi)
+ !> linear index
+ integer, intent(in) :: l
+ !> second dimension
+ integer, intent(in) :: ny
+ !> third dimension
+ integer, intent(in) :: nz
+ !> grid-index
+ integer, dimension(3) :: gi
+
+ integer :: i, j, k
+
+ k = mod(l, nz)
+ if (k .eq. 0) k = nz
+ j = mod((l - k)/nz, ny) + 1
+ i = (l - k - (j - 1)*nz)/(nz*ny) + 1
+ gi = [i, j, k]
+end function
+
+!> convert a triplet index to a singlet
+pure function triplet_to_singlet(gi, ny, nz) result(l)
+ !> grid-index
+ integer, dimension(3), intent(in) :: gi
+ !> second dimension
+ integer, intent(in) :: ny
+ !> third dimension
+ integer, intent(in) :: nz
+ !> linear index
+ integer :: l
+
+ l = (gi(1) - 1)*ny*nz + (gi(2) - 1)*nz + gi(3)
+end function
+
+!> returns the index on the grid that gives q3=-q1-q2
+pure function fft_third_grid_index(i1, i2, dims) result(i3)
+ !> index to q1
+ integer, intent(in) :: i1
+ !> index to q2
+ integer, intent(in) :: i2
+ !> dimensions of the grid
+ integer, dimension(3), intent(in) :: dims
+ !> index to q3
+ integer :: i3
+
+ integer, dimension(3) :: gi1, gi2, gi3
+ integer :: l, k
+
+ ! Convert triplet to singlet
+ gi1 = singlet_to_triplet(i1, dims(2), dims(3))
+ gi2 = singlet_to_triplet(i2, dims(2), dims(3))
+ do l = 1, 3
+ gi3(l) = 3 - gi1(l) - gi2(l)
+ end do
+ do k = 1, 3
+ do l = 1, 3
+ if (gi3(l) .lt. 1) gi3(l) = gi3(l) + dims(l)
+ if (gi3(l) .gt. dims(l)) gi3(l) = gi3(l) - dims(l)
+ end do
+ end do
+ ! convert it back to a singlet
+ i3 = triplet_to_singlet(gi3, dims(2), dims(3))
+end function
+
+!> returns the index on the grid that gives q4=-q3-q2-q1
+pure function fft_fourth_grid_index(i1, i2, i3, dims) result(i4)
+ !> index to q1
+ integer, intent(in) :: i1
+ !> index to q2
+ integer, intent(in) :: i2
+ !> index to q3
+ integer, intent(in) :: i3
+ !> dimensions of the grid
+ integer, dimension(3), intent(in) :: dims
+ !> index to q4
+ integer :: i4
+
+ integer, dimension(3) :: gi1, gi2, gi3, gi4
+ integer :: l, k
+ ! Convert triplet to singlet
+ gi1 = singlet_to_triplet(i1, dims(2), dims(3))
+ gi2 = singlet_to_triplet(i2, dims(2), dims(3))
+ gi3 = singlet_to_triplet(i3, dims(2), dims(3))
+ do l = 1, 3
+ gi4(l) = 4 - gi1(l) - gi2(l) - gi3(l)
+ end do
+ do k = 1, 3
+ do l = 1, 3
+ if (gi4(l) .lt. 1) gi4(l) = gi4(l) + dims(l)
+ if (gi4(l) .gt. dims(l)) gi4(l) = gi4(l) - dims(l)
+ end do
+ end do
+
+ ! convert it back to a singlet
+ i4 = triplet_to_singlet(gi4, dims(2), dims(3))
+end function
+end module
diff --git a/src/libolle/lo_symmetry_of_interactions_nullspace.f90 b/src/libolle/lo_symmetry_of_interactions_nullspace.f90
index fc5829e3..4755af7b 100644
--- a/src/libolle/lo_symmetry_of_interactions_nullspace.f90
+++ b/src/libolle/lo_symmetry_of_interactions_nullspace.f90
@@ -473,7 +473,7 @@ module subroutine nullspace_fc_quartet(sl,sh,ss,mw,mem)
m0=0.0_r8
do iop=1,size(op,3)
call lo_gemm(op(:,:,iop),sl%fc_quartet_shell(shell)%coeff,m0)
- if ( norm2(m0-sl%fc_quartet_shell(shell)%coeff) .gt. lo_sqtol*size(m0) ) then
+ if ( norm2(m0-sl%fc_quartet_shell(shell)%coeff) .gt. lo_tol*size(m0) ) then
call lo_stop_gracefully(['Not as invariant as it should be.'],lo_exitcode_symmetry,__FILE__,__LINE__,mw%comm)
endif
enddo
diff --git a/src/libolle/type_phonon_dispersions.f90 b/src/libolle/type_phonon_dispersions.f90
index 7f9a73cf..e9877bc0 100644
--- a/src/libolle/type_phonon_dispersions.f90
+++ b/src/libolle/type_phonon_dispersions.f90
@@ -535,6 +535,10 @@ subroutine write_to_hdf5(dr, qp, uc, filename, mem, temperature)
end do
end do
call h5%store_data(dd, h5%file_id, trim(dname), enhet='Hz', dimensions='q-vector,mode')
+ call mem%deallocate(dd, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ end if
+
+ if (allocated(dr%iq(1)%p_minus) .and. present(temperature)) then
dname = 'scattering_rates_minus'
do i = 1, qp%n_full_point
k = qp%ap(i)%irreducible_index
@@ -562,52 +566,55 @@ subroutine write_to_hdf5(dr, qp, uc, filename, mem, temperature)
end do
call h5%store_data(dd, h5%file_id, trim(dname), enhet='Hz', dimensions='q-vector,mode')
call mem%deallocate(dd, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ end if
+ if (allocated(dr%iq(1)%Fn) .and. present(temperature)) then
call mem%allocate(dddd, [3, 3, dr%n_mode, qp%n_full_point], &
persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
dname = 'thermal_conductivity'
dddd = 0.0_r8
- if (allocated(dr%iq(1)%Fn)) then
- do i = 1, qp%n_full_point
- l = qp%ap(i)%irreducible_index
- k = qp%ap(i)%operation_from_irreducible
- do j = 1, dr%n_mode
- if (dr%aq(i)%omega(j) .lt. dr%omega_min*0.5_r8) cycle
- if (k .gt. 0) then
- v0 = lo_operate_on_vector(uc%sym%op(k), dr%iq(l)%Fn(:, j), reciprocal=.true.)
- v1 = lo_operate_on_vector(uc%sym%op(k), dr%iq(l)%vel(:, j), reciprocal=.true.)
- else
- v0 = -lo_operate_on_vector(uc%sym%op(abs(k)), dr%iq(l)%Fn(:, j), reciprocal=.true.)
- v1 = -lo_operate_on_vector(uc%sym%op(abs(k)), dr%iq(l)%vel(:, j), reciprocal=.true.)
- end if
- ! Get kappa for this q-point
- omega = dr%iq(l)%omega(j)
- n = lo_planck(temperature, omega)
- f0 = omega*(n + 1)*n
- dddd(:, :, j, i) = f0*lo_outerproduct(v0, v1)/(uc%volume*lo_kb_hartree*temperature)
- end do
+ do i = 1, qp%n_full_point
+ l = qp%ap(i)%irreducible_index
+ k = qp%ap(i)%operation_from_irreducible
+ do j = 1, dr%n_mode
+ if (dr%aq(i)%omega(j) .lt. dr%omega_min*0.5_r8) cycle
+ if (k .gt. 0) then
+ v0 = lo_operate_on_vector(uc%sym%op(k), dr%iq(l)%Fn(:, j), reciprocal=.true.)
+ v1 = lo_operate_on_vector(uc%sym%op(k), dr%iq(l)%vel(:, j), reciprocal=.true.)
+ else
+ v0 = -lo_operate_on_vector(uc%sym%op(abs(k)), dr%iq(l)%Fn(:, j), reciprocal=.true.)
+ v1 = -lo_operate_on_vector(uc%sym%op(abs(k)), dr%iq(l)%vel(:, j), reciprocal=.true.)
+ end if
+ ! Get kappa for this q-point
+ omega = dr%iq(l)%omega(j)
+ n = lo_planck(temperature, omega)
+ f0 = omega*(n + 1)*n
+ dddd(:, :, j, i) = f0*lo_outerproduct(v0, v1)/(uc%volume*lo_kb_hartree*temperature)
end do
- else
- do i = 1, qp%n_full_point
- l = qp%ap(i)%irreducible_index
- k = qp%ap(i)%operation_from_irreducible
- do j = 1, dr%n_mode
- if (dr%aq(i)%omega(j) .lt. dr%omega_min*0.5_r8) cycle
- if (k .gt. 0) then
- v0 = lo_operate_on_vector(uc%sym%op(k), dr%iq(l)%vel(:, j), reciprocal=.true.)
- else
- v0 = -lo_operate_on_vector(uc%sym%op(abs(k)), dr%iq(l)%vel(:, j), reciprocal=.true.)
- end if
- ! Get kappa for this q-point
- omega = dr%iq(l)%omega(j)
- cv = lo_harmonic_oscillator_cv(temperature, omega)
- if (dr%iq(l)%linewidth(j) .gt. lo_freqtol) then
- tau = 1.0_r8/(2.0_r8*dr%iq(l)%linewidth(j))
- dddd(:, :, j, i) = lo_outerproduct(v0, v0)*cv*tau/uc%volume
- end if
- end do
+ end do
+ dddd = dddd*lo_kappa_au_to_SI
+ call h5%store_data(dddd, h5%file_id, trim(dname), enhet='W/mK', dimensions='q-vector,mode,xyz,xyz')
+ call mem%deallocate(dddd, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ else if (allocated(dr%iq(1)%linewidth) .and. present(temperature)) then
+ do i = 1, qp%n_full_point
+ l = qp%ap(i)%irreducible_index
+ k = qp%ap(i)%operation_from_irreducible
+ do j = 1, dr%n_mode
+ if (dr%aq(i)%omega(j) .lt. dr%omega_min*0.5_r8) cycle
+ if (k .gt. 0) then
+ v0 = lo_operate_on_vector(uc%sym%op(k), dr%iq(l)%vel(:, j), reciprocal=.true.)
+ else
+ v0 = -lo_operate_on_vector(uc%sym%op(abs(k)), dr%iq(l)%vel(:, j), reciprocal=.true.)
+ end if
+ ! Get kappa for this q-point
+ omega = dr%iq(l)%omega(j)
+ cv = lo_harmonic_oscillator_cv(temperature, omega)
+ if (dr%iq(l)%linewidth(j) .gt. lo_freqtol) then
+ tau = 1.0_r8/(2.0_r8*dr%iq(l)%linewidth(j))
+ dddd(:, :, j, i) = lo_outerproduct(v0, v0)*cv*tau/uc%volume
+ end if
end do
- end if
+ end do
dddd = dddd*lo_kappa_au_to_SI
call h5%store_data(dddd, h5%file_id, trim(dname), enhet='W/mK', dimensions='q-vector,mode,xyz,xyz')
call mem%deallocate(dddd, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
diff --git a/src/thermal_conductivity/Makefile b/src/thermal_conductivity/Makefile
index fd0192a2..627dfa38 100644
--- a/src/thermal_conductivity/Makefile
+++ b/src/thermal_conductivity/Makefile
@@ -6,10 +6,8 @@ OBJECT_PATH=../../build/$(CODE)/
OBJS = \
$(OBJECT_PATH)main.o\
$(OBJECT_PATH)options.o\
-$(OBJECT_PATH)scatteringstrengths.o\
-$(OBJECT_PATH)pbe.o\
-$(OBJECT_PATH)phononevents.o\
-$(OBJECT_PATH)mfp.o
+$(OBJECT_PATH)scattering.o\
+$(OBJECT_PATH)kappa.o\
LPATH = -L../../lib $(blaslapackLPATH) $(incLPATHmpi) $(incLPATHhdf)
IPATH = -I../../inc/libolle -I../../inc/libflap $(blaslapackIPATH) $(incIPATHmpi) $(incIPATHhdf)
@@ -17,7 +15,7 @@ LIBS = -lolle -lflap $(blaslapackLIBS) $(incLIBSmpi) $(incLIBShdf)
#OPT = -O0 -fbacktrace -fcheck=all -finit-real=nan -finit-derived
F90 = $(FC) $(LPATH) $(IPATH) $(MODULE_FLAG) $(OBJECT_PATH) #$(warnings_gcc)
-F90FLAGS = $(OPT) $(MODS) $(LIBS)
+F90FLAGS = $(OPT) $(MODS) $(LIBS)
all: $(PROG)
@@ -29,19 +27,14 @@ clean:
$(OBJECT_PATH)main.o: \
$(OBJECT_PATH)options.o\
-$(OBJECT_PATH)scatteringstrengths.o\
-$(OBJECT_PATH)pbe.o\
-$(OBJECT_PATH)phononevents.o\
-$(OBJECT_PATH)mfp.o
+$(OBJECT_PATH)scattering.o\
+$(OBJECT_PATH)kappa.o
$(F90) $(OPT) $(F90FLAGS) -c main.f90 $(LIBS) -o $@
-$(OBJECT_PATH)scatteringstrengths.o: $(OBJECT_PATH)phononevents.o
- $(F90) $(OPT) $(F90FLAGS) -c scatteringstrengths.f90 $(LIBS) -o $@
-$(OBJECT_PATH)pbe.o: $(OBJECT_PATH)phononevents.o
- $(F90) $(OPT) $(F90FLAGS) -c pbe.f90 $(LIBS) -o $@
-$(OBJECT_PATH)phononevents.o:
- $(F90) $(OPT) $(F90FLAGS) -c phononevents.f90 $(LIBS) -o $@
-$(OBJECT_PATH)mfp.o:
- $(F90) $(OPT) $(F90FLAGS) -c mfp.f90 $(LIBS) -o $@
+$(OBJECT_PATH)scattering.o:
+ $(F90) $(OPT) $(F90FLAGS) -c scattering.f90 $(LIBS) -o $@
+$(OBJECT_PATH)kappa.o:\
+ kappa.f90\
+ $(OBJECT_PATH)scattering.o
+ $(F90) $(OPT) $(F90FLAGS) -c kappa.f90 $(LIBS) -o $@
$(OBJECT_PATH)options.o:
$(F90) $(OPT) $(F90FLAGS) -c options.f90 $(LIBS) -o $@
-
diff --git a/src/thermal_conductivity/kappa.f90 b/src/thermal_conductivity/kappa.f90
new file mode 100644
index 00000000..bc324c4a
--- /dev/null
+++ b/src/thermal_conductivity/kappa.f90
@@ -0,0 +1,487 @@
+#include "precompilerdefinitions"
+module kappa
+use konstanter, only: r8, lo_sqtol, lo_kb_hartree, lo_freqtol, lo_kappa_au_to_SI, &
+ lo_groupvel_Hartreebohr_to_ms
+use gottochblandat, only: lo_sqnorm, lo_planck, lo_outerproduct, lo_chop, lo_harmonic_oscillator_cv
+use mpi_wrappers, only: lo_mpi_helper
+use lo_memtracker, only: lo_mem_helper
+use type_crystalstructure, only: lo_crystalstructure
+use type_qpointmesh, only: lo_qpoint_mesh
+use type_phonon_dispersions, only: lo_phonon_dispersions
+use type_symmetryoperation, only: lo_operate_on_vector, lo_eigenvector_transformation_matrix, &
+ lo_operate_on_secondorder_tensor
+use type_blas_lapack_wrappers, only: lo_gemm, lo_gemv
+use type_forceconstant_secondorder, only: lo_forceconstant_secondorder
+
+use scattering, only: lo_scattering_rates
+
+implicit none
+
+private
+public :: get_kappa
+public :: get_kappa_offdiag
+public :: iterative_solution
+public :: symmetrize_kappa
+contains
+
+!> Calculate the thermal conductivity
+subroutine get_kappa(dr, qp, uc, temperature, classical, kappa)
+ !> dispersions
+ type(lo_phonon_dispersions), intent(inout) :: dr
+ !> q-mesh
+ class(lo_qpoint_mesh), intent(in) :: qp
+ !> structure
+ type(lo_crystalstructure), intent(in) :: uc
+ !> temperature
+ real(r8), intent(in) :: temperature
+ !> Are we in the classical limit ?
+ logical, intent(in) :: classical
+ !> thermal conductivity tensor
+ real(r8), dimension(3, 3), intent(out) :: kappa
+
+ real(r8), dimension(3) :: v0, v1
+ real(r8) :: om1, cv
+ integer :: j
+
+ integer :: q1, b1
+ real(r8), dimension(3, 3) :: v2, buf
+
+ kappa = 0.0_r8
+ do q1 = 1, qp%n_irr_point
+ dr%iq(q1)%kappa = 0.0_r8
+ do b1 = 1, dr%n_mode
+ om1 = dr%iq(q1)%omega(b1)
+ if (om1 .lt. lo_freqtol) cycle
+
+ if (classical) then
+ cv = lo_kb_hartree
+ else
+ cv = lo_harmonic_oscillator_cv(temperature, om1)
+ end if
+
+ ! To ensure the symmetry, we average over the symmetry operation of the crystal
+ v2 = 0.0_r8
+ do j = 1, uc%sym%n
+ v0 = lo_operate_on_vector(uc%sym%op(j), dr%iq(q1)%Fn(:, b1), reciprocal=.true.)
+ v1 = lo_operate_on_vector(uc%sym%op(j), dr%iq(q1)%vel(:, b1), reciprocal=.true.)
+ v2 = v2 + lo_outerproduct(v0, v1)/uc%sym%n
+ end do
+ buf = cv*v2/uc%volume
+ dr%iq(q1)%kappa(:, :, b1) = buf
+ kappa = kappa + buf*qp%ip(q1)%integration_weight
+ end do
+ end do
+ kappa = lo_chop(kappa, sum(abs(kappa))*1e-6_r8)
+end subroutine
+
+subroutine get_kappa_offdiag(dr, qp, uc, fc, temperature, classical, mem, mw, kappa_offdiag)
+ !> dispersions
+ type(lo_phonon_dispersions), intent(in) :: dr
+ !> q-mesh
+ class(lo_qpoint_mesh), intent(in) :: qp
+ !> structure
+ type(lo_crystalstructure), intent(in) :: uc
+ !> second order force constant
+ type(lo_forceconstant_secondorder), intent(inout) :: fc
+ !> temperature
+ real(r8), intent(in) :: temperature
+ !> Are we in the classical limit ?
+ logical, intent(in) :: classical
+ !> memory tracker
+ type(lo_mem_helper), intent(inout) :: mem
+ !> mpi helper
+ type(lo_mpi_helper), intent(inout) :: mw
+ !> thermal conductivity tensor
+ real(r8), dimension(3, 3), intent(out) :: kappa_offdiag
+
+ !> The off diagonal group velocity
+ real(r8), dimension(:, :, :), allocatable :: buf_vel
+ !> The off diagonal group velocity, squared
+ real(r8), dimension(:, :, :, :), allocatable :: buf_velsq
+ !> The qpoint
+ integer :: iq
+
+ call mem%allocate(buf_vel, [3, dr%n_mode, dr%n_mode], persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(buf_velsq, [3, 3, dr%n_mode, dr%n_mode], persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+
+ kappa_offdiag = 0.0_r8
+
+ do iq = 1, qp%n_irr_point
+ if (mod(iq, mw%n) .ne. mw%r) cycle
+ buf_vel = 0.0_r8
+ buf_velsq = 0.0_r8
+
+ ! Calculate the off-diagonal group velocity.
+ groupvel: block
+ complex(r8), dimension(:, :, :), allocatable :: buf_grad_dynmat
+ complex(r8), dimension(:, :), allocatable :: kronegv, buf_egv, buf_egw, buf_cm0, buf_cm1, buf_cm2
+ complex(r8), dimension(3) :: cv0
+ real(r8), dimension(3) :: v0, v1
+ integer :: a1, a2, ia, ib, ic, ix, iy, iz, k, iop, i, ii, j, jj
+
+ ! Some buffers
+ call mem%allocate(buf_grad_dynmat, [dr%n_mode, dr%n_mode, 3], persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(buf_cm0, [dr%n_mode, dr%n_mode], persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(buf_cm1, [dr%n_mode**2, 3], persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(buf_cm2, [dr%n_mode**2, 3], persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(buf_egv, [dr%n_mode, dr%n_mode], persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(buf_egw, [dr%n_mode, dr%n_mode], persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(kronegv, [dr%n_mode**2, dr%n_mode**2], persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+
+ buf_grad_dynmat = 0.0_r8
+ buf_cm0 = 0.0_r8
+ buf_cm1 = 0.0_r8
+ buf_cm2 = 0.0_r8
+ buf_egv = 0.0_r8
+ buf_egw = 0.0_r8
+ kronegv = 0.0_r8
+
+ ! Dynamical matrix and derivatives
+ call fc%dynamicalmatrix(uc, qp%ip(iq), buf_cm0, mem, buf_grad_dynmat, qdirection=[1.0_r8, 0.0_r8, 0.0_r8])
+
+ ! Flatten gradient of dynamical matrix
+ do iz = 1, 3
+ do a1 = 1, uc%na !ise%n_atom
+ do a2 = 1, uc%na !ise%n_atom
+ do ix = 1, 3
+ do iy = 1, 3
+ ib = (a1 - 1)*3 + ix
+ ia = (a2 - 1)*3 + iy
+ ic = flattenind(a1, a2, ix, iy, dr%n_mode)
+ buf_cm1(ic, iz) = buf_grad_dynmat(ia, ib, iz)/(uc%invsqrtmass(a1)*uc%invsqrtmass(a2))
+ end do
+ end do
+ end do
+ end do
+ end do
+
+ ! Average over all operations
+ kronegv = 0.0_r8
+ do k = 1, qp%ip(iq)%n_invariant_operation
+ iop = qp%ip(iq)%invariant_operation(k)
+ ! Rotate eigenvectors
+ call lo_eigenvector_transformation_matrix(buf_cm0, uc%rcart, qp%ip(iq)%r, uc%sym%op(abs(iop)))
+ if (iop .lt. 0) then
+ call lo_gemm(buf_cm0, dr%iq(iq)%egv, buf_egw)
+ buf_egw = conjg(buf_egw)
+ else
+ call lo_gemm(buf_cm0, dr%iq(iq)%egv, buf_egw)
+ end if
+
+ do i = 1, dr%n_mode
+ if (dr%iq(iq)%omega(i) .gt. lo_freqtol) then
+ do a1 = 1, uc%na
+ do ix = 1, 3
+ ib = (a1 - 1)*3 + ix
+ buf_egw(ib, i) = buf_egw(ib, i)*uc%invsqrtmass(a1)/sqrt(dr%iq(iq)%omega(i)*2.0_r8)
+ end do
+ end do
+ else
+ buf_egw(:, i) = 0.0_r8
+ end if
+ end do
+
+ do i = 1, dr%n_mode
+ do j = 1, dr%n_mode
+ buf_egv = buf_egw*conjg(buf_egw(j, i))
+ do ii = 1, dr%n_mode
+ do jj = 1, dr%n_mode
+ ia = (i - 1)*dr%n_mode + ii
+ ib = (j - 1)*dr%n_mode + jj
+ kronegv(ia, ib) = kronegv(ia, ib) + buf_egv(jj, ii)
+ end do
+ end do
+ end do
+ end do
+ end do
+ kronegv = kronegv/real(qp%ip(iq)%n_invariant_operation, r8)
+ ! this means sandwich with eigenvectors, frequencies,
+ ! prefactors and masses are already in there.
+ call lo_gemm(kronegv, buf_cm1, buf_cm2)
+
+ ! Keep the group velocities?
+ do i = 1, dr%n_mode
+ do j = 1, dr%n_mode
+ ii = (i - 1)*dr%n_mode + j
+ jj = (j - 1)*dr%n_mode + i
+ cv0 = buf_cm2(ii, :)
+ ! remove tiny numbers.
+ cv0 = lo_chop(cv0, 1E-10/(lo_groupvel_Hartreebohr_to_ms/1000))
+ ! I can take the real part since at the end we sum over
+ ! both modes and the imaginary components disappear.
+ buf_vel(:, i, j) = real(cv0, r8)
+ end do
+ end do
+
+ ! Be very careful with degeneracies. Will give very wrong contribution
+ ! to thermal transport if not taken care of properly, I'm pretty sure.
+ ! Feels like there could be significant double-counting otherwise.
+ ! This seems ok for now, but have to keep an eye out.
+
+ ! Rotate out the group velocities to get the symmetry averaged ones
+ buf_velsq = 0.0_r8
+ do i = 1, dr%n_mode
+ do j = 1, dr%n_mode
+ v0 = buf_vel(:, i, j)
+ do k = 1, qp%ip(iq)%n_full_point
+ iop = qp%ip(iq)%operation_full_point(k)
+ v1 = matmul(uc%sym%op(abs(iop))%m, v0)
+ buf_velsq(:, :, i, j) = buf_velsq(:, :, i, j) + lo_outerproduct(v1, v1)
+ end do
+ end do
+ end do
+ buf_velsq = buf_velsq/real(qp%ip(iq)%n_full_point, r8)
+
+ ! cleanup
+ call mem%deallocate(buf_grad_dynmat, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(buf_cm0, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(buf_cm1, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(buf_cm2, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(buf_egv, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(buf_egw, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(kronegv, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ end block groupvel
+
+ ! Now we can compute the off diagonal contribution
+ offdiag: block
+ !> prefactor and buffers
+ real(r8) :: pref, f0, tau, om1, om2, tau1, tau2
+ !> Some integers for do loop on so on
+ integer :: jmode, kmode
+
+ pref = qp%ip(iq)%integration_weight/uc%volume
+
+ do jmode = 1, dr%n_mode
+ ! Skip gamma for acoustic branches
+ if (dr%iq(iq)%omega(jmode) .lt. lo_freqtol) cycle
+ om1 = dr%iq(iq)%omega(jmode)
+ tau1 = dr%iq(iq)%linewidth(jmode)
+ do kmode = 1, dr%n_mode
+ if (jmode .eq. kmode) cycle ! We only want the off diagonal contribution
+ ! Skip gamma for acoustic branches
+ om2 = dr%iq(iq)%omega(kmode)
+ if (om2 .lt. lo_freqtol) cycle
+
+ tau2 = dr%iq(iq)%linewidth(kmode)
+
+ ! This is consistent with the paper, but a bit different from QHGK
+ ! This comes from the fact that we don't work with creation/annihilation but
+ ! directly with displacement/momentup operator
+ if (classical) then
+ f0 = lo_kb_hartree
+ else
+ f0 = 0.5_r8*(lo_harmonic_oscillator_cv(temperature, om1) + &
+ lo_harmonic_oscillator_cv(temperature, om2))
+ end if
+
+ tau = (tau1 + tau2)/((tau1 + tau2)**2 + (om1 - om2)**2)
+ kappa_offdiag(:, :) = kappa_offdiag(:, :) + buf_velsq(:, :, jmode, kmode)*tau*f0*pref
+ end do ! k mode
+ end do ! j mode
+ end block offdiag
+ end do ! i qpt
+ call mem%deallocate(buf_vel, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(buf_velsq, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+
+ call mw%allreduce('sum', kappa_offdiag)
+
+ kappa_offdiag = lo_chop(kappa_offdiag, sum(abs(kappa_offdiag))*1E-6_r8)
+
+contains
+ ! Consistent index flattening? Impossibru to get consistent.
+ function flattenind(a1, a2, ix, iy, nb) result(i)
+ integer, intent(in) :: a1, a2, ix, iy, nb
+ integer :: i
+
+ integer :: ia, ib
+
+ ia = (a1 - 1)*3 + ix
+ ib = (a2 - 1)*3 + iy
+ i = (ib - 1)*nb + ia
+ end function
+end subroutine
+
+subroutine symmetrize_kappa(kappa, uc)
+ !> The kappa to symmetrize
+ real(r8), dimension(3, 3), intent(inout) :: kappa
+ !> The unit cell
+ type(lo_crystalstructure), intent(in) :: uc
+
+ real(r8), dimension(3, 3) :: tmp
+ integer :: iop
+
+ tmp = 0.0_r8
+ do iop = 1, uc%sym%n
+ tmp = tmp + lo_operate_on_secondorder_tensor(uc%sym%op(iop), kappa)
+ end do
+
+ kappa = tmp/uc%sym%n
+ kappa = lo_chop(kappa, sum(abs(kappa))*1e-6_r8)
+end subroutine
+
+subroutine iterative_solution(sr, dr, qp, uc, temperature, niter, tol, classical, mw, mem)
+ !> integration weights
+ type(lo_scattering_rates), intent(inout) :: sr
+ !> dispersions
+ type(lo_phonon_dispersions), intent(inout) :: dr
+ !> q-mesh
+ class(lo_qpoint_mesh), intent(in) :: qp
+ !> structure
+ type(lo_crystalstructure), intent(in) :: uc
+ !> Temperature
+ real(r8), intent(in) :: temperature
+ !> Max number of iterations
+ integer, intent(in) :: niter
+ !> Tolerance
+ real(r8), intent(in) :: tol
+ !> Are we in the classical limit
+ logical, intent(in) :: classical
+ !> MPI helper
+ type(lo_mpi_helper), intent(inout) :: mw
+ !> memory tracker
+ type(lo_mem_helper), intent(inout) :: mem
+
+ !> The F vector on cart, mode, irr-qpoint
+ real(r8), dimension(:, :, :), allocatable :: Fnb
+ !> The F vector on full q-point, flattened along mode/qpoint for BLAS vector-matrix multiplication
+ real(r8), dimension(:, :), allocatable :: Fbb, buf
+ !> The thermal conductivity
+ real(r8), dimension(3, 3) :: kappa
+ !> Buffer to check convergence
+ real(r8), dimension(niter) :: scfcheck
+ !> The mixing parameter between iterations
+ real(r8) :: mixingparameter
+ !> Integer for the do loop
+ integer :: iter
+
+ ! set some things and make space
+ init: block
+ real(r8), dimension(3, 3) :: m0
+ mixingparameter = 0.95_r8
+ call mem%allocate(Fnb, [3, dr%n_mode, qp%n_irr_point], persistent=.false., &
+ scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(Fbb, [3, dr%n_mode*qp%n_full_point], persistent=.false., &
+ scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(buf, [3, sr%my_nqpoints], persistent=.false., &
+ scalable=.false., file=__FILE__, line=__LINE__)
+ Fnb = 0.0_r8
+ Fbb = 0.0_r8
+ scfcheck = 0.0_r8
+ ! Get the first kappa-value
+ call get_kappa(dr, qp, uc, temperature, classical, kappa)
+ m0 = kappa*lo_kappa_au_to_SI
+ if (mw%talk) write (*, "(1X,I4,6(1X,F14.4))") 0, m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
+ end block init
+
+ scfloop: do iter = 1, niter
+ ! get the Fn values all across the BZ
+ foldout: block
+ real(r8), dimension(3) :: v
+ integer :: iq, jq, iop, b1, i2
+
+ Fnb = 0.0_r8
+ Fbb = 0.0_r8
+ do iq = 1, qp%n_full_point
+ if (mod(iq, mw%n) .ne. mw%r) cycle
+ iop = qp%ap(iq)%operation_from_irreducible
+ jq = qp%ap(iq)%irreducible_index
+ do b1 = 1, dr%n_mode
+ i2 = (iq - 1)*dr%n_mode + b1
+ if (iop .gt. 0) then
+ v = lo_operate_on_vector(uc%sym%op(iop), dr%iq(jq)%Fn(:, b1), reciprocal=.true.)
+ Fbb(:, i2) = v
+ else
+ v = -lo_operate_on_vector(uc%sym%op(abs(iop)), dr%iq(jq)%Fn(:, b1), reciprocal=.true.)
+ Fbb(:, i2) = v
+ end if
+ end do
+ end do
+ call mw%allreduce('sum', Fbb)
+ end block foldout
+
+ ! Multiply the off-diagonal part of Xi with F
+ applyXi: block
+ integer :: il, b1, q1, a
+
+ ! We use BLAS for this, and we have to do it for each cartesian direction
+ do a = 1, 3
+ call lo_gemv(sr%Xi, Fbb(a, :), buf(a, :))
+ end do
+ ! And now we distribute the results on the irreducible qpoints
+ do il = 1, sr%my_nqpoints
+ q1 = sr%my_qpoints(il)
+ b1 = sr%my_modes(il)
+ Fnb(:, b1, q1) = -buf(:, il)/dr%iq(q1)%qs(b1)
+ end do
+ call mw%allreduce('sum', Fnb)
+ end block applyXi
+
+ ! make sure degeneracies are satisfied properly and add the previous thing
+ distributeF: block
+ real(r8), dimension(3) :: v0
+ integer :: q1, b1, b2, j
+ do q1 = 1, qp%n_irr_point
+ do b1 = 1, dr%n_mode
+ v0 = 0.0_r8
+ do j = 1, dr%iq(q1)%degeneracy(b1)
+ b2 = dr%iq(q1)%degenmode(j, b1)
+ v0 = v0 + Fnb(:, b2, q1)
+ end do
+ v0 = v0/real(dr%iq(q1)%degeneracy(b1), r8)
+ do j = 1, dr%iq(q1)%degeneracy(b1)
+ b2 = dr%iq(q1)%degenmode(j, b1)
+ Fnb(:, b2, q1) = v0
+ end do
+ ! Add the previous thing
+ Fnb(:, b1, q1) = dr%iq(q1)%F0(:, b1) + Fnb(:, b1, q1)
+ end do
+ end do
+ end block distributeF
+
+ ! Add everything together and check convergency
+ addandcheck: block
+ real(r8), dimension(3, 3) :: m0
+ real(r8) :: g0, g1, g2
+ integer :: i, j
+
+ g0 = 0.0_r8
+ do i = 1, dr%n_irr_qpoint
+ do j = 1, dr%n_mode
+ g1 = lo_sqnorm(dr%iq(i)%Fn(:, j) - Fnb(:, j, i))
+ g2 = lo_sqnorm(dr%iq(i)%Fn(:, j))
+ if (g2 .gt. lo_sqtol) then
+ g0 = g0 + g1/g2
+ end if
+ dr%iq(i)%Fn(:, j) = dr%iq(i)%Fn(:, j)*(1.0_r8 - mixingparameter) + &
+ mixingparameter*(Fnb(:, j, i))
+ end do
+ end do
+ scfcheck(iter) = g0/qp%n_irr_point/dr%n_mode
+
+ ! Get the current kappa, to print to stdout.
+ call get_kappa(dr, qp, uc, temperature, classical, kappa)
+ m0 = kappa*lo_kappa_au_to_SI
+ if (mw%r .eq. 0) write (*, "(1X,I4,6(1X,F14.4),2X,ES10.3)") &
+ iter, m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3), scfcheck(iter)
+
+ ! Check for convergence. The criterion is that the relative difference between the new
+ ! and old Fn is to be one part in 1E-5, for two consecutive iterations.
+ if (iter .ge. 3) then
+ g0 = sum(scfcheck(iter - 2:iter))
+ if (g0 .lt. tol) then
+ exit scfloop
+ end if
+ end if
+
+ ! We are not converged if we made it here.
+ ! If we had too many iterations I want to adjust the mixing a little
+ if (iter .gt. 15 .and. mixingparameter .gt. 0.50) then
+ mixingparameter = mixingparameter*0.98_r8
+ end if
+ end block addandcheck
+ end do scfloop
+ call mem%deallocate(Fnb, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(Fbb, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(buf, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+end subroutine
+end module
diff --git a/src/thermal_conductivity/main.f90 b/src/thermal_conductivity/main.f90
index 6e355a3c..f5f221cf 100644
--- a/src/thermal_conductivity/main.f90
+++ b/src/thermal_conductivity/main.f90
@@ -1,73 +1,86 @@
#include "precompilerdefinitions"
program thermal_conductivity
-!!{!src/thermal_conductivity/manual.md!}
-use konstanter, only: r8, lo_temperaturetol, lo_status, lo_kappa_au_to_SI, lo_freqtol
-use gottochblandat, only: walltime, tochar, lo_linspace, lo_logspace, lo_mean
+use konstanter, only: r8, lo_temperaturetol, lo_status, lo_kappa_au_to_SI, lo_freqtol, lo_m_to_Bohr, lo_emu_to_amu
+use gottochblandat, only: walltime, tochar, open_file
use mpi_wrappers, only: lo_mpi_helper
use lo_memtracker, only: lo_mem_helper
use type_crystalstructure, only: lo_crystalstructure
-use type_mdsim, only: lo_mdsim
use type_forceconstant_secondorder, only: lo_forceconstant_secondorder
use type_forceconstant_thirdorder, only: lo_forceconstant_thirdorder
+use type_forceconstant_fourthorder, only: lo_forceconstant_fourthorder
use type_qpointmesh, only: lo_qpoint_mesh, lo_generate_qmesh
use type_phonon_dispersions, only: lo_phonon_dispersions
-use type_phonon_dos, only: lo_phonon_dos
-use dump_data, only: lo_dump_gnuplot_2d_real
+use lo_timetracker, only: lo_timer
-! unique
use options, only: lo_opts
-use scatteringstrengths, only: calculate_scattering_amplitudes
-use pbe, only: get_kappa, get_kappa_offdiag, calculate_qs, get_selfconsistent_solution
-use phononevents, only: lo_threephononevents, lo_find_all_scattering_events
-use mfp, only: lo_mfp, get_cumulative_plots, write_cumulative_plots
+use kappa, only: get_kappa, get_kappa_offdiag, iterative_solution, symmetrize_kappa
+use scattering, only: lo_scattering_rates
implicit none
-! Standard, from libolle
+
+! Standard from libolle
type(lo_opts) :: opts
type(lo_forceconstant_secondorder) :: fc
type(lo_forceconstant_thirdorder) :: fct
+type(lo_forceconstant_fourthorder) :: fcf
type(lo_phonon_dispersions) :: dr
-type(lo_phonon_dos) :: pd
type(lo_crystalstructure) :: uc
class(lo_qpoint_mesh), allocatable :: qp
type(lo_mpi_helper) :: mw
type(lo_mem_helper) :: mem
-! Unique
-type(lo_threephononevents) :: sc
-type(lo_mfp) :: mf
-! Small stuff
-real(r8), dimension(:, :), allocatable :: thermal_cond
-real(r8), dimension(:), allocatable :: temperatures
-! timers
-real(r8) :: timer_init, timer_count, timer_matrixelements, timer_scf
-real(r8) :: timer_kappa, timer_qs, timer_cumulative, tt0
+type(lo_timer) :: tmr_init, tmr_scat, tmr_kappa, tmr_tot
+! The scattering rates
+type(lo_scattering_rates) :: sr
+real(r8) :: t0
! Set up all harmonic properties. That involves reading all the input file,
! creating grids, getting the harmonic properties on those grids.
initharmonic: block
- integer :: i, j
+ integer :: i, j, q1
! Start MPI and timers
- tt0 = walltime()
- timer_init = tt0
- timer_qs = 0.0_r8
- timer_kappa = 0.0_r8
- timer_scf = 0.0_r8
- timer_cumulative = 0.0_r8
call mw%init()
+ t0 = walltime()
+ ! Start the initialization timer
+ call tmr_tot%start()
+ call tmr_init%start()
! Get options
call opts%parse()
- if (mw%r .ne. 0) opts%verbosity = -100
+ if (.not. mw%talk) opts%verbosity = -100
! Init memory tracker
call mem%init()
- if (mw%talk) write (*, *) '... using ', tochar(mw%n), ' MPI ranks'
+ if (mw%talk) then
+ write (*, *) 'Recap of the parameters governing the calculation'
+ write (*, '(1X,A40,F20.12)') 'Temperature ', opts%temperature
+ write (*, '(1X,A40,L3)') 'Thirdorder scattering ', opts%thirdorder
+ write (*, '(1X,A40,L3)') 'Fourthorder scattering ', opts%fourthorder
+ write (*, '(1X,A40,L3)') 'Isotope scattering ', opts%isotopescattering
+ write (*, '(1X,A40,L3)') 'Classical limit ', opts%classical
+ write (*, '(1X,A40,I4,I4,I4)') 'full q-point grid ', opts%qgrid
+ write (*, '(1X,A40,I4,I4,I4)') 'Monte-Carlo 3rd order q-point grid ', opts%qg3ph
+ write (*, '(1X,A40,I4,I4,I4)') 'Monte-Carlo 4th order q-point grid ', opts%qg4ph
+ write (*, '(1X,A40,I5)') 'Max number of iteration ', opts%itermaxsteps
+ write (*, '(1X,A40,E20.12)') 'Max mean free path (in m) ', opts%mfp_max/lo_m_to_Bohr
+ write (*, '(1X,A40,E20.12)') 'Tolerance for the iterative solution ', opts%itertol
+ select case (opts%integrationtype)
+ case (1)
+ write (*, '(1X,A40,2X,A)') 'Integration type ', 'Gaussian with fixed broadening'
+ case (2)
+ write (*, '(1X,A40,2X,A)') 'Integration type ', 'Adaptive Gaussian'
+ write (*, '(1X,A40,E20.12)') 'Sigma factor for gaussian smearing ', opts%sigma
+ end select
+ write (*, '(1X,A40,I10)') 'Number of MPI ranks ', mw%n
+ if (opts%seed .gt. 0) write(*, '(1X,A40,I10)') 'Random seed ', opts%seed
+ write (*, *) ''
+ end if
+
+ if (mw%talk) write (*, *) 'Initialize calculation'
! There is a bunch of stuff that all ranks need, first the unit cell:
call uc%readfromfile('infile.ucposcar', verbosity=opts%verbosity)
call uc%classify('wedge', timereversal=opts%timereversal)
if (mw%talk) write (*, *) '... read unitcell poscar'
! Perhaps non-natural isotope distribution
- ! write (*, *) 'FIXME OUTPUT UNITS'
if (opts%readiso) then
if (mw%talk) write (*, *) '... reading isotope distribution from file'
call uc%readisotopefromfile()
@@ -75,21 +88,21 @@ program thermal_conductivity
do i = 1, uc%na
do j = 1, uc%isotope(i)%n
write (*, "(' isotope: ',I2,' concentration: ',F8.5,' mass: ',F12.6)") &
- j, uc%isotope(i)%conc(j), uc%isotope(i)%mass(j)
+ j, uc%isotope(i)%conc(j), uc%isotope(i)%mass(j) * lo_emu_to_amu
end do
write (*, "(' element: ',A2,' mean mass: ',F12.6,' mass disorder parameter',F12.9)") &
- trim(uc%atomic_symbol(uc%species(i))), uc%isotope(i)%mean_mass, &
+ trim(uc%atomic_symbol(uc%species(i))), uc%isotope(i)%mean_mass * lo_emu_to_amu, &
uc%isotope(i)%disorderparameter
end do
end if
- elseif (opts%verbosity .gt. 0) then
+ elseif (mw%talk .and. opts%verbosity .gt. 0) then
do i = 1, uc%na
do j = 1, uc%isotope(i)%n
write (*, "(' isotope: ',I2,' concentration: ',F8.5,' mass: ',F12.6)") &
- j, uc%isotope(i)%conc(j), uc%isotope(i)%mass(j)
+ j, uc%isotope(i)%conc(j), uc%isotope(i)%mass(j) * lo_emu_to_amu
end do
write (*, "(' element: ',A2,' mean mass: ',F12.6,' mass disorder parameter',F12.9)") &
- trim(uc%atomic_symbol(uc%species(i))), uc%isotope(i)%mean_mass, &
+ trim(uc%atomic_symbol(uc%species(i))), uc%isotope(i)%mean_mass * lo_emu_to_amu, &
uc%isotope(i)%disorderparameter
end do
end if
@@ -99,19 +112,23 @@ program thermal_conductivity
if (mw%talk) write (*, *) '... read second order forceconstant'
call fct%readfromfile(uc, 'infile.forceconstant_thirdorder')
if (mw%talk) write (*, *) '... read third order forceconstant'
+ if (opts%fourthorder) then
+ call fcf%readfromfile(uc, 'infile.forceconstant_fourthorder')
+ if (mw%talk) write (*, *) '... read fourth order forceconstant'
+ end if
+
+ call tmr_init%tock('read input files')
+ if (mw%talk) write (*, *) '... generating q-point mesh'
! Get q-point mesh
call lo_generate_qmesh(qp, uc, opts%qgrid, 'fft', timereversal=opts%timereversal, &
headrankonly=.false., mw=mw, mem=mem, verbosity=opts%verbosity, nosym=.not. opts%qpsymmetry)
+ call tmr_init%tock('generated q-mesh')
+
! Get frequencies in the whole BZ
- if (mw%talk) then
- write (*, *) '... getting the full dispersion relations'
- end if
+ if (mw%talk) write (*, *) '... generating harmonic properties on the q-point mesh'
call dr%generate(qp, fc, uc, mw=mw, mem=mem, verbosity=opts%verbosity)
- ! Also the phonon DOS, for diagnostics
- call pd%generate(dr, qp, uc, mw, mem, verbosity=opts%verbosity, &
- sigma=opts%sigma, n_dos_point=opts%mfppts*2, integrationtype=opts%integrationtype)
! Make sure it's stable, no point in going further if it is unstable.
if (dr%omega_min .lt. -lo_freqtol) then
@@ -122,203 +139,167 @@ program thermal_conductivity
stop
end if
+ ! Make some space to keep intermediate values
+ do q1 = 1, qp%n_irr_point
+ allocate (dr%iq(q1)%linewidth(dr%n_mode))
+ allocate (dr%iq(q1)%F0(3, dr%n_mode))
+ allocate (dr%iq(q1)%Fn(3, dr%n_mode))
+ allocate (dr%iq(q1)%qs(dr%n_mode))
+ allocate (dr%iq(q1)%mfp(3, dr%n_mode))
+ allocate (dr%iq(q1)%scalar_mfp(dr%n_mode))
+ allocate (dr%iq(q1)%kappa(3, 3, dr%n_mode))
+ dr%iq(q1)%linewidth = 0.0_r8
+ dr%iq(q1)%F0 = 0.0_r8
+ dr%iq(q1)%Fn = 0.0_r8
+ dr%iq(q1)%qs = 0.0_r8
+ dr%iq(q1)%mfp = 0.0_r8
+ dr%iq(q1)%scalar_mfp = 0.0_r8
+ dr%iq(q1)%kappa = 0.0_r8
+ end do
+ call tmr_init%tock('harmonic dispersions')
+ call tmr_tot%tock('initialization')
+
! now I have all harmonic things, stop the init timer
- timer_init = walltime() - timer_init
+ t0 = walltime() - t0
+ if (mw%talk) write (*, "(1X,A,F12.3,A)") '... done in ', t0, ' s'
+ call tmr_init%stop()
end block initharmonic
-! Get the integration weights and matrix elements
-weights_elements: block
- real(r8) :: t0
- t0 = walltime()
- timer_count = walltime()
- call lo_find_all_scattering_events(sc, qp, dr, uc, mw, mem, opts%sigma, opts%thres, opts%integrationtype, &
- opts%correctionlevel, opts%mfp_max, opts%isotopescattering)
- call mpi_barrier(mw%comm, mw%error)
-
- ! stop counting timer, start matrixelement timer
- timer_count = walltime() - timer_count
- timer_matrixelements = walltime()
+get_scattering_rates: block
+ call tmr_scat%start()
+ if (mw%talk) then
+ write (*, *) ''
+ write (*, *) 'Calculating scattering events'
+ end if
- ! Calculate scattering amplitudes
t0 = walltime()
- call calculate_scattering_amplitudes(uc, qp, sc, dr, fct, mw)
- call mpi_barrier(mw%comm, mw%error)
- ! stop matrix element timer, start some other timer
- timer_matrixelements = walltime() - timer_matrixelements
- if (mw%talk) write (*, *) 'Counted and got scattering amplitudes in ', tochar(walltime() - t0)
-end block weights_elements
-
-! Make space and initialize everything to calculate thermal conductivity
-initkappa: block
- integer :: i
-
- ! space to store the actual thermal conductivity
- allocate (thermal_cond(10, opts%trangenpts))
- thermal_cond = 0.0_r8
-
- ! temperature axis
- allocate (temperatures(opts%trangenpts))
- if (opts%logtempaxis) then
- call lo_logspace(opts%trangemin, opts%trangemax, temperatures)
- else
- call lo_linspace(opts%trangemin, opts%trangemax, temperatures)
- end if
- ! Setup the mean-free-path vs kappa plots.
- ! how many points on the x-axis?
- mf%np = opts%mfppts
- ! how many temperatures?
- mf%nt = opts%trangenpts
- ! one plot for each temperature
- allocate (mf%temp(mf%nt))
+ call sr%generate(qp, dr, uc, fct, fcf, opts, tmr_scat, mw, mem)
+ t0 = walltime() - t0
- ! Make some space to keep intermediate values
- do i = 1, qp%n_irr_point
- allocate (dr%iq(i)%p_plus(dr%n_mode))
- allocate (dr%iq(i)%p_minus(dr%n_mode))
- allocate (dr%iq(i)%p_iso(dr%n_mode))
- allocate (dr%iq(i)%qs(dr%n_mode))
- allocate (dr%iq(i)%linewidth(dr%n_mode))
- allocate (dr%iq(i)%F0(3, dr%n_mode))
- allocate (dr%iq(i)%Fn(3, dr%n_mode))
- allocate (dr%iq(i)%mfp(3, dr%n_mode))
- allocate (dr%iq(i)%scalar_mfp(dr%n_mode))
- dr%iq(i)%linewidth = 0.0_r8
- dr%iq(i)%p_plus = 0.0_r8
- dr%iq(i)%p_minus = 0.0_r8
- dr%iq(i)%p_iso = 0.0_r8
- dr%iq(i)%qs = 0.0_r8
- dr%iq(i)%F0 = 0.0_r8
- dr%iq(i)%Fn = 0.0_r8
- dr%iq(i)%mfp = 0.0_r8
- dr%iq(i)%scalar_mfp = 0.0_r8
- end do
- do i = 1, qp%n_full_point
- allocate (dr%aq(i)%kappa(3, 3, dr%n_mode))
- dr%aq(i)%kappa = 0.0_r8
- end do
-end block initkappa
+ call tmr_scat%stop()
+ call tmr_tot%tock('scattering computation')
-! Iteratively solve the BTE for each temperature. Additionally calculate
-! mean free path plots and things like that.
-getkappa: block
- real(r8), dimension(3, 3) :: kappa, kappa_offdiag, kappa_sma, m0
- real(r8) :: t0
- integer :: i
+ if (mw%talk) write (*, "(1X,A,F12.3,A)") '... done in ', t0, ' s'
+end block get_scattering_rates
- if (mw%talk) then
- write (*, *) ''
- write (*, *) 'THERMAL CONDUCTIVITY'
- if (opts%scfiterations .eq. 0) then
- write (*, "(3X,A11,6(1X,A14))") 'Temperature', 'kxx ', 'kyy ', 'kzz ', 'kxy ', 'kxz ', 'kyz '
- end if
- end if
- ! Main loop over temperatures to solve the BTE
- do i = 1, opts%trangenpts
-
- ! I might get a silly tiny temperature, then things will break.
- if (temperatures(i) .lt. lo_temperaturetol) then
- kappa = 0.0_r8
- thermal_cond(1, i) = temperatures(i)
- thermal_cond(2:10, i) = 0.0_r8
- cycle
- end if
+blockkappa: block
+ real(r8), dimension(3, 3) :: kappa_iter, kappa_offdiag, kappa_sma, m0
+ real(r8) :: t0
+ integer :: i, u, q1, b1
- ! Scattering rates
- t0 = walltime()
- call calculate_qs(qp, sc, dr, temperatures(i), mw, mem)
- timer_qs = timer_qs + walltime() - t0
+ call tmr_kappa%start()
+ t0 = walltime()
- call get_kappa(dr, qp, uc, temperatures(i), kappa_sma)
- call get_kappa_offdiag(dr, qp, uc, temperatures(i), fc, mem, mw, kappa_offdiag)
+ ! I might get a silly tiny temperature, then things will break.
+ if (opts%temperature .lt. lo_temperaturetol) then
+ kappa_iter = 0.0_r8
+ kappa_sma = 0.0_r8
+ kappa_offdiag = 0.0_r8
+ end if
- ! Get the self-consistent solution
- call mpi_barrier(mw%comm, mw%error)
- if (opts%scfiterations .gt. 0) then
- if (mw%talk) then
- write (*, *) ''
- write (*, *) 'Temperature: ', tochar(temperatures(i))
- write (*, "(1X,A4,6(1X,A14),2X,A10)") 'iter', &
- 'kxx ', 'kyy ', 'kzz ', 'kxy ', 'kxz ', 'kyz ', 'DeltaF/F'
- end if
- t0 = walltime()
- call get_selfconsistent_solution(sc, dr, qp, uc, temperatures(i), opts%scfiterations, opts%scftol, mw, mem)
- !call get_selfconsistent_solution(sc,dr,qp,uc,mw,temperatures(i),opts%scfiterations,opts%scftol)
- timer_scf = timer_scf + walltime() - t0
- call get_kappa(dr, qp, uc, temperatures(i), kappa)
- m0 = kappa*lo_kappa_au_to_SI
- if (mw%talk) write (*, "(5X,6(1X,F14.4),2X,E10.3)") m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
- else
- call get_kappa(dr, qp, uc, temperatures(i), kappa)
- m0 = kappa*lo_kappa_au_to_SI
- if (mw%talk) write (*, "(1X,F12.3,6(1X,F14.4),2X,E10.3)") &
- temperatures(i), m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
- end if
+ if (mw%talk) write (*, *) ''
+ if (mw%talk) write (*, *) 'Thermal conductivity calculation'
+ if (mw%talk) write (*, *) '... computing kappa in the single mode approximation'
+ call get_kappa(dr, qp, uc, opts%temperature, opts%classical, kappa_sma)
+ call tmr_kappa%tock('single mode approximation')
+ if (mw%talk) write (*, *) '... computing off diagonal (coherence) contribution'
+ call get_kappa_offdiag(dr, qp, uc, fc, opts%temperature, opts%classical, mem, mw, kappa_offdiag)
+ call tmr_kappa%tock('off-diagonal contribution')
+ if (opts%itermaxsteps .gt. 0) then
if (mw%talk) then
- m0 = kappa_sma*lo_kappa_au_to_SI
- write (*, *) ''
- write (*, "(1X,A52)") 'Decomposition of the thermal conductivity (in W/m/K)'
- write (*, "(1X,A85)") 'Single mode relaxation time approximation (RTA) to Boltzmann transport equation (BTE)'
- write (*, "(1X,A4,6(1X,A14))") '', 'kxx ', 'kyy ', 'kzz ', 'kxy ', 'kxz ', 'kyz '
- write (*, "(5X,6(1X,F14.4),2X,E10.3)") m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
- m0 = (kappa - kappa_sma)*lo_kappa_au_to_SI
- write (*, "(1X,A73)") 'Correction to full solution of the linearized BTE via iterative procedure'
- write (*, "(1X,A4,6(1X,A14))") '', 'kxx ', 'kyy ', 'kzz ', 'kxy ', 'kxz ', 'kyz '
- write (*, "(5X,6(1X,F14.4),2X,E10.3)") m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
- m0 = kappa_offdiag*lo_kappa_au_to_SI
- write (*, "(1X,A)") "Off-diagonal (coherences') contribution"
- write (*, "(1X,A4,6(1X,A14))") '', 'kxx ', 'kyy ', 'kzz ', 'kxy ', 'kxz ', 'kyz '
- write (*, "(5X,6(1X,F14.4),2X,E10.3)") m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
- m0 = (kappa + kappa_offdiag)*lo_kappa_au_to_SI
- write (*, "(1X,A26)") 'Total thermal conductivity'
- write (*, "(1X,A4,6(1X,A14))") '', 'kxx ', 'kyy ', 'kzz ', 'kxy ', 'kxz ', 'kyz '
- write (*, "(5X,6(1X,F14.4),2X,E10.3)") m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
+ write (*, *) '... solving iteratively the collective contribution'
+ write (*, "(1X,A4,6(1X,A14),2X,A10)") 'iter', &
+ 'kxx ', 'kyy ', 'kzz ', 'kxy ', 'kxz ', 'kyz ', 'DeltaF/F'
end if
-
- ! Store thermal conductivity tensor
- thermal_cond(1, i) = temperatures(i)
- thermal_cond(2, i) = (kappa(1, 1) + kappa_offdiag(1, 1))*lo_kappa_au_to_SI
- thermal_cond(3, i) = (kappa(2, 2) + kappa_offdiag(2, 2))*lo_kappa_au_to_SI
- thermal_cond(4, i) = (kappa(3, 3) + kappa_offdiag(3, 3))*lo_kappa_au_to_SI
- thermal_cond(5, i) = (kappa(1, 3) + kappa_offdiag(1, 3))*lo_kappa_au_to_SI
- thermal_cond(6, i) = (kappa(2, 3) + kappa_offdiag(2, 3))*lo_kappa_au_to_SI
- thermal_cond(7, i) = (kappa(1, 2) + kappa_offdiag(1, 2))*lo_kappa_au_to_SI
- thermal_cond(8, i) = (kappa(3, 1) + kappa_offdiag(3, 1))*lo_kappa_au_to_SI
- thermal_cond(9, i) = (kappa(3, 2) + kappa_offdiag(3, 2))*lo_kappa_au_to_SI
- thermal_cond(10, i) = (kappa(2, 1) + kappa_offdiag(2, 1))*lo_kappa_au_to_SI
-
- ! Calculate the cumulative plots
t0 = walltime()
- call mpi_barrier(mw%comm, mw%error)
- call get_cumulative_plots(mf%temp(i), qp, dr, pd, uc, opts%mfppts, temperatures(i), opts%sigma, kappa, mw, mem)
-
- timer_cumulative = timer_cumulative + walltime() - t0
- call mpi_barrier(mw%comm, mw%error)
- end do
-end block getkappa
+ call iterative_solution(sr, dr, qp, uc, opts%temperature, opts%itermaxsteps, opts%itertol, opts%classical, mw, mem)
+ t0 = walltime() - t0
+ if (mw%talk) write (*, "(1X,A,F12.3,A)") '... done in ', t0, ' s'
+ call tmr_kappa%tock('collective contribution')
+ end if
+ call get_kappa(dr, qp, uc, opts%temperature, opts%classical, kappa_iter)
+ if (mw%talk) write (*, *) ''
+ if (mw%talk) write (*, *) '... symmetrizing the thermal conductivity tensors'
+ call symmetrize_kappa(kappa_iter, uc)
+ call symmetrize_kappa(kappa_offdiag, uc)
+ call symmetrize_kappa(kappa_sma, uc)
+ call tmr_kappa%tock('symmetrization')
+ call tmr_kappa%stop()
+ if (mw%talk) then
+ ! First we write in the standard output
+ u = open_file('out', 'outfile.thermal_conductivity')
+ write (u, '(A2,A5,15X,A)') '# ', 'Unit:', 'W/m/K'
+ write (u, '(A2,A12,8X,E20.12)') '# ', 'Temperature:', opts%temperature
-! dump things to file and print timings
-finalize_and_write: block
- real(r8) :: t0
+ write (*, *) ''
+ write (*, "(1X,A52)") 'Decomposition of the thermal conductivity (in W/m/K)'
+ m0 = kappa_sma*lo_kappa_au_to_SI
+ ! First in the standard output
+ write (*, "(1X,A)") 'Single mode approximation (SMA)'
+ write (*, "(1X,A4,6(1X,A14))") '', 'kxx ', 'kyy ', 'kzz ', 'kxy ', 'kxz ', 'kyz '
+ write (*, "(5X,6(1X,F14.4))") m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
+ ! Then in the outfile
+ write (u, "(A)") '# Single mode approximation'
+ write (u, "(A1,6(1X,A24))") '#', 'kxx', 'kyy', 'kzz', 'kxy', 'kxz', 'kyz'
+ write (u, "(1X,6(1X,E24.12))") m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
+
+ m0 = (kappa_iter - kappa_sma)*lo_kappa_au_to_SI
+ ! First in the standard output
+ write (*, "(1X,A)") 'Correction to include collective contribution via iterative procedure'
+ write (*, "(1X,A4,6(1X,A14))") '', 'kxx ', 'kyy ', 'kzz ', 'kxy ', 'kxz ', 'kyz '
+ write (*, "(5X,6(1X,F14.4))") m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
+ ! Then in the outfile
+ write (u, "(A)") '# Collective contribution'
+ write (u, "(A1,6(1X,A24))") '#', 'kxx', 'kyy', 'kzz', 'kxy', 'kxz', 'kyz'
+ write (u, "(1X,6(1X,E24.12))") m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
+
+ m0 = kappa_offdiag*lo_kappa_au_to_SI
+ ! First in the standard output
+ write (*, "(1X,A)") 'Off diagonal (coherence) contribution'
+ write (*, "(1X,A4,6(1X,A14))") '', 'kxx ', 'kyy ', 'kzz ', 'kxy ', 'kxz ', 'kyz '
+ write (*, "(5X,6(1X,F14.4))") m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
+ ! Then in the outfile
+ write (u, "(A)") '# Off diagonal (coherence) contribution'
+ write (u, "(A1,6(1X,A24))") '#', 'kxx', 'kyy', 'kzz', 'kxy', 'kxz', 'kyz'
+ write (u, "(1X,6(1X,E24.12))") m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
+
+ m0 = (kappa_iter + kappa_offdiag)*lo_kappa_au_to_SI
+ ! First in the standard output
+ write (*, "(1X,A26)") 'Total thermal conductivity'
+ write (*, "(1X,A4,6(1X,A14))") '', 'kxx ', 'kyy ', 'kzz ', 'kxy ', 'kxz ', 'kyz '
+ write (*, "(5X,6(1X,F14.4))") m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
+ ! Then in the outfile
+ write (u, "(A28)") '# Total thermal conductivity'
+ write (u, "(A1,6(1X,A24))") '#', 'kxx', 'kyy', 'kzz', 'kxy', 'kxz', 'kyz'
+ write (u, "(1X,6(1X,E24.12))") m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
+
+ close (u)
+ end if
- ! Write thermal conductivity to file
- if (mw%talk) call lo_dump_gnuplot_2d_real(thermal_cond, 'outfile.thermal_conductivity', &
- ylabel='\kappa W/mK', xlabel='Temperature (K)')
+ ! In a last step, we have to add a prefactor to Fn, to have the right kappa per mode in the outfile
+ do q1 = 1, qp%n_irr_point
+ do b1 = 1, dr%n_mode
+ if (dr%iq(q1)%omega(b1) .lt. lo_freqtol) cycle
+ dr%iq(q1)%Fn(:, b1) = dr%iq(q1)%Fn(:, b1)*dr%iq(q1)%omega(b1)/opts%temperature
+ end do
+ end do
- ! Write the cumulative kappa
- if (mw%talk) call write_cumulative_plots(mf, pd, uc, 'thz', 'outfile.cumulative_kappa.hdf5', opts%verbosity)
+ call tmr_tot%tock('thermal conductivity computation')
+ t0 = walltime() - t0
+end block blockkappa
- ! Maybe dump data on a grid
- if (mw%talk .and. opts%dumpgrid .and. opts%trangenpts .eq. 1) then
- write (*, *) '... dumping auxiliary data to files:'
- call dr%write_to_hdf5(qp, uc, 'outfile.grid_thermal_conductivity.hdf5', mem, temperatures(1))
- end if
+finalize_and_write: block
+ if (mw%talk) then
+ write (*, *) ''
+ write (*, *) '... dumping auxiliary data to files'
+ call dr%write_to_hdf5(qp, uc, 'outfile.thermal_conductivity_grid.hdf5', mem, opts%temperature)
- ! sum up the total time
- if (mw%talk) tt0 = walltime() - tt0
+ write (*, *) ''
+ write (*, '(A,A)') 'Scattering rates can be found in ', 'outfile.thermal_conductivity.hdf5'
+ write (*, '(A,A)') 'Thermal conductivity tensor can be found in ', 'outfile.thermal_conductivity'
- ! Print timings
- if (mw%talk) then
+ ! Print timings
write (*, *) ''
write (*, '(1X,A)') 'SUGGESTED CITATIONS:'
write (*, '(1X,A41,A)') 'Software: ', 'F. Knoop et al., J. Open Source Softw 9(94), 6150 (2024)'
@@ -328,25 +309,21 @@ program thermal_conductivity
write (*, '(1X,A41,A)') "Off-diagonal (coherences') contribution: ", 'M. Simoncelli et al., Nat Phys 15 809-813 (2019)'
write (*, '(1X,A41,A)') ' ', 'L. Isaeva et al., Nat Commun 10 3853 (2019)'
write (*, '(1X,A41,A)') ' ', 'A. Fiorentino et al., Phys Rev B 107, 054311 (2023)'
-
- t0 = timer_init + timer_count + timer_matrixelements + timer_qs + timer_kappa + timer_scf + timer_cumulative
- write (*, *) ' '
- write (*, *) 'Timings:'
- write (*, "(A,F12.3,A,F7.3,A)") ' initialization:', timer_init, ' s, ', real(timer_init*100/tt0), '%'
- write (*, "(A,F12.3,A,F7.3,A)") ' integration weights:', timer_count, ' s, ', real(timer_count*100/tt0), '%'
- write (*, "(A,F12.3,A,F7.3,A)") ' matrix elements:', timer_matrixelements, &
- ' s, ', real(timer_matrixelements*100/tt0), '%'
- write (*, "(A,F12.3,A,F7.3,A)") ' QS calculation:', timer_qs, ' s, ', real(timer_qs*100/tt0), '%'
- write (*, "(A,F12.3,A,F7.3,A)") ' kappa:', timer_kappa, ' s, ', real(timer_kappa*100/tt0), '%'
- write (*, "(A,F12.3,A,F7.3,A)") ' self consistency:', timer_scf, ' s, ', real(timer_scf*100/tt0), '%'
- write (*, "(A,F12.3,A,F7.3,A)") ' cumulative plots:', timer_cumulative, &
- ' s, ', real(timer_cumulative*100/tt0), '%'
- write (*, "(A,F12.3,A)") ' total:', tt0, ' seconds'
+ write (*, '(1X,A41,A52)') 'Theory : ', 'A. Castellano et al, J. Chem. Phys. 159 (23), (2023)'
+ write (*, '(1X,A41,A46)') 'Theory and algorithm : ', 'A. Castellano et al, ArXiv:2411.14949 (2024)'
end if
+ call tmr_tot%tock('io')
+
+ call tmr_tot%stop()
+ if (mw%talk) write (*, *) ''
+ call tmr_init%dump(mw, 'Initialization timings:')
+ call tmr_scat%dump(mw, 'Scattering timings:')
+ call tmr_kappa%dump(mw, 'Thermal conductivity timings:')
+ call tmr_tot%dump(mw, 'Total timings:')
end block finalize_and_write
! And we are done!
+call sr%destroy()
call mpi_barrier(mw%comm, mw%error)
call mpi_finalize(lo_status)
-
end program
diff --git a/src/thermal_conductivity/manual.md b/src/thermal_conductivity/manual.md
index e57a1ced..08786d4f 100644
--- a/src/thermal_conductivity/manual.md
+++ b/src/thermal_conductivity/manual.md
@@ -1,693 +1,296 @@
### Longer summary
-Heat transport can be determined by solving the inelastic phonon Boltzmann equation. By applying a temperature gradient $\nabla T_\alpha$ in direction $\alpha$, the heat current is given by the group velocities of phonon mode $\lambda$ and non-equilibrium phonon distribution function $\tilde{n}_\lambda$:[^peierls1955quantum]
+The thermal conductivity tensor can be computed from the Green-Kubo formula
$$
\begin{equation}
-J_{\alpha}=\frac{1}{V}\sum_\lambda
-\hbar \omega_\lambda v_{\lambda\alpha} \tilde{n}_{\lambda\alpha}.
+\kappa^{\alpha\beta} = \frac{\beta}{V T} \int_0^\infty dt \int_0^\beta d\lambda \langle J_{\alpha}(i\hbar\lambda) J_\beta(t) \rangle
\end{equation}
$$
-Assuming the thermal gradient is small, the non-equilibrium distribution function can be linearised as,
+where $J_{\alpha}$ is the heat current operator.
+In a crystal, the heat current operator can be approximated as
$$
-\tilde{n}_{\lambda\alpha} \approx n_{\lambda}-
-v_{\lambda\alpha}
-\tau_{\lambda\alpha}
-\frac{d n_{\lambda}}{d T}
-\frac{d T}{d \alpha} \, ,
-$$
-
-That is a linear deviation from the equilibrium distribution function $n_{\lambda}$. Inserting this into the equation 1, and exploiting the fact that the equilibrium occupation carries no heat, we arrive at,
-
-$$
-J_{\alpha}=\frac{1}{V}\sum_{\lambda}
-\hbar \omega_{\lambda}
-\frac{d n_{\lambda}}{d T}
-v_{\lambda\alpha}
-v_{\lambda\alpha}
-\tau_{\lambda\alpha}
-\frac{d T}{d \alpha}.
-$$
-
-Utilizing Fourier's law, $J=\kappa \nabla T$, and identifying the phonon heat capacity,
-
-$$
-c_{\lambda}=
-\hbar \omega_\lambda
-\frac{d n_{\lambda}}{d T},
-$$
-
-we arrive at,
-
-$$
-\kappa_{\alpha\beta}=\frac{1}{V} \sum_{\lambda}
-c_{\lambda}
-v_{\alpha \lambda}v_{\beta \lambda} \tau_{\beta \lambda},
-$$
-
-which can be interpreted as follows: the heat transported by each phonon will depend on how much heat it carries, how fast it travels, and how long it lives. The phonon-phonon induced lifetime can be determined from the self-energy $\Gamma_{\lambda}$. In addition, one must consider the scattering with mass impurities (isotopes), and the boundaries of the sample.
-
-### Lifetimes
-
-With the third order force constants we can calculate the phonon lifetimes needed as input to the thermal conductivity calculations. The lifetime due to phonon-phonon scattering is related to the imaginary part of the phonon self energy ( $\Sigma=\Delta+i\Gamma$ ).
-
-$$
-\frac{1}{\tau_{\lambda}}=2 \Gamma_{\lambda},
-$$
-
-where $\tau_{\lambda}$ is the lifetime phonon mode $\lambda$, and
-
-$$
-\begin{split}
-\Gamma_{\lambda}=& \frac{\hbar \pi}{16} % _{\lambda'}
-\sum_{\lambda'\lambda''}
-\left|\Phi_{\lambda\lambda'\lambda''}\right|^2
-\bigl[(n_{\lambda'}+n_{\lambda''}+1)
-\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''}) \\
-+ & 2(n_{\lambda'}-n_{\lambda''})
-\delta(\omega_{\lambda}-\omega_{\lambda'}+\omega_{\lambda''}) \bigr]
-\end{split}
-$$
-
-$n_{\lambda}$ is the equilibrium occupation number. The sum is over momentum conserving three-phonon processes, $\textbf{q}+\textbf{q}'+\textbf{q}''=\textbf{G}$, and the deltafunctions in frequency ensure energy conservation. The three-phonon matrix elements are given by
-
-$$
-\Phi_{\lambda\lambda'\lambda''} =
-\sum_{ijk}
-\sum_{\alpha\beta\gamma}
-\frac{
-\epsilon_{\lambda}^{i \alpha}
-\epsilon_{\lambda'}^{j \beta}
-\epsilon_{\lambda''}^{k \gamma}
-}{
-\sqrt{m_{i}m_{j}m_{j}}
-\sqrt{
- \omega_{\lambda}
- \omega_{\lambda'}
- \omega_{\lambda''}}
-}
-\Phi^{\alpha\beta\gamma}_{ijk}
-e^{i \mathbf{q}\cdot\mathbf{r}_i + i \mathbf{q}'\cdot\mathbf{r}_j+i \mathbf{q}''\cdot\mathbf{r}_k}
+\begin{equation}
+J_{\alpha} = - \sum_{ij} \sum_{\beta\gamma} \big( \langle R_i^\alpha \rangle - \langle R_j^\alpha \rangle ) \Phi_{ij}^{\beta\gamma} u_i^\beta v_j^\gamma
+\end{equation}
$$
-where $m_i$ is the mass of atom $i$, $\epsilon_{\lambda}^{\alpha i}$ is component $\alpha$ of the eigenvector for mode $\lambda$ and atom $i$ and $\textbf{r}_i$ is the lattice vector associated with atom $i$.
-
-Mass disorder, in the form of natural isotope distributions also cause thermal resistance. According to Tamura[^Tamura1983], if the isotopes are randomly distributed on the lattice sites then the strength of the isotope scattering can be given by a mass variance parameter $g$:
+which can be projected on phonons to give
$$
-g_i=\sum_j c_{i}^j \left(\frac{m_i^j-\bar{m_i}}{\bar{m_i}}\right)^2
+\begin{equation}
+J_{\alpha} = \frac{1}{2}\sum_{\lambda \lambda'} \hbar \omega_{\lambda'} v_{\lambda\lambda'}^{\alpha} A_\lambda B_{\lambda'}
+\end{equation}
$$
-where $\bar{m_i}$ is the average isotopic mass( $\bar{m_i}=\sum_j c_i^j m_i^j$ ), $m^j_i$ is the mass of isotope $j$ of atom $i$ and $c^j_i$ is its concentration. The contribution to the imaginary part of the self-energy is
+In this equation, $A_\lambda$ and $B_\lambda$ are respectively the displacements and momentum phonon operators and $v_{\lambda\lambda'}^{\alpha}$ is the generalized off-diagonal phonon group-velocity [^Dangic2021], written
$$
-\Gamma^{\textrm{iso}}_{\lambda}=
-\frac{\pi}{4} \sum_{\lambda'}
-\underbrace{\omega_{\lambda}\omega_{\lambda'} \sum_i g_i \left| \epsilon_{\lambda}^{i \dagger} \epsilon_{\lambda'}^{i} \right|^2}_{\Lambda_{\lambda\lambda'}}
-\delta(\omega_{\lambda}-\omega_{\lambda'})
+\begin{equation}
+v_{\lambda\lambda'}^\alpha = \frac{i}{2 \sqrt{\omega_\lambda \omega_{\lambda'}}} \sum_{ij \beta\gamma} \epsilon_\lambda^{i\beta} \sum_{\mathbf{R}} \big( \langle R_i^\alpha \rangle - \langle R_j^\alpha \rangle \big) \frac{\Phi_{ij}^{\beta\gamma}}{\sqrt{m_i m_j}} \epsilon_{\lambda'}^{j\gamma}
+\end{equation}
$$
-Per default, the isotope distribution will be the natural distribution. In case some other distribution is desired, this can be specified.
-
-Scattering by domain boundaries is implemented as
+and whose diagonal contributions are equal to the usual phonon group velocities $\mathbf{v}_{\lambda\lambda} = \mathbf{v}_{\lambda}$.
+Now, the heat current can be separated in a diagonal and a non diagonal contribution as
$$
-\Gamma^{\textrm{boundary}}_{\lambda} = \frac{ v_{\lambda} }{2d}
+\begin{equation}
+J_{\alpha} = J_{\alpha}^{\mathrm{d}} + J_{\alpha}^{\mathrm{nd}}
+\end{equation}
$$
-Where $d$ is a characteristic domain size.
-
-### Beyond the relaxation time approximation
+Here, we will only provide a sketch of the derivation.
+For more informations, we refer reader to the article describing the implementation [^Castellano2024] and the references at the bottom of the page.
-So far we have have considered the phonon heat conduction as an elastic process, whereas it is inelastic. This can be treated by iteratively solving the phonon boltzmann equation, formulated in terms of the (linear) deviations from equilibrium occupation numbers.[^peierls1929],[^Omini1996],[^Omini],[^Broido2007],[^Broido2005]
-### Phonon scattering rates and the phonon Boltzmann equation
+### Scattering rates
-I always found it confusing how you arrived at most of these things. This is something I put together for myself, to clear it up a bit. Please bear in mind that this is not an attempt at a formal derivation whatsoever, just to make it a bit easier to interpret the different terms. There might be an arbitrary number of plusses and minuses and other things missing. Recall the transformation we introduced [earlier](phonon_dispersion_relations.md):
-
-$$
-\begin{equation}\label{eq:normalmodetransformation}
-\hat{u}_{i\alpha} = \sqrt{ \frac{\hbar}{2N m_\alpha} }
-\sum_\lambda \frac{\epsilon_\lambda^{i\alpha}}{ \sqrt{ \omega_\lambda} }
-e^{i\mathbf{q}\cdot\mathbf{r}_i}
-\left( \hat{a}^{\mathstrut}_\lambda + \hat{a}^\dagger_\lambda \right)
-\end{equation}
-$$
+Before handling the thermal conductivity tensor, we will discuss the scattering rates of the phonons.
+Due to interaction with other phonons or quasiparticles, isotopic disorder, boundaries, ..., the phonons scatters.
+This scattering is encoded in the self-energy (or memory kernel).
-and consider the three-phonon process where two phonons combine into one:
-
-$$
-\begin{equation*}
-\begin{split}
-\mathbf{q} + \mathbf{q}' + \mathbf{q}'' & = \mathbf{G} \\
-\omega + \omega' & = \omega''
-\end{split}
-\end{equation*}
-$$
-
-This process changes the state of the system:
+Here, we will make the approximation that these interactions are weak enough so that we can work in the Markovian approximation (or equivalently apply Fermi's golden rule).
+In this case, the self-energy can be simplified to a single number $\Gamma_\lambda$, which allows to define the phonon lifetime
$$
\begin{equation}
-\underbrace{\left| \ldots , n_{\lambda},n_{\lambda'},n_{\lambda''} , \ldots \right\rangle}_{\left\vert i \right\rangle}
-\rightarrow
-\underbrace{\left| \ldots , n_{\lambda}-1,n_{\lambda'}-1,n_{\lambda''}+1, \ldots \right\rangle}_{\left\vert f \right\rangle}
+\tau_\lambda = \frac{1}{2 \Gamma_\lambda}
\end{equation}
$$
-that is, we lost one phonon at $\lambda$ and one at $\lambda'$, and created a phonon at $\lambda''$.
-Mostly out of habit, we sandwich the Hamiltonian between the initial and final states:
+Within this approximation, the phonon spectral function $\chi''(\Omega)$ reduces to a Lorentzian centered on $\omega_\lambda$ with a width of $\Gamma_\lambda$.
-$$
-\begin{equation}\label{eq:sandwich}
-{\left\langle f \middle\vert \hat{H} \middle\vert i \right\rangle} =
-{\left\langle f \middle\vert \sum_i \frac{p^2_i}{2m} +
-\frac{1}{2!}\sum_{ij} \sum_{\alpha\beta}\Phi_{ij}^{\alpha\beta}
-u_i^\alpha u_j^\beta +\frac{1}{3!}
-\sum_{ijk} \sum_{\alpha\beta\gamma}\Phi_{ijk}^{\alpha\beta\gamma}
-u_i^\alpha u_j^\beta u_k^\gamma \ldots
-\middle\vert i \right\rangle}
-\end{equation}
-$$
-
-and remember the rules for ladder operators, and that the eigenstates to the quantum harmonic oscillator are orthogonal:
+The contribution to $\Gamma_\lambda$ given by third order interaction is written
$$
-\begin{equation*}
+\begin{equation}
\begin{split}
-\hat{a}^\dagger \left\vert n \right\rangle & = \sqrt{n+1} \left\vert n + 1 \right\rangle \\
-\hat{a} \left\vert n \right\rangle & = \sqrt{n} \left\vert n -1 \right\rangle \\
-\left\langle i \middle\vert j \right\rangle & = \delta_{ij}
+\Gamma_\lambda^{3\mathrm{ph}} = \frac{\pi}{16} \sum_{\lambda' \lambda''} \vert \Psi_{\lambda\lambda'\lambda''} \vert^2 &\big[(n_{\lambda'} + n_{\lambda''} + 1) (\delta(\omega_\lambda - \omega_{\lambda'} - \omega_{\lambda''}) - (\delta(\omega_\lambda + \omega_{\lambda'} + \omega_{\lambda''})) \\
+&+ (n_{\lambda'} - n_{\lambda''}) (\delta(\omega_\lambda + \omega_{\lambda'} - \omega_{\lambda''}) - (\delta(\omega_\lambda - \omega_{\lambda'} + \omega_{\lambda''})) \big]
\end{split}
-\end{equation*}
+\end{equation}
$$
-Inserting eq \ref{eq:normalmodetransformation} into \ref{eq:sandwich} (and realising that the kinetic energy part and the second order part disappears), we end up with a pretty large expression, that we will deal with in steps, first identify
+with $n_\lambda = (e^{\hbar\omega_\lambda / k_{\mathrm{B}}T} - 1)^{-1}$ the Bose-Einstein distribution of phonon $\lambda$.
+In this equation, the sum is over momentum conserving processes, $\mathbf{q} + \mathbf{q}' + \mathbf{q}'' = \mathbf{G}$ and the three-phonon matrix elements are given by
$$
-\begin{equation}\label{eq:uprod}
-\begin{split}
-u^\alpha_{i}u^\beta_{j}u^\gamma_{k} & =
-%
-\left(\frac{\hbar}{2N}\right)^{3/2} \frac{1}{\sqrt{m_{i}m_{j}m_{k}}}
-\sum_{\lambda\lambda'\lambda''}
-\frac{
-\epsilon_{\lambda}^{i \alpha}
-\epsilon_{\lambda'}^{j \beta}
-\epsilon_{\lambda''}^{k \gamma}
-}{
-\sqrt{
- \omega_{\lambda}
- \omega_{\lambda'}
- \omega_{\lambda''}}
-}
-e^{i \mathbf{q}\cdot\mathbf{r}_i + i \mathbf{q}'\cdot\mathbf{r}_j+i \mathbf{q}''\cdot\mathbf{r}_k}
- \left(a_{\lambda}+a_{\lambda}^\dagger \right)
-\left(a_{\lambda'}+a_{\lambda'}^\dagger \right)
-\left(a_{\lambda''}+a_{\lambda''}^\dagger \right)
-\end{split}
+\begin{equation}
+\Psi_{\lambda\lambda'\lambda''} = \sum_{ijk} \sum_{\alpha\beta\gamma} \frac{\epsilon_{\lambda}^{i \alpha}\epsilon_{\lambda'}^{j \beta}\epsilon_{\lambda''}^{k \gamma}}
+{\sqrt{m_{i}m_{j}m_{k}}\sqrt{\omega_{\lambda}\omega_{\lambda'}\omega_{\lambda''}}}\Phi^{\alpha\beta\gamma}_{ijk}e^{i \mathbf{q}\cdot\mathbf{r}_i + i \mathbf{q}'\cdot\mathbf{r}_j+i \mathbf{q}''\cdot\mathbf{r}_k}
\end{equation}
$$
-as well as
+At the fourth-order, the contribution is
$$
\begin{equation}
\begin{split}
-& \sum_{\lambda\lambda'\lambda''}
-\left\langle f \middle\vert
-\left(a_{\lambda}+a_{\lambda}^\dagger \right)
-\left(a_{\lambda'}+a_{\lambda'}^\dagger \right)
-\left(a_{\lambda''}+a_{\lambda''}^\dagger \right)
-\middle\vert i \right\rangle = \\
-= & \sum_{\lambda\lambda'\lambda''} \left\langle f \middle\vert
-\hat{a}_{\lambda} \hat{a}_{\lambda'} \hat{a}_{\lambda''} + \hat{a}_{\lambda} \hat{a}_{\lambda'} \hat{a}^{\dagger}_{\lambda''} + \hat{a}_{\lambda} \hat{a}^{\dagger}_{\lambda'} \hat{a}_{\lambda''} + \hat{a}_{\lambda} \hat{a}^{\dagger}_{\lambda'} \hat{a}^{\dagger}_{\lambda''} + \hat{a}^{\dagger}_{\lambda} \hat{a}_{\lambda'} \hat{a}_{\lambda''} + \hat{a}^{\dagger}_{\lambda} \hat{a}_{\lambda'} \hat{a}^{\dagger}_{\lambda''} + \hat{a}^{\dagger}_{\lambda} \hat{a}^{\dagger}_{\lambda'} \hat{a}_{\lambda''} + \hat{a}^{\dagger}_{\lambda} \hat{a}^{\dagger}_{\lambda'} \hat{a}^{\dagger}_{\lambda''}
-\middle\vert i \right\rangle = \\
-= & \sum_{\lambda\lambda'\lambda''} \left\langle f \middle\vert
-a_{\lambda}a_{\lambda'}a^\dagger_{\lambda''}
-\middle\vert i \right\rangle
- = 3 \sqrt{n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)}
+\Gamma_\lambda^{4\mathrm{ph}} = \frac{\pi}{96} \sum_{\lambda'\lambda''\lambda'''} \vert \Psi_{\lambda\lambda'\lambda''\lambda'''} \vert^2
+&\big[ (n_{\lambda'} + 1)(n_{\lambda''} + 1)(n_{\lambda'''} + 1) - n_{\lambda'}n_{\lambda''}n_{\lambda'''} (\delta(\omega_{\lambda} - \omega_{\lambda'} - \omega_{\lambda''} - \omega_{\lambda'''}) - \delta(\omega_{\lambda} + \omega_{\lambda'} + \omega_{\lambda''} + \omega_{\lambda'''})) \\
+&+ 3 n_{\lambda'}(n_{\lambda''} + 1)(n_{\lambda'''} + 1) - (n_{\lambda'} + 1) n_{\lambda''}n_{\lambda'''} (\delta(\omega_{\lambda} + \omega_{\lambda'} - \omega_{\lambda''} - \omega_{\lambda'''}) - \delta(\omega_{\lambda} - \omega_{\lambda'} + \omega_{\lambda''} + \omega_{\lambda'''}))]
\end{split}
\end{equation}
$$
-where the factor 3 comes from the multiplicity, to get at
+where the sum is also over momentum conserving processes, $\mathbf{q} + \mathbf{q}' + \mathbf{q}'' + \mathbf{q}''' = \mathbf{G}$ and the four-phonon matrix elements are given by
$$
\begin{equation}
-{\left\langle f \middle\vert \hat{H}_3 \middle\vert i \right\rangle} =
-\frac{1}{2}
-\sum_{ijk} \sum_{\alpha\beta\gamma}\Phi_{ijk}^{\alpha\beta\gamma}
-\sqrt{n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)}
-%
-\left(\frac{\hbar}{2N}\right)^{3/2}
-\frac{
-\epsilon_{\lambda}^{i \alpha}
-\epsilon_{\lambda'}^{j \beta}
-\epsilon_{\lambda''}^{k \gamma}
-}{
-\sqrt{m_{i}m_{j}m_{j}}
-\sqrt{
- \omega_{\lambda}
- \omega_{\lambda'}
- \omega_{\lambda''}}
-}
-e^{i \mathbf{q}\cdot\mathbf{r}_i + i \mathbf{q}'\cdot\mathbf{r}_j+i \mathbf{q}''\cdot\mathbf{r}_k}
+\Psi_{\lambda\lambda'\lambda''\lambda'''} = \sum_{ijkl} \sum_{\alpha\beta\gamma\delta} \frac{\epsilon_{\lambda}^{i \alpha}\epsilon_{\lambda'}^{j \beta}\epsilon_{\lambda''}^{k \gamma}\epsilon_{\lambda'''}^{l \delta}}
+{\sqrt{m_{i}m_{j}m_{k}m_{l}}\sqrt{\omega_{\lambda}\omega_{\lambda'}\omega_{\lambda''}\omega_{\lambda'''}}}\Phi^{\alpha\beta\gamma\delta}_{ijkl}e^{i \mathbf{q}\cdot\mathbf{r}_i + i \mathbf{q}'\cdot\mathbf{r}_j+i \mathbf{q}''\cdot\mathbf{r}_k+i\mathbf{q}'''\cdot\mathbf{r}_l}
\end{equation}
$$
-The initial factor 1/2 is the multiplicity cancelled by the 3! from the Hamiltonian. Here, as it happens, we can identify the three-phonon matrix elements and simplify a little bit more
+The contribution to the scattering rate by isotopic disorder can be computed to Tamura's model[^Tamura1983], written
$$
\begin{equation}
-{\left\langle f \middle\vert \hat{H}_3 \middle\vert i \right\rangle} =
-\frac{1}{2}
-\sqrt{n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)}
-\left(\frac{\hbar}{2N}\right)^{3/2}
-\Phi_{\lambda\lambda'\lambda''}
+\Gamma_{\lambda}^{\mathrm{iso}} = \frac{\pi}{4} \sum_{\lambda'} \omega_{\lambda} \omega_{\lambda'} \sum_i g_i \vert \epsilon_\lambda^{i\dagger} \epsilon_{\lambda'}^{i} \vert \delta(\omega_{\lambda} - \omega_{\lambda'})
\end{equation}
$$
-The probability of this particular three-phonon process can be estimated via the Fermi golden rule:
+where the mass variance parameter $g$ is written
$$
\begin{equation}
-\begin{split}
-P_{\lambda\lambda'\rightarrow\lambda''} & =\frac{2\pi}{\hbar}
-\left|{\left\langle f \middle\vert \hat{H}_3 \middle\vert i \right\rangle}\right|^2
-\delta(E_f-E_i) =
-\frac{\hbar^2\pi}{16N}
-n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)
-\left| \Phi_{\lambda\lambda'\lambda''} \right|^2
-\delta(E_f-E_i)
-\end{split}
-\end{equation}
-$$
-
-With near identical reasoning, we can also arrive at
-
-$$
-\begin{equation}\label{pplus}
-P_{\lambda\rightarrow\lambda'\lambda''} =
-\frac{\hbar^2\pi}{16N}
-n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1)
-\left| \Phi_{\lambda\lambda'\lambda''} \right|^2
-\delta(E_f-E_i)
+g_i=\sum_j c_{i}^j \left(\frac{m_i^j-\bar{m_i}}{\bar{m_i}}\right)^2
\end{equation}
$$
-for the other kind of three-phonon processes, and
+In this equation, $\bar{m_i}$ is the average isotopic mass( $\bar{m_i}=\sum_j c_i^j m_i^j$ ), $m^j_i$ is the mass of isotope $j$ of atom $i$ and $c^j_i$ is its concentration.
-$$
-\begin{equation}\label{pminus}
-P_{\lambda\rightarrow\lambda'} =\frac{2\pi}{\hbar}\left|\langle f | H^{\textrm{iso}} | i \rangle \right|^2\delta(E_f-E_i) =
-\frac{\pi\hbar}{2N} n_{\lambda}(n_{\lambda'}+1) \Lambda_{\lambda\lambda'}\delta(E_f-E_i)
-\end{equation}
-$$
-
-for the isotope scattering. I leave those derivations as an exercise. The phonon Boltzmann equation is stated as:
+Finally, scattering by domain boundaries is implemented as
$$
-\begin{equation}\label{eq:pbe}
-\frac{\partial \tilde{n}_\lambda}{\partial T} \mathbf{v}_\lambda \cdot \nabla T =
-\left. \frac{\partial \tilde{n}_\lambda }{\partial t} \right|_{\mathrm{coll}}
+\begin{equation}
+\Gamma_{\lambda}^{\mathrm{boundary}} = \frac{v_{\lambda}}{2 L}
\end{equation}
$$
-Where $\tilde{n}$ is the non-equilibrium occupation number. This is ridiculously complicated. To make life easier, we only consider the terms we outlined above as possible collisions. Gathering all possible events that involve mode $\lambda$ we get
-
-$$
-\begin{equation}\label{manyprob}
-\begin{split}
-\left. \frac{\partial n_{\lambda}}{ \partial t} \right|_{\mathrm{coll}}
-= & \sum_{\lambda'}
-\left( P_{\lambda\rightarrow\lambda'}-P_{\lambda'\rightarrow\lambda } \right) +
-\sum_{\lambda'\lambda''}
-- P_{\lambda \rightarrow \lambda' \lambda'' }
-- P_{\lambda \rightarrow \lambda''\lambda' }
-+ P_{\lambda' \rightarrow \lambda \lambda'' }
-+ P_{\lambda' \rightarrow \lambda''\lambda }
-+ P_{\lambda''\rightarrow \lambda \lambda' }
-+ P_{\lambda''\rightarrow \lambda' \lambda } \\
-& - P_{\lambda \lambda' \rightarrow \lambda'' }
-- P_{\lambda \lambda'' \rightarrow \lambda' }
-- P_{\lambda' \lambda \rightarrow \lambda'' }
-+ P_{\lambda' \lambda'' \rightarrow \lambda }
-- P_{\lambda'' \lambda \rightarrow \lambda' }
-+ P_{\lambda'' \lambda' \rightarrow \lambda }
-\end{split}
-\end{equation}
-$$
+where $L$ is a characteristic domain size.
-Which does not seem to make life easier. To make it slightly worse, we insert \ref{pplus} and \ref{pminus} into this, and at the same time say that the non-equilibrium distribution functions are the equilibrium distributions, plus a (small) deviation:
-$$
-\begin{equation}
-\tilde{n}_{\lambda}\approx n_{\lambda}+\epsilon_{\lambda}
-\end{equation}
-$$
+### The diagonal contribution
-After some [hard work](https://reference.wolfram.com/language/ref/FullSimplify.html), and discarding terms of $\epsilon^2$ and higher, we get
+The diagonal contribution to the heat current is written
$$
\begin{equation}
-\begin{split}
-\left. \frac{\partial n_{\lambda}}{ \partial t} \right|_{\mathrm{coll}}
-= & \sum_{\lambda'\lambda''}
-\frac{\hbar\pi}{8N}
-\left| \Phi_{\lambda\lambda'\lambda''} \right|^2 \Big(
-\left[
--n_{\lambda} \epsilon_{\lambda'} + n_{\lambda''} (\epsilon_{\lambda} + \epsilon_{\lambda'}) + \epsilon_{\lambda''} + n_{\lambda} \epsilon_{\lambda''} + n_{\lambda'} (-\epsilon_{\lambda} + \epsilon_{\lambda''})
-\right]\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''}) + \\
-& \left[
-\epsilon_{\lambda'} + n_{\lambda} \epsilon_{\lambda'} + n_{\lambda''} (-\epsilon_{\lambda} + \epsilon_{\lambda'}) - n_{\lambda} \epsilon_{\lambda''} +
- n_{\lambda'} (\epsilon_{\lambda} + \epsilon_{\lambda''} )
-\right]\delta(\omega_{\lambda}-\omega_{\lambda'}+\omega_{\lambda''}) - \\
-& \left[(1 + n_{\lambda'} + n_{\lambda''})\epsilon_{\lambda} - n_{\lambda''}\epsilon_{\lambda''} - n_{\lambda'} \epsilon_{\lambda''} + n_{\lambda} (\epsilon_{\lambda'} + \epsilon_{\lambda''} )\right]
-\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''}) \Big)
-\end{split}
+J_{\alpha}^{\mathrm{d}} = \sum_\lambda \hbar \omega_\lambda v_{\lambda}^{\alpha} A_\lambda B_\lambda
\end{equation}
$$
-Which does not seem like a lot of help. If we make another substitution, and say that the deviation from equilibrium behaves sort of like the equilibrium (with no loss of generality, just to make life easier):
+Injecting it into the Green-Kubo formula, we obtain that the thermal conductivity tensor is proportional to a four-point correlation
$$
\begin{equation}
-\epsilon_{\lambda} =
-\frac{\partial n_{\lambda} }{\partial \omega_\lambda}
-\frac{k_B T}{\hbar} \zeta_{\lambda}=-n_{\lambda}(n_{\lambda}+1) \zeta_{\lambda}
+\kappa_{\alpha\beta}^\mathrm{d} \propto \int_0^\infty dt \int_0^\beta d\lambda \langle A_\lambda(i\hbar\lambda) B_\lambda(i\hbar\lambda) A_{\lambda'}(t) B_{\lambda'}(t) \rangle
\end{equation}
$$
-Inserting this, and more tedious algebra, we get
+Solving the integral of this four-point correlation is a cumbersome task, and we refer the reader to references [^Fiorentino2023],[^Castellano2024] for the detailed derivation.
+In a nutshell, an equation of motion is formulated for the four-point correlation.
+This equation of motion is then solved using a Laplace transform and injected in the thermal conductivity tensor to give
$$
\begin{equation}
\begin{split}
-\left. \frac{\partial n_{\lambda}}{ \partial t} \right|_{\mathrm{coll}}
-=& \frac{\hbar\pi}{4N}
-\sum_{\lambda'\lambda''}
-\left| \Phi_{\lambda\lambda'\lambda''} \right|^2 \Big(
-n_{\lambda} n_{\lambda'} (n_{\lambda''}+1) \delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''} )
-\left( \zeta_{\lambda} + \zeta_{\lambda'} - \zeta_{\lambda''} \right) + \\
-& \frac{1}{2} n_{\lambda} (n_{\lambda'}+1) (n_{\lambda''}+1) \delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})
-\left( \zeta_{\lambda} - \zeta_{\lambda'} -\zeta_{\lambda''} \right) \Big)
+\kappa_{\alpha\beta}^{\mathrm{d}} =& \frac{1}{V} \sum_{\lambda\lambda'} v_{\lambda}^{\alpha} v_{\lambda}^{\beta} c_\lambda \Xi^{-1}({\lambda\lambda'}) \\
+=& \frac{1}{V} \sum_{\lambda} c_\lambda v_{\lambda}^{\alpha} F_{\lambda\beta}
\end{split}
\end{equation}
$$
-If we add the isotope term again, that I forgot at some point between the beginning and here, we can rearrange this in terms of scattering rates that should look familiar (using strange relations for occupation numbers that only hold when the deltafunctions in energy are satisfied):
+with $c_\lambda = n_\lambda (n_\lambda + 1) \omega_\lambda^2 / k_{\mathrm{B}}T^2$ and where the vector $F_{\lambda}^{\beta}$, defined as
$$
\begin{equation}
-\left. \frac{\partial n_{\lambda}}{ \partial t} \right|_{\mathrm{coll}} =
-\sum_{\lambda'\lambda''}
-\tilde{P}^{+}_{\lambda\lambda'\lambda''}
-\left( \zeta_{\lambda}+\zeta_{\lambda'}-\zeta_{\lambda''} \right)
-+\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
-\left( \zeta_{\lambda}-\zeta_{\lambda'}-\zeta_{\lambda''} \right)+
-\sum_{\lambda'}
-\tilde{P}^\textrm{iso}_{\lambda\lambda'} \left( \zeta_{\lambda}-\zeta_{\lambda'} \right)
+F_{\lambda\alpha} = \Xi^{-1} v_{\lambda}^{\alpha}
\end{equation}
$$
-where
+is simply introduced to ease the computation of the thermal conductivity tensor.
-$$
-\begin{align}
-\tilde{P}^{+}_{\lambda\lambda'\lambda''}&=
-\frac{\hbar \pi}{4 N}
-n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)\left|\Phi_{\lambda\lambda'\lambda''}\right|^2
-\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''})
-\\
-\tilde{P}^{-}_{\lambda\lambda'\lambda''}&=
-\frac{\hbar \pi}{4 N}
-n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1)\left|\Phi_{\lambda\lambda'\lambda''}\right|^2
-\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})
-\\
-\tilde{P}^\textrm{iso}_{\lambda\lambda'} &=
-\frac{\pi}{2N} n_{\lambda}(n_{\lambda'}+1) \Lambda_{\lambda\lambda'}
-\delta(\omega_{\lambda}-\omega_{\lambda})
-\end{align}
-$$
+In the previous equation, $\Xi$ is called the scattering matrix.
+The diagonal component of this matrix is equal to the scattering rates $\Gamma_\lambda$ of phonons while the off-diagonal part describes the coupling between modes, which introduce collective phonon contributions to heat transport.
-What we have done here is to rearrange the transition propabilities to scattering rates. If we let
+Using the Neumann series for matrix inversion, $F_{\lambda}^{\alpha}$ can be computed self-consistently [^Omini],[^Omini1996] as
$$
\begin{equation}
-\zeta_{\lambda}=\frac{\hbar}{k_B T} \mathbf{F}_{\lambda} \cdot \nabla T
+F_{\lambda\alpha}^{n+1} = F_{\lambda\alpha}^0 - \tau_\lambda \sum_{\lambda'} \Xi_{\lambda\lambda'} F_{\lambda\alpha}^n
\end{equation}
$$
-and combine everything we end up with
+where the starting point is given by
$$
\begin{equation}
-\begin{split}
--\frac{\omega_{\lambda}}{T}n_{\lambda}(n_{\lambda}+1)\mathbf{v}_{\lambda} \cdot \nabla T = &
- \sum_{\lambda'}
-\tilde{P}^\textrm{iso}_{\lambda\lambda'}
-\left(\mathbf{F}_{\lambda}-\mathbf{F}_{\lambda'}\right)\cdot\nabla T +
-\sum_{\lambda'\lambda''}
-\tilde{P}^{+}_{\lambda\lambda'\lambda''}
-\left(\mathbf{F}_{\lambda}+\mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''}\right)\cdot\nabla T+
-\tilde{P}^{-}_{\lambda\lambda'\lambda''}
-\left(\mathbf{F}_{\lambda}-\mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''}\right)\cdot\nabla T =
-\\ = &
-\mathbf{F}_{\lambda}\cdot\nabla T
-\left(
-\sum_{\lambda'}
-\tilde{P}^\textrm{iso}_{\lambda\lambda'}
-+
-\sum_{\lambda'\lambda''}
-\tilde{P}^{+}_{\lambda\lambda'\lambda''}+
-\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
-\right)- \\
-& - \sum_{\lambda'}
-\tilde{P}^\textrm{iso}_{\lambda\lambda'}\mathbf{F}_{\lambda'}\cdot\nabla T +
-\sum_{\lambda'\lambda''}
-\tilde{P}^{+}_{\lambda\lambda'\lambda''}
-\left(\mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''}\right)\cdot\nabla T-
-\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
-\left(\mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''}\right)\cdot\nabla T
-\end{split}
+F_{\lambda\alpha}^0 = v_{\lambda}^{\alpha} \tau_{\lambda\alpha}
\end{equation}
$$
-Where we can identify
+If the off-diagonal part of the scattering matrix are neglected, one obtain the single mode approximation, written
$$
\begin{equation}
-Q_{\lambda}=\sum_{\lambda'}
-\tilde{P}^\textrm{iso}_{\lambda\lambda'}
-+
-\sum_{\lambda'\lambda''}
-\tilde{P}^{+}_{\lambda\lambda'\lambda''}+
-\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
+\kappa_{\alpha\beta}^{\mathrm{d},\mathrm{SMA}} = \frac{1}{V} \sum_{\lambda\lambda'} v_{\lambda}^{\alpha} v_{\lambda}^{\beta} c_\lambda \tau_\lambda
\end{equation}
$$
-And rearrange terms
+This approximation consists in neglecting the collective phonon contribution to the thermal conductivity tensor and can also be obtain by decoupling the four-point correlation in product of two-point correlation.
-$$
-\begin{equation}
-\mathbf{F}_{\lambda}=
-\frac{\omega_{\lambda} \bar{n}_{\lambda}(\bar{n}_{\lambda}+1)\mathbf{v}_{\lambda} }{T Q_{\lambda}}
-+
-\frac{1}{Q_{\lambda}}\left[
-\sum_{\mathbf{q}'\mathbf{q}''}\sum_{s's''}
-\tilde{P}^{+}_{\lambda\lambda'\lambda''}
-\left( \mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''} \right)-
-\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
-\left( \mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''} \right)
-\right]
-\end{equation}
-$$
+It is important to note that while starting from differents considerations, this formulation and the Boltzmann equation [^peierls1929],[^peierls1955quantum],[^Broido2007],[^Broido2005] are strictly equivalent [^Fiorentino2023].
-And we have a set of equations for $F$ that we can solve self-consistently. Previously, we used the imaginary part of the self-energy to get a phonon lifetime. What we got here, from Fermi golden rule, is related:
-$$
-\sum_{\lambda'} \tilde{P}^\textrm{iso}_{\lambda\lambda'} =
-\frac{\pi}{2N} n_{\lambda}(n_{\lambda}+1) \sum_{\lambda'} \Lambda_{\lambda\lambda'}
-\delta(\omega_{\lambda}-\omega_{\lambda}) = 2 n_{\lambda}(n_{\lambda}+1) \Gamma^{\textrm{iso}}_{\lambda}
-$$
+### The off-diagonal contribution
-This can also be done for the three-phonon terms:
+The off diagonal heat tensor is written
$$
\begin{equation}
-\begin{split}
-\sum_{\lambda'\lambda''} \tilde{P}^{+}_{\lambda\lambda'\lambda''}+
-\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''} & =
-\frac{\hbar \pi}{8 N}
-\sum_{\lambda'\lambda''} \left|\Phi_{\lambda\lambda'\lambda''}\right|^2
-\left[
-n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1) \delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})+
-2n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''})
-\right] \\
-& = n_{\lambda}(n_{\lambda}+1) \frac{\hbar \pi}{8 N}
-\sum_{\lambda'\lambda''} \left|\Phi_{\lambda\lambda'\lambda''}\right|^2
-\left[
-\frac{n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1)}{n_{\lambda}(n_{\lambda}+1)}
-\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})
-+
-\frac{2n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)}{n_{\lambda}(n_{\lambda}+1)}
-\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''})
-\right] \\
-& =
-n_{\lambda}(n_{\lambda}+1) \frac{\hbar \pi}{8 N}
-\sum_{\lambda'\lambda''} \left|\Phi_{\lambda\lambda'\lambda''}\right|^2
-\left[
-(n_{\lambda'}+n_{\lambda''}+1)
-\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})
-+
-(n_{\lambda'}-n_{\lambda''})
-\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''})
-\right] \\
-& = 2 n_{\lambda}(n_{\lambda}+1) \Gamma_{\lambda}
-\end{split}
+J_{\alpha}^{\mathrm{nd}} = \sum'_{\lambda \lambda'} \hbar \omega_{\lambda'} v_{\lambda\lambda'}^{\alpha} A_\lambda B_{\lambda'}
\end{equation}
$$
-Where the second to last step seems a little impossible, but with $\hbar\omega/k_BT = x$, you get
+where $\sum'$ indicates that $\lambda = \lambda'$ is excluded from the sum.
+Injecting this contribution into the Green-Kubo formula also ends up in something proportional to a four-point correlation function
$$
\begin{equation}
-\frac{ n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1) }{ n_{\lambda}(n_{\lambda}+1) } -
-\left( n_{\lambda'} + n_{\lambda''} + 1 \right)
-=
-\frac{
-1-\exp[x'+x''-x]
-}{
-\left( \exp[x'] -1 \right) \left( \exp[x''] -1 \right)
-}
+\kappa_{\alpha\beta}^\mathrm{nd} \propto \int_0^\infty dt \int_0^\beta d\lambda \langle A_{\lambda}(i\hbar\lambda) B_{\lambda'}(i\hbar\lambda) A_{\lambda''}(t) B_{\lambda'''}(t) \rangle
\end{equation}
$$
-which comes out to 0 when $x=x'+x''$, which the deltafunction ensures. In the same way
+For this contribution, we will directly neglect the collective part and decouple the four-point correlation in product of two-point correlations
$$
\begin{equation}
-\frac{ n_{\lambda}n_{\lambda'}(n_{\lambda''}+1) }{ n_{\lambda}(n_{\lambda}+1) } -
-\left( n_{\lambda'} - n_{\lambda''} \right)
-=
-\frac{
-\exp[-x]\left(\exp[x+x']-\exp[x''] \right)
-}{
-\left( \exp[x'] -1 \right) \left( \exp[x''] -1 \right)
-}
+\langle A_{\lambda} B_{\lambda'} A_{\lambda''} B_{\lambda'''} \rangle \approx \langle A_{\lambda} A_{\lambda''} \rangle \langle B_{\lambda'} B_{\lambda'''} \rangle + ...
\end{equation}
$$
-comes out to 0 when $x''=x+x'$. We can directly relate the relaxation time lifetime
+Performing some Fourier transform, we can now express the integral in term of spectral function $\chi_{\lambda}''(\Omega)$
$$
\begin{equation}
-\tau_{\lambda} = \frac{1}{2\Gamma_{\lambda}} = \frac{ n_{\lambda}(n_{\lambda}+1) }{Q_{\lambda}}
+\int_0^\infty dt \int_0^\beta d\lambda \langle A_{\lambda}(i\hbar\lambda) B_{\lambda'}(i\hbar\lambda) A_{\lambda''}(t) B_{\lambda'''}(t) \rangle \approx \int_{-\infty}^{\infty} d\Omega \chi_{\lambda}''(\Omega) \chi_{\lambda'}''(\Omega) \Omega^2 n(\Omega) (n(\Omega) + 1)
\end{equation}
$$
-to an initial guess
-
-$$
-\mathbf{F}^0_{\lambda} =
-\frac{\tau_{\lambda} \omega_{\lambda} \mathbf{v}_{\lambda} }{T}
-$$
-
-and iteratively solve
+Recalling that we are working in the Markovian approximation, we can approximate these spectral functions as Lorentzian, and we can make the approximation that these will act as Dirac deltas centered on the harmonic frequencies.
+This allows to perform the integral analytically, and we finally obtain the off diagonal contribution to the thermal conductivity tensor as
$$
\begin{equation}
-\mathbf{F}^{i+1}_{\lambda}=
-\mathbf{F}^0_{\lambda}
-+
-\frac{1}{Q_{\lambda}}\left[
-\sum_{\lambda'\lambda''}
-\tilde{P}^{+}_{\lambda\lambda'\lambda''}
-\left( \mathbf{F}^{i}_{\lambda'}-\mathbf{F}^{i}_{\lambda''} \right)-
-\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
-\left( \mathbf{F}^{i}_{\lambda'}-\mathbf{F}^{i}_{\lambda''} \right)
-\right]
+\kappa_{\alpha\beta}^{\mathrm{nd}} = \frac{1}{V} \sum_{\lambda\lambda'} v_{\lambda\lambda'}^{\alpha}v_{\lambda\lambda'}^{\beta} \frac{c_\lambda + c_{\lambda'}}{2} \Gamma_{\lambda\lambda'}
\end{equation}
$$
-to arrive at the non-equilibrium distributions. The thermal conductivity tensor is then given as
+with
$$
\begin{equation}
-\kappa_{\alpha\beta} =
-\frac{1}{V}
-\sum_{\lambda}
-\frac{T c_{\lambda} v_{\lambda}^\alpha F_{\lambda}^\beta}{\omega_{\lambda}}
+\Gamma_{\lambda\lambda'} = \frac{\Gamma_\lambda + \Gamma_{\lambda'}}{(\omega_\lambda - \omega_{\lambda'})^2 + (\Gamma_\lambda + \Gamma_{\lambda'})^2}
\end{equation}
$$
-### Cumulative kappa
-
-@todo Check code snippets
-
-@todo Spectral kappa, links to things.
-
-Experimentally, the cumulative thermal conductivity with respect to phonon mean free path,
-
-$$
-l_{\lambda} = \left| v_{\lambda} \right| \tau_{\lambda} \,,
-$$
-
-can be measured.[^Minnich2012] The cumulative thermal conductivity can then be computed as a sum of the fraction of heat that is carried by phonons with mean free paths smaller than $l$:
+This off-diagonal contribution, describing wavelike-interference between phonons of similar frequencies, becomes important for system with complex unitcell.
+While the derivation sketched here is based on the Hardy current[^Isaeva2019], it can also be obtain from a Wigner description of heat transport [^Simoncelli2019], with very similar results[^Caldarelli2022].
-$$
-\kappa_{\alpha\beta}^{\textrm{acc}}(l)=
-\frac{1}{V} \sum_{\lambda}
-C_{\lambda} v^{\alpha}_{\lambda} v^{\beta}_{\lambda} \tau_{\lambda} \Theta(l- l_{\lambda} ) \,,
-$$
+### Monte-Carlo integration for the scattering rates
-where $\Theta$ is the Heaviside step function.
+To reach the thermodynamic limit, the thermal conductivity has to be computed on a large grid of q-points, which can make the computation quite expensive.
+This cost comes almost entirely from the computation of the scattering.
-One can also define a spectral thermal conductivity as
-
-$$
-\kappa_{\alpha\beta}(\omega)=
-\frac{1}{V} \sum_{\lambda}
-C_{\lambda} v^{\alpha}_{\lambda} v^{\beta}_{\lambda} \tau_{\lambda} \delta(\omega- \omega_{\lambda} )
-$$
+However, one can observe that the computation of $\kappa$ actually requires two kind of integrations.
+The first is the sum of the contribution of each q-point to the thermal conductivity, while the second one correspond to the computation of the scattering.
-which is a measure which frequencies contribute most to thermal transport.
+Fortunately for us, these two integrations converges at different rates.
+In particular, the expensive scattering integration converges more quickly than the thermal conductivity integration.
-### Thin film scattering
+Thus, to improve the computational cost, the code offers the possibility to decouple these two integrations by using a Monte-Carlo integration of the scattering.
+For this, we generate a full grid, on which the thermal conductivity will be integrated.
+A subset of this full grid can then be selected to perform the scattering integration.
+In order to improve the convergence, these point are not selected entirely at random but using a stratified approached in order to sample more uniformly the Brillouin zone.
-Constrained geometries will incur additional scattering from domain boundaries. For a thin film (thin, but thick enough that the interior of the film is accurately described by bulk phonons) one can estimate the suppression due to film thinkness.[^Minnich2015] Assyming the cross-plane direction of the film is in the $y$-direction, and the thermal gradient is applied in the $z$-direction, the in-plane thermal conductivity $\kappa_{zz}$ is supressed as:
+This is schematically represented in the following picture, where each dot represents a point on a $8\times8$ grid, with the red dot corresponding to point selected for a Monte-Carlo integration equivalent to a $4\times4$ grid and the bar representing the way the grid is stratified.
-$$
-\kappa_{zz}(d)=A+B+C,
-$$
+
+
+
-where
+The code allows to use different Monte-Carlo grids for third and fourth order, using the variables `--qpoint_grid3ph` and `--qpoint_grid4ph`.
-$$
-\begin{split}
-x_{\lambda} = & \frac{\hbar\omega_{\lambda}}{V}
-\frac{\partial n_{\lambda}} {\partial T}
-v_{\lambda}^z l_{\lambda}^z
- \\
-A = & -\frac{1}{d} \sum_{v_y>0}
-x_{\lambda}
-\left( -l^{y}_{\lambda} \exp\left[\frac{d}{l^{y}_{\lambda}}\right]+l^{y}_{\lambda}-d \right) \\
-B = & -\frac{1}{d} \sum_{v_y<0}
-x_{\lambda}
-\left(
-l^{y}_{\lambda}
-\exp\left[ -\frac{d}{l^{y}_{\lambda}} \right]
--l^{y}_{\lambda}-d
-\right) \\
-C = & \sum_{v_y=0} x_{\lambda}
-\end{split}
-$$
+It is important to note that since the points are selected randomly, the results will be noisy.
+However, the noise reduces as the density of the Monte-Carlo grids increases, to finally vanish if the Monte-Carlo and full grid density are the same (which is the default).
+Similarly to the full grid on which the thermal conductivity is computed, the Monte-Carlo grid densities are parameters to be carefully converged.
-where $v_y$ and $v_z$ are the components of the phonon group velocity along the $y$ and $z$ directions, $\tau_{\lambda}$ is the phonon relaxation time. $l^{y}_{\lambda}$ is the $y$ component of the MFP and $d$ is the thickness of the film in $y$-direction.
### Input files
@@ -700,126 +303,37 @@ These files are necesarry:
and these are optional:
* [infile.isotopes](../files.md#infile.isotopes) (for non-natural isotope distribution)
+* [infile.forceconstant_fourthorder](extract_forceconstants.md#infile.forceconstant_fourthorder)
### Output files
-Depending on options, the set of output files may differ. We start with the basic files that are written after running this code.
-
-#### `outfile.thermal_conductivity`
-
-This file contains components of the thermal conductivity tensor $\kappa_{\alpha \beta}$ for each temperature.
-
-
-
- | Row |
- Description |
-
-
-
- | 1 |
-
- \( T_1 \qquad \kappa_{xx} \quad \kappa_{yy} \quad \kappa_{zz} \quad \kappa_{xz} \quad \kappa_{yz} \quad \kappa_{xy} \quad \kappa_{zx} \quad \kappa_{zy} \quad \kappa_{yx} \)
- |
-
-
- | 2 |
-
- \( T_2 \qquad \kappa_{xx} \quad \kappa_{yy} \quad \kappa_{zz} \quad \kappa_{xz} \quad \kappa_{yz} \quad \kappa_{xy} \quad \kappa_{zx} \quad \kappa_{zy} \quad \kappa_{yx} \)
- |
-
-
- | ... |
- ... |
-
-
-
-
-#### `outfile.cumulative_kappa.hdf5`
-
-This file is self-explainatory. It contains the different cumulative plots described above, at a series of temperatures. Below is a matlab snippet that plots part of the output.
+### `outfile.kappa`
-```matlab
-figure(1); clf; hold on; box on;
+This file contains the thermal conductivity tensor, with the decomposition from all contributions, in a format that can be parsed with tools such as numpy.
+It looks like this
-% filename
-fn='outfile.cumulative_kappa.hdf5';
-% which temperature?
-t=1;
-
-subplot(1,3,1); hold on; box on;
-
- % read in cumulative kappa vs mean free path from file
- x=h5read(fn,['/temperature_' num2str(t) '/mean_free_path_axis']);
- xunit=h5readatt(fn,['/temperature_' num2str(t) '/mean_free_path_axis'],'unit');
- y=h5read(fn,['/temperature_' num2str(t) '/cumulative_kappa_vs_mean_free_path_total']);
- % projections to modes and/or atoms
- z=h5read(fn,['/temperature_' num2str(t) '/cumulative_kappa_vs_mean_free_path_per_atom']);
- %z=h5read(fn,['/temperature_' num2str(t) '/cumulative_kappa_vs_mean_free_path_per_mode']);
-
- yunit=h5readatt(fn,['/temperature_' num2str(t) '/cumulative_kappa_vs_mean_free_path_total'],'unit');
-
- % plot
- plot(x,y)
- plot(x,z)
-
- % set a legend
- lgd{1}='Total';
- for i=1:size(z,2)
- lgd{i+1}=['Atom ' num2str(i)];
- end
- l=legend(lgd);
- set(l,'edgecolor','none','location','northwest');
-
- % some titles
- title('Cumulative kappa vs mean free path');
- ylabel(['Cumulative \kappa (' yunit ')']);
- xlabel(['Mean free path (' xunit ')']);
-
- % get some reasonable ranges
- minx=x(max(find(ymax(y*0.9999))))*2;
- xlim([minx maxx]);
- set(gca,'xscale','log','yminortick','on');
-
-subplot(1,3,2); hold on; box on;
-
- % read in spectral kappa vs frequency
- x=h5read(fn,['/temperature_' num2str(t) '/frequency_axis']);
- y=h5read(fn,['/temperature_' num2str(t) '/spectral_kappa_vs_frequency_total']);
- z=h5read(fn,['/temperature_' num2str(t) '/spectral_kappa_vs_frequency_per_mode']);
- %z=h5read(fn,['/temperature_' num2str(t) '/spectral_kappa_vs_frequency_per_atom']);
-
- plot(x,y)
- plot(x,z)
-
- set(gca,'xminortick','on','yminortick','on')
- xlabel('Frequency (THz)')
- ylabel('Spectral \kappa (W/m/K/THz)')
- title('Spectral kappa vs frequency')
-
-subplot(1,3,3); hold on; box on;
-
- % read in cumulative kappa vs mean free path from file
- x=h5read(fn,['/temperature_' num2str(t) '/boundary_scattering_lengths']);
- y=h5read(fn,['/temperature_' num2str(t) '/boundary_scattering_kappa']);
- % grab only kxx
- y=squeeze(y(1,1,:));
- plot(x,y)
-
- set(gca,'xscale','log','yminortick','on')
- xlabel('Domain size (m)')
- ylabel('Kappa (W/mK)')
- title('Kappa vs boundary scattering')
-
- % get a reasonable range in x
- minx=max(x(find(ymax(y*0.9999))))*2
- xlim([minx maxx])
+```
+# Unit: W/m/K
+# Temperature: 0.300000000000E+03
+# Single mode approximation
+# kxx kyy kzz kxy kxz kyz
+ 0.708649123335E+02 0.708649123335E+02 0.708649123335E+02 0.000000000000E+00 0.000000000000E+00 0.000000000000E+00
+# Collective contribution
+# kxx kyy kzz kxy kxz kyz
+ 0.475409189194E+01 0.475409189194E+01 0.475409189194E+01 0.000000000000E+00 0.000000000000E+00 0.000000000000E+00
+# Off diagonal (coherence) contribution
+# kxx kyy kzz kxy kxz kyz
+ 0.854567548533E-03 0.854567548533E-03 0.854567548533E-03 0.000000000000E+00 0.000000000000E+00 0.000000000000E+00
+# Total thermal conductivity
+# kxx kyy kzz kxy kxz kyz
+ 0.756198587929E+02 0.756198587929E+02 0.756198587929E+02 0.000000000000E+00 0.000000000000E+00 0.000000000000E+00
```
-#### `outfile.grid_thermal_conductivity.hdf5`
-Option `--dumpgrid` produces this self-explainatory file. It will not get written if you use more than one temperature, the reason is that this file can get uncomfortably large, nearly all quantities on the full q-grid are written. Below is a matlab snippet that plots a subset:
+#### `outfile.grid_kappa.hdf5`
+
+This file contains nearly all quantities on the full q-grid.
+Below is a matlab snippet that plots a subset:
```matlab
@@ -868,14 +382,12 @@ subplot(1,3,3); hold on; box on;
```
+[^Castellano2024]: Castellano, A & Batista, J. P. & Verstraete, M. J. (2024). Temperature generalization of thermal transport in anharmonic crystals: theory and efficient implementation. ArXiv
+
[^peierls1929]: Peierls, R. E. (1929). Annalen der Physik, 3, 1055
[^peierls1955quantum]: [Peierls, R. E. (1955). Quantum Theory of Solids. Clarendon Press.](https://books.google.com/books?id=WvPcBUsSJBAC)
-[^Minnich2012]: [Minnich, A. J. (2012). Determining phonon mean free paths from observations of quasiballistic thermal transport. Physical Review Letters, 109(20), 1–5.](http://doi.org/10.1103/PhysRevLett.109.205901)
-
-[^Minnich2015]: [Minnich, A. J. (2015). Thermal phonon boundary scattering in anisotropic thin films. Applied Physics Letters, 107(18), 8–11.](http://doi.org/10.1063/1.4935160)
-
[^Tamura1983]: [Tamura, S. (1983). Isotope scattering of dispersive phonons in Ge. Physical Review B, 27(2), 858–866.](http://doi.org/10.1103/PhysRevB.27.858)
[^Omini1996]: [Omini, M., & Sparavigna, A. (1996). Beyond the isotropic-model approximation in the theory of thermal conductivity. Physical Review B, 53(14), 9064–9073.](http://doi.org/10.1103/PhysRevB.53.9064)
@@ -885,3 +397,13 @@ subplot(1,3,3); hold on; box on;
[^Broido2007]: [Broido, D. A., Malorny, M., Birner, G., Mingo, N., & Stewart, D. A. (2007). Intrinsic lattice thermal conductivity of semiconductors from first principles. Applied Physics Letters, 91(23), 231922.](http://doi.org/10.1063/1.2822891)
[^Broido2005]: [Broido, D. A., Ward, A., & Mingo, N. (2005). Lattice thermal conductivity of silicon from empirical interatomic potentials. Physical Review B, 72(1), 1–8.](http://doi.org/10.1103/PhysRevB.72.014308)
+
+[^Isaeva2019]: [Isaeva, L & Barbalinardo, G. & Donadio, D. & Baroni, S. (2019). Modeling heat transport in crystals and glasses from a unified lattice-dynamical approach. Nature Communications 10 3853](https://doi.org/10.1038/s41467-019-11572-4)
+
+[^Fiorentino2023]: [Fiorentino, A. & Baroni, S (2023). From Green-Kubo to the full Boltzmann kinetic approach to heat transport in crystals and glasses. Physical Review B, 107, 054311](https://doi.org/10.1103/PhysRevB.107.054311)
+
+[^Simoncelli2019]: [Simoncelli, M. & Marzari, N. & Mauri, F. (2019). Unified theory of thermal transport in crystals and glasses. Nature physics 15 803-819](https://doi.org/10.1038/s41567-019-0520-x)
+
+[^Caldarelli2022]: [Caldarelli, G. & Simoncelli, M. & Marzari, N. & Mauri, F. & Benfatto, L. (2022). Many-body Green's function approach to lattice thermal transport. Physical Review B 106 024312](https://doi.org/10.1103/PhysRevB.106.024312)
+
+[^Dangic2021]: [Dangić, Đ. & Hellman, O. & Fahy, S. and Savić, I. (2021) The origin of the lattice thermal conductivity enhancement at the ferroelectric phase transition in GeTe. Nature Computational Materials 7, 57](https://doi.org/10.1038/s41524-021-00523-7)
diff --git a/src/thermal_conductivity/options.f90 b/src/thermal_conductivity/options.f90
index 64ac896f..86c7cd43 100644
--- a/src/thermal_conductivity/options.f90
+++ b/src/thermal_conductivity/options.f90
@@ -1,36 +1,33 @@
#include "precompilerdefinitions"
module options
-use konstanter, only: flyt, lo_status, lo_author, lo_version, lo_licence, lo_m_to_bohr
+use konstanter, only: flyt, lo_status, lo_author, lo_version, lo_licence, lo_m_to_bohr, lo_hugeint
use flap, only: command_line_interface
implicit none
private
public :: lo_opts
type lo_opts
- integer, dimension(3) :: qgrid !< the main q-grid
- logical :: readqmesh !< read q-grid from file
- integer :: trangenpts !< how many temperatures
- real(flyt) :: trangemin !< minimum temperature
- real(flyt) :: trangemax !< max temperature
- logical :: logtempaxis !< logarithmically spaced temperature points
- real(flyt) :: sigma !< scaling factor for adaptice gaussian
- real(flyt) :: thres !< consider Gaussian 0 if x-mu is larger than this number times sigma.
- real(flyt) :: tau_boundary !< add a constant as boundary scattering
- real(flyt) :: mfp_max !< add a length as boundary scattering
- logical :: readiso !< read isotope distribution from file
- integer :: integrationtype !< gaussian or tetrahedron
- integer :: scfiterations !< maximum number of self-consistent iterations
- real(flyt) :: scftol !< tolerance for the SCF cycle
-
- integer :: correctionlevel !< how hard to correct
- integer :: mfppts !< number of points on mfp-plots
- logical :: dumpgrid !< print everything on a grid
- !logical :: thinfilm !< Austins thin film thing
+ integer, dimension(3) :: qgrid !< the main q-grid
+ integer, dimension(3) :: qg3ph !< The grid for the threephonon integration
+ integer, dimension(3) :: qg4ph !< The grid for the fourphonon integration
+ logical :: readqmesh !< read q-grid from file
+ real(flyt) :: temperature !< temperature
+ real(flyt) :: sigma !< scaling factor for adaptive gaussian
+ real(flyt) :: tau_boundary !< add a constant as boundary scattering
+ real(flyt) :: mfp_max !< add a length as boundary scattering
+ real(flyt) :: itertol !< tolerance for the iterative solution
+ integer :: itermaxsteps !< Number of iteration for the Boltzmann equation
+ logical :: classical !< Use a classical formulation
+ logical :: readiso !< read isotope distribution from file
+ logical :: thirdorder !< use fourth order contribution
+ logical :: fourthorder !< use fourth order contribution
+ logical :: isotopescattering !< use isotope scattering
+ integer :: integrationtype !< adaptive or standard gaussian integration
+ integer :: seed !< seed for the Monte-Carlo grid
! Debugging things
logical :: timereversal
logical :: qpsymmetry
- logical :: isotopescattering
!
integer :: verbosity
contains
@@ -48,6 +45,7 @@ subroutine parse(opts)
logical :: dumlog
real(flyt) :: f0
real(flyt), dimension(3) :: dumflytv
+ integer :: i
! basic info
call cli%init(progname='thermal_conductivity', &
@@ -55,12 +53,11 @@ subroutine parse(opts)
version=lo_version, &
license=lo_licence, &
help='Usage: ', &
- description='Calculates the lattice thermal conductivity from the iterative solution of the &
- &phonon Boltzmann equation. In addition, cumulative plots and raw data dumps &
- &of intermediate values are available.', &
- examples=["mpirun thermal_conductivity --temperature 300 ", &
- "mpirun thermal_conductivity -qg 15 15 15 --temperature_range 200 600 50 ", &
- "mpirun thermal_conductivity --integrationtype 2 -qg 30 30 30 --max_mfp 1E-6"], &
+ description='Calculates the lattice thermal conductivity in the&
+ & mode-coupling formalism, including collective and off-diagonal&
+ & contributions up to fourth-order interactions.',&
+ examples=["mpirun thermal_conductivity --temperature 300 ", &
+ "mpirun thermal_conductivity --fourthorder -qg 30 30 30 -qg4ph 4 4 4 "], &
epilog=new_line('a')//"...")
! real options
call cli%add(switch='--readiso', &
@@ -68,74 +65,73 @@ subroutine parse(opts)
help_markdown='The format is specified [here](../page/files.html#infile.isotopes).', &
required=.false., act='store_true', def='.false.', error=lo_status)
if (lo_status .ne. 0) stop
- cli_qpoint_grid
call cli%add(switch='--integrationtype', switch_ab='-it', &
- help='Type of integration for the phonon DOS. 1 is Gaussian, 2 adaptive Gaussian and 3 Tetrahedron.', &
- required=.false., act='store', def='2', choices='1,2,3', error=lo_status)
+ help='Type of integration for the phonon DOS. 1 is Gaussian, 2 adaptive Gaussian.', &
+ required=.false., act='store', def='2', choices='1,2,6', error=lo_status)
if (lo_status .ne. 0) stop
+ call cli%add(switch='--nothirdorder', &
+ help='Do not consider third order contributions to the scattering.', &
+ required=.false., act='store_true', def='.false.', error=lo_status)
+ if (lo_status .ne. 0) stop
+ call cli%add(switch='--fourthorder', &
+ help='Consider four-phonon contributions to the scattering.', &
+ required=.false., act='store_true', def='.false.', error=lo_status)
+ if (lo_status .ne. 0) stop
+ cli_qpoint_grid
call cli%add(switch='--sigma', &
- help='Global scaling factor for adaptive Gaussian smearing.', &
+ help='Global scaling factor for Gaussian/adaptive Gaussian smearing. The default is determined procedurally, and scaled by this number.', &
required=.false., act='store', def='1.0', error=lo_status)
if (lo_status .ne. 0) stop
- call cli%add(switch='--threshold', &
- help='Consider a Gaussian distribution to be 0 after this many standard deviations.', &
- required=.false., act='store', def='4.0', error=lo_status)
- if (lo_status .ne. 0) stop
cli_readqmesh
call cli%add(switch='--temperature', &
help='Evaluate thermal conductivity at a single temperature.', &
- required=.false., act='store', def='-1', error=lo_status)
+ required=.false., act='store', def='300', error=lo_status)
if (lo_status .ne. 0) stop
- call cli%add(switch='--temperature_range', &
- help='Series of temperatures for thermal conductivity. Specify min, max and the number of points.', &
- nargs='3', required=.false., act='store', def='100 300 5', error=lo_status)
- if (lo_status .ne. 0) stop
- call cli%add(switch='--logtempaxis', &
- help='Space the temperature points logarithmically instead of linearly.', &
- required=.false., act='store_true', def='.false.', error=lo_status)
call cli%add(switch='--max_mfp', &
help='Add a limit on the mean free path as an approximation of domain size (in m).', &
required=.false., act='store', def='-1', error=lo_status)
if (lo_status .ne. 0) stop
- call cli%add(switch='--dumpgrid', &
- help='Write files with q-vectors, frequencies, eigenvectors and group velocities for a grid.', &
- required=.false., act='store_true', def='.false.', error=lo_status)
+ call cli%add(switch='--iterative_tolerance', &
+ help='Tolerance for the iterative solution.', &
+ required=.false., act='store', def='1e-5', error=lo_status)
if (lo_status .ne. 0) stop
call cli%add(switch='--noisotope', &
help='Do not consider isotope scattering.', &
required=.false., act='store_true', def='.false.', error=lo_status)
if (lo_status .ne. 0) stop
+ call cli%add(switch='--classical', &
+ help='Use the classical limit for phonon occupation and heat capacity.', &
+ required=.false., act='store_true', def='.false.', error=lo_status)
+ if (lo_status .ne. 0) stop
+ call cli%add(switch='--iterative_maxsteps', &
+ help='Max number of iterations for the iterative solution.', &
+ required=.false., act='store', def='200', error=lo_status)
+ if (lo_status .ne. 0) stop
+ call cli%add(switch='--qpoint_grid3ph', switch_ab='-qg3ph', &
+ help='Dimension of the grid for the threephonon integration.', &
+ nargs='3', required=.false., act='store', def='-1 -1 -1', error=lo_status)
+ if (lo_status .ne. 0) stop
+ call cli%add(switch='--qpoint_grid4ph', switch_ab='-qg4ph', &
+ help='Dimension of the grid for the fourphonon integration.', &
+ nargs='3', required=.false., act='store', def='-1 -1 -1', error=lo_status)
+ if (lo_status .ne. 0) stop
+ call cli%add(switch='--seed', &
+ help='Positive integer to seed the random number generator for the Monte-Carlo grids.', &
+ required=.false., act='store', def='-1', error=lo_status)
+ if (lo_status .ne. 0) stop
! hidden
call cli%add(switch='--tau_boundary', hidden=.true., &
help='Add a constant boundary scattering term to the lifetimes.', &
required=.false., act='store', def='-1', error=lo_status)
if (lo_status .ne. 0) stop
- call cli%add(switch='--mfppts', hidden=.true., help='', &
- required=.false., act='store', def='200', error=lo_status)
- if (lo_status .ne. 0) stop
- call cli%add(switch='--scfiterations', hidden=.true., help='', &
- required=.false., act='store', def='200', error=lo_status)
- if (lo_status .ne. 0) stop
call cli%add(switch='--notr', hidden=.true., help='', &
required=.false., act='store_true', def='.false.', error=lo_status)
if (lo_status .ne. 0) stop
call cli%add(switch='--nosym', hidden=.true., help='', &
required=.false., act='store_true', def='.false.', error=lo_status)
if (lo_status .ne. 0) stop
- call cli%add(switch='--correctionlevel', hidden=.true., &
- help='How agressively things are corrected due to broken symmetries.', &
- required=.false., act='store', def='4', error=lo_status)
- if (lo_status .ne. 0) stop
- call cli%add(switch='--scftol', hidden=.true., &
- help='What tolerance to converge the self-consistent cycle to.', &
- required=.false., act='store', def='1E-5', error=lo_status)
- if (lo_status .ne. 0) stop
- !call cli%add(switch='--thinfilm',hidden=.true.,&
- ! help='Calculate the suppression of kappa from in a thin film.',&
- ! required=.false.,act='store_true',def='.false.',error=lo_status)
- ! if ( lo_status .ne. 0 ) stop
cli_manpage
cli_verbose
@@ -156,34 +152,25 @@ subroutine parse(opts)
! store things in the right place
- call cli%get(switch='--temperature', val=f0)
- call cli%get(switch='--temperature_range', val=dumflytv)
- opts%trangemin = dumflytv(1)
- opts%trangemax = dumflytv(2)
- opts%trangenpts = int(anint(dumflytv(3)))
- ! if --temperature is specified, override the range
- if (f0 .gt. 0.0_flyt) then
- opts%trangemin = f0
- opts%trangemax = f0
- opts%trangenpts = 1
- end if
+ call cli%get(switch='--temperature', val=opts%temperature)
call cli%get(switch='--qpoint_grid', val=opts%qgrid)
+ call cli%get(switch='--qpoint_grid3ph', val=opts%qg3ph)
+ call cli%get(switch='--qpoint_grid4ph', val=opts%qg4ph)
+ call cli%get(switch='--iterative_maxsteps', val=opts%itermaxsteps)
call cli%get(switch='--sigma', val=opts%sigma)
- call cli%get(switch='--threshold', val=opts%thres)
call cli%get(switch='--tau_boundary', val=opts%tau_boundary)
+ call cli%get(switch='--nothirdorder', val=dumlog)
+ opts%thirdorder = .not. dumlog
+ call cli%get(switch='--fourthorder', val=opts%fourthorder)
if (opts%tau_boundary .gt. 0.0_flyt) opts%tau_boundary = 1E10_flyt
call cli%get(switch='--readqmesh', val=opts%readqmesh)
call cli%get(switch='--integrationtype', val=opts%integrationtype)
- call cli%get(switch='--logtempaxis', val=opts%logtempaxis)
call cli%get(switch='--readiso', val=opts%readiso)
- call cli%get(switch='--mfppts', val=opts%mfppts)
- call cli%get(switch='--scfiterations', val=opts%scfiterations)
call cli%get(switch='--max_mfp', val=opts%mfp_max)
- call cli%get(switch='--dumpgrid', val=opts%dumpgrid)
- !call cli%get(switch='--thinfilm',val=opts%thinfilm)
+ call cli%get(switch='--iterative_tolerance', val=opts%itertol)
+ call cli%get(switch='--classical', val=opts%classical)
+ call cli%get(switch='--seed', val=opts%seed)
! stuff that's not really an option
- call cli%get(switch='--correctionlevel', val=opts%correctionlevel)
- call cli%get(switch='--scftol', val=opts%scftol)
call cli%get(switch='--notr', val=dumlog)
opts%timereversal = .not. dumlog
call cli%get(switch='--nosym', val=dumlog)
@@ -200,6 +187,23 @@ subroutine parse(opts)
! Get things to atomic units
opts%mfp_max = opts%mfp_max*lo_m_to_Bohr
+ if (maxval(opts%qg4ph) .gt. 0 .and. .not. opts%fourthorder) then
+ write(*, *) 'You have to enable fourthorder to use a fourth order Monte-Carlo grid, stopping calculation.'
+ stop
+ end if
+
+ ! Set automatic values for Monte-Carlo grids
+ if (opts%thirdorder) then
+ do i = 1, 3
+ if (opts%qg3ph(i) .lt. 0 .or. opts%qg3ph(i) .gt. opts%qgrid(i)) opts%qg3ph(i) = opts%qgrid(i)
+ end do
+ end if
+ if (opts%fourthorder) then
+ do i = 1, 3
+ if (opts%qg4ph(i) .lt. 0 .or. opts%qg4ph(i) .gt. opts%qgrid(i)) opts%qg4ph(i) = opts%qgrid(i)
+ end do
+ end if
+
end subroutine
end module
diff --git a/src/thermal_conductivity/scattering.f90 b/src/thermal_conductivity/scattering.f90
new file mode 100644
index 00000000..45f1ad1b
--- /dev/null
+++ b/src/thermal_conductivity/scattering.f90
@@ -0,0 +1,355 @@
+#include "precompilerdefinitions"
+module scattering
+use konstanter, only: r8, i8, lo_freqtol, lo_twopi, lo_exitcode_param, lo_hugeint, lo_pi, lo_tol, &
+ lo_phonongroupveltol, lo_tol, lo_frequency_THz_to_Hartree, lo_kb_hartree, lo_huge
+use gottochblandat, only: walltime, lo_trueNtimes, lo_progressbar_init, lo_progressbar, lo_gauss, lo_planck, lo_return_unique
+use mpi_wrappers, only: lo_mpi_helper, lo_stop_gracefully
+use lo_memtracker, only: lo_mem_helper
+use type_crystalstructure, only: lo_crystalstructure
+use type_forceconstant_secondorder, only: lo_forceconstant_secondorder
+use type_forceconstant_thirdorder, only: lo_forceconstant_thirdorder
+use type_forceconstant_fourthorder, only: lo_forceconstant_fourthorder
+use type_qpointmesh, only: lo_qpoint_mesh, lo_fft_mesh
+use type_phonon_dispersions, only: lo_phonon_dispersions
+use type_symmetryoperation, only: lo_operate_on_vector
+use lo_randomnumbers, only: lo_mersennetwister
+use lo_fftgrid_helper, only: lo_montecarlo_grid, singlet_to_triplet, triplet_to_singlet, &
+ fft_third_grid_index, fft_fourth_grid_index
+use lo_timetracker, only: lo_timer
+
+use options, only: lo_opts
+
+use lo_sorting, only: lo_qsort
+
+implicit none
+private
+public :: lo_scattering_rates
+
+real(r8), parameter :: isotope_prefactor = lo_pi/4.0_r8
+real(r8), parameter :: threephonon_prefactor = lo_pi/16.0_r8
+real(r8), parameter :: fourphonon_prefactor = lo_pi/96.0_r8
+
+! Container for scattering rates
+type lo_scattering_rates
+ !> The number of qpoint/mode on this rank
+ integer :: my_nqpoints
+ !> The list of qpoint and modes for this rank
+ integer, dimension(:), allocatable :: my_qpoints, my_modes
+ !> Bose-Einstein and squared smearing for each mode on irreducible q-point
+ real(r8), dimension(:, :), allocatable :: be, sigsq
+ !> The scattering matrix
+ real(r8), dimension(:, :), allocatable :: Xi
+
+contains
+ !> Generate the scattering amplitudes
+ procedure :: generate
+ !> destroy the scattering amplitues
+ procedure :: destroy => sr_destroy
+ !> Measure size in memory, in bytes
+ procedure :: size_in_mem => sr_size_in_mem
+end type
+
+contains
+subroutine generate(sr, qp, dr, uc, fct, fcf, opts, tmr, mw, mem)
+ !> The scattering rate
+ class(lo_scattering_rates), intent(out) :: sr
+ !> The qpoint mesh
+ class(lo_qpoint_mesh), intent(in) :: qp
+ !> phonon dispersions
+ type(lo_phonon_dispersions), intent(inout) :: dr
+ !> structure
+ type(lo_crystalstructure), intent(in) :: uc
+ !> The third order force constants
+ type(lo_forceconstant_thirdorder), intent(in) :: fct
+ !> The fourth order force constants
+ type(lo_forceconstant_fourthorder), intent(in) :: fcf
+ !> The options
+ type(lo_opts), intent(in) :: opts
+ !> Timer
+ type(lo_timer), intent(inout) :: tmr
+ !> MPI helper
+ type(lo_mpi_helper), intent(inout) :: mw
+ !> memory tracker
+ type(lo_mem_helper), intent(inout) :: mem
+
+ !> The random number generator
+ type(lo_mersennetwister) :: rng
+ !> The grids for monte-carlo integration
+ type(lo_montecarlo_grid) :: mcg3, mcg4
+
+ init: block
+ !> To initialize the random number generator and timing
+ real(r8) :: rseed
+ !> The q-point grid dimension
+ integer, dimension(3) :: dims
+ !> Some integers for the do loop/indices
+ integer :: q1, b1, il, j, my_nqpoints, ctr
+ !> The seed for the random number generator for the Monte-Carlo integration
+
+ ! grid dimensions
+ select type (qp)
+ class is (lo_fft_mesh)
+ dims = qp%griddensity
+ class default
+ call lo_stop_gracefully(['This routine only works with FFT meshes'], lo_exitcode_param, __FILE__, __LINE__)
+ end select
+
+ if (opts%seed .gt. 0) then
+ rseed = 1.0 / real(opts%seed, r8)
+ else
+ rseed = walltime()
+ if (mw%talk) write(*, *) '... walltime() used to generate random state'
+ end if
+
+ ! Initialize the random number generator
+ call rng%init(iseed=mw%r, rseed=rseed)
+
+ if (mw%talk) write (*, *) '... creating Monte-Carlo grid'
+ ! Initialize the monte-carlo grid
+ if (opts%thirdorder) then
+ call mcg3%initialize(dims, opts%qg3ph)
+ end if
+ if (opts%fourthorder) then
+ call mcg4%initialize(dims, opts%qg4ph)
+ end if
+
+ ! We can start some precomputation
+ allocate (sr%be(qp%n_irr_point, dr%n_mode))
+ allocate (sr%sigsq(qp%n_irr_point, dr%n_mode))
+ sr%be = 0.0_r8
+ sr%sigsq = 0.0_r8
+ do q1 = 1, qp%n_irr_point
+ do b1 = 1, dr%n_mode
+ if (opts%classical) then
+ sr%be(q1, b1) = lo_kb_hartree*opts%temperature/dr%iq(q1)%omega(b1)
+ else
+ sr%be(q1, b1) = lo_planck(opts%temperature, dr%iq(q1)%omega(b1))
+ end if
+
+ sr%sigsq(q1, b1) = qp%smearingparameter(dr%iq(q1)%vel(:, b1), dr%default_smearing(b1), opts%sigma)**2
+ end do
+ end do
+
+ if (mw%talk) write (*, *) '... distributing q-point/modes on MPI ranks'
+ ctr = 0
+ my_nqpoints = 0
+ do q1 = 1, qp%n_irr_point
+ do b1 = 1, dr%n_mode
+ ! We skip the acoustic mode at Gamma
+ if (dr%iq(q1)%omega(b1) .lt. lo_freqtol) cycle
+ ctr = ctr + 1
+
+ ! MPI thing
+ if (mod(ctr, mw%n) .ne. mw%r) cycle
+ my_nqpoints = my_nqpoints + 1
+ end do
+ end do
+
+ ! We can allocate all we need
+ sr%my_nqpoints = my_nqpoints
+ allocate (sr%my_qpoints(my_nqpoints))
+ allocate (sr%my_modes(my_nqpoints))
+ allocate (sr%Xi(my_nqpoints, qp%n_full_point*dr%n_mode))
+ sr%my_qpoints = -lo_hugeint
+ sr%my_modes = -lo_hugeint
+ sr%Xi = 0.0_r8
+
+ ! Let's attribute the q1/b1 indices to the ranks
+ il = 0
+ ctr = 0
+ do q1 = 1, qp%n_irr_point
+ do b1 = 1, dr%n_mode
+ ! We skip the acoustic mode at Gamma
+ if (dr%iq(q1)%omega(b1) .lt. lo_freqtol) cycle
+ ctr = ctr + 1
+
+ ! MPI thing
+ if (mod(ctr, mw%n) .ne. mw%r) cycle
+ il = il + 1
+ sr%my_qpoints(il) = q1
+ sr%my_modes(il) = b1
+ end do
+ end do
+ if (mw%talk) write (*, *) '... everything is ready, starting scattering computation'
+ call tmr%tock('initialization')
+ end block init
+
+ scatt: block
+ !> Buffer to contains the linewidth
+ real(r8), dimension(:, :), allocatable :: buf_lw
+ !> Buffer for the linewidth of the local point
+ real(r8) :: buf, f0, velnorm, t0
+ !> Some integers for the loops
+ integer :: il, j, q1, b1, b2
+
+ call mem%allocate(buf_lw, [qp%n_irr_point, dr%n_mode], persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ buf_lw = 0.0_r8
+
+ t0 = walltime()
+ if (mw%talk) call lo_progressbar_init()
+ do il = 1, sr%my_nqpoints
+ buf = 0.0_r8
+ if (opts%isotopescattering) then
+ call compute_isotope_scattering(il, sr, qp, dr, uc, opts%temperature, buf, &
+ opts%integrationtype, opts%sigma, mw, mem)
+ call tmr%tock('isotope scattering')
+ end if
+ if (opts%thirdorder) then
+ call compute_threephonon_scattering(il, sr, qp, dr, uc, fct, mcg3, rng, &
+ buf, opts%integrationtype, opts%sigma, mw, mem)
+ call tmr%tock('threephonon scattering')
+ end if
+ if (opts%fourthorder) then
+ call compute_fourphonon_scattering(il, sr, qp, dr, uc, fcf, mcg4, rng, &
+ buf, opts%integrationtype, opts%sigma, mw, mem)
+ call tmr%tock('fourphonon scattering')
+ end if
+ ! We end with the boundary scattering
+ if (opts%mfp_max .gt. 0.0_r8) then
+ velnorm = norm2(dr%iq(sr%my_qpoints(il))%vel(:, sr%my_modes(il)))
+ if (velnorm .gt. lo_phonongroupveltol) then
+ buf = buf + velnorm/opts%mfp_max
+ end if
+ end if
+ ! Now we can update the linewidth for this mode
+ buf_lw(sr%my_qpoints(il), sr%my_modes(il)) = buf
+
+ if (mw%talk .and. lo_trueNtimes(il, 127, sr%my_nqpoints)) then
+ call lo_progressbar(' ... computing scattering amplitude', il, sr%my_nqpoints, walltime() - t0)
+ end if
+ end do
+ if (mw%talk) call lo_progressbar(' ... computing scattering amplitude', sr%my_nqpoints, sr%my_nqpoints, walltime() - t0)
+
+ ! Reduce the linewidth
+ call mw%allreduce('sum', buf_lw)
+
+ ! Distribute it after fixing the degeneracies
+ do q1 = 1, qp%n_irr_point
+ do b1 = 1, dr%n_mode
+ if (dr%iq(q1)%omega(b1) .lt. lo_freqtol) cycle
+ ! First we fix the degeneracy
+ f0 = 0.0_r8
+ do j = 1, dr%iq(q1)%degeneracy(b1)
+ b2 = dr%iq(q1)%degenmode(j, b1)
+ f0 = f0 + buf_lw(q1, b2)
+ end do
+ f0 = f0/real(dr%iq(q1)%degeneracy(b1), r8)
+ do j = 1, dr%iq(q1)%degeneracy(b1)
+ b2 = dr%iq(q1)%degenmode(j, b1)
+ buf_lw(q1, b2) = f0
+ end do
+ ! Now we can set the linewidth
+ dr%iq(q1)%linewidth(b1) = buf_lw(q1, b1)
+
+ ! While we are at it, we can set other things
+ dr%iq(q1)%qs(b1) = 2.0_r8*dr%iq(q1)%linewidth(b1)
+ velnorm = norm2(dr%iq(q1)%vel(:, b1))
+ if (velnorm .gt. lo_phonongroupveltol) then
+ dr%iq(q1)%mfp(:, b1) = dr%iq(q1)%vel(:, b1)/dr%iq(q1)%qs(b1)
+ dr%iq(q1)%scalar_mfp(b1) = velnorm/dr%iq(q1)%qs(b1)
+ dr%iq(q1)%F0(:, b1) = dr%iq(q1)%mfp(:, b1)
+ dr%iq(q1)%Fn(:, b1) = dr%iq(q1)%F0(:, b1)
+ end if
+ end do
+ end do
+ call mem%deallocate(buf_lw, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ end block scatt
+
+ symmetrize_scatmat: block
+ !> To hold the q-point in reduced coordinates
+ real(r8), dimension(3) :: qv2, qv2p
+ !> To get the index of the new triplet on the fft_grid
+ integer, dimension(3) :: gi
+ !> Some buffer
+ real(r8), dimension(:), allocatable :: buf
+ !> Integer for do loops and so on
+ integer :: il, jl, q1, j, k, q2, b2, q2p, n
+
+ call tmr%tick()
+ if (mw%talk) write (*, *) '... symmetrizing scattering matrix'
+
+ ! We use the relation Xi_{R*q, R*q'} = Xi_{q, q'''} to enforce the symmetry of Xi
+ ! TODO look if these irreducible pair could reduce the cost
+ call mem%allocate(buf, dr%n_mode, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ do il = 1, sr%my_nqpoints
+ q1 = sr%my_qpoints(il)
+ allq2: do q2 = 1, qp%n_full_point
+ buf = 0.0_r8
+ n = 0
+ do j = 1, qp%ip(q1)%n_invariant_operation
+ k = qp%ip(q1)%invariant_operation(j)
+ ! Here, we generate q''=R*q from the symmetry that leaves q invariant
+ select type (qp); type is (lo_fft_mesh)
+ qv2 = matmul(uc%inv_reciprocal_latticevectors, qp%ap(q2)%r)
+ qv2p = lo_operate_on_vector(uc%sym%op(k), qv2, reciprocal=.true., fractional=.true.)
+ if (qp%is_point_on_grid(qv2p) .eqv. .false.) cycle
+ gi = qp%index_from_coordinate(qv2p)
+ q2p = qp%gridind2ind(gi(1), gi(2), gi(3)) ! this is R*q'
+ end select
+ if (q2p .lt. q2) cycle allq2 ! If q2p < q2, we already did this guy
+ n = n + 1
+ ! We accumulate the values for each bands
+ do b2 = 1, dr%n_mode
+ jl = (q2p - 1)*dr%n_mode + b2
+ buf(b2) = buf(b2) + sr%Xi(il, jl)
+ end do
+ end do
+ if (n .eq. 0) cycle
+ buf = buf/real(n, r8)
+ ! And now we distribute
+ do j = 1, qp%ip(q1)%n_invariant_operation
+ k = qp%ip(q1)%invariant_operation(j)
+ ! Here, we generate q''=R*q from the symmetry that leaves q invariant
+ select type (qp); type is (lo_fft_mesh)
+ qv2 = matmul(uc%inv_reciprocal_latticevectors, qp%ap(q2)%r)
+ qv2p = lo_operate_on_vector(uc%sym%op(k), qv2, reciprocal=.true., fractional=.true.)
+ if (qp%is_point_on_grid(qv2p) .eqv. .false.) cycle
+ gi = qp%index_from_coordinate(qv2p)
+ q2p = qp%gridind2ind(gi(1), gi(2), gi(3)) ! this is R*q'
+ end select
+ do b2 = 1, dr%n_mode
+ jl = (q2p - 1)*dr%n_mode + b2
+ sr%Xi(il, jl) = buf(b2)
+ end do
+ end do
+ end do allq2
+ end do
+ call mem%deallocate(buf, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call tmr%tock('scattering matrix symmetrization')
+ end block symmetrize_scatmat
+end subroutine
+
+#include "scattering_isotope.f90"
+#include "scattering_threephonon.f90"
+#include "scattering_fourphonon.f90"
+
+subroutine sr_destroy(sr)
+ !> The scattering amplitudes
+ class(lo_scattering_rates), intent(inout) :: sr
+
+ integer :: il
+
+ if (allocated(sr%Xi)) deallocate (sr%Xi)
+ if (allocated(sr%my_qpoints)) deallocate (sr%my_qpoints)
+ if (allocated(sr%my_modes)) deallocate (sr%my_modes)
+ if (allocated(sr%be)) deallocate (sr%be)
+ if (allocated(sr%sigsq)) deallocate (sr%sigsq)
+ sr%my_nqpoints = -lo_hugeint
+end subroutine
+
+! Function to measure the size of the memory
+function sr_size_in_mem(sr) result(mem)
+ !> The scattering amplitudes
+ class(lo_scattering_rates), intent(inout) :: sr
+ !> size in memory, bytes
+ integer(i8) :: mem
+
+ mem = storage_size(sr)
+ if (allocated(sr%my_qpoints)) mem = mem + storage_size(sr%my_qpoints)*size(sr%my_qpoints)
+ if (allocated(sr%my_modes)) mem = mem + storage_size(sr%my_modes)*size(sr%my_modes)
+ if (allocated(sr%be)) mem = mem + storage_size(sr%be)*size(sr%be)
+ if (allocated(sr%Xi)) mem = mem + storage_size(sr%Xi)*size(sr%Xi)
+ if (allocated(sr%sigsq)) mem = mem + storage_size(sr%sigsq)*size(sr%sigsq)
+ mem = mem/8
+end function
+end module
diff --git a/src/thermal_conductivity/scattering_fourphonon.f90 b/src/thermal_conductivity/scattering_fourphonon.f90
new file mode 100644
index 00000000..32ba10e9
--- /dev/null
+++ b/src/thermal_conductivity/scattering_fourphonon.f90
@@ -0,0 +1,447 @@
+
+subroutine compute_fourphonon_scattering(il, sr, qp, dr, uc, fcf, mcg, rng, &
+ g0, integrationtype, smearing, mw, mem)
+ !> The qpoint and mode indices considered here
+ integer, intent(in) :: il
+ !> The scattering amplitudes
+ type(lo_scattering_rates), intent(inout) :: sr
+ ! The qpoint mesh
+ class(lo_qpoint_mesh), intent(in) :: qp
+ ! Harmonic dispersions
+ type(lo_phonon_dispersions), intent(inout) :: dr
+ !> structure
+ type(lo_crystalstructure), intent(in) :: uc
+ !> Fourth order force constants
+ type(lo_forceconstant_fourthorder), intent(in) :: fcf
+ !> The monte-carlo grid
+ type(lo_montecarlo_grid), intent(in) :: mcg
+ !> The random number generator
+ type(lo_mersennetwister), intent(inout) :: rng
+ !> The linewidth for this mode
+ real(r8), intent(inout) :: g0
+ !> what kind of integration are we doing
+ integer, intent(in) :: integrationtype
+ !> The smearing width
+ real(r8), intent(in) :: smearing
+ !> Mpi helper
+ type(lo_mpi_helper), intent(inout) :: mw
+ !> Memory helper
+ type(lo_mem_helper), intent(inout) :: mem
+
+ !> Frequency scaled eigenvectors
+ complex(r8), dimension(:), allocatable :: egv1, egv2, egv3, egv4
+ !> Helper for Fourier transform of psi3
+ complex(r8), dimension(:), allocatable :: ptf, evp1, evp2, evp3
+ !> The full qpoint grid, to be shuffled
+ integer, dimension(:), allocatable :: qgridfull1, qgridfull2
+ !> The qpoints in cartesian coordinates
+ real(r8), dimension(3) :: qv2, qv3, qv4
+ !> The complex scattering amplitude
+ complex(r8) :: c0
+ !> Frequencies, bose-einstein occupation and scattering strength
+ real(r8) :: om1, om2, om3, om4, psisq
+ ! The gaussian integration width
+ real(r8) :: sigma
+ !> Stuff for the linewidths
+ real(r8) :: n2, n3, n4, n2p, n3p, n4p, plf0, plf1, plf2, plf3
+ !> Integers for do loops
+ integer :: q1, q2, q3, q4, q2p, q3p, q4p, b1, b2, b3, b4, qi, qj, i
+ !> Is the quartet irreducible ?
+ logical :: isred
+ !> If so, what is its multiplicity
+ real(r8) :: mult0, mult1, mult2, mult3
+ !> All the prefactors for the scattering
+ real(r8) :: f0, f1, f2, f3, f4, f5, f6, f7, fall
+ !> The reducible triplet corresponding to the currently computed quartet
+ integer, dimension(:, :), allocatable :: red_quartet
+
+ real(r8), dimension(:, :), allocatable :: od_terms
+
+ ! We start by allocating everything
+ call mem%allocate(ptf, dr%n_mode**4, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(evp1, dr%n_mode**2, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(evp2, dr%n_mode**3, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(evp3, dr%n_mode**4, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(egv1, dr%n_mode, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(egv2, dr%n_mode, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(egv3, dr%n_mode, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(egv4, dr%n_mode, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+
+ call mem%allocate(od_terms, [qp%n_full_point, dr%n_mode], persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ od_terms = 0.0_r8
+
+ ! Already set some buffer values for mode (q1, b1)
+ q1 = sr%my_qpoints(il)
+ b1 = sr%my_modes(il)
+ om1 = dr%iq(q1)%omega(b1)
+ egv1 = dr%iq(q1)%egv(:, b1)/sqrt(om1)
+
+ ! Prepare the grid for the monte-carlo average
+ call mem%allocate(qgridfull1, qp%n_full_point, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(qgridfull2, qp%n_full_point, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mcg%generate_grid(qgridfull1, rng)
+ call mcg%generate_grid(qgridfull2, rng)
+
+ compute_loop: do qi = 1, mcg%npoints
+ do qj = 1, mcg%npoints
+ q2 = qgridfull1(qi)
+ q3 = qgridfull2(qj)
+ if (q3 .lt. q2) cycle
+ q4 = fft_fourth_grid_index(qp%ip(q1)%full_index, q2, q3, mcg%full_dims)
+ if (q4 .lt. q3) cycle
+
+ call quartet_is_irreducible(qp, uc, q1, q2, q3, q4, isred, red_quartet, mw, mem)
+ if (isred) cycle
+
+ qv2 = qp%ap(q2)%r
+ qv3 = qp%ap(q3)%r
+ qv4 = qp%ap(q4)%r
+ call pretransform_phi4(fcf, qv2, qv3, qv4, ptf)
+
+ ! We can already take care of the multiplicity caused by permutation
+ if (q2 .eq. q3 .and. q3 .eq. q4) then
+ mult0 = 1.0_r8
+ mult1 = 1.0_r8
+ mult2 = 1.0_r8
+ mult3 = 1.0_r8
+ else if ((q2 .ne. q3 .and. q3 .eq. q4) .or. &
+ (q3 .ne. q2 .and. q2 .eq. q4) .or. &
+ (q4 .ne. q2 .and. q2 .eq. q3)) then
+ mult0 = 3.0_r8
+ mult1 = 1.0_r8
+ mult2 = 1.0_r8
+ mult3 = 1.0_r8
+ else
+ mult0 = 6.0_r8
+ mult1 = 2.0_r8
+ mult2 = 2.0_r8
+ mult3 = 2.0_r8
+ end if
+
+ do b2 = 1, dr%n_mode
+ om2 = dr%aq(q2)%omega(b2)
+ if (om2 .lt. lo_freqtol) cycle
+
+ n2 = sr%be(qp%ap(q2)%irreducible_index, b2)
+ n2p = n2 + 1.0_r8
+ egv2 = dr%aq(q2)%egv(:, b2)/sqrt(om2)
+
+ evp1 = 0.0_r8
+ call zgeru(dr%n_mode, dr%n_mode, (1.0_r8, 0.0_r8), egv2, 1, egv1, 1, evp1, dr%n_mode)
+ do b3 = 1, dr%n_mode
+ om3 = dr%aq(q3)%omega(b3)
+ if (om3 .lt. lo_freqtol) cycle
+
+ n3 = sr%be(qp%ap(q3)%irreducible_index, b3)
+ n3p = n3 + 1.0_r8
+ egv3 = dr%aq(q3)%egv(:, b3)/sqrt(om3)
+
+ evp2 = 0.0_r8
+ call zgeru(dr%n_mode, dr%n_mode**2, (1.0_r8, 0.0_r8), egv3, 1, evp1, 1, evp2, dr%n_mode)
+ do b4 = 1, dr%n_mode
+ om4 = dr%aq(q4)%omega(b4)
+ if (om4 .lt. lo_freqtol) cycle
+
+ n4 = sr%be(qp%ap(q4)%irreducible_index, b4)
+ n4p = n4 + 1.0_r8
+ egv4 = dr%aq(q4)%egv(:, b4)/sqrt(om4)
+
+ select case (integrationtype)
+ case (1)
+ sigma = smearing*lo_frequency_THz_to_Hartree
+ case (2)
+ sigma = sqrt(sr%sigsq(q1, b1) + &
+ sr%sigsq(qp%ap(q2)%irreducible_index, b2) + &
+ sr%sigsq(qp%ap(q3)%irreducible_index, b3) + &
+ sr%sigsq(qp%ap(q4)%irreducible_index, b4))
+ case (6)
+ sigma = qp%smearingparameter(dr%aq(q3)%vel(:, b3) - dr%aq(q4)%vel(:, b4), &
+ dr%default_smearing(b3), smearing)
+ end select
+
+ evp3 = 0.0_r8
+ call zgeru(dr%n_mode, dr%n_mode**3, (1.0_r8, 0.0_r8), egv4, 1, evp2, 1, evp3, dr%n_mode)
+ evp3 = conjg(evp3)
+ c0 = dot_product(evp3, ptf)
+ psisq = fourphonon_prefactor*abs(c0*conjg(c0))*mcg%weight**2
+
+ ! Prefactors, only the Bose-Einstein distributions
+ plf0 = n2p*n3p*n4p - n2*n3*n4
+ plf1 = 3.0_r8*n2*n3p*n4p - n2p*n3*n4
+ plf2 = 3.0_r8*n3*n2p*n4p - n3p*n2*n4
+ plf3 = 3.0_r8*n4*n3p*n2p - n4p*n3*n2
+
+ ! Prefactors, including the matrix elements and dirac
+ f0 = mult0*psisq*plf0*(lo_gauss(om1, om2 + om3 + om4, sigma) - lo_gauss(om1, -om2 - om3 - om4, sigma))
+ f1 = mult1*psisq*plf1*(lo_gauss(om1, -om2 + om3 + om4, sigma) - lo_gauss(om1, om2 - om3 - om4, sigma))
+ f2 = mult2*psisq*plf2*(lo_gauss(om1, -om3 + om2 + om4, sigma) - lo_gauss(om1, om3 - om2 - om4, sigma))
+ f3 = mult3*psisq*plf3*(lo_gauss(om1, -om4 + om3 + om2, sigma) - lo_gauss(om1, om4 - om3 - om2, sigma))
+
+ fall = f0 + f1 + f2 + f3
+
+ ! Add everything to the linewidth
+ do i = 1, size(red_quartet, 2)
+ g0 = g0 + fall
+
+ q2p = red_quartet(1, i)
+ q3p = red_quartet(2, i)
+ q4p = red_quartet(3, i)
+
+ od_terms(q2p, b2) = od_terms(q2p, b2) + 2.0_r8*fall*om2/om1
+ od_terms(q3p, b3) = od_terms(q3p, b3) + 2.0_r8*fall*om3/om1
+ od_terms(q4p, b4) = od_terms(q4p, b4) + 2.0_r8*fall*om4/om1
+ end do
+ end do
+ end do
+ end do
+ end do
+ end do compute_loop
+
+ ! Now we can symmetrize the off-diagonal contribution
+ ! This can be done in a way to put a value to for mode that have been skipped by the Monte-Carlo !
+ symmetrize_and_distribute: block
+ integer, dimension(dr%n_mode) :: nn
+ !> To hold the q-point in reduced coordinates
+ real(r8), dimension(3) :: qv2p
+ !> To get the index of the new triplet on the fft_grid
+ integer, dimension(3) :: gi
+ !> Some buffer
+ real(r8), dimension(dr%n_mode) :: buf_xi
+ !> Some integers for the do loop
+ integer :: j, k, i2
+
+ ! Let's average the off diagonal term
+ allq2: do qi = 1, mcg%npoints
+ q2 = qgridfull1(qi)
+ buf_xi = 0.0_r8
+ nn = 0
+ do j = 1, qp%ip(q1)%n_invariant_operation
+ k = qp%ip(q1)%invariant_operation(j)
+ select type (qp); type is (lo_fft_mesh)
+ qv2 = matmul(uc%inv_reciprocal_latticevectors, qp%ap(q2)%r)
+ qv2p = lo_operate_on_vector(uc%sym%op(k), qv2, reciprocal=.true., fractional=.true.)
+ if (qp%is_point_on_grid(qv2p) .eqv. .false.) cycle
+ gi = qp%index_from_coordinate(qv2p)
+ q2p = qp%gridind2ind(gi(1), gi(2), gi(3)) ! this is R*q'
+ end select
+ if (q2p .lt. q2) cycle allq2 ! If q2p < q2, we already did this guy
+ do b2 = 1, dr%n_mode
+ if (od_terms(q2p, b2) .gt. 0.0_r8) then
+ nn(b2) = nn(b2) + 1
+ buf_xi(b2) = buf_xi(b2) + od_terms(q2p, b2)
+ end if
+ end do
+ end do
+ do j = 1, qp%ip(q1)%n_invariant_operation
+ k = qp%ip(q1)%invariant_operation(j)
+ select type (qp); type is (lo_fft_mesh)
+ qv2 = matmul(uc%inv_reciprocal_latticevectors, qp%ap(q2)%r)
+ qv2p = lo_operate_on_vector(uc%sym%op(k), qv2, reciprocal=.true., fractional=.true.)
+ if (qp%is_point_on_grid(qv2p) .eqv. .false.) cycle
+ gi = qp%index_from_coordinate(qv2p)
+ q2p = qp%gridind2ind(gi(1), gi(2), gi(3)) ! this is R*q'
+ end select
+ do b2 = 1, dr%n_mode
+ if (nn(b2) .eq. 0) cycle
+ od_terms(q2p, b2) = buf_xi(b2)/real(nn(b2), r8)
+ end do
+ end do
+ end do allq2
+ ! And now we distribute the off-diagonal terms on the scattering matrix
+ do q2 = 1, qp%n_full_point
+ do b2 = 1, dr%n_mode
+ i2 = (q2 - 1)*dr%n_mode + b2
+ sr%Xi(il, i2) = sr%Xi(il, i2) + od_terms(q2, b2)
+ end do
+ end do
+ end block symmetrize_and_distribute
+
+ ! And we can deallocate everything
+ call mem%deallocate(ptf, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(evp1, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(evp2, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(evp3, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(egv1, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(egv2, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(egv3, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(egv4, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(qgridfull1, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(qgridfull2, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(od_terms, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+end subroutine
+
+subroutine quartet_is_irreducible(qp, uc, q1, q2, q3, q4, isred, red_quartet, mw, mem)
+ !> The qpoint mesh
+ class(lo_qpoint_mesh), intent(in) :: qp
+ !> structure
+ type(lo_crystalstructure), intent(in) :: uc
+ !> The indices of the qpoints - q1 is irreducible, q2, q3 and q4 are full
+ integer, intent(in) :: q1, q2, q3, q4
+ !> Is the triplet reducible ?
+ logical, intent(out) :: isred
+ !> The equivalent triplet
+ integer, dimension(:, :), allocatable, intent(out) :: red_quartet
+ !> Mpi helper
+ type(lo_mpi_helper), intent(inout) :: mw
+ !> Memory helper
+ type(lo_mem_helper), intent(inout) :: mem
+
+ !> The new-qpoints and the temporary invariant triplet
+ integer, dimension(:, :), allocatable :: newqp, newqp_sort
+ !> The number of invariant operation for q1
+ integer :: n
+
+ n = qp%ip(q1)%n_invariant_operation
+
+ ! We will need this to keep equivalent point
+ call mem%allocate(newqp, [3, n], persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(newqp_sort, [3, n], persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ newqp = -lo_hugeint
+ newqp_sort = -lo_hugeint
+
+ ! The first part is to generate all qpoint invariant in little star of q1
+ get_equivalent: block
+ !> To hold the q-point in reduced coordinates
+ real(r8), dimension(3) :: qv2, qv3, qv4, qv2p, qv3p, qv4p
+ !> To get the index of the new triplet on the fft_grid
+ integer, dimension(3) :: gi
+ !> The new triplet after the operation
+ integer, dimension(3) :: qpp
+ !> Integers for the do loops
+ integer :: j, k
+
+ ! First get the reciprocal lattice vectors, in reduce coordinates
+ qv2 = matmul(uc%inv_reciprocal_latticevectors, qp%ap(q2)%r)
+ qv3 = matmul(uc%inv_reciprocal_latticevectors, qp%ap(q3)%r)
+ qv4 = matmul(uc%inv_reciprocal_latticevectors, qp%ap(q4)%r)
+
+ isred = .false.
+ ! Let's try all operations that leaves q1 invariant
+ do j = 1, n
+ k = qp%ip(q1)%invariant_operation(j)
+ qpp = -lo_hugeint
+ select type (qp); type is (lo_fft_mesh)
+ ! Transform q2 and check if it's on the grid
+ qv2p = lo_operate_on_vector(uc%sym%op(k), qv2, reciprocal=.true., fractional=.true.)
+ if (qp%is_point_on_grid(qv2p) .eqv. .false.) cycle
+
+ ! Transform q3 and check if it's on the grid
+ qv3p = lo_operate_on_vector(uc%sym%op(k), qv3, reciprocal=.true., fractional=.true.)
+ if (qp%is_point_on_grid(qv3p) .eqv. .false.) cycle
+
+ ! Transform q4 and check if it's on the grid
+ qv4p = lo_operate_on_vector(uc%sym%op(k), qv4, reciprocal=.true., fractional=.true.)
+ if (qp%is_point_on_grid(qv4p) .eqv. .false.) cycle
+
+ ! If everything is on the grid, get the location of each point
+ gi = qp%index_from_coordinate(qv2p)
+ qpp(1) = qp%gridind2ind(gi(1), gi(2), gi(3))
+ gi = qp%index_from_coordinate(qv3p)
+ qpp(2) = qp%gridind2ind(gi(1), gi(2), gi(3))
+ gi = qp%index_from_coordinate(qv4p)
+ qpp(3) = qp%gridind2ind(gi(1), gi(2), gi(3))
+ end select
+ ! We need to keep the symmetry equivalent point in the order they came in
+ newqp(:, j) = qpp
+ ! We also need them sorted, to check for redundancy and reducibility
+ call lo_qsort(qpp)
+ newqp_sort(:, j) = qpp
+ if (qpp(1) .gt. q2 .or. qpp(2) .gt. q3) isred = .true.
+ end do
+
+ if (minval(newqp) .lt. 0) then
+ do j = 1, size(newqp, 2)
+ if (any(newqp(:, j) .lt. 0)) newqp(:, j) = [q2, q3, q4]
+ if (any(newqp(:, j) .lt. 0)) newqp_sort(:, j) = [q2, q3, q4]
+ end do
+ end if
+ end block get_equivalent
+
+ ! Then we just get rid of redundant point in the list
+ sort_reducible: block
+ !> Integers for the loops
+ integer :: j, k, ctr, ctr2
+
+ ! Now we have the same problem of permutation as in the third order
+ ! So first, we count the quartet equivalent by permutation, a little bit harder
+ ctr = 0
+ do j = 1, n
+ ctr2 = 0
+ do k = j, n
+ if (k .eq. j) cycle
+ if (all(newqp_sort(:, j) .eq. newqp_sort(:, k))) ctr2 = ctr2 + 1
+ end do
+ if (ctr2 .eq. 0) ctr = ctr + 1
+ end do
+
+ ! Now we can create the list of equivalent quartet with permutation removed
+ allocate (red_quartet(3, ctr))
+ ctr = 0
+ do j = 1, n
+ ctr2 = 0
+ do k = j, n
+ if (k .eq. j) cycle
+ if (all(newqp_sort(:, j) .eq. newqp_sort(:, k))) ctr2 = ctr2 + 1
+ end do
+ if (ctr2 .eq. 0) then
+ ctr = ctr + 1
+ red_quartet(1, ctr) = newqp(1, j)
+ red_quartet(2, ctr) = newqp(2, j)
+ red_quartet(3, ctr) = newqp(3, j)
+ end if
+ end do
+
+ end block sort_reducible
+ call mem%deallocate(newqp, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(newqp_sort, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+end subroutine
+
+!> Do half the transform to q-space
+pure subroutine pretransform_phi4(fcf, q2, q3, q4, ptf)
+ !> Fourth order forceconstant
+ class(lo_forceconstant_fourthorder), intent(in) :: fcf
+ !> The q-vectors, without the 2pi
+ real(r8), dimension(3), intent(in) :: q2, q3, q4
+ !> Flattened, pre-transformed matrix element
+ complex(r8), dimension(:), intent(out) :: ptf
+
+ integer :: i, j, k, l, m
+
+ complex(r8) :: expiqr
+ real(r8), dimension(3) :: rv2, rv3, rv4
+ real(r8) :: iqr
+ integer :: a1, a2, a3, a4, ia, ib, ic, id, q, nb
+
+ nb = fcf%na*3
+ ptf = 0.0_r8
+ do a1 = 1, fcf%na
+ do q = 1, fcf%atom(a1)%n
+ a2 = fcf%atom(a1)%quartet(q)%i2
+ a3 = fcf%atom(a1)%quartet(q)%i3
+ a4 = fcf%atom(a1)%quartet(q)%i4
+
+ rv2 = fcf%atom(a1)%quartet(q)%lv2
+ rv3 = fcf%atom(a1)%quartet(q)%lv3
+ rv4 = fcf%atom(a1)%quartet(q)%lv4
+
+ iqr = dot_product(q2, rv2) + dot_product(q3, rv3) + dot_product(q4, rv4)
+ iqr = -iqr*lo_twopi
+ expiqr = cmplx(cos(iqr), sin(iqr), r8)
+ do l = 1, 3
+ do k = 1, 3
+ do j = 1, 3
+ do i = 1, 3
+ ia = (a1 - 1)*3 + i
+ ib = (a2 - 1)*3 + j
+ ic = (a3 - 1)*3 + k
+ id = (a4 - 1)*3 + l
+ ! Now for the grand flattening scheme, consistent with the zgeru operations.
+ m = (ia - 1)*nb*nb*nb + (ib - 1)*nb*nb + (ic - 1)*nb + id
+ ptf(m) = ptf(m) + fcf%atom(a1)%quartet(q)%mwm(i, j, k, l)*expiqr
+ end do
+ end do
+ end do
+ end do
+ end do
+ end do
+end subroutine
diff --git a/src/thermal_conductivity/scattering_isotope.f90 b/src/thermal_conductivity/scattering_isotope.f90
new file mode 100644
index 00000000..f0855cbc
--- /dev/null
+++ b/src/thermal_conductivity/scattering_isotope.f90
@@ -0,0 +1,89 @@
+
+subroutine compute_isotope_scattering(il, sr, qp, dr, uc, temperature, &
+ g0, integrationtype, smearing, mw, mem)
+ !> The local point
+ integer, intent(in) :: il
+ !> The scattering amplitudes
+ type(lo_scattering_rates), intent(inout) :: sr
+ !> The q-point mesh
+ class(lo_qpoint_mesh), intent(in) :: qp
+ !> phonon dispersions
+ type(lo_phonon_dispersions), intent(inout) :: dr
+ !> structure
+ type(lo_crystalstructure), intent(in) :: uc
+ !> The temperature
+ real(r8), intent(in) :: temperature
+ !> The linewidth for this mode
+ real(r8), intent(inout) :: g0
+ !> what kind of integration are we doing
+ integer, intent(in) :: integrationtype
+ !> The smearing width
+ real(r8), intent(in) :: smearing
+ !> MPI helper
+ type(lo_mpi_helper), intent(inout) :: mw
+ !> memory tracker
+ type(lo_mem_helper), intent(inout) :: mem
+
+ ! Eigenvectors
+ complex(r8), dimension(uc%na*3, 2) :: egviso
+ ! prefactor and phonon buffers
+ real(r8) :: om1, om2, sigma, psisq, prefactor, f0
+ ! Integers for do loops
+ integer :: q1, b1, q2, b2, i, niso
+
+ q1 = sr%my_qpoints(il)
+ b1 = sr%my_modes(il)
+ om1 = dr%iq(q1)%omega(b1)
+ egviso(:, 1) = dr%iq(q1)%egv(:, b1)
+
+ do q2 = 1, qp%n_full_point
+ prefactor = isotope_prefactor*qp%ap(q2)%integration_weight
+ do b2 = 1, dr%n_mode
+ om2 = dr%aq(q2)%omega(b2)
+ if (om2 .lt. lo_freqtol) cycle
+
+ select case (integrationtype)
+ case (1)
+ sigma = lo_frequency_THz_to_Hartree*smearing
+ case (2)
+ sigma = sqrt(sr%sigsq(q1, b1) + &
+ sr%sigsq(qp%ap(q2)%irreducible_index, b2))
+ case (6)
+ sigma = qp%smearingparameter(dr%aq(q2)%vel(:, b2), &
+ dr%default_smearing(b2), smearing)
+ end select
+
+ i = (q2 - 1)*dr%n_mode + b2
+
+ egviso(:, 2) = dr%aq(q2)%egv(:, b2)
+
+ psisq = isotope_scattering_strength(uc, egviso)*prefactor
+
+ f0 = psisq*om1*om2*lo_gauss(om1, om2, sigma)
+ g0 = g0 + f0
+ sr%Xi(il, i) = sr%Xi(il, i) + f0*om2/om1
+ end do
+ end do
+end subroutine
+
+real(r8) function isotope_scattering_strength(uc, egv)
+ type(lo_crystalstructure), intent(in) :: uc
+ complex(r8), dimension(:, :), intent(in) :: egv
+ !
+ integer :: i, j
+ real(r8) :: f0, f1
+ complex(r8), dimension(3) :: cv0, cv1
+
+ f1 = 0.0_r8
+ do i = 1, uc%na
+ cv0 = egv((i - 1)*3 + 1:(i*3), 1)
+ cv1 = egv((i - 1)*3 + 1:(i*3), 2)
+ f0 = 0.0_r8
+ do j = 1, 3
+ f0 = f0 + abs(conjg(cv0(j))*cv1(j))
+ end do
+ f0 = f0**2
+ f1 = f1 + f0*uc%isotope(i)%disorderparameter
+ end do
+ isotope_scattering_strength = f1
+end function
diff --git a/src/thermal_conductivity/scattering_threephonon.f90 b/src/thermal_conductivity/scattering_threephonon.f90
new file mode 100644
index 00000000..f164acdc
--- /dev/null
+++ b/src/thermal_conductivity/scattering_threephonon.f90
@@ -0,0 +1,399 @@
+
+subroutine compute_threephonon_scattering(il, sr, qp, dr, uc, fct, mcg, rng, &
+ g0, integrationtype, smearing, mw, mem)
+ ! The qpoint and mode indices considered here
+ integer, intent(in) :: il
+ !> The scattering amplitudes
+ type(lo_scattering_rates), intent(inout) :: sr
+ !> The qpoint mesh
+ class(lo_qpoint_mesh), intent(in) :: qp
+ ! Harmonic dispersions
+ type(lo_phonon_dispersions), intent(inout) :: dr
+ !> structure
+ type(lo_crystalstructure), intent(in) :: uc
+ !> Third order force constants
+ type(lo_forceconstant_thirdorder), intent(in) :: fct
+ !> The monte-carlo grid
+ type(lo_montecarlo_grid), intent(in) :: mcg
+ !> The random number generator
+ type(lo_mersennetwister), intent(inout) :: rng
+ !> The linewidth for this mode
+ real(r8), intent(inout) :: g0
+ !> what kind of integration are we doing
+ integer, intent(in) :: integrationtype
+ !> The smearing width
+ real(r8), intent(in) :: smearing
+ !> Mpi helper
+ type(lo_mpi_helper), intent(inout) :: mw
+ !> Memory helper
+ type(lo_mem_helper), intent(inout) :: mem
+
+ !> Frequency scaled eigenvectors
+ complex(r8), dimension(:), allocatable :: egv1, egv2, egv3
+ !> Helper for Fourier transform of psi3
+ complex(r8), dimension(:), allocatable :: ptf, evp1, evp2
+ !> The full qpoint grid, to be shuffled
+ integer, dimension(:), allocatable :: qgridfull
+ !> The qpoints and the dimension of the qgrid
+ real(r8), dimension(3) :: qv2, qv3
+ !> Frequencies, bose-einstein occupation and scattering strength and some other buffer
+ real(r8) :: sigma, om1, om2, om3, n2, n3, psisq, f0, f1, plf0, plf1, perm
+ !> The complex threephonon matrix element
+ complex(r8) :: c0
+ !> Integers for do loops and counting
+ integer :: qi, q1, q2, q3, q2p, q3p, b1, b2, b3, i
+ !> Is the triplet irreducible ?
+ logical :: isred
+ !> The reducible triplet corresponding to the currently computed triplet
+ integer, dimension(:, :), allocatable :: red_triplet
+ !> buff to keep the off diagonal scattering matrix elements
+ real(r8), dimension(:, :), allocatable :: od_terms
+
+ ! We start by allocating everything
+ call mem%allocate(ptf, dr%n_mode**3, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(evp1, dr%n_mode**2, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(evp2, dr%n_mode**3, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(egv1, dr%n_mode, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(egv2, dr%n_mode, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(egv3, dr%n_mode, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+
+ call mem%allocate(od_terms, [qp%n_full_point, dr%n_mode], persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ od_terms = 0.0_r8
+
+ ! Already set some values for mode (q1, b1)
+ q1 = sr%my_qpoints(il)
+ b1 = sr%my_modes(il)
+ om1 = dr%iq(q1)%omega(b1)
+ egv1 = dr%iq(q1)%egv(:, b1)/sqrt(om1)
+
+ call mem%allocate(qgridfull, mcg%npoints, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mcg%generate_grid(qgridfull, rng)
+
+ compute_loop: do qi = 1, mcg%npoints
+ q2 = qgridfull(qi)
+ q3 = fft_third_grid_index(qp%ip(q1)%full_index, q2, mcg%full_dims)
+ if (q3 .lt. q2) cycle
+ call triplet_is_irreducible(qp, uc, q1, q2, q3, isred, red_triplet, mw, mem)
+ if (isred) cycle
+
+ ! Let's compute the multiplicity due to permutation now
+ if (q2 .eq. q3) then
+ perm = 1.0_r8
+ else
+ perm = 2.0_r8
+ end if
+
+ ! This get the ifc3 in Fourier space, but not on phonons
+ call pretransform_phi3(fct, qp%ap(q2)%r, qp%ap(q3)%r, ptf)
+ do b2 = 1, dr%n_mode
+ om2 = dr%aq(q2)%omega(b2)
+ if (om2 .lt. lo_freqtol) cycle
+
+ egv2 = dr%aq(q2)%egv(:, b2)/sqrt(om2)
+
+ ! This is the multiplication of eigv of phonons 1 and 2
+ evp1 = 0.0_r8
+ call zgeru(dr%n_mode, dr%n_mode, (1.0_r8, 0.0_r8), egv2, 1, egv1, 1, evp1, dr%n_mode)
+ do b3 = 1, dr%n_mode
+ om3 = dr%aq(q3)%omega(b3)
+ if (om3 .lt. lo_freqtol) cycle
+ egv3 = dr%aq(q3)%egv(:, b3)/sqrt(om3)
+
+ select case (integrationtype)
+ case (1)
+ sigma = smearing*lo_frequency_THz_to_Hartree
+ case (2)
+ sigma = sqrt(sr%sigsq(q1, b1) + &
+ sr%sigsq(qp%ap(q2)%irreducible_index, b2) + &
+ sr%sigsq(qp%ap(q3)%irreducible_index, b3))
+ case (6)
+ sigma = qp%smearingparameter(dr%aq(q2)%vel(:, b2) - dr%aq(q3)%vel(:, b3), &
+ dr%default_smearing(b3), smearing)
+ end select
+
+ ! This is the multiplication of eigv of phonons 1 and 2 and now 3
+ evp2 = 0.0_r8
+ call zgeru(dr%n_mode, dr%n_mode**2, (1.0_r8, 0.0_r8), egv3, 1, evp1, 1, evp2, dr%n_mode)
+ evp2 = conjg(evp2)
+ ! And with this, we have the scattering matrix element coming in
+ c0 = dot_product(evp2, ptf)
+ psisq = threephonon_prefactor*abs(c0*conjg(c0))*mcg%weight
+
+ ! Let's get the Bose-Einstein distributions
+ n2 = sr%be(qp%ap(q2)%irreducible_index, b2)
+ n3 = sr%be(qp%ap(q3)%irreducible_index, b3)
+
+ ! The prefactor for the scattering
+ plf0 = n2 - n3
+ plf1 = n2 + n3 + 1.0_r8
+ f0 = perm*psisq*plf0*(lo_gauss(om1, -om2 + om3, sigma) - lo_gauss(om1, om2 - om3, sigma))
+ f1 = perm*psisq*plf1*(lo_gauss(om1, om2 + om3, sigma) - lo_gauss(om1, -om2 - om3, sigma))
+
+ ! And we add everything for each triplet equivalent to the one we are actually computing
+ do i = 1, size(red_triplet, 2)
+ ! We accumulate the linewidth
+ g0 = g0 + f0 + f1
+
+ ! And also the off-diagonal part of the scattering matrix
+ q2p = red_triplet(1, i)
+ q3p = red_triplet(2, i)
+ od_terms(q2p, b2) = od_terms(q2p, b2) + 2.0_r8*(f0 + f1)*om2/om1
+ od_terms(q3p, b3) = od_terms(q3p, b3) + 2.0_r8*(f0 + f1)*om3/om1
+ end do
+ end do
+ end do
+ end do compute_loop
+
+ ! Now we can symmetrize the off-diagonal contribution
+ ! For this, we only compute the average values from q-point on the Monte-Carlo grid.
+ ! But we distribute averaged entries of the scattering matrix for every equivalent points
+ symmetrize_and_distribute: block
+ !> To keep track of the number of mode we actually add
+ integer, dimension(dr%n_mode) :: nn
+ !> To hold the q-point in reduced coordinates
+ real(r8), dimension(3) :: qv2p
+ !> To get the index of the new triplet on the fft_grid
+ integer, dimension(3) :: gi
+ !> Some buffer
+ real(r8), dimension(dr%n_mode) :: buf_xi
+ !> Some integers for the do loops and stuff
+ integer :: j, k, i2
+
+ ! Let's average the off diagonal term
+ allq2: do qi = 1, mcg%npoints
+ q2 = qgridfull(qi)
+ buf_xi = 0.0_r8
+ nn = 0
+ ! First we get the average value
+ do j = 1, qp%ip(q1)%n_invariant_operation
+ k = qp%ip(q1)%invariant_operation(j)
+ ! First we generate q2'
+ select type (qp); type is (lo_fft_mesh)
+ qv2 = matmul(uc%inv_reciprocal_latticevectors, qp%ap(q2)%r)
+ qv2p = lo_operate_on_vector(uc%sym%op(k), qv2, reciprocal=.true., fractional=.true.)
+ if (qp%is_point_on_grid(qv2p) .eqv. .false.) cycle
+ gi = qp%index_from_coordinate(qv2p)
+ q2p = qp%gridind2ind(gi(1), gi(2), gi(3)) ! this is R*q'
+ end select
+ ! If q2p < q2, we already did this guy
+ if (q2p .lt. q2) cycle allq2
+ ! Accumulate values for each equivalent term, but avoid those which where not computed
+ do b2 = 1, dr%n_mode
+ if (od_terms(q2p, b2) .gt. 0.0_r8) then
+ nn(b2) = nn(b2) + 1
+ buf_xi(b2) = buf_xi(b2) + od_terms(q2p, b2)
+ end if
+ end do
+ end do
+ ! And now we can distribute it
+ do j = 1, qp%ip(q1)%n_invariant_operation
+ k = qp%ip(q1)%invariant_operation(j)
+ select type (qp); type is (lo_fft_mesh)
+ qv2 = matmul(uc%inv_reciprocal_latticevectors, qp%ap(q2)%r)
+ qv2p = lo_operate_on_vector(uc%sym%op(k), qv2, reciprocal=.true., fractional=.true.)
+ if (qp%is_point_on_grid(qv2p) .eqv. .false.) cycle
+ gi = qp%index_from_coordinate(qv2p)
+ q2p = qp%gridind2ind(gi(1), gi(2), gi(3)) ! this is R*q'
+ end select
+ do b2 = 1, dr%n_mode
+ if (nn(b2) .eq. 0) cycle
+ od_terms(q2p, b2) = buf_xi(b2)/real(nn(b2), r8)
+ end do
+ end do
+ end do allq2
+
+ ! And now we add things, with the normalization
+ od_terms = od_terms
+ do q2 = 1, qp%n_full_point
+ do b2 = 1, dr%n_mode
+ i2 = (q2 - 1)*dr%n_mode + b2
+ sr%Xi(il, i2) = sr%Xi(il, i2) + od_terms(q2, b2)
+ end do
+ end do
+ end block symmetrize_and_distribute
+
+ ! And we can deallocate everything
+ call mem%deallocate(ptf, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(evp1, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(evp2, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(egv1, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(egv2, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(egv3, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(qgridfull, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(od_terms, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ deallocate (red_triplet)
+end subroutine
+
+!> Get the Fourier transform of the third order matrix element
+subroutine pretransform_phi3(fct, q2, q3, ptf)
+ !> third order forceconstant
+ type(lo_forceconstant_thirdorder), intent(in) :: fct
+ !> q-vectors
+ real(r8), dimension(3), intent(in) :: q2, q3
+ !> flattened, pretransformed matrix element
+ complex(r8), dimension(:), intent(out) :: ptf
+
+ integer :: i, j, k, l
+
+ complex(r8) :: expiqr
+ real(r8), dimension(3) :: rv2, rv3
+ real(r8) :: iqr
+ integer :: a1, a2, a3, ia, ib, ic, t, nb
+
+ nb = fct%na*3
+ ptf = 0.0_r8
+ do a1 = 1, fct%na
+ do t = 1, fct%atom(a1)%n
+ a2 = fct%atom(a1)%triplet(t)%i2
+ a3 = fct%atom(a1)%triplet(t)%i3
+
+ rv2 = fct%atom(a1)%triplet(t)%lv2
+ rv3 = fct%atom(a1)%triplet(t)%lv3
+
+ iqr = dot_product(q2, rv2) + dot_product(q3, rv3)
+ iqr = -iqr*lo_twopi
+ expiqr = cmplx(cos(iqr), sin(iqr), r8)
+ do i = 1, 3
+ do j = 1, 3
+ do k = 1, 3
+ ia = (a1 - 1)*3 + i
+ ib = (a2 - 1)*3 + j
+ ic = (a3 - 1)*3 + k
+ ! Now for the grand flattening scheme, consistent with the zgeru operations above.
+ l = (ia - 1)*nb*nb + (ib - 1)*nb + ic
+ ptf(l) = ptf(l) + fct%atom(a1)%triplet(t)%mwm(i, j, k)*expiqr
+ end do
+ end do
+ end do
+ end do
+ end do
+end subroutine
+
+subroutine triplet_is_irreducible(qp, uc, q1, q2, q3, isred, red_triplet, mw, mem)
+ !> The qpoint mesh
+ class(lo_qpoint_mesh), intent(in) :: qp
+ !> structure
+ type(lo_crystalstructure), intent(in) :: uc
+ !> The indices of the qpoints - q1 is irreducible, q2 and q3 are full
+ integer, intent(in) :: q1, q2, q3
+ !> Is the triplet reducible ?
+ logical, intent(out) :: isred
+ !> The equivalent triplet
+ integer, dimension(:, :), allocatable, intent(out) :: red_triplet
+ !> Mpi helper
+ type(lo_mpi_helper), intent(inout) :: mw
+ !> Memory helper
+ type(lo_mem_helper), intent(inout) :: mem
+
+ !> The new-qpoints and the temporary invariant triplet
+ integer, dimension(:, :), allocatable :: newqp, newqp_sort
+ !> An integer to keep size of thing
+ integer :: n
+
+ ! Little explanation of what is happening here
+ ! First I generate all triplet point that are equivalent to the input in the little star of q1
+ ! Also, I return the fact that it's irreducible if possible by permutation
+ ! Then I remove the doublet (including permutation) in order to return the list of equivalent triplet
+ ! With this, I can generate equivalent triplet even if the grid does not respect the space group of the crystal
+ ! You should make your grid respect it, but you never now what people do
+
+ n = qp%ip(q1)%n_invariant_operation
+
+ ! We will need this to keep equivalent point
+ call mem%allocate(newqp, [2, n], persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%allocate(newqp_sort, [2, n], persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ newqp = -lo_hugeint
+ newqp_sort = -lo_hugeint
+
+ ! The first part is to generate all qpoint invariant in little star of q1
+ get_equivalent: block
+ !> To hold the q-point in reduced coordinates
+ real(r8), dimension(3) :: qv2, qv3, qv2p, qv3p
+ !> To get the index of the new triplet on the fft_grid
+ integer, dimension(3) :: gi
+ !> The new triplet after the operation
+ integer, dimension(2) :: qpp
+ !> Integers for the do loops
+ integer :: j, k
+
+ ! First get the q-points in reduce coordinates
+ qv2 = matmul(uc%inv_reciprocal_latticevectors, qp%ap(q2)%r)
+ qv3 = matmul(uc%inv_reciprocal_latticevectors, qp%ap(q3)%r)
+
+ isred = .false.
+ ! Let's try all operations that leaves q1 invariant
+ do j = 1, n
+ k = qp%ip(q1)%invariant_operation(j)
+ qpp = -lo_hugeint
+ select type (qp); type is (lo_fft_mesh)
+ ! Rotate q2 and look if it's the on grid
+ qv2p = lo_operate_on_vector(uc%sym%op(k), qv2, reciprocal=.true., fractional=.true.)
+ if (qp%is_point_on_grid(qv2p) .eqv. .false.) cycle
+
+ ! Rotate q3 and look if it's the on grid
+ qv3p = lo_operate_on_vector(uc%sym%op(k), qv3, reciprocal=.true., fractional=.true.)
+ if (qp%is_point_on_grid(qv3p) .eqv. .false.) cycle
+
+ ! If everything is on the grid, get the index of each point
+ gi = qp%index_from_coordinate(qv2p)
+ qpp(1) = qp%gridind2ind(gi(1), gi(2), gi(3))
+ gi = qp%index_from_coordinate(qv3p)
+ qpp(2) = qp%gridind2ind(gi(1), gi(2), gi(3))
+ end select
+ ! If we got here, it means that the new triplet is on the grid
+ ! We need to keep the symmetry equivalent point in the order they came in
+ newqp(:, j) = qpp
+ ! We also need them sorted, to check for redundancy and reducibility
+ call lo_qsort(qpp)
+ newqp_sort(:, j) = qpp
+ ! Now I check if I can reduce this triplet
+ if (qpp(1) .gt. q2) isred = .true.
+ end do
+
+ ! For stability, replace points not on the grid with starting q-point
+ ! Should not be needed if the grid respect the symmetries of the lattice though
+ if (minval(newqp) .lt. 0) then
+ do j = 1, n
+ if (any(newqp(:, j) .lt. 0)) then
+ newqp(:, j) = [q2, q3]
+ newqp_sort(:, j) = [q2, q3]
+ end if
+ end do
+ end if
+ end block get_equivalent
+
+ ! Then we just get rid of redundant point in the list
+ sort_reducible: block
+ !> Integers for the loops
+ integer :: j, k, ctr, ctr2
+
+ ctr = 0
+ do j = 1, n
+ ctr2 = 0
+ do k = j, n
+ if (k .eq. j) cycle
+ if (all(newqp_sort(:, j) .eq. newqp_sort(:, k))) ctr2 = ctr2 + 1
+ end do
+ ! If ctr2 is 0, it means that this guy is unique by permutation
+ if (ctr2 .eq. 0) ctr = ctr + 1
+ end do
+ allocate (red_triplet(2, ctr))
+ ctr = 0
+ do j = 1, n
+ ctr2 = 0
+ do k = j, n
+ if (k .eq. j) cycle
+ if (all(newqp_sort(:, j) .eq. newqp_sort(:, k))) ctr2 = ctr2 + 1
+ end do
+ ! If ctr2 is 0, it means that this guy is unique by permutation
+ if (ctr2 .eq. 0) then
+ ctr = ctr + 1
+ red_triplet(1, ctr) = newqp(1, j)
+ red_triplet(2, ctr) = newqp(2, j)
+ end if
+ end do
+ end block sort_reducible
+
+ call mem%deallocate(newqp, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+ call mem%deallocate(newqp_sort, persistent=.false., scalable=.false., file=__FILE__, line=__LINE__)
+end subroutine
diff --git a/src/thermal_conductivity_2023/Makefile b/src/thermal_conductivity_2023/Makefile
new file mode 100644
index 00000000..e1f40001
--- /dev/null
+++ b/src/thermal_conductivity_2023/Makefile
@@ -0,0 +1,47 @@
+include Makefile.inc
+CODE = thermal_conductivity_2023
+PROG = ../../build/$(CODE)/$(CODE)
+OBJECT_PATH=../../build/$(CODE)/
+
+OBJS = \
+$(OBJECT_PATH)main.o\
+$(OBJECT_PATH)options.o\
+$(OBJECT_PATH)scatteringstrengths.o\
+$(OBJECT_PATH)pbe.o\
+$(OBJECT_PATH)phononevents.o\
+$(OBJECT_PATH)mfp.o
+
+LPATH = -L../../lib $(blaslapackLPATH) $(incLPATHmpi) $(incLPATHhdf)
+IPATH = -I../../inc/libolle -I../../inc/libflap $(blaslapackIPATH) $(incIPATHmpi) $(incIPATHhdf)
+LIBS = -lolle -lflap $(blaslapackLIBS) $(incLIBSmpi) $(incLIBShdf)
+
+#OPT = -O0 -fbacktrace -fcheck=all -finit-real=nan -finit-derived
+F90 = $(FC) $(LPATH) $(IPATH) $(MODULE_FLAG) $(OBJECT_PATH) #$(warnings_gcc)
+F90FLAGS = $(OPT) $(MODS) $(LIBS)
+
+all: $(PROG)
+
+$(PROG): $(OBJS)
+ $(F90) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)
+
+clean:
+ rm -f $(PROG) $(OBJS) $(OBJECT_PATH)*.mod
+
+$(OBJECT_PATH)main.o: \
+$(OBJECT_PATH)options.o\
+$(OBJECT_PATH)scatteringstrengths.o\
+$(OBJECT_PATH)pbe.o\
+$(OBJECT_PATH)phononevents.o\
+$(OBJECT_PATH)mfp.o
+ $(F90) $(OPT) $(F90FLAGS) -c main.f90 $(LIBS) -o $@
+$(OBJECT_PATH)scatteringstrengths.o: $(OBJECT_PATH)phononevents.o
+ $(F90) $(OPT) $(F90FLAGS) -c scatteringstrengths.f90 $(LIBS) -o $@
+$(OBJECT_PATH)pbe.o: $(OBJECT_PATH)phononevents.o
+ $(F90) $(OPT) $(F90FLAGS) -c pbe.f90 $(LIBS) -o $@
+$(OBJECT_PATH)phononevents.o:
+ $(F90) $(OPT) $(F90FLAGS) -c phononevents.f90 $(LIBS) -o $@
+$(OBJECT_PATH)mfp.o:
+ $(F90) $(OPT) $(F90FLAGS) -c mfp.f90 $(LIBS) -o $@
+$(OBJECT_PATH)options.o:
+ $(F90) $(OPT) $(F90FLAGS) -c options.f90 $(LIBS) -o $@
+
diff --git a/src/thermal_conductivity_2023/main.f90 b/src/thermal_conductivity_2023/main.f90
new file mode 100644
index 00000000..6e24ab82
--- /dev/null
+++ b/src/thermal_conductivity_2023/main.f90
@@ -0,0 +1,350 @@
+#include "precompilerdefinitions"
+program thermal_conductivity_2023
+!!{!src/thermal_conductivity_2023/manual.md!}
+use konstanter, only: r8, lo_temperaturetol, lo_status, lo_kappa_au_to_SI, lo_freqtol
+use gottochblandat, only: walltime, tochar, lo_linspace, lo_logspace, lo_mean
+use mpi_wrappers, only: lo_mpi_helper
+use lo_memtracker, only: lo_mem_helper
+use type_crystalstructure, only: lo_crystalstructure
+use type_mdsim, only: lo_mdsim
+use type_forceconstant_secondorder, only: lo_forceconstant_secondorder
+use type_forceconstant_thirdorder, only: lo_forceconstant_thirdorder
+use type_qpointmesh, only: lo_qpoint_mesh, lo_generate_qmesh
+use type_phonon_dispersions, only: lo_phonon_dispersions
+use type_phonon_dos, only: lo_phonon_dos
+use dump_data, only: lo_dump_gnuplot_2d_real
+
+! unique
+use options, only: lo_opts
+use scatteringstrengths, only: calculate_scattering_amplitudes
+use pbe, only: get_kappa, get_kappa_offdiag, calculate_qs, get_selfconsistent_solution
+use phononevents, only: lo_threephononevents, lo_find_all_scattering_events
+use mfp, only: lo_mfp, get_cumulative_plots, write_cumulative_plots
+
+implicit none
+! Standard, from libolle
+type(lo_opts) :: opts
+type(lo_forceconstant_secondorder) :: fc
+type(lo_forceconstant_thirdorder) :: fct
+type(lo_phonon_dispersions) :: dr
+type(lo_phonon_dos) :: pd
+type(lo_crystalstructure) :: uc
+class(lo_qpoint_mesh), allocatable :: qp
+type(lo_mpi_helper) :: mw
+type(lo_mem_helper) :: mem
+! Unique
+type(lo_threephononevents) :: sc
+type(lo_mfp) :: mf
+! Small stuff
+real(r8), dimension(:, :), allocatable :: thermal_cond
+real(r8), dimension(:), allocatable :: temperatures
+! timers
+real(r8) :: timer_init, timer_count, timer_matrixelements, timer_scf
+real(r8) :: timer_kappa, timer_qs, timer_cumulative, tt0
+
+! Set up all harmonic properties. That involves reading all the input file,
+! creating grids, getting the harmonic properties on those grids.
+initharmonic: block
+ integer :: i, j
+ ! Start MPI and timers
+ tt0 = walltime()
+ timer_init = tt0
+ timer_qs = 0.0_r8
+ timer_kappa = 0.0_r8
+ timer_scf = 0.0_r8
+ timer_cumulative = 0.0_r8
+ call mw%init()
+ ! Get options
+ call opts%parse()
+ if (mw%r .ne. 0) opts%verbosity = -100
+ ! Init memory tracker
+ call mem%init()
+
+ if (mw%talk) write (*, *) '... using ', tochar(mw%n), ' MPI ranks'
+ ! There is a bunch of stuff that all ranks need, first the unit cell:
+ call uc%readfromfile('infile.ucposcar', verbosity=opts%verbosity)
+ call uc%classify('wedge', timereversal=opts%timereversal)
+ if (mw%talk) write (*, *) '... read unitcell poscar'
+
+ ! Perhaps non-natural isotope distribution
+ ! write (*, *) 'FIXME OUTPUT UNITS'
+ if (opts%readiso) then
+ if (mw%talk) write (*, *) '... reading isotope distribution from file'
+ call uc%readisotopefromfile()
+ if (mw%talk) then
+ do i = 1, uc%na
+ do j = 1, uc%isotope(i)%n
+ write (*, "(' isotope: ',I2,' concentration: ',F8.5,' mass: ',F12.6)") &
+ j, uc%isotope(i)%conc(j), uc%isotope(i)%mass(j)
+ end do
+ write (*, "(' element: ',A2,' mean mass: ',F12.6,' mass disorder parameter',F12.9)") &
+ trim(uc%atomic_symbol(uc%species(i))), uc%isotope(i)%mean_mass, &
+ uc%isotope(i)%disorderparameter
+ end do
+ end if
+ elseif (opts%verbosity .gt. 0) then
+ do i = 1, uc%na
+ do j = 1, uc%isotope(i)%n
+ write (*, "(' isotope: ',I2,' concentration: ',F8.5,' mass: ',F12.6)") &
+ j, uc%isotope(i)%conc(j), uc%isotope(i)%mass(j)
+ end do
+ write (*, "(' element: ',A2,' mean mass: ',F12.6,' mass disorder parameter',F12.9)") &
+ trim(uc%atomic_symbol(uc%species(i))), uc%isotope(i)%mean_mass, &
+ uc%isotope(i)%disorderparameter
+ end do
+ end if
+
+ ! Read the force constants
+ call fc%readfromfile(uc, 'infile.forceconstant', mem, -1)
+ if (mw%talk) write (*, *) '... read second order forceconstant'
+ call fct%readfromfile(uc, 'infile.forceconstant_thirdorder')
+ if (mw%talk) write (*, *) '... read third order forceconstant'
+
+ ! Get q-point mesh
+ call lo_generate_qmesh(qp, uc, opts%qgrid, 'fft', timereversal=opts%timereversal, &
+ headrankonly=.false., mw=mw, mem=mem, verbosity=opts%verbosity, nosym=.not. opts%qpsymmetry)
+
+ ! Get frequencies in the whole BZ
+ if (mw%talk) then
+ write (*, *) '... getting the full dispersion relations'
+ end if
+ call dr%generate(qp, fc, uc, mw=mw, mem=mem, verbosity=opts%verbosity)
+ ! Also the phonon DOS, for diagnostics
+ call pd%generate(dr, qp, uc, mw, mem, verbosity=opts%verbosity, &
+ sigma=opts%sigma, n_dos_point=opts%mfppts*2, integrationtype=opts%integrationtype)
+
+ ! Make sure it's stable, no point in going further if it is unstable.
+ if (dr%omega_min .lt. -lo_freqtol) then
+ write (*, *) ''
+ write (*, *) 'FOUND UNSTABLE MODES. WILL STOP NOW.'
+ call mpi_barrier(mw%comm, mw%error)
+ call mpi_finalize(lo_status)
+ stop
+ end if
+
+ ! now I have all harmonic things, stop the init timer
+ timer_init = walltime() - timer_init
+end block initharmonic
+
+! Get the integration weights and matrix elements
+weights_elements: block
+ real(r8) :: t0
+ t0 = walltime()
+ timer_count = walltime()
+ call lo_find_all_scattering_events(sc, qp, dr, uc, mw, mem, opts%sigma, opts%thres, opts%integrationtype, &
+ opts%correctionlevel, opts%mfp_max, opts%isotopescattering)
+ call mpi_barrier(mw%comm, mw%error)
+
+ ! stop counting timer, start matrixelement timer
+ timer_count = walltime() - timer_count
+ timer_matrixelements = walltime()
+
+ ! Calculate scattering amplitudes
+ t0 = walltime()
+ call calculate_scattering_amplitudes(uc, qp, sc, dr, fct, mw)
+ call mpi_barrier(mw%comm, mw%error)
+ ! stop matrix element timer, start some other timer
+ timer_matrixelements = walltime() - timer_matrixelements
+ if (mw%talk) write (*, *) 'Counted and got scattering amplitudes in ', tochar(walltime() - t0)
+end block weights_elements
+
+! Make space and initialize everything to calculate thermal conductivity
+initkappa: block
+ integer :: i
+
+ ! space to store the actual thermal conductivity
+ allocate (thermal_cond(10, opts%trangenpts))
+ thermal_cond = 0.0_r8
+
+ ! temperature axis
+ allocate (temperatures(opts%trangenpts))
+ if (opts%logtempaxis) then
+ call lo_logspace(opts%trangemin, opts%trangemax, temperatures)
+ else
+ call lo_linspace(opts%trangemin, opts%trangemax, temperatures)
+ end if
+ ! Setup the mean-free-path vs kappa plots.
+ ! how many points on the x-axis?
+ mf%np = opts%mfppts
+ ! how many temperatures?
+ mf%nt = opts%trangenpts
+ ! one plot for each temperature
+ allocate (mf%temp(mf%nt))
+
+ ! Make some space to keep intermediate values
+ do i = 1, qp%n_irr_point
+ allocate (dr%iq(i)%p_plus(dr%n_mode))
+ allocate (dr%iq(i)%p_minus(dr%n_mode))
+ allocate (dr%iq(i)%p_iso(dr%n_mode))
+ allocate (dr%iq(i)%qs(dr%n_mode))
+ allocate (dr%iq(i)%linewidth(dr%n_mode))
+ allocate (dr%iq(i)%F0(3, dr%n_mode))
+ allocate (dr%iq(i)%Fn(3, dr%n_mode))
+ allocate (dr%iq(i)%mfp(3, dr%n_mode))
+ allocate (dr%iq(i)%scalar_mfp(dr%n_mode))
+ dr%iq(i)%linewidth = 0.0_r8
+ dr%iq(i)%p_plus = 0.0_r8
+ dr%iq(i)%p_minus = 0.0_r8
+ dr%iq(i)%p_iso = 0.0_r8
+ dr%iq(i)%qs = 0.0_r8
+ dr%iq(i)%F0 = 0.0_r8
+ dr%iq(i)%Fn = 0.0_r8
+ dr%iq(i)%mfp = 0.0_r8
+ dr%iq(i)%scalar_mfp = 0.0_r8
+ end do
+ do i = 1, qp%n_full_point
+ allocate (dr%aq(i)%kappa(3, 3, dr%n_mode))
+ dr%aq(i)%kappa = 0.0_r8
+ end do
+end block initkappa
+
+! Iteratively solve the BTE for each temperature. Additionally calculate
+! mean free path plots and things like that.
+getkappa: block
+ real(r8), dimension(3, 3) :: kappa, kappa_offdiag, kappa_sma, m0
+ real(r8) :: t0
+ integer :: i
+
+ if (mw%talk) then
+ write (*, *) ''
+ write (*, *) 'THERMAL CONDUCTIVITY'
+ if (opts%scfiterations .eq. 0) then
+ write (*, "(3X,A11,6(1X,A14))") 'Temperature', 'kxx ', 'kyy ', 'kzz ', 'kxy ', 'kxz ', 'kyz '
+ end if
+ end if
+ ! Main loop over temperatures to solve the BTE
+ do i = 1, opts%trangenpts
+
+ ! I might get a silly tiny temperature, then things will break.
+ if (temperatures(i) .lt. lo_temperaturetol) then
+ kappa = 0.0_r8
+ thermal_cond(1, i) = temperatures(i)
+ thermal_cond(2:10, i) = 0.0_r8
+ cycle
+ end if
+
+ ! Scattering rates
+ t0 = walltime()
+ call calculate_qs(qp, sc, dr, temperatures(i), mw, mem)
+ timer_qs = timer_qs + walltime() - t0
+
+ call get_kappa(dr, qp, uc, temperatures(i), kappa_sma)
+ call get_kappa_offdiag(dr, qp, uc, temperatures(i), fc, mem, mw, kappa_offdiag)
+
+ ! Get the self-consistent solution
+ call mpi_barrier(mw%comm, mw%error)
+ if (opts%scfiterations .gt. 0) then
+ if (mw%talk) then
+ write (*, *) ''
+ write (*, *) 'Temperature: ', tochar(temperatures(i))
+ write (*, "(1X,A4,6(1X,A14),2X,A10)") 'iter', &
+ 'kxx ', 'kyy ', 'kzz ', 'kxy ', 'kxz ', 'kyz ', 'DeltaF/F'
+ end if
+ t0 = walltime()
+ call get_selfconsistent_solution(sc, dr, qp, uc, temperatures(i), opts%scfiterations, opts%scftol, mw, mem)
+ !call get_selfconsistent_solution(sc,dr,qp,uc,mw,temperatures(i),opts%scfiterations,opts%scftol)
+ timer_scf = timer_scf + walltime() - t0
+ call get_kappa(dr, qp, uc, temperatures(i), kappa)
+ m0 = kappa*lo_kappa_au_to_SI
+ if (mw%talk) write (*, "(5X,6(1X,F14.4),2X,E10.3)") m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
+ else
+ call get_kappa(dr, qp, uc, temperatures(i), kappa)
+ m0 = kappa*lo_kappa_au_to_SI
+ if (mw%talk) write (*, "(1X,F12.3,6(1X,F14.4),2X,E10.3)") &
+ temperatures(i), m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
+ end if
+
+ if (mw%talk) then
+ m0 = kappa_sma*lo_kappa_au_to_SI
+ write (*, *) ''
+ write (*, "(1X,A52)") 'Decomposition of the thermal conductivity (in W/m/K)'
+ write (*, "(1X,A85)") 'Single mode relaxation time approximation (RTA) to Boltzmann transport equation (BTE)'
+ write (*, "(1X,A4,6(1X,A14))") '', 'kxx ', 'kyy ', 'kzz ', 'kxy ', 'kxz ', 'kyz '
+ write (*, "(5X,6(1X,F14.4),2X,E10.3)") m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
+ m0 = (kappa - kappa_sma)*lo_kappa_au_to_SI
+ write (*, "(1X,A73)") 'Correction to full solution of the linearized BTE via iterative procedure'
+ write (*, "(1X,A4,6(1X,A14))") '', 'kxx ', 'kyy ', 'kzz ', 'kxy ', 'kxz ', 'kyz '
+ write (*, "(5X,6(1X,F14.4),2X,E10.3)") m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
+ m0 = kappa_offdiag*lo_kappa_au_to_SI
+ write (*, "(1X,A36)") 'Off diagonal (coherent) contribution'
+ write (*, "(1X,A4,6(1X,A14))") '', 'kxx ', 'kyy ', 'kzz ', 'kxy ', 'kxz ', 'kyz '
+ write (*, "(5X,6(1X,F14.4),2X,E10.3)") m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
+ m0 = (kappa + kappa_offdiag)*lo_kappa_au_to_SI
+ write (*, "(1X,A26)") 'Total thermal conductivity'
+ write (*, "(1X,A4,6(1X,A14))") '', 'kxx ', 'kyy ', 'kzz ', 'kxy ', 'kxz ', 'kyz '
+ write (*, "(5X,6(1X,F14.4),2X,E10.3)") m0(1, 1), m0(2, 2), m0(3, 3), m0(1, 2), m0(1, 3), m0(2, 3)
+ end if
+
+ ! Store thermal conductivity tensor
+ thermal_cond(1, i) = temperatures(i)
+ thermal_cond(2, i) = (kappa(1, 1) + kappa_offdiag(1, 1))*lo_kappa_au_to_SI
+ thermal_cond(3, i) = (kappa(2, 2) + kappa_offdiag(2, 2))*lo_kappa_au_to_SI
+ thermal_cond(4, i) = (kappa(3, 3) + kappa_offdiag(3, 3))*lo_kappa_au_to_SI
+ thermal_cond(5, i) = (kappa(1, 3) + kappa_offdiag(1, 3))*lo_kappa_au_to_SI
+ thermal_cond(6, i) = (kappa(2, 3) + kappa_offdiag(2, 3))*lo_kappa_au_to_SI
+ thermal_cond(7, i) = (kappa(1, 2) + kappa_offdiag(1, 2))*lo_kappa_au_to_SI
+ thermal_cond(8, i) = (kappa(3, 1) + kappa_offdiag(3, 1))*lo_kappa_au_to_SI
+ thermal_cond(9, i) = (kappa(3, 2) + kappa_offdiag(3, 2))*lo_kappa_au_to_SI
+ thermal_cond(10, i) = (kappa(2, 1) + kappa_offdiag(2, 1))*lo_kappa_au_to_SI
+
+ ! Calculate the cumulative plots
+ t0 = walltime()
+ call mpi_barrier(mw%comm, mw%error)
+ call get_cumulative_plots(mf%temp(i), qp, dr, pd, uc, opts%mfppts, temperatures(i), opts%sigma, kappa, mw, mem)
+
+ timer_cumulative = timer_cumulative + walltime() - t0
+ call mpi_barrier(mw%comm, mw%error)
+ end do
+end block getkappa
+
+! dump things to file and print timings
+finalize_and_write: block
+ real(r8) :: t0
+
+ ! Write thermal conductivity to file
+ if (mw%talk) call lo_dump_gnuplot_2d_real(thermal_cond, 'outfile.thermal_conductivity', &
+ ylabel='\kappa W/mK', xlabel='Temperature (K)')
+
+ ! Write the cumulative kappa
+ if (mw%talk) call write_cumulative_plots(mf, pd, uc, 'thz', 'outfile.cumulative_kappa.hdf5', opts%verbosity)
+
+ ! Maybe dump data on a grid
+ if (mw%talk .and. opts%dumpgrid .and. opts%trangenpts .eq. 1) then
+ write (*, *) '... dumping auxiliary data to files:'
+ call dr%write_to_hdf5(qp, uc, 'outfile.grid_thermal_conductivity.hdf5', mem, temperatures(1))
+ end if
+
+ ! sum up the total time
+ if (mw%talk) tt0 = walltime() - tt0
+
+ ! Print timings
+ if (mw%talk) then
+ write (*, *) ''
+ write (*, '(1X,A21)') 'Suggested citations :'
+ write (*, '(1X,A41,A56)') 'Software : ', 'F. Knoop et al., J. Open Source Softw 9(94), 6150 (2024)'
+ write (*, '(1X,A41,A53)') 'Method : ', 'D. A. Broido et al., Appl Phys Lett 91, 231922 (2007)'
+ write (*, '(1X,A41,A43)') 'Iterative Boltzmann transport equation : ', 'M. Omini et al., Phys Rev B 53, 9064 (1996)'
+ write (*, '(1X,A41,A49)') 'Algorithm : ', 'A. H. Romero et al., Phys Rev B 91, 214310 (2015)'
+ write (*, '(1X,A41,A43)') 'Off diagonal coherent contribution : ', 'L. Isaeva et al., Nat Commun 10 3853 (2019)'
+
+ t0 = timer_init + timer_count + timer_matrixelements + timer_qs + timer_kappa + timer_scf + timer_cumulative
+ write (*, *) ' '
+ write (*, *) 'Timings:'
+ write (*, "(A,F12.3,A,F7.3,A)") ' initialization:', timer_init, ' s, ', real(timer_init*100/tt0), '%'
+ write (*, "(A,F12.3,A,F7.3,A)") ' integration weights:', timer_count, ' s, ', real(timer_count*100/tt0), '%'
+ write (*, "(A,F12.3,A,F7.3,A)") ' matrix elements:', timer_matrixelements, &
+ ' s, ', real(timer_matrixelements*100/tt0), '%'
+ write (*, "(A,F12.3,A,F7.3,A)") ' QS calculation:', timer_qs, ' s, ', real(timer_qs*100/tt0), '%'
+ write (*, "(A,F12.3,A,F7.3,A)") ' kappa:', timer_kappa, ' s, ', real(timer_kappa*100/tt0), '%'
+ write (*, "(A,F12.3,A,F7.3,A)") ' self consistency:', timer_scf, ' s, ', real(timer_scf*100/tt0), '%'
+ write (*, "(A,F12.3,A,F7.3,A)") ' cumulative plots:', timer_cumulative, &
+ ' s, ', real(timer_cumulative*100/tt0), '%'
+ write (*, "(A,F12.3,A)") ' total:', tt0, ' seconds'
+ end if
+end block finalize_and_write
+
+! And we are done!
+call mpi_barrier(mw%comm, mw%error)
+call mpi_finalize(lo_status)
+
+end program
diff --git a/src/thermal_conductivity_2023/manual.md b/src/thermal_conductivity_2023/manual.md
new file mode 100644
index 00000000..e57a1ced
--- /dev/null
+++ b/src/thermal_conductivity_2023/manual.md
@@ -0,0 +1,887 @@
+
+### Longer summary
+
+Heat transport can be determined by solving the inelastic phonon Boltzmann equation. By applying a temperature gradient $\nabla T_\alpha$ in direction $\alpha$, the heat current is given by the group velocities of phonon mode $\lambda$ and non-equilibrium phonon distribution function $\tilde{n}_\lambda$:[^peierls1955quantum]
+
+$$
+\begin{equation}
+J_{\alpha}=\frac{1}{V}\sum_\lambda
+\hbar \omega_\lambda v_{\lambda\alpha} \tilde{n}_{\lambda\alpha}.
+\end{equation}
+$$
+
+Assuming the thermal gradient is small, the non-equilibrium distribution function can be linearised as,
+
+$$
+\tilde{n}_{\lambda\alpha} \approx n_{\lambda}-
+v_{\lambda\alpha}
+\tau_{\lambda\alpha}
+\frac{d n_{\lambda}}{d T}
+\frac{d T}{d \alpha} \, ,
+$$
+
+That is a linear deviation from the equilibrium distribution function $n_{\lambda}$. Inserting this into the equation 1, and exploiting the fact that the equilibrium occupation carries no heat, we arrive at,
+
+$$
+J_{\alpha}=\frac{1}{V}\sum_{\lambda}
+\hbar \omega_{\lambda}
+\frac{d n_{\lambda}}{d T}
+v_{\lambda\alpha}
+v_{\lambda\alpha}
+\tau_{\lambda\alpha}
+\frac{d T}{d \alpha}.
+$$
+
+Utilizing Fourier's law, $J=\kappa \nabla T$, and identifying the phonon heat capacity,
+
+$$
+c_{\lambda}=
+\hbar \omega_\lambda
+\frac{d n_{\lambda}}{d T},
+$$
+
+we arrive at,
+
+$$
+\kappa_{\alpha\beta}=\frac{1}{V} \sum_{\lambda}
+c_{\lambda}
+v_{\alpha \lambda}v_{\beta \lambda} \tau_{\beta \lambda},
+$$
+
+which can be interpreted as follows: the heat transported by each phonon will depend on how much heat it carries, how fast it travels, and how long it lives. The phonon-phonon induced lifetime can be determined from the self-energy $\Gamma_{\lambda}$. In addition, one must consider the scattering with mass impurities (isotopes), and the boundaries of the sample.
+
+### Lifetimes
+
+With the third order force constants we can calculate the phonon lifetimes needed as input to the thermal conductivity calculations. The lifetime due to phonon-phonon scattering is related to the imaginary part of the phonon self energy ( $\Sigma=\Delta+i\Gamma$ ).
+
+$$
+\frac{1}{\tau_{\lambda}}=2 \Gamma_{\lambda},
+$$
+
+where $\tau_{\lambda}$ is the lifetime phonon mode $\lambda$, and
+
+$$
+\begin{split}
+\Gamma_{\lambda}=& \frac{\hbar \pi}{16} % _{\lambda'}
+\sum_{\lambda'\lambda''}
+\left|\Phi_{\lambda\lambda'\lambda''}\right|^2
+\bigl[(n_{\lambda'}+n_{\lambda''}+1)
+\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''}) \\
++ & 2(n_{\lambda'}-n_{\lambda''})
+\delta(\omega_{\lambda}-\omega_{\lambda'}+\omega_{\lambda''}) \bigr]
+\end{split}
+$$
+
+$n_{\lambda}$ is the equilibrium occupation number. The sum is over momentum conserving three-phonon processes, $\textbf{q}+\textbf{q}'+\textbf{q}''=\textbf{G}$, and the deltafunctions in frequency ensure energy conservation. The three-phonon matrix elements are given by
+
+$$
+\Phi_{\lambda\lambda'\lambda''} =
+\sum_{ijk}
+\sum_{\alpha\beta\gamma}
+\frac{
+\epsilon_{\lambda}^{i \alpha}
+\epsilon_{\lambda'}^{j \beta}
+\epsilon_{\lambda''}^{k \gamma}
+}{
+\sqrt{m_{i}m_{j}m_{j}}
+\sqrt{
+ \omega_{\lambda}
+ \omega_{\lambda'}
+ \omega_{\lambda''}}
+}
+\Phi^{\alpha\beta\gamma}_{ijk}
+e^{i \mathbf{q}\cdot\mathbf{r}_i + i \mathbf{q}'\cdot\mathbf{r}_j+i \mathbf{q}''\cdot\mathbf{r}_k}
+$$
+
+where $m_i$ is the mass of atom $i$, $\epsilon_{\lambda}^{\alpha i}$ is component $\alpha$ of the eigenvector for mode $\lambda$ and atom $i$ and $\textbf{r}_i$ is the lattice vector associated with atom $i$.
+
+Mass disorder, in the form of natural isotope distributions also cause thermal resistance. According to Tamura[^Tamura1983], if the isotopes are randomly distributed on the lattice sites then the strength of the isotope scattering can be given by a mass variance parameter $g$:
+
+$$
+g_i=\sum_j c_{i}^j \left(\frac{m_i^j-\bar{m_i}}{\bar{m_i}}\right)^2
+$$
+
+where $\bar{m_i}$ is the average isotopic mass( $\bar{m_i}=\sum_j c_i^j m_i^j$ ), $m^j_i$ is the mass of isotope $j$ of atom $i$ and $c^j_i$ is its concentration. The contribution to the imaginary part of the self-energy is
+
+$$
+\Gamma^{\textrm{iso}}_{\lambda}=
+\frac{\pi}{4} \sum_{\lambda'}
+\underbrace{\omega_{\lambda}\omega_{\lambda'} \sum_i g_i \left| \epsilon_{\lambda}^{i \dagger} \epsilon_{\lambda'}^{i} \right|^2}_{\Lambda_{\lambda\lambda'}}
+\delta(\omega_{\lambda}-\omega_{\lambda'})
+$$
+
+Per default, the isotope distribution will be the natural distribution. In case some other distribution is desired, this can be specified.
+
+Scattering by domain boundaries is implemented as
+
+$$
+\Gamma^{\textrm{boundary}}_{\lambda} = \frac{ v_{\lambda} }{2d}
+$$
+
+Where $d$ is a characteristic domain size.
+
+### Beyond the relaxation time approximation
+
+So far we have have considered the phonon heat conduction as an elastic process, whereas it is inelastic. This can be treated by iteratively solving the phonon boltzmann equation, formulated in terms of the (linear) deviations from equilibrium occupation numbers.[^peierls1929],[^Omini1996],[^Omini],[^Broido2007],[^Broido2005]
+
+### Phonon scattering rates and the phonon Boltzmann equation
+
+I always found it confusing how you arrived at most of these things. This is something I put together for myself, to clear it up a bit. Please bear in mind that this is not an attempt at a formal derivation whatsoever, just to make it a bit easier to interpret the different terms. There might be an arbitrary number of plusses and minuses and other things missing. Recall the transformation we introduced [earlier](phonon_dispersion_relations.md):
+
+$$
+\begin{equation}\label{eq:normalmodetransformation}
+\hat{u}_{i\alpha} = \sqrt{ \frac{\hbar}{2N m_\alpha} }
+\sum_\lambda \frac{\epsilon_\lambda^{i\alpha}}{ \sqrt{ \omega_\lambda} }
+e^{i\mathbf{q}\cdot\mathbf{r}_i}
+\left( \hat{a}^{\mathstrut}_\lambda + \hat{a}^\dagger_\lambda \right)
+\end{equation}
+$$
+
+and consider the three-phonon process where two phonons combine into one:
+
+$$
+\begin{equation*}
+\begin{split}
+\mathbf{q} + \mathbf{q}' + \mathbf{q}'' & = \mathbf{G} \\
+\omega + \omega' & = \omega''
+\end{split}
+\end{equation*}
+$$
+
+This process changes the state of the system:
+
+$$
+\begin{equation}
+\underbrace{\left| \ldots , n_{\lambda},n_{\lambda'},n_{\lambda''} , \ldots \right\rangle}_{\left\vert i \right\rangle}
+\rightarrow
+\underbrace{\left| \ldots , n_{\lambda}-1,n_{\lambda'}-1,n_{\lambda''}+1, \ldots \right\rangle}_{\left\vert f \right\rangle}
+\end{equation}
+$$
+
+that is, we lost one phonon at $\lambda$ and one at $\lambda'$, and created a phonon at $\lambda''$.
+Mostly out of habit, we sandwich the Hamiltonian between the initial and final states:
+
+$$
+\begin{equation}\label{eq:sandwich}
+{\left\langle f \middle\vert \hat{H} \middle\vert i \right\rangle} =
+{\left\langle f \middle\vert \sum_i \frac{p^2_i}{2m} +
+\frac{1}{2!}\sum_{ij} \sum_{\alpha\beta}\Phi_{ij}^{\alpha\beta}
+u_i^\alpha u_j^\beta +\frac{1}{3!}
+\sum_{ijk} \sum_{\alpha\beta\gamma}\Phi_{ijk}^{\alpha\beta\gamma}
+u_i^\alpha u_j^\beta u_k^\gamma \ldots
+\middle\vert i \right\rangle}
+\end{equation}
+$$
+
+and remember the rules for ladder operators, and that the eigenstates to the quantum harmonic oscillator are orthogonal:
+
+$$
+\begin{equation*}
+\begin{split}
+\hat{a}^\dagger \left\vert n \right\rangle & = \sqrt{n+1} \left\vert n + 1 \right\rangle \\
+\hat{a} \left\vert n \right\rangle & = \sqrt{n} \left\vert n -1 \right\rangle \\
+\left\langle i \middle\vert j \right\rangle & = \delta_{ij}
+\end{split}
+\end{equation*}
+$$
+
+Inserting eq \ref{eq:normalmodetransformation} into \ref{eq:sandwich} (and realising that the kinetic energy part and the second order part disappears), we end up with a pretty large expression, that we will deal with in steps, first identify
+
+$$
+\begin{equation}\label{eq:uprod}
+\begin{split}
+u^\alpha_{i}u^\beta_{j}u^\gamma_{k} & =
+%
+\left(\frac{\hbar}{2N}\right)^{3/2} \frac{1}{\sqrt{m_{i}m_{j}m_{k}}}
+\sum_{\lambda\lambda'\lambda''}
+\frac{
+\epsilon_{\lambda}^{i \alpha}
+\epsilon_{\lambda'}^{j \beta}
+\epsilon_{\lambda''}^{k \gamma}
+}{
+\sqrt{
+ \omega_{\lambda}
+ \omega_{\lambda'}
+ \omega_{\lambda''}}
+}
+e^{i \mathbf{q}\cdot\mathbf{r}_i + i \mathbf{q}'\cdot\mathbf{r}_j+i \mathbf{q}''\cdot\mathbf{r}_k}
+ \left(a_{\lambda}+a_{\lambda}^\dagger \right)
+\left(a_{\lambda'}+a_{\lambda'}^\dagger \right)
+\left(a_{\lambda''}+a_{\lambda''}^\dagger \right)
+\end{split}
+\end{equation}
+$$
+
+as well as
+
+$$
+\begin{equation}
+\begin{split}
+& \sum_{\lambda\lambda'\lambda''}
+\left\langle f \middle\vert
+\left(a_{\lambda}+a_{\lambda}^\dagger \right)
+\left(a_{\lambda'}+a_{\lambda'}^\dagger \right)
+\left(a_{\lambda''}+a_{\lambda''}^\dagger \right)
+\middle\vert i \right\rangle = \\
+= & \sum_{\lambda\lambda'\lambda''} \left\langle f \middle\vert
+\hat{a}_{\lambda} \hat{a}_{\lambda'} \hat{a}_{\lambda''} + \hat{a}_{\lambda} \hat{a}_{\lambda'} \hat{a}^{\dagger}_{\lambda''} + \hat{a}_{\lambda} \hat{a}^{\dagger}_{\lambda'} \hat{a}_{\lambda''} + \hat{a}_{\lambda} \hat{a}^{\dagger}_{\lambda'} \hat{a}^{\dagger}_{\lambda''} + \hat{a}^{\dagger}_{\lambda} \hat{a}_{\lambda'} \hat{a}_{\lambda''} + \hat{a}^{\dagger}_{\lambda} \hat{a}_{\lambda'} \hat{a}^{\dagger}_{\lambda''} + \hat{a}^{\dagger}_{\lambda} \hat{a}^{\dagger}_{\lambda'} \hat{a}_{\lambda''} + \hat{a}^{\dagger}_{\lambda} \hat{a}^{\dagger}_{\lambda'} \hat{a}^{\dagger}_{\lambda''}
+\middle\vert i \right\rangle = \\
+= & \sum_{\lambda\lambda'\lambda''} \left\langle f \middle\vert
+a_{\lambda}a_{\lambda'}a^\dagger_{\lambda''}
+\middle\vert i \right\rangle
+ = 3 \sqrt{n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)}
+\end{split}
+\end{equation}
+$$
+
+where the factor 3 comes from the multiplicity, to get at
+
+$$
+\begin{equation}
+{\left\langle f \middle\vert \hat{H}_3 \middle\vert i \right\rangle} =
+\frac{1}{2}
+\sum_{ijk} \sum_{\alpha\beta\gamma}\Phi_{ijk}^{\alpha\beta\gamma}
+\sqrt{n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)}
+%
+\left(\frac{\hbar}{2N}\right)^{3/2}
+\frac{
+\epsilon_{\lambda}^{i \alpha}
+\epsilon_{\lambda'}^{j \beta}
+\epsilon_{\lambda''}^{k \gamma}
+}{
+\sqrt{m_{i}m_{j}m_{j}}
+\sqrt{
+ \omega_{\lambda}
+ \omega_{\lambda'}
+ \omega_{\lambda''}}
+}
+e^{i \mathbf{q}\cdot\mathbf{r}_i + i \mathbf{q}'\cdot\mathbf{r}_j+i \mathbf{q}''\cdot\mathbf{r}_k}
+\end{equation}
+$$
+
+The initial factor 1/2 is the multiplicity cancelled by the 3! from the Hamiltonian. Here, as it happens, we can identify the three-phonon matrix elements and simplify a little bit more
+
+$$
+\begin{equation}
+{\left\langle f \middle\vert \hat{H}_3 \middle\vert i \right\rangle} =
+\frac{1}{2}
+\sqrt{n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)}
+\left(\frac{\hbar}{2N}\right)^{3/2}
+\Phi_{\lambda\lambda'\lambda''}
+\end{equation}
+$$
+
+The probability of this particular three-phonon process can be estimated via the Fermi golden rule:
+
+$$
+\begin{equation}
+\begin{split}
+P_{\lambda\lambda'\rightarrow\lambda''} & =\frac{2\pi}{\hbar}
+\left|{\left\langle f \middle\vert \hat{H}_3 \middle\vert i \right\rangle}\right|^2
+\delta(E_f-E_i) =
+\frac{\hbar^2\pi}{16N}
+n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)
+\left| \Phi_{\lambda\lambda'\lambda''} \right|^2
+\delta(E_f-E_i)
+\end{split}
+\end{equation}
+$$
+
+With near identical reasoning, we can also arrive at
+
+$$
+\begin{equation}\label{pplus}
+P_{\lambda\rightarrow\lambda'\lambda''} =
+\frac{\hbar^2\pi}{16N}
+n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1)
+\left| \Phi_{\lambda\lambda'\lambda''} \right|^2
+\delta(E_f-E_i)
+\end{equation}
+$$
+
+for the other kind of three-phonon processes, and
+
+$$
+\begin{equation}\label{pminus}
+P_{\lambda\rightarrow\lambda'} =\frac{2\pi}{\hbar}\left|\langle f | H^{\textrm{iso}} | i \rangle \right|^2\delta(E_f-E_i) =
+\frac{\pi\hbar}{2N} n_{\lambda}(n_{\lambda'}+1) \Lambda_{\lambda\lambda'}\delta(E_f-E_i)
+\end{equation}
+$$
+
+for the isotope scattering. I leave those derivations as an exercise. The phonon Boltzmann equation is stated as:
+
+$$
+\begin{equation}\label{eq:pbe}
+\frac{\partial \tilde{n}_\lambda}{\partial T} \mathbf{v}_\lambda \cdot \nabla T =
+\left. \frac{\partial \tilde{n}_\lambda }{\partial t} \right|_{\mathrm{coll}}
+\end{equation}
+$$
+
+Where $\tilde{n}$ is the non-equilibrium occupation number. This is ridiculously complicated. To make life easier, we only consider the terms we outlined above as possible collisions. Gathering all possible events that involve mode $\lambda$ we get
+
+$$
+\begin{equation}\label{manyprob}
+\begin{split}
+\left. \frac{\partial n_{\lambda}}{ \partial t} \right|_{\mathrm{coll}}
+= & \sum_{\lambda'}
+\left( P_{\lambda\rightarrow\lambda'}-P_{\lambda'\rightarrow\lambda } \right) +
+\sum_{\lambda'\lambda''}
+- P_{\lambda \rightarrow \lambda' \lambda'' }
+- P_{\lambda \rightarrow \lambda''\lambda' }
++ P_{\lambda' \rightarrow \lambda \lambda'' }
++ P_{\lambda' \rightarrow \lambda''\lambda }
++ P_{\lambda''\rightarrow \lambda \lambda' }
++ P_{\lambda''\rightarrow \lambda' \lambda } \\
+& - P_{\lambda \lambda' \rightarrow \lambda'' }
+- P_{\lambda \lambda'' \rightarrow \lambda' }
+- P_{\lambda' \lambda \rightarrow \lambda'' }
++ P_{\lambda' \lambda'' \rightarrow \lambda }
+- P_{\lambda'' \lambda \rightarrow \lambda' }
++ P_{\lambda'' \lambda' \rightarrow \lambda }
+\end{split}
+\end{equation}
+$$
+
+Which does not seem to make life easier. To make it slightly worse, we insert \ref{pplus} and \ref{pminus} into this, and at the same time say that the non-equilibrium distribution functions are the equilibrium distributions, plus a (small) deviation:
+
+$$
+\begin{equation}
+\tilde{n}_{\lambda}\approx n_{\lambda}+\epsilon_{\lambda}
+\end{equation}
+$$
+
+After some [hard work](https://reference.wolfram.com/language/ref/FullSimplify.html), and discarding terms of $\epsilon^2$ and higher, we get
+
+$$
+\begin{equation}
+\begin{split}
+\left. \frac{\partial n_{\lambda}}{ \partial t} \right|_{\mathrm{coll}}
+= & \sum_{\lambda'\lambda''}
+\frac{\hbar\pi}{8N}
+\left| \Phi_{\lambda\lambda'\lambda''} \right|^2 \Big(
+\left[
+-n_{\lambda} \epsilon_{\lambda'} + n_{\lambda''} (\epsilon_{\lambda} + \epsilon_{\lambda'}) + \epsilon_{\lambda''} + n_{\lambda} \epsilon_{\lambda''} + n_{\lambda'} (-\epsilon_{\lambda} + \epsilon_{\lambda''})
+\right]\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''}) + \\
+& \left[
+\epsilon_{\lambda'} + n_{\lambda} \epsilon_{\lambda'} + n_{\lambda''} (-\epsilon_{\lambda} + \epsilon_{\lambda'}) - n_{\lambda} \epsilon_{\lambda''} +
+ n_{\lambda'} (\epsilon_{\lambda} + \epsilon_{\lambda''} )
+\right]\delta(\omega_{\lambda}-\omega_{\lambda'}+\omega_{\lambda''}) - \\
+& \left[(1 + n_{\lambda'} + n_{\lambda''})\epsilon_{\lambda} - n_{\lambda''}\epsilon_{\lambda''} - n_{\lambda'} \epsilon_{\lambda''} + n_{\lambda} (\epsilon_{\lambda'} + \epsilon_{\lambda''} )\right]
+\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''}) \Big)
+\end{split}
+\end{equation}
+$$
+
+Which does not seem like a lot of help. If we make another substitution, and say that the deviation from equilibrium behaves sort of like the equilibrium (with no loss of generality, just to make life easier):
+
+$$
+\begin{equation}
+\epsilon_{\lambda} =
+\frac{\partial n_{\lambda} }{\partial \omega_\lambda}
+\frac{k_B T}{\hbar} \zeta_{\lambda}=-n_{\lambda}(n_{\lambda}+1) \zeta_{\lambda}
+\end{equation}
+$$
+
+Inserting this, and more tedious algebra, we get
+
+$$
+\begin{equation}
+\begin{split}
+\left. \frac{\partial n_{\lambda}}{ \partial t} \right|_{\mathrm{coll}}
+=& \frac{\hbar\pi}{4N}
+\sum_{\lambda'\lambda''}
+\left| \Phi_{\lambda\lambda'\lambda''} \right|^2 \Big(
+n_{\lambda} n_{\lambda'} (n_{\lambda''}+1) \delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''} )
+\left( \zeta_{\lambda} + \zeta_{\lambda'} - \zeta_{\lambda''} \right) + \\
+& \frac{1}{2} n_{\lambda} (n_{\lambda'}+1) (n_{\lambda''}+1) \delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})
+\left( \zeta_{\lambda} - \zeta_{\lambda'} -\zeta_{\lambda''} \right) \Big)
+\end{split}
+\end{equation}
+$$
+
+If we add the isotope term again, that I forgot at some point between the beginning and here, we can rearrange this in terms of scattering rates that should look familiar (using strange relations for occupation numbers that only hold when the deltafunctions in energy are satisfied):
+
+$$
+\begin{equation}
+\left. \frac{\partial n_{\lambda}}{ \partial t} \right|_{\mathrm{coll}} =
+\sum_{\lambda'\lambda''}
+\tilde{P}^{+}_{\lambda\lambda'\lambda''}
+\left( \zeta_{\lambda}+\zeta_{\lambda'}-\zeta_{\lambda''} \right)
++\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
+\left( \zeta_{\lambda}-\zeta_{\lambda'}-\zeta_{\lambda''} \right)+
+\sum_{\lambda'}
+\tilde{P}^\textrm{iso}_{\lambda\lambda'} \left( \zeta_{\lambda}-\zeta_{\lambda'} \right)
+\end{equation}
+$$
+
+where
+
+$$
+\begin{align}
+\tilde{P}^{+}_{\lambda\lambda'\lambda''}&=
+\frac{\hbar \pi}{4 N}
+n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)\left|\Phi_{\lambda\lambda'\lambda''}\right|^2
+\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''})
+\\
+\tilde{P}^{-}_{\lambda\lambda'\lambda''}&=
+\frac{\hbar \pi}{4 N}
+n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1)\left|\Phi_{\lambda\lambda'\lambda''}\right|^2
+\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})
+\\
+\tilde{P}^\textrm{iso}_{\lambda\lambda'} &=
+\frac{\pi}{2N} n_{\lambda}(n_{\lambda'}+1) \Lambda_{\lambda\lambda'}
+\delta(\omega_{\lambda}-\omega_{\lambda})
+\end{align}
+$$
+
+What we have done here is to rearrange the transition propabilities to scattering rates. If we let
+
+$$
+\begin{equation}
+\zeta_{\lambda}=\frac{\hbar}{k_B T} \mathbf{F}_{\lambda} \cdot \nabla T
+\end{equation}
+$$
+
+and combine everything we end up with
+
+$$
+\begin{equation}
+\begin{split}
+-\frac{\omega_{\lambda}}{T}n_{\lambda}(n_{\lambda}+1)\mathbf{v}_{\lambda} \cdot \nabla T = &
+ \sum_{\lambda'}
+\tilde{P}^\textrm{iso}_{\lambda\lambda'}
+\left(\mathbf{F}_{\lambda}-\mathbf{F}_{\lambda'}\right)\cdot\nabla T +
+\sum_{\lambda'\lambda''}
+\tilde{P}^{+}_{\lambda\lambda'\lambda''}
+\left(\mathbf{F}_{\lambda}+\mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''}\right)\cdot\nabla T+
+\tilde{P}^{-}_{\lambda\lambda'\lambda''}
+\left(\mathbf{F}_{\lambda}-\mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''}\right)\cdot\nabla T =
+\\ = &
+\mathbf{F}_{\lambda}\cdot\nabla T
+\left(
+\sum_{\lambda'}
+\tilde{P}^\textrm{iso}_{\lambda\lambda'}
++
+\sum_{\lambda'\lambda''}
+\tilde{P}^{+}_{\lambda\lambda'\lambda''}+
+\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
+\right)- \\
+& - \sum_{\lambda'}
+\tilde{P}^\textrm{iso}_{\lambda\lambda'}\mathbf{F}_{\lambda'}\cdot\nabla T +
+\sum_{\lambda'\lambda''}
+\tilde{P}^{+}_{\lambda\lambda'\lambda''}
+\left(\mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''}\right)\cdot\nabla T-
+\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
+\left(\mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''}\right)\cdot\nabla T
+\end{split}
+\end{equation}
+$$
+
+Where we can identify
+
+$$
+\begin{equation}
+Q_{\lambda}=\sum_{\lambda'}
+\tilde{P}^\textrm{iso}_{\lambda\lambda'}
++
+\sum_{\lambda'\lambda''}
+\tilde{P}^{+}_{\lambda\lambda'\lambda''}+
+\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
+\end{equation}
+$$
+
+And rearrange terms
+
+$$
+\begin{equation}
+\mathbf{F}_{\lambda}=
+\frac{\omega_{\lambda} \bar{n}_{\lambda}(\bar{n}_{\lambda}+1)\mathbf{v}_{\lambda} }{T Q_{\lambda}}
++
+\frac{1}{Q_{\lambda}}\left[
+\sum_{\mathbf{q}'\mathbf{q}''}\sum_{s's''}
+\tilde{P}^{+}_{\lambda\lambda'\lambda''}
+\left( \mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''} \right)-
+\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
+\left( \mathbf{F}_{\lambda'}-\mathbf{F}_{\lambda''} \right)
+\right]
+\end{equation}
+$$
+
+And we have a set of equations for $F$ that we can solve self-consistently. Previously, we used the imaginary part of the self-energy to get a phonon lifetime. What we got here, from Fermi golden rule, is related:
+
+$$
+\sum_{\lambda'} \tilde{P}^\textrm{iso}_{\lambda\lambda'} =
+\frac{\pi}{2N} n_{\lambda}(n_{\lambda}+1) \sum_{\lambda'} \Lambda_{\lambda\lambda'}
+\delta(\omega_{\lambda}-\omega_{\lambda}) = 2 n_{\lambda}(n_{\lambda}+1) \Gamma^{\textrm{iso}}_{\lambda}
+$$
+
+This can also be done for the three-phonon terms:
+
+$$
+\begin{equation}
+\begin{split}
+\sum_{\lambda'\lambda''} \tilde{P}^{+}_{\lambda\lambda'\lambda''}+
+\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''} & =
+\frac{\hbar \pi}{8 N}
+\sum_{\lambda'\lambda''} \left|\Phi_{\lambda\lambda'\lambda''}\right|^2
+\left[
+n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1) \delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})+
+2n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''})
+\right] \\
+& = n_{\lambda}(n_{\lambda}+1) \frac{\hbar \pi}{8 N}
+\sum_{\lambda'\lambda''} \left|\Phi_{\lambda\lambda'\lambda''}\right|^2
+\left[
+\frac{n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1)}{n_{\lambda}(n_{\lambda}+1)}
+\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})
++
+\frac{2n_{\lambda}n_{\lambda'}(n_{\lambda''}+1)}{n_{\lambda}(n_{\lambda}+1)}
+\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''})
+\right] \\
+& =
+n_{\lambda}(n_{\lambda}+1) \frac{\hbar \pi}{8 N}
+\sum_{\lambda'\lambda''} \left|\Phi_{\lambda\lambda'\lambda''}\right|^2
+\left[
+(n_{\lambda'}+n_{\lambda''}+1)
+\delta(\omega_{\lambda}-\omega_{\lambda'}-\omega_{\lambda''})
++
+(n_{\lambda'}-n_{\lambda''})
+\delta(\omega_{\lambda}+\omega_{\lambda'}-\omega_{\lambda''})
+\right] \\
+& = 2 n_{\lambda}(n_{\lambda}+1) \Gamma_{\lambda}
+\end{split}
+\end{equation}
+$$
+
+Where the second to last step seems a little impossible, but with $\hbar\omega/k_BT = x$, you get
+
+$$
+\begin{equation}
+\frac{ n_{\lambda}(n_{\lambda'}+1)(n_{\lambda''}+1) }{ n_{\lambda}(n_{\lambda}+1) } -
+\left( n_{\lambda'} + n_{\lambda''} + 1 \right)
+=
+\frac{
+1-\exp[x'+x''-x]
+}{
+\left( \exp[x'] -1 \right) \left( \exp[x''] -1 \right)
+}
+\end{equation}
+$$
+
+which comes out to 0 when $x=x'+x''$, which the deltafunction ensures. In the same way
+
+$$
+\begin{equation}
+\frac{ n_{\lambda}n_{\lambda'}(n_{\lambda''}+1) }{ n_{\lambda}(n_{\lambda}+1) } -
+\left( n_{\lambda'} - n_{\lambda''} \right)
+=
+\frac{
+\exp[-x]\left(\exp[x+x']-\exp[x''] \right)
+}{
+\left( \exp[x'] -1 \right) \left( \exp[x''] -1 \right)
+}
+\end{equation}
+$$
+
+comes out to 0 when $x''=x+x'$. We can directly relate the relaxation time lifetime
+
+$$
+\begin{equation}
+\tau_{\lambda} = \frac{1}{2\Gamma_{\lambda}} = \frac{ n_{\lambda}(n_{\lambda}+1) }{Q_{\lambda}}
+\end{equation}
+$$
+
+to an initial guess
+
+$$
+\mathbf{F}^0_{\lambda} =
+\frac{\tau_{\lambda} \omega_{\lambda} \mathbf{v}_{\lambda} }{T}
+$$
+
+and iteratively solve
+
+$$
+\begin{equation}
+\mathbf{F}^{i+1}_{\lambda}=
+\mathbf{F}^0_{\lambda}
++
+\frac{1}{Q_{\lambda}}\left[
+\sum_{\lambda'\lambda''}
+\tilde{P}^{+}_{\lambda\lambda'\lambda''}
+\left( \mathbf{F}^{i}_{\lambda'}-\mathbf{F}^{i}_{\lambda''} \right)-
+\frac{1}{2}\tilde{P}^{-}_{\lambda\lambda'\lambda''}
+\left( \mathbf{F}^{i}_{\lambda'}-\mathbf{F}^{i}_{\lambda''} \right)
+\right]
+\end{equation}
+$$
+
+to arrive at the non-equilibrium distributions. The thermal conductivity tensor is then given as
+
+$$
+\begin{equation}
+\kappa_{\alpha\beta} =
+\frac{1}{V}
+\sum_{\lambda}
+\frac{T c_{\lambda} v_{\lambda}^\alpha F_{\lambda}^\beta}{\omega_{\lambda}}
+\end{equation}
+$$
+
+### Cumulative kappa
+
+@todo Check code snippets
+
+@todo Spectral kappa, links to things.
+
+Experimentally, the cumulative thermal conductivity with respect to phonon mean free path,
+
+$$
+l_{\lambda} = \left| v_{\lambda} \right| \tau_{\lambda} \,,
+$$
+
+can be measured.[^Minnich2012] The cumulative thermal conductivity can then be computed as a sum of the fraction of heat that is carried by phonons with mean free paths smaller than $l$:
+
+$$
+\kappa_{\alpha\beta}^{\textrm{acc}}(l)=
+\frac{1}{V} \sum_{\lambda}
+C_{\lambda} v^{\alpha}_{\lambda} v^{\beta}_{\lambda} \tau_{\lambda} \Theta(l- l_{\lambda} ) \,,
+$$
+
+where $\Theta$ is the Heaviside step function.
+
+One can also define a spectral thermal conductivity as
+
+$$
+\kappa_{\alpha\beta}(\omega)=
+\frac{1}{V} \sum_{\lambda}
+C_{\lambda} v^{\alpha}_{\lambda} v^{\beta}_{\lambda} \tau_{\lambda} \delta(\omega- \omega_{\lambda} )
+$$
+
+which is a measure which frequencies contribute most to thermal transport.
+
+### Thin film scattering
+
+Constrained geometries will incur additional scattering from domain boundaries. For a thin film (thin, but thick enough that the interior of the film is accurately described by bulk phonons) one can estimate the suppression due to film thinkness.[^Minnich2015] Assyming the cross-plane direction of the film is in the $y$-direction, and the thermal gradient is applied in the $z$-direction, the in-plane thermal conductivity $\kappa_{zz}$ is supressed as:
+
+$$
+\kappa_{zz}(d)=A+B+C,
+$$
+
+where
+
+$$
+\begin{split}
+x_{\lambda} = & \frac{\hbar\omega_{\lambda}}{V}
+\frac{\partial n_{\lambda}} {\partial T}
+v_{\lambda}^z l_{\lambda}^z
+ \\
+A = & -\frac{1}{d} \sum_{v_y>0}
+x_{\lambda}
+\left( -l^{y}_{\lambda} \exp\left[\frac{d}{l^{y}_{\lambda}}\right]+l^{y}_{\lambda}-d \right) \\
+B = & -\frac{1}{d} \sum_{v_y<0}
+x_{\lambda}
+\left(
+l^{y}_{\lambda}
+\exp\left[ -\frac{d}{l^{y}_{\lambda}} \right]
+-l^{y}_{\lambda}-d
+\right) \\
+C = & \sum_{v_y=0} x_{\lambda}
+\end{split}
+$$
+
+where $v_y$ and $v_z$ are the components of the phonon group velocity along the $y$ and $z$ directions, $\tau_{\lambda}$ is the phonon relaxation time. $l^{y}_{\lambda}$ is the $y$ component of the MFP and $d$ is the thickness of the film in $y$-direction.
+
+### Input files
+
+These files are necesarry:
+
+* [infile.ucposcar](../files.md#infile.ucposcar)
+* [infile.forceconstant](extract_forceconstants.md#infile.forceconstant)
+* [infile.forceconstant_thirdorder](extract_forceconstants.md#infile.forceconstant_thirdorder)
+
+and these are optional:
+
+* [infile.isotopes](../files.md#infile.isotopes) (for non-natural isotope distribution)
+
+### Output files
+
+Depending on options, the set of output files may differ. We start with the basic files that are written after running this code.
+
+#### `outfile.thermal_conductivity`
+
+This file contains components of the thermal conductivity tensor $\kappa_{\alpha \beta}$ for each temperature.
+
+
+
+ | Row |
+ Description |
+
+
+
+ | 1 |
+
+ \( T_1 \qquad \kappa_{xx} \quad \kappa_{yy} \quad \kappa_{zz} \quad \kappa_{xz} \quad \kappa_{yz} \quad \kappa_{xy} \quad \kappa_{zx} \quad \kappa_{zy} \quad \kappa_{yx} \)
+ |
+
+
+ | 2 |
+
+ \( T_2 \qquad \kappa_{xx} \quad \kappa_{yy} \quad \kappa_{zz} \quad \kappa_{xz} \quad \kappa_{yz} \quad \kappa_{xy} \quad \kappa_{zx} \quad \kappa_{zy} \quad \kappa_{yx} \)
+ |
+
+
+ | ... |
+ ... |
+
+
+
+
+#### `outfile.cumulative_kappa.hdf5`
+
+This file is self-explainatory. It contains the different cumulative plots described above, at a series of temperatures. Below is a matlab snippet that plots part of the output.
+
+```matlab
+figure(1); clf; hold on; box on;
+
+% filename
+fn='outfile.cumulative_kappa.hdf5';
+% which temperature?
+t=1;
+
+subplot(1,3,1); hold on; box on;
+
+ % read in cumulative kappa vs mean free path from file
+ x=h5read(fn,['/temperature_' num2str(t) '/mean_free_path_axis']);
+ xunit=h5readatt(fn,['/temperature_' num2str(t) '/mean_free_path_axis'],'unit');
+ y=h5read(fn,['/temperature_' num2str(t) '/cumulative_kappa_vs_mean_free_path_total']);
+ % projections to modes and/or atoms
+ z=h5read(fn,['/temperature_' num2str(t) '/cumulative_kappa_vs_mean_free_path_per_atom']);
+ %z=h5read(fn,['/temperature_' num2str(t) '/cumulative_kappa_vs_mean_free_path_per_mode']);
+
+ yunit=h5readatt(fn,['/temperature_' num2str(t) '/cumulative_kappa_vs_mean_free_path_total'],'unit');
+
+ % plot
+ plot(x,y)
+ plot(x,z)
+
+ % set a legend
+ lgd{1}='Total';
+ for i=1:size(z,2)
+ lgd{i+1}=['Atom ' num2str(i)];
+ end
+ l=legend(lgd);
+ set(l,'edgecolor','none','location','northwest');
+
+ % some titles
+ title('Cumulative kappa vs mean free path');
+ ylabel(['Cumulative \kappa (' yunit ')']);
+ xlabel(['Mean free path (' xunit ')']);
+
+ % get some reasonable ranges
+ minx=x(max(find(ymax(y*0.9999))))*2;
+ xlim([minx maxx]);
+ set(gca,'xscale','log','yminortick','on');
+
+subplot(1,3,2); hold on; box on;
+
+ % read in spectral kappa vs frequency
+ x=h5read(fn,['/temperature_' num2str(t) '/frequency_axis']);
+ y=h5read(fn,['/temperature_' num2str(t) '/spectral_kappa_vs_frequency_total']);
+ z=h5read(fn,['/temperature_' num2str(t) '/spectral_kappa_vs_frequency_per_mode']);
+ %z=h5read(fn,['/temperature_' num2str(t) '/spectral_kappa_vs_frequency_per_atom']);
+
+ plot(x,y)
+ plot(x,z)
+
+ set(gca,'xminortick','on','yminortick','on')
+ xlabel('Frequency (THz)')
+ ylabel('Spectral \kappa (W/m/K/THz)')
+ title('Spectral kappa vs frequency')
+
+subplot(1,3,3); hold on; box on;
+
+ % read in cumulative kappa vs mean free path from file
+ x=h5read(fn,['/temperature_' num2str(t) '/boundary_scattering_lengths']);
+ y=h5read(fn,['/temperature_' num2str(t) '/boundary_scattering_kappa']);
+ % grab only kxx
+ y=squeeze(y(1,1,:));
+ plot(x,y)
+
+ set(gca,'xscale','log','yminortick','on')
+ xlabel('Domain size (m)')
+ ylabel('Kappa (W/mK)')
+ title('Kappa vs boundary scattering')
+
+ % get a reasonable range in x
+ minx=max(x(find(ymax(y*0.9999))))*2
+ xlim([minx maxx])
+```
+
+#### `outfile.grid_thermal_conductivity.hdf5`
+
+Option `--dumpgrid` produces this self-explainatory file. It will not get written if you use more than one temperature, the reason is that this file can get uncomfortably large, nearly all quantities on the full q-grid are written. Below is a matlab snippet that plots a subset:
+
+```matlab
+
+% file to read from
+fn='outfile.grid_thermal_conductivity.hdf5';
+% convert units to THz from Hz?
+toTHz=1/1E12/2/pi;
+
+figure(1); clf; hold on;
+
+subplot(1,3,1); hold on; box on;
+
+ x=h5read(fn,'/frequencies');
+ y=h5read(fn,'/linewidths');
+
+ for i=1:size(x,1)
+ plot(x(i,:)*toTHz,y(i,:)*toTHz,'marker','.','linestyle','none','markersize',8)
+ end
+ set(gca,'xminortick','on','yminortick','on')
+ xlabel('Frequency (THz)')
+ ylabel('Linewidth (THz)')
+
+subplot(1,3,2); hold on; box on;
+
+ x=h5read(fn,'/frequencies');
+ y=h5read(fn,'/lifetimes');
+
+ for i=1:size(x,1)
+ plot(x(i,:)*toTHz,y(i,:),'marker','.','linestyle','none','markersize',8)
+ end
+ set(gca,'yscale','log','xminortick','on')
+ xlabel('Frequency (THz)')
+ ylabel('Lifetime (s)')
+
+subplot(1,3,3); hold on; box on;
+
+ x=h5read(fn,'/frequencies');
+ y=h5read(fn,'/mean_free_paths');
+
+ for i=1:size(x,1)
+ plot(x(i,:)*toTHz,y(i,:),'marker','.','linestyle','none','markersize',8)
+ end
+ set(gca,'yscale','log','xminortick','on')
+ xlabel('Frequency (THz)')
+ ylabel('Mean free paths (m)')
+
+```
+
+[^peierls1929]: Peierls, R. E. (1929). Annalen der Physik, 3, 1055
+
+[^peierls1955quantum]: [Peierls, R. E. (1955). Quantum Theory of Solids. Clarendon Press.](https://books.google.com/books?id=WvPcBUsSJBAC)
+
+[^Minnich2012]: [Minnich, A. J. (2012). Determining phonon mean free paths from observations of quasiballistic thermal transport. Physical Review Letters, 109(20), 1–5.](http://doi.org/10.1103/PhysRevLett.109.205901)
+
+[^Minnich2015]: [Minnich, A. J. (2015). Thermal phonon boundary scattering in anisotropic thin films. Applied Physics Letters, 107(18), 8–11.](http://doi.org/10.1063/1.4935160)
+
+[^Tamura1983]: [Tamura, S. (1983). Isotope scattering of dispersive phonons in Ge. Physical Review B, 27(2), 858–866.](http://doi.org/10.1103/PhysRevB.27.858)
+
+[^Omini1996]: [Omini, M., & Sparavigna, A. (1996). Beyond the isotropic-model approximation in the theory of thermal conductivity. Physical Review B, 53(14), 9064–9073.](http://doi.org/10.1103/PhysRevB.53.9064)
+
+[^Omini]: [Omini, M., & Sparavigna, A. (1997). Heat transport in dielectric solids with diamond structure. Nuovo Cimento Della Societa Italiana Di Fisica D, 19D, 1537–63.](http://www.sif.it/riviste/ncd/econtents/1997/019/10/article/5)
+
+[^Broido2007]: [Broido, D. A., Malorny, M., Birner, G., Mingo, N., & Stewart, D. A. (2007). Intrinsic lattice thermal conductivity of semiconductors from first principles. Applied Physics Letters, 91(23), 231922.](http://doi.org/10.1063/1.2822891)
+
+[^Broido2005]: [Broido, D. A., Ward, A., & Mingo, N. (2005). Lattice thermal conductivity of silicon from empirical interatomic potentials. Physical Review B, 72(1), 1–8.](http://doi.org/10.1103/PhysRevB.72.014308)
diff --git a/src/thermal_conductivity/mfp.f90 b/src/thermal_conductivity_2023/mfp.f90
similarity index 100%
rename from src/thermal_conductivity/mfp.f90
rename to src/thermal_conductivity_2023/mfp.f90
diff --git a/src/thermal_conductivity_2023/options.f90 b/src/thermal_conductivity_2023/options.f90
new file mode 100644
index 00000000..6c1ddc8f
--- /dev/null
+++ b/src/thermal_conductivity_2023/options.f90
@@ -0,0 +1,205 @@
+#include "precompilerdefinitions"
+module options
+use konstanter, only: flyt, lo_status, lo_author, lo_version, lo_licence, lo_m_to_bohr
+use flap, only: command_line_interface
+implicit none
+private
+public :: lo_opts
+
+type lo_opts
+ integer, dimension(3) :: qgrid !< the main q-grid
+ logical :: readqmesh !< read q-grid from file
+ integer :: trangenpts !< how many temperatures
+ real(flyt) :: trangemin !< minimum temperature
+ real(flyt) :: trangemax !< max temperature
+ logical :: logtempaxis !< logarithmically spaced temperature points
+ real(flyt) :: sigma !< scaling factor for adaptice gaussian
+ real(flyt) :: thres !< consider Gaussian 0 if x-mu is larger than this number times sigma.
+ real(flyt) :: tau_boundary !< add a constant as boundary scattering
+ real(flyt) :: mfp_max !< add a length as boundary scattering
+ logical :: readiso !< read isotope distribution from file
+ integer :: integrationtype !< gaussian or tetrahedron
+ integer :: scfiterations !< maximum number of self-consistent iterations
+ real(flyt) :: scftol !< tolerance for the SCF cycle
+
+ integer :: correctionlevel !< how hard to correct
+ integer :: mfppts !< number of points on mfp-plots
+ logical :: dumpgrid !< print everything on a grid
+ !logical :: thinfilm !< Austins thin film thing
+
+ ! Debugging things
+ logical :: timereversal
+ logical :: qpsymmetry
+ logical :: isotopescattering
+ !
+ integer :: verbosity
+contains
+ procedure :: parse
+end type
+
+contains
+
+subroutine parse(opts)
+ !> the options
+ class(lo_opts), intent(out) :: opts
+ !> the helper parser
+ type(command_line_interface) :: cli
+ !
+ logical :: dumlog
+ real(flyt) :: f0
+ real(flyt), dimension(3) :: dumflytv
+
+ ! basic info
+ call cli%init(progname='thermal_conductivity_2023', &
+ authors=lo_author, &
+ version=lo_version, &
+ license=lo_licence, &
+ help='Usage: ', &
+ description='Calculates the lattice thermal conductivity from the iterative solution of the &
+ &phonon Boltzmann equation. In addition, cumulative plots and raw data dumps &
+ &of intermediate values are available.', &
+ examples=["mpirun thermal_conductivity_2023 --temperature 300 ", &
+ "mpirun thermal_conductivity_2023 -qg 15 15 15 --temperature_range 200 600 50 ", &
+ "mpirun thermal_conductivity_2023 --integrationtype 2 -qg 30 30 30 --max_mfp 1E-6"], &
+ epilog=new_line('a')//"...")
+ ! real options
+ call cli%add(switch='--readiso', &
+ help='Read the isotope distribution from `infile.isotopes`.', &
+ help_markdown='The format is specified [here](../page/files.html#infile.isotopes).', &
+ required=.false., act='store_true', def='.false.', error=lo_status)
+ if (lo_status .ne. 0) stop
+ cli_qpoint_grid
+ call cli%add(switch='--integrationtype', switch_ab='-it', &
+ help='Type of integration for the phonon DOS. 1 is Gaussian, 2 adaptive Gaussian and 3 Tetrahedron.', &
+ required=.false., act='store', def='2', choices='1,2,3', error=lo_status)
+ if (lo_status .ne. 0) stop
+ call cli%add(switch='--sigma', &
+ help='Global scaling factor for adaptive Gaussian smearing.', &
+ required=.false., act='store', def='1.0', error=lo_status)
+ if (lo_status .ne. 0) stop
+ call cli%add(switch='--threshold', &
+ help='Consider a Gaussian distribution to be 0 after this many standard deviations.', &
+ required=.false., act='store', def='4.0', error=lo_status)
+ if (lo_status .ne. 0) stop
+ cli_readqmesh
+
+ call cli%add(switch='--temperature', &
+ help='Evaluate thermal conductivity at a single temperature.', &
+ required=.false., act='store', def='-1', error=lo_status)
+ if (lo_status .ne. 0) stop
+ call cli%add(switch='--temperature_range', &
+ help='Series of temperatures for thermal conductivity. Specify min, max and the number of points.', &
+ nargs='3', required=.false., act='store', def='100 300 5', error=lo_status)
+ if (lo_status .ne. 0) stop
+ call cli%add(switch='--logtempaxis', &
+ help='Space the temperature points logarithmically instead of linearly.', &
+ required=.false., act='store_true', def='.false.', error=lo_status)
+ call cli%add(switch='--max_mfp', &
+ help='Add a limit on the mean free path as an approximation of domain size.', &
+ required=.false., act='store', def='-1', error=lo_status)
+ if (lo_status .ne. 0) stop
+ call cli%add(switch='--dumpgrid', &
+ help='Write files with q-vectors, frequencies, eigenvectors and group velocities for a grid.', &
+ required=.false., act='store_true', def='.false.', error=lo_status)
+ if (lo_status .ne. 0) stop
+ call cli%add(switch='--noisotope', &
+ help='Do not consider isotope scattering.', &
+ required=.false., act='store_true', def='.false.', error=lo_status)
+ if (lo_status .ne. 0) stop
+
+ ! hidden
+ call cli%add(switch='--tau_boundary', hidden=.true., &
+ help='Add a constant boundary scattering term to the lifetimes.', &
+ required=.false., act='store', def='-1', error=lo_status)
+ if (lo_status .ne. 0) stop
+ call cli%add(switch='--mfppts', hidden=.true., help='', &
+ required=.false., act='store', def='200', error=lo_status)
+ if (lo_status .ne. 0) stop
+ call cli%add(switch='--scfiterations', hidden=.true., help='', &
+ required=.false., act='store', def='200', error=lo_status)
+ if (lo_status .ne. 0) stop
+ call cli%add(switch='--notr', hidden=.true., help='', &
+ required=.false., act='store_true', def='.false.', error=lo_status)
+ if (lo_status .ne. 0) stop
+ call cli%add(switch='--nosym', hidden=.true., help='', &
+ required=.false., act='store_true', def='.false.', error=lo_status)
+ if (lo_status .ne. 0) stop
+ call cli%add(switch='--correctionlevel', hidden=.true., &
+ help='How agressively things are corrected due to broken symmetries.', &
+ required=.false., act='store', def='4', error=lo_status)
+ if (lo_status .ne. 0) stop
+ call cli%add(switch='--scftol', hidden=.true., &
+ help='What tolerance to converge the self-consistent cycle to.', &
+ required=.false., act='store', def='1E-5', error=lo_status)
+ if (lo_status .ne. 0) stop
+ !call cli%add(switch='--thinfilm',hidden=.true.,&
+ ! help='Calculate the suppression of kappa from in a thin film.',&
+ ! required=.false.,act='store_true',def='.false.',error=lo_status)
+ ! if ( lo_status .ne. 0 ) stop
+ cli_manpage
+ cli_verbose
+
+ ! actually parse it
+ call cli%parse(error=lo_status)
+ if (lo_status .ne. 0) stop
+ !
+ ! Should the manpage be generated? In that case, no point to go further than here.
+ !
+ dumlog = .false.
+ call cli%get(switch='--manpage', val=dumlog)
+ if (dumlog) then
+ call cli%save_man_page(trim(cli%progname)//'.1')
+ call cli%save_usage_to_markdown(trim(cli%progname)//'.md')
+ write (*, *) 'Wrote manpage for "'//trim(cli%progname)//'"'
+ stop
+ end if
+
+ ! store things in the right place
+
+ call cli%get(switch='--temperature', val=f0)
+ call cli%get(switch='--temperature_range', val=dumflytv)
+ opts%trangemin = dumflytv(1)
+ opts%trangemax = dumflytv(2)
+ opts%trangenpts = int(anint(dumflytv(3)))
+ ! if --temperature is specified, override the range
+ if (f0 .gt. 0.0_flyt) then
+ opts%trangemin = f0
+ opts%trangemax = f0
+ opts%trangenpts = 1
+ end if
+ call cli%get(switch='--qpoint_grid', val=opts%qgrid)
+ call cli%get(switch='--sigma', val=opts%sigma)
+ call cli%get(switch='--threshold', val=opts%thres)
+ call cli%get(switch='--tau_boundary', val=opts%tau_boundary)
+ if (opts%tau_boundary .gt. 0.0_flyt) opts%tau_boundary = 1E10_flyt
+ call cli%get(switch='--readqmesh', val=opts%readqmesh)
+ call cli%get(switch='--integrationtype', val=opts%integrationtype)
+ call cli%get(switch='--logtempaxis', val=opts%logtempaxis)
+ call cli%get(switch='--readiso', val=opts%readiso)
+ call cli%get(switch='--mfppts', val=opts%mfppts)
+ call cli%get(switch='--scfiterations', val=opts%scfiterations)
+ call cli%get(switch='--max_mfp', val=opts%mfp_max)
+ call cli%get(switch='--dumpgrid', val=opts%dumpgrid)
+ !call cli%get(switch='--thinfilm',val=opts%thinfilm)
+ ! stuff that's not really an option
+ call cli%get(switch='--correctionlevel', val=opts%correctionlevel)
+ call cli%get(switch='--scftol', val=opts%scftol)
+ call cli%get(switch='--notr', val=dumlog)
+ opts%timereversal = .not. dumlog
+ call cli%get(switch='--nosym', val=dumlog)
+ opts%qpsymmetry = .not. dumlog
+ call cli%get(switch='--verbose', val=dumlog)
+ if (dumlog) then
+ opts%verbosity = 2
+ else
+ opts%verbosity = 0
+ end if
+ call cli%get(switch='--noisotope', val=dumlog)
+ opts%isotopescattering = .not. dumlog
+
+ ! Get things to atomic units
+ opts%mfp_max = opts%mfp_max*lo_m_to_Bohr
+
+end subroutine
+
+end module
diff --git a/src/thermal_conductivity/pbe.f90 b/src/thermal_conductivity_2023/pbe.f90
similarity index 100%
rename from src/thermal_conductivity/pbe.f90
rename to src/thermal_conductivity_2023/pbe.f90
diff --git a/src/thermal_conductivity/phononevents.f90 b/src/thermal_conductivity_2023/phononevents.f90
similarity index 100%
rename from src/thermal_conductivity/phononevents.f90
rename to src/thermal_conductivity_2023/phononevents.f90
diff --git a/src/thermal_conductivity/phononevents_gaussian.f90 b/src/thermal_conductivity_2023/phononevents_gaussian.f90
similarity index 100%
rename from src/thermal_conductivity/phononevents_gaussian.f90
rename to src/thermal_conductivity_2023/phononevents_gaussian.f90
diff --git a/src/thermal_conductivity/phononevents_tetrahedron.f90 b/src/thermal_conductivity_2023/phononevents_tetrahedron.f90
similarity index 100%
rename from src/thermal_conductivity/phononevents_tetrahedron.f90
rename to src/thermal_conductivity_2023/phononevents_tetrahedron.f90
diff --git a/src/thermal_conductivity/scatteringstrengths.f90 b/src/thermal_conductivity_2023/scatteringstrengths.f90
similarity index 100%
rename from src/thermal_conductivity/scatteringstrengths.f90
rename to src/thermal_conductivity_2023/scatteringstrengths.f90
diff --git a/tests/make_all_testfiles.sh b/tests/make_all_testfiles.sh
index e68aab2a..3292df39 100644
--- a/tests/make_all_testfiles.sh
+++ b/tests/make_all_testfiles.sh
@@ -8,6 +8,7 @@ generate_structure/
lineshape/
pack_simulation/
phonon_dispersion_relations/
+thermal_conductivity_2023/
thermal_conductivity/
"
diff --git a/tests/thermal_conductivity/Makefile b/tests/thermal_conductivity/Makefile
index 36fcfe16..0b002a26 100644
--- a/tests/thermal_conductivity/Makefile
+++ b/tests/thermal_conductivity/Makefile
@@ -1,8 +1,9 @@
testfiles:
- extract_forceconstants -rc2 0 -rc3 3 --polar
+ extract_forceconstants -rc2 0 -rc3 3 -rc4 0 --polar
ln -sf outfile.forceconstant infile.forceconstant
ln -sf outfile.forceconstant_thirdorder infile.forceconstant_thirdorder
- thermal_conductivity -qg 5 5 5
+ ln -sf outfile.forceconstant_fourthorder infile.forceconstant_fourthorder
+ thermal_conductivity -qg 5 5 5 --fourthorder -qg4ph 3 3 3 --seed 42
clean:
rm -f outfile.*
diff --git a/tests/thermal_conductivity/reference/outfile.thermal_conductivity b/tests/thermal_conductivity/reference/outfile.thermal_conductivity
index 1d2706f3..64f54c76 100644
--- a/tests/thermal_conductivity/reference/outfile.thermal_conductivity
+++ b/tests/thermal_conductivity/reference/outfile.thermal_conductivity
@@ -1,5 +1,14 @@
- 0.1000000000E+03 0.9484478410E+02 0.9484241022E+02 0.9485158261E+02 -0.6914668975E-02 0.4605053895E-02 0.6943480313E-02 -0.3419399398E-02 -0.4609991003E-02 -0.5346988618E-02
- 0.1500000000E+03 0.7765603795E+02 0.7765355763E+02 0.7765502331E+02 -0.4821178917E-02 0.7814554039E-03 0.4901081212E-02 -0.3165046775E-02 -0.6334968858E-03 -0.1153689678E-02
- 0.2000000000E+03 0.6534894912E+02 0.6534778505E+02 0.6534831799E+02 -0.3344794089E-02 0.3490276769E-03 0.3488810159E-02 -0.1518867629E-02 0.8153799649E-04 -0.4263176402E-03
- 0.2500000000E+03 0.5612079252E+02 0.5612029951E+02 0.5612055692E+02 -0.2469080777E-02 0.1152899888E-03 0.2680884747E-02 -0.6505521420E-03 0.1152899888E-03 -0.1224973560E-03
- 0.3000000000E+03 0.4900053998E+02 0.4900037109E+02 0.4900051271E+02 -0.1924940461E-02 0.1486555118E-03 0.2204538394E-02 -0.2163160728E-03 0.1486555118E-03 0.4581584256E-04
+# Unit: W/m/K
+# Temperature: 0.300000000000E+03
+# Single mode approximation
+# kxx kyy kzz kxy kxz kyz
+ 0.487382103035E+02 0.487382103035E+02 0.487382103035E+02 0.000000000000E+00 0.000000000000E+00 0.000000000000E+00
+# Collective contribution
+# kxx kyy kzz kxy kxz kyz
+ 0.255832668695E+01 0.255832668695E+01 0.255832668695E+01 0.000000000000E+00 0.000000000000E+00 0.000000000000E+00
+# Off diagonal (coherence) contribution
+# kxx kyy kzz kxy kxz kyz
+ 0.215161320755E-01 0.215161320755E-01 0.215161320755E-01 0.000000000000E+00 0.000000000000E+00 0.000000000000E+00
+# Total thermal conductivity
+# kxx kyy kzz kxy kxz kyz
+ 0.513180531225E+02 0.513180531225E+02 0.513180531225E+02 0.000000000000E+00 0.000000000000E+00 0.000000000000E+00
diff --git a/tests/thermal_conductivity/reference/outfile.thermal_conductivity_grid.hdf5 b/tests/thermal_conductivity/reference/outfile.thermal_conductivity_grid.hdf5
new file mode 100644
index 00000000..f8bd4e9e
Binary files /dev/null and b/tests/thermal_conductivity/reference/outfile.thermal_conductivity_grid.hdf5 differ
diff --git a/tests/thermal_conductivity/test_thermal_conductivity.py b/tests/thermal_conductivity/test_thermal_conductivity.py
index 9a0e6832..c8f36bfa 100644
--- a/tests/thermal_conductivity/test_thermal_conductivity.py
+++ b/tests/thermal_conductivity/test_thermal_conductivity.py
@@ -6,7 +6,7 @@
folder = parent / "reference"
files_hdf5 = [
- "outfile.cumulative_kappa.hdf5",
+ "outfile.thermal_conductivity_grid.hdf5",
]
diff --git a/tests/thermal_conductivity/.gitignore b/tests/thermal_conductivity_2023/.gitignore
similarity index 100%
rename from tests/thermal_conductivity/.gitignore
rename to tests/thermal_conductivity_2023/.gitignore
diff --git a/tests/thermal_conductivity_2023/Makefile b/tests/thermal_conductivity_2023/Makefile
new file mode 100644
index 00000000..de0381a8
--- /dev/null
+++ b/tests/thermal_conductivity_2023/Makefile
@@ -0,0 +1,9 @@
+testfiles:
+ extract_forceconstants -rc2 0 -rc3 3 --polar
+ ln -sf outfile.forceconstant infile.forceconstant
+ ln -sf outfile.forceconstant_thirdorder infile.forceconstant_thirdorder
+ thermal_conductivity_2023 -qg 5 5 5
+
+clean:
+ rm -f outfile.*
+ rm -f infile.forceconstant*
diff --git a/tests/thermal_conductivity_2023/infile.lotosplitting b/tests/thermal_conductivity_2023/infile.lotosplitting
new file mode 120000
index 00000000..9347662c
--- /dev/null
+++ b/tests/thermal_conductivity_2023/infile.lotosplitting
@@ -0,0 +1 @@
+../infiles/infile.lotosplitting
\ No newline at end of file
diff --git a/tests/thermal_conductivity_2023/infile.sim.hdf5 b/tests/thermal_conductivity_2023/infile.sim.hdf5
new file mode 120000
index 00000000..4df3cf7f
--- /dev/null
+++ b/tests/thermal_conductivity_2023/infile.sim.hdf5
@@ -0,0 +1 @@
+../infiles/infile.sim.hdf5
\ No newline at end of file
diff --git a/tests/thermal_conductivity_2023/infile.ssposcar b/tests/thermal_conductivity_2023/infile.ssposcar
new file mode 120000
index 00000000..1cfa068d
--- /dev/null
+++ b/tests/thermal_conductivity_2023/infile.ssposcar
@@ -0,0 +1 @@
+../infiles/infile.ssposcar
\ No newline at end of file
diff --git a/tests/thermal_conductivity_2023/infile.ucposcar b/tests/thermal_conductivity_2023/infile.ucposcar
new file mode 120000
index 00000000..b943f946
--- /dev/null
+++ b/tests/thermal_conductivity_2023/infile.ucposcar
@@ -0,0 +1 @@
+../infiles/infile.ucposcar
\ No newline at end of file
diff --git a/tests/thermal_conductivity/reference/outfile.cumulative_kappa.hdf5 b/tests/thermal_conductivity_2023/reference/outfile.cumulative_kappa.hdf5
similarity index 100%
rename from tests/thermal_conductivity/reference/outfile.cumulative_kappa.hdf5
rename to tests/thermal_conductivity_2023/reference/outfile.cumulative_kappa.hdf5
diff --git a/tests/thermal_conductivity_2023/reference/outfile.thermal_conductivity b/tests/thermal_conductivity_2023/reference/outfile.thermal_conductivity
new file mode 100644
index 00000000..1d2706f3
--- /dev/null
+++ b/tests/thermal_conductivity_2023/reference/outfile.thermal_conductivity
@@ -0,0 +1,5 @@
+ 0.1000000000E+03 0.9484478410E+02 0.9484241022E+02 0.9485158261E+02 -0.6914668975E-02 0.4605053895E-02 0.6943480313E-02 -0.3419399398E-02 -0.4609991003E-02 -0.5346988618E-02
+ 0.1500000000E+03 0.7765603795E+02 0.7765355763E+02 0.7765502331E+02 -0.4821178917E-02 0.7814554039E-03 0.4901081212E-02 -0.3165046775E-02 -0.6334968858E-03 -0.1153689678E-02
+ 0.2000000000E+03 0.6534894912E+02 0.6534778505E+02 0.6534831799E+02 -0.3344794089E-02 0.3490276769E-03 0.3488810159E-02 -0.1518867629E-02 0.8153799649E-04 -0.4263176402E-03
+ 0.2500000000E+03 0.5612079252E+02 0.5612029951E+02 0.5612055692E+02 -0.2469080777E-02 0.1152899888E-03 0.2680884747E-02 -0.6505521420E-03 0.1152899888E-03 -0.1224973560E-03
+ 0.3000000000E+03 0.4900053998E+02 0.4900037109E+02 0.4900051271E+02 -0.1924940461E-02 0.1486555118E-03 0.2204538394E-02 -0.2163160728E-03 0.1486555118E-03 0.4581584256E-04
diff --git a/tests/thermal_conductivity_2023/test_thermal_conductivity_2023.py b/tests/thermal_conductivity_2023/test_thermal_conductivity_2023.py
new file mode 100644
index 00000000..9a0e6832
--- /dev/null
+++ b/tests/thermal_conductivity_2023/test_thermal_conductivity_2023.py
@@ -0,0 +1,41 @@
+import numpy as np
+import xarray as xr
+from pathlib import Path
+
+parent = Path(__file__).parent
+folder = parent / "reference"
+
+files_hdf5 = [
+ "outfile.cumulative_kappa.hdf5",
+]
+
+
+def test_thermal_conductivity(file="outfile.thermal_conductivity", atol=20, rtol=5):
+ file_ref = folder / file
+ file_new = parent / file
+
+ data_ref = np.loadtxt(file_ref)
+ data_new = np.loadtxt(file_new)
+
+ np.testing.assert_allclose(
+ data_ref, data_new, atol=atol, rtol=rtol, err_msg=file_new.absolute()
+ )
+
+
+def test_hdf5(files=files_hdf5, atol=1, rtol=0.01):
+ for file in files:
+ file_ref = folder / file
+ file_new = parent / file
+
+ ds_ref = xr.load_dataset(file_ref)
+ ds_new = xr.load_dataset(file_new)
+
+ for var in ds_ref.data_vars:
+ x = ds_ref[var]
+ y = ds_new[var]
+ np.testing.assert_allclose(x, y, atol=atol, rtol=rtol, err_msg=var)
+
+
+if __name__ == "__main__":
+ test_thermal_conductivity()
+ test_hdf5()