Skip to content

Commit 253c6c6

Browse files
authored
Merge pull request #1615 from PrincetonUniversity/issue-1591
Issue 1591 - Maxwell Solid, Nelder-Mead-Algorithm, tau_epsilon computation
2 parents 8ce97e7 + 2bc9288 commit 253c6c6

File tree

20 files changed

+2628
-3
lines changed

20 files changed

+2628
-3
lines changed

core/specfem/attenuation.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
#pragma once
22
#include "attenuation/compute_tau_sigma.hpp"
33
#include "attenuation/compute_tau_sigma.tpp"
4+
#include "attenuation/maxwell.hpp"
5+
#include "attenuation/compute_tau_eps.hpp"
6+
#include "attenuation/compute_tau_eps.tpp"
47

58

69
/**
710
* @brief Basic functions for the computation of attenuation related parameters.
8-
*
11+
*
912
*/
1013
namespace specfem::attenuation {} // namespace specfem::attenuation
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#pragma once
2+
3+
#include "compute_tau_sigma.hpp"
4+
#include "constants.hpp"
5+
#include "maxwell.hpp"
6+
#include "specfem/optimization.hpp"
7+
#include "specfem_setup.hpp"
8+
#include <Kokkos_Core.hpp>
9+
#include <cmath>
10+
11+
namespace specfem {
12+
namespace attenuation {
13+
14+
/**
15+
* @brief Number of frequencies used for evaluating attenuation objective
16+
*
17+
* Matching SPECFEM3D Fortran implementation which uses 100 frequencies.
18+
*/
19+
constexpr int NF_ATTENUATION = 100;
20+
21+
/**
22+
* @brief Objective function for \f$\tau_\epsilon\f$ optimization
23+
*
24+
* This callable struct computes the misfit between the achieved \f$Q\f$ (from
25+
* the Maxwell solid model) and the target \f$Q\f$ value. It is designed to be
26+
* used with the Nelder-Mead optimizer.
27+
*
28+
* The objective is computed as the sum of squared differences:
29+
* \f[
30+
* \sum_i \left( \tan\delta(f_i) - \frac{1}{Q_\text{target}} \right)^2
31+
* \f]
32+
*
33+
* where \f$\tan\delta = B/A\f$ from the Maxwell solid moduli.
34+
*
35+
* @tparam N_SLS Number of standard linear solids
36+
*/
37+
template <int N_SLS> struct AttenuationObjective {
38+
type_real Q; ///< Target quality factor \f$Q\f$
39+
type_real iQ; ///< \f$1/Q\f$ (target \f$\tan\delta\f$)
40+
Kokkos::View<type_real[NF_ATTENUATION], Kokkos::LayoutRight,
41+
Kokkos::HostSpace>
42+
f; ///< Evaluation frequencies \f$f\f$ (Hz)
43+
Kokkos::View<type_real[N_SLS], Kokkos::LayoutRight, Kokkos::HostSpace>
44+
tau_sigma; ///< Stress relaxation times \f$\tau_\sigma\f$
45+
46+
/**
47+
* @brief Evaluate the objective function for given \f$\tau_\epsilon\f$ values
48+
*
49+
* @param tau_eps Candidate strain relaxation times \f$\tau_\epsilon\f$
50+
* @return Sum of squared misfit between achieved and target \f$1/Q\f$
51+
*/
52+
type_real operator()(
53+
Kokkos::View<type_real[N_SLS], Kokkos::LayoutRight, Kokkos::HostSpace>
54+
tau_eps) const {
55+
56+
// Compute Maxwell moduli for all frequencies
57+
auto moduli = maxwell<NF_ATTENUATION, N_SLS>(f, tau_sigma, tau_eps);
58+
59+
// Compute sum of squared misfit
60+
type_real misfit = 0.0;
61+
for (int i = 0; i < NF_ATTENUATION; ++i) {
62+
// tan_delta = B / A ≈ 1/Q
63+
type_real tan_delta = moduli.B(i) / moduli.A(i);
64+
type_real diff = tan_delta - iQ;
65+
misfit += diff * diff;
66+
}
67+
68+
return misfit;
69+
}
70+
};
71+
72+
/**
73+
* @brief Compute strain relaxation times via simplex optimization
74+
*
75+
* This function finds the strain relaxation times \f$\tau_\epsilon\f$ that
76+
* achieve a target quality factor \f$Q\f$ for a generalized Maxwell solid with
77+
* \f$N_\text{SLS}\f$ standard linear solids.
78+
*
79+
* The algorithm:
80+
* 1. Computes \f$\tau_\sigma\f$ from the period range using compute_tau_sigma
81+
* 2. Sets up \f$N_F=100\f$ evaluation frequencies equally spaced in
82+
* \f$\log_{10}\f$
83+
* 3. Uses Nelder-Mead simplex to minimize the misfit between achieved
84+
* and target \f$1/Q\f$ values over the frequency range
85+
*
86+
* The initial guess for \f$\tau_\epsilon\f$ is \f$\tau_\sigma\f$ (no
87+
* attenuation), and the optimization typically converges within a few hundred
88+
* iterations.
89+
*
90+
* @tparam N_SLS Number of standard linear solids (requires \f$N_\text{SLS} >
91+
* 1\f$)
92+
*
93+
* @param Q Target quality factor \f$Q\f$ (must be positive)
94+
* @param tau_sigma Pre-computed stress relaxation times \f$\tau_\sigma\f$ from
95+
* compute_tau_sigma
96+
* @param min_period Minimum period \f$T_\text{min}\f$ (s) for frequency range
97+
* @param max_period Maximum period \f$T_\text{max}\f$ (s) for frequency range
98+
*
99+
* @return View containing \f$N_\text{SLS}\f$ strain relaxation times
100+
* \f$\tau_\epsilon\f$
101+
*
102+
* @note The returned \f$\tau_\epsilon\f$ values should satisfy \f$\tau_\epsilon
103+
* > \tau_\sigma\f$ for positive \f$Q\f$ (physical attenuation).
104+
*
105+
* @code
106+
* // Example: compute tau_eps for Q=200 with 3 SLS
107+
* constexpr int N_SLS = 3;
108+
* type_real Q = 200.0;
109+
* type_real min_period = 0.01;
110+
* type_real max_period = 10.0;
111+
* auto tau_sigma = compute_tau_sigma<N_SLS>(min_period, max_period);
112+
* auto tau_eps = compute_tau_eps<N_SLS>(Q, tau_sigma, min_period, max_period);
113+
* @endcode
114+
*/
115+
template <int N_SLS>
116+
Kokkos::View<type_real[N_SLS], Kokkos::LayoutRight, Kokkos::HostSpace>
117+
compute_tau_eps(
118+
type_real Q,
119+
Kokkos::View<type_real[N_SLS], Kokkos::LayoutRight, Kokkos::HostSpace>
120+
tau_sigma,
121+
type_real min_period, type_real max_period);
122+
123+
} // namespace attenuation
124+
} // namespace specfem
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#pragma once
2+
#include "compute_tau_eps.hpp"
3+
#include "maxwell.hpp"
4+
#include "constants.hpp"
5+
#include "specfem/optimization.hpp"
6+
#include "specfem_setup.hpp"
7+
#include <Kokkos_Core.hpp>
8+
#include <cmath>
9+
10+
namespace specfem {
11+
namespace attenuation {
12+
13+
template <int N_SLS>
14+
Kokkos::View<type_real[N_SLS], Kokkos::LayoutRight, Kokkos::HostSpace>
15+
compute_tau_eps(
16+
type_real Q,
17+
Kokkos::View<type_real[N_SLS], Kokkos::LayoutRight, Kokkos::HostSpace>
18+
tau_sigma,
19+
type_real min_period, type_real max_period) {
20+
21+
static_assert(N_SLS > 1,
22+
"N_SLS must be greater than 1 for tau_eps computation");
23+
24+
// Set up evaluation frequencies equally spaced in log10
25+
// These span the same range as tau_sigma but with NF_ATTENUATION points
26+
Kokkos::View<type_real[NF_ATTENUATION], Kokkos::LayoutRight, Kokkos::HostSpace>
27+
f("frequencies");
28+
29+
const type_real f1 = 1.0 / max_period; // min frequency
30+
const type_real f2 = 1.0 / min_period; // max frequency
31+
const type_real log_f1 = std::log10(f1);
32+
const type_real log_f2 = std::log10(f2);
33+
const type_real d_log_f =
34+
(log_f2 - log_f1) / (static_cast<type_real>(NF_ATTENUATION) - 1);
35+
36+
for (int i = 0; i < NF_ATTENUATION; ++i) {
37+
f(i) = std::pow(10.0, log_f1 + i * d_log_f);
38+
}
39+
40+
// Create the objective function
41+
AttenuationObjective<N_SLS> objective;
42+
objective.Q = Q;
43+
objective.iQ = 1.0 / Q;
44+
objective.f = f;
45+
objective.tau_sigma = tau_sigma;
46+
47+
// Initial guess: tau_eps = tau_sigma * (1 + 2/Q)
48+
// This matches the Fortran SPECFEM3D implementation and provides a better
49+
// starting point for the optimization (derived from tan_delta ≈ 1/Q at
50+
// omega*tau = 1)
51+
Kokkos::View<type_real[N_SLS], Kokkos::LayoutRight, Kokkos::HostSpace> x0(
52+
"tau_eps_init");
53+
for (int j = 0; j < N_SLS; ++j) {
54+
x0(j) = tau_sigma(j) + (tau_sigma(j) * 2.0 / Q);
55+
}
56+
57+
// Run Nelder-Mead optimization
58+
// Use default tolerances matching Fortran SPECFEM3D
59+
optimization::NelderMeadOptions<N_SLS> options;
60+
options.x0 = x0;
61+
options.max_iterations = -1; // default max iterations
62+
options.tol_f = 1.0e-4;
63+
options.tol_x = 1.0e-4;
64+
65+
auto result = optimization::optimize(optimization::NelderMeadSimplex{},
66+
objective, options);
67+
68+
return result();
69+
}
70+
71+
} // namespace attenuation
72+
} // namespace specfem
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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

Comments
 (0)