|
| 1 | +#pragma once |
| 2 | + |
| 3 | +#include "constants.hpp" |
| 4 | +#include "specfem_setup.hpp" |
| 5 | +#include <Kokkos_Core.hpp> |
| 6 | +#include <cmath> |
| 7 | + |
| 8 | +namespace specfem { |
| 9 | +namespace attenuation { |
| 10 | + |
| 11 | +/** |
| 12 | + * @brief Result containing real (\f$A\f$) and imaginary (\f$B\f$) moduli from |
| 13 | + * Maxwell solid computation |
| 14 | + * |
| 15 | + * For a standard linear solid (SLS), the complex modulus can be written as: |
| 16 | + * \f[ |
| 17 | + * M^*(\omega) = M_R \left( 1 + \sum_i \frac{(\tau_{\epsilon_i} - |
| 18 | + * \tau_{\sigma_i}) \omega^2 \tau_{\sigma_i}}{1 + \omega^2 \tau_{\sigma_i}^2} |
| 19 | + * \right) |
| 20 | + * + i M_R \sum_i \frac{(\tau_{\epsilon_i} - \tau_{\sigma_i}) |
| 21 | + * \omega}{1 + \omega^2 \tau_{\sigma_i}^2} |
| 22 | + * \f] |
| 23 | + * |
| 24 | + * \f$A\f$ represents the real part (storage modulus ratio), |
| 25 | + * \f$B\f$ represents the imaginary part (loss modulus ratio). |
| 26 | + * The quality factor \f$Q = A / B = 1 / \tan\delta\f$. |
| 27 | + * |
| 28 | + * @tparam NF Number of frequencies |
| 29 | + * |
| 30 | + */ |
| 31 | +template <int NF> struct MaxwellModuli { |
| 32 | + Kokkos::View<type_real[NF], Kokkos::LayoutRight, Kokkos::HostSpace> A; |
| 33 | + Kokkos::View<type_real[NF], Kokkos::LayoutRight, Kokkos::HostSpace> B; |
| 34 | +}; |
| 35 | + |
| 36 | +/** |
| 37 | + * @brief Compute Maxwell solid moduli for a series of standard linear solids |
| 38 | + * |
| 39 | + * This function computes the real (\f$A\f$) and imaginary (\f$B\f$) parts of |
| 40 | + * the viscoelastic modulus for a generalized Maxwell solid (also known as |
| 41 | + * generalized Zener model) consisting of \f$N_\text{SLS}\f$ standard linear |
| 42 | + * solids in parallel. |
| 43 | + * |
| 44 | + * The formulas match Jeroen's Attenuation notes (43)-(44), with 1/L |
| 45 | + * normalization: |
| 46 | + * |
| 47 | + * For angular frequency \f$\omega = 2 \pi f\f$: |
| 48 | + * |
| 49 | + * \f[A(\omega) = \frac{1}{L} \sum_{i=1}^{L} \frac{1 + \omega^2 |
| 50 | + * \tau_{\epsilon_i} \tau_{\sigma_i}}{1 + \omega^2 \tau_{\sigma_i}^2}\f] |
| 51 | + * |
| 52 | + * \f[B(\omega) = \frac{1}{L} \sum_{i=1}^{L} \frac{\omega (\tau_{\epsilon_i} - |
| 53 | + * \tau_{\sigma_i})}{1 + \omega^2 \tau_{\sigma_i}^2}\f] |
| 54 | + * |
| 55 | + * where \f$L = N_\text{SLS}\f$. At low frequency, \f$A\f$ approaches \f$1\f$. |
| 56 | + * The quality factor \f$Q = A/B\f$ is independent of the normalization. |
| 57 | + * \f$B\f$ is independent of the normalization. |
| 58 | + * |
| 59 | + * The quality factor \f$Q = A / B\f$, so \f$\tan(\delta) = B / A = 1 / Q\f$ |
| 60 | + * |
| 61 | + * @tparam NF Number of frequencies to evaluate |
| 62 | + * @tparam N_SLS Number of standard linear solids |
| 63 | + * |
| 64 | + * @param f View containing \f$N_F\f$ frequencies \f$f\f$ (in Hz, not |
| 65 | + * \f$\log_{10}\f$) |
| 66 | + * @param tau_s View containing \f$N_\text{SLS}\f$ stress relaxation times |
| 67 | + * \f$\tau_\sigma\f$ |
| 68 | + * @param tau_eps View containing \f$N_\text{SLS}\f$ strain relaxation times |
| 69 | + * \f$\tau_\epsilon\f$ |
| 70 | + * |
| 71 | + * @return MaxwellModuli containing \f$A\f$ (real) and \f$B\f$ (imaginary) |
| 72 | + * moduli |
| 73 | + * |
| 74 | + * @code |
| 75 | + * // Example: compute moduli for 3 SLS over a frequency range |
| 76 | + * constexpr int NF = 100; |
| 77 | + * constexpr int N_SLS = 3; |
| 78 | + * Kokkos::View<type_real[NF], ...> f("f"); |
| 79 | + * Kokkos::View<type_real[N_SLS], ...> tau_s("tau_s"); |
| 80 | + * Kokkos::View<type_real[N_SLS], ...> tau_eps("tau_eps"); // ... fill in values |
| 81 | + * ... auto moduli = maxwell<NF, N_SLS>(f, tau_s, tau_eps); // Q at |
| 82 | + * frequency i is approximately moduli.A(i) / moduli.B(i) |
| 83 | + * @endcode |
| 84 | + */ |
| 85 | +template <int NF, int N_SLS> |
| 86 | +MaxwellModuli<NF> |
| 87 | +maxwell(Kokkos::View<type_real[NF], Kokkos::LayoutRight, Kokkos::HostSpace> f, |
| 88 | + Kokkos::View<type_real[N_SLS], Kokkos::LayoutRight, Kokkos::HostSpace> |
| 89 | + tau_s, |
| 90 | + Kokkos::View<type_real[N_SLS], Kokkos::LayoutRight, Kokkos::HostSpace> |
| 91 | + tau_eps) { |
| 92 | + |
| 93 | + MaxwellModuli<NF> result; |
| 94 | + result.A = |
| 95 | + Kokkos::View<type_real[NF], Kokkos::LayoutRight, Kokkos::HostSpace>("A"); |
| 96 | + result.B = |
| 97 | + Kokkos::View<type_real[NF], Kokkos::LayoutRight, Kokkos::HostSpace>("B"); |
| 98 | + |
| 99 | + for (int i = 0; i < NF; ++i) { |
| 100 | + // Angular frequency: w = 2 * pi * f |
| 101 | + const type_real w = 2.0 * pi * f(i); |
| 102 | + const type_real w2 = w * w; |
| 103 | + |
| 104 | + type_real A_sum = 0.0; |
| 105 | + type_real B_sum = 0.0; |
| 106 | + |
| 107 | + for (int j = 0; j < N_SLS; ++j) { |
| 108 | + const type_real tau_s_j = tau_s(j); |
| 109 | + const type_real tau_eps_j = tau_eps(j); |
| 110 | + const type_real tau_s_j_sq = tau_s_j * tau_s_j; |
| 111 | + const type_real denom = 1.0 + w2 * tau_s_j_sq; |
| 112 | + |
| 113 | + // Real part: (1 + w^2 * tau_eps * tau_s) / denom |
| 114 | + A_sum += (1.0 + w2 * tau_eps_j * tau_s_j) / denom; |
| 115 | + |
| 116 | + // Imaginary part: w * (tau_eps - tau_s) / denom |
| 117 | + B_sum += w * (tau_eps_j - tau_s_j) / denom; |
| 118 | + } |
| 119 | + |
| 120 | + // Apply 1/L normalization per Jeroen Tromp's Attenuation notes |
| 121 | + result.A(i) = A_sum / N_SLS; |
| 122 | + result.B(i) = B_sum / N_SLS; |
| 123 | + } |
| 124 | + |
| 125 | + return result; |
| 126 | +} |
| 127 | + |
| 128 | +} // namespace attenuation |
| 129 | +} // namespace specfem |
0 commit comments