OGS
SaturationLuMcCartney.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
2// SPDX-License-Identifier: BSD-3-Clause
3
5
6#include <algorithm>
7#include <cmath>
8#include <numbers>
9
11using namespace boost::math::differentiation;
12
14
15namespace MaterialPropertyLib
16{
17
18template <typename PressureType, typename TemperatureType>
19promote<PressureType, TemperatureType> SaturationLuMcCartney::water_content(
20 PressureType p_cap, TemperatureType T,
21 VariableArray const& variable_array) const
22{
23 return adsorptive_water_content(p_cap, T, variable_array) +
24 capillary_water_content(p_cap, T, variable_array);
25}
26
27template <typename PressureType, typename TemperatureType>
28promote<PressureType, TemperatureType>
30 PressureType p_cap, TemperatureType T,
31 VariableArray const& variable_array) const
32{
33 return theta_a_max(T, variable_array) *
34 (1 - pow(exp((p_cap - psi_max(T)) / p_cap), M));
35}
36
37template <typename PressureType, typename TemperatureType>
38promote<PressureType, TemperatureType>
40 PressureType p_cap, TemperatureType T,
41 VariableArray const& variable_array) const
42{
43 auto const theta_mean =
44 (variable_array.porosity -
45 adsorptive_water_content(p_cap, T, variable_array)) /
46 2.0;
47 auto const chi_frac = (chi(T) + T) / (chi_r + Tr);
48 auto const erf_term =
49 erf(sqrt(2) * (p_cap / psi_c(T, variable_array) * chi_frac - 1));
50 auto const power_term = pow(alpha(T) * p_cap * chi_frac, N);
51 auto const cap_term =
52 theta_mean * (1 - erf_term) * pow(1 + power_term, ((1 / N) - 1));
53 return cap_term;
54}
55
56template <typename TemperatureType>
57promote<TemperatureType> SaturationLuMcCartney::theta_a_max(
58 TemperatureType T, VariableArray const& variable_array) const
59{
60 return (1 - variable_array.porosity) * (CEC(T) / zeta_s(T) + bw);
61}
62
63template <typename TemperatureType>
64promote<TemperatureType> SaturationLuMcCartney::psi_max(TemperatureType T) const
65{
67 (3.0 * nu_w);
68}
69
70template <typename TemperatureType>
71promote<TemperatureType> SaturationLuMcCartney::c(TemperatureType T) const
72{
73 return exp((E1_minus_EL) /
75}
76
77template <typename TemperatureType>
78promote<TemperatureType> SaturationLuMcCartney::CEC(TemperatureType T) const
79{
80 auto const cos_arg = A * std::numbers::pi * (B * T - T1) / (T2 - T1);
81 return 0.5 * CEC_max * (cos(cos_arg) + 1);
82}
83
84template <typename TemperatureType>
85promote<TemperatureType> SaturationLuMcCartney::psi_c(
86 TemperatureType T, VariableArray const& variable_array) const
87{
88 return A_H(T) / (6 * std::numbers::pi) *
89 pow(theta_a_max(T, variable_array) / (SSA * variable_array.density),
90 -3.0);
91}
92
93template <typename TemperatureType>
94promote<TemperatureType> SaturationLuMcCartney::A_H(TemperatureType T) const
95{
96 auto const dielectric_term =
97 pow((epsilon_s - epsilon_w(T)) / (epsilon_s + epsilon_w(T)), 2);
98 auto const RI_term =
99 pow(n_s * n_s - n_w * n_w, 2) / pow(n_s * n_s + n_w * n_w, 3. / 2.);
100 auto const prefactor_dielectic =
102 auto const prefactor_RI =
104 (16 * sqrt(2));
105 return prefactor_dielectic * dielectric_term + prefactor_RI * RI_term;
106}
107
108template <typename TemperatureType>
109promote<TemperatureType> SaturationLuMcCartney::chi(TemperatureType T) const
110{
111 return delta_h(T) / C1;
112}
113
114template <typename TemperatureType>
115promote<TemperatureType> SaturationLuMcCartney::delta_h(TemperatureType T) const
116{
117 // delta_hr = -0.516; //J / m ^ 2 high plasticity clay
118 auto const delta_hr = -0.5151936137548038;
119 return delta_hr * pow((1 - Tr) / (1 - T), 0.38);
120}
121
122template <typename TemperatureType>
123promote<TemperatureType> SaturationLuMcCartney::alpha(TemperatureType T) const
124{
125 auto const psi_aev = alpha_0_inv * exp(eta_alpha * (Tr / T - 1));
126 return 1.0 / psi_aev;
127}
128
129template <typename TemperatureType>
130promote<TemperatureType> SaturationLuMcCartney::epsilon_w(
131 TemperatureType T) const
132{
133 auto const r_w = rho_w(T) / 1000.0;
134 return pow(
135 10, (0.7017 + 642.0 / T - 1.167e5 / pow(T, 2) + 9.190e6 / pow(T, 3) +
136 (1.667 - 11.41 / T - 3.526e4 / pow(T, 2)) * log(r_w) / log(10)) +
137 1);
138}
139
140template <typename TemperatureType>
141promote<TemperatureType> SaturationLuMcCartney::zeta_s(TemperatureType T) const
142{
143 return (c(T) - 1) / c(T) * CEC(T) / nu_mr;
144}
145
146template <typename TemperatureType>
147promote<TemperatureType> SaturationLuMcCartney::rho_w(TemperatureType T) const
148{
149 double const pi = 0.0;
150 auto const tau = ref_T_ / T;
151
152 return ref_p_ /
154 T * gibbs_free_energy_.get_dgamma_dpi(tau, pi));
155}
156
158 std::string const& material)
159 : material_(material),
161 MaterialLib::Fluid::DimensionLessGibbsFreeEnergyRegion1())
162{
163 name_ = std::move(name);
164 if (material_ == "MX80")
165 {
166 E1_minus_EL = 7533.930402019812;
167 nu_mr = 2.752035e-01;
168 CEC_max = 0.8004082526701221;
169 bw = 0.18;
170 M = 0.105;
171 N = 1.15;
172 SSA = 700.0e3;
173 epsilon_s = 3.2089251122574;
174 n_s = 1.3298392371979677;
175 eta_alpha = 5.0;
176 alpha_0_inv = 3.3e6;
177 chi_r = delta_h(Tr) / C1;
178 }
179 else if (material_ == "BoomClay")
180 {
181 E1_minus_EL = 6803;
182 nu_mr = 7.994125e-02;
183 CEC_max = 0.2534798545419938;
184 bw = 0.11;
185 M = 0.085;
186 N = 1.29;
187 SSA = 260e3;
188 epsilon_s = 1.3267932723717908;
189 n_s = 35.435652398543425;
190 eta_alpha = 0.6;
191 alpha_0_inv = 36e3;
192 chi_r = delta_h(Tr) / C1;
193 }
194 else if (material_ == "FEBEX")
195 {
196 E1_minus_EL = 7904.547103777231;
197 nu_mr = 3.374814e-01;
198 CEC_max = 1.0108839312584308;
199 bw = 0.18;
200 M = 0.132;
201 N = 1.22;
202 SSA = 860.0e3;
203 epsilon_s = 0.940367803768519;
204 n_s = 748.7804881357005;
205 eta_alpha = 3.5;
206 alpha_0_inv = 330e3;
207 chi_r = delta_h(Tr) / C1;
208 }
209 else if (material_ == "GMZ01")
210 {
211 E1_minus_EL = 8369.804359014617;
212 nu_mr = 1.505956e-01;
213 CEC_max = 0.8004082526701221;
214 bw = 0.25;
215 M = 0.135;
216 N = 1.3;
217 SSA = 700.0e3;
218 epsilon_s = 2.1590265530454893;
219 n_s = 2175.885110079861;
220 eta_alpha = 8.5;
221 alpha_0_inv = 8.0e6;
222 chi_r = delta_h(Tr) / C1;
223 }
224
225 else
226 {
227 OGS_FATAL(
228 "Material '{}' not implemented in SaturationLuMcCartney model.",
229 material_);
230 }
231}
232
234 VariableArray const& variable_array,
235 ParameterLib::SpatialPosition const& pos, double const t,
236 double const dt) const
237{
238 const double p_cap = variable_array.capillary_pressure;
239 const double T = variable_array.temperature;
240
241 auto const& medium = *std::get<Medium*>(scale_);
242 auto const& solid_phase = medium.phase(PhaseName::Solid);
243 auto const& porosity_property = medium[PropertyType::porosity];
244 auto const& solid_denisty_property = solid_phase[PropertyType::density];
245
246 auto const phi =
247 std::get<double>(porosity_property.value(variable_array, pos, t, dt));
248 auto const rho_s = std::get<double>(
249 solid_denisty_property.value(variable_array, pos, t, dt));
250 VariableArray rho_d_and_phi;
251 rho_d_and_phi.density = rho_s * (1.0 - phi);
252 rho_d_and_phi.porosity = phi;
253 double const p_cut = 1e-10;
254
255 auto const S_L_max = water_content(p_cut, T, rho_d_and_phi) / phi;
256 auto const p_cap_max = psi_max(T);
257 if (p_cap <= p_cut)
258 {
259 return S_L_max;
260 }
261 if (p_cap >= p_cap_max)
262 {
263 return 0.0;
264 }
265 return water_content(p_cap, T, rho_d_and_phi) / phi;
266}
267
269 VariableArray const& variable_array, Variable const variable,
270 ParameterLib::SpatialPosition const& pos, double const t,
271 double const dt) const
272{
273 if (variable != Variable::capillary_pressure &&
274 variable != Variable::temperature)
275 {
276 OGS_FATAL(
277 "SaturationLuMcCartney::dValue is implemented for derivatives "
278 "with respect to capillary pressure or temperature only.");
279 }
280
281 const double p_cap = variable_array.capillary_pressure;
282 const double T = variable_array.temperature;
283
284 auto const& medium = *std::get<Medium*>(scale_);
285 auto const& solid_phase = medium.phase(PhaseName::Solid);
286 auto const& porosity_property = medium[PropertyType::porosity];
287 auto const& solid_denisty_property = solid_phase[PropertyType::density];
288
289 auto const phi =
290 std::get<double>(porosity_property.value(variable_array, pos, t, dt));
291 auto const rho_s = std::get<double>(
292 solid_denisty_property.value(variable_array, pos, t, dt));
293 VariableArray rho_d_and_phi;
294 rho_d_and_phi.density = rho_s * (1.0 - phi);
295 rho_d_and_phi.porosity = phi;
296
297 auto const p_cap_max = psi_max(T);
298 auto const p_cut = 1.0e-10;
299
300 auto const variables = make_ftuple<double, 1, 1>(p_cap, T);
301 auto const& v = std::get<0>(variables);
302 auto const& w = std::get<1>(variables);
303 auto const y = water_content(v, w, rho_d_and_phi);
304 double DS = 0.0;
305 if (p_cap >= p_cap_max)
306 {
307 return 0.0;
308 }
309 if (variable == Variable::capillary_pressure)
310 {
311 if (p_cap <= 1.0e-10)
312 {
313 return 0.;
314 }
315 return y.derivative(1, 0) / phi;
316 }
317 if (variable == Variable::temperature)
318 {
319 if (p_cap <= p_cut)
320 {
321 auto const variables_b2 = make_ftuple<double, 1, 1>(p_cut, T);
322 auto const& v_b2 = std::get<0>(variables_b2);
323 auto const& w_b2 = std::get<1>(variables_b2);
324 auto const y_b2 = water_content(v_b2, w_b2, rho_d_and_phi);
325 return y_b2.derivative(0, 1) / phi;
326 }
327 DS = y.derivative(0, 1);
328 }
329 return DS / phi;
330}
331
333 VariableArray const& variable_array, Variable const variable1,
334 Variable const variable2, ParameterLib::SpatialPosition const& pos,
335 double const t, double const dt) const
336{
337 (void)variable1;
338 (void)variable2;
339 assert(((variable1 == Variable::capillary_pressure) ||
340 (variable1 == Variable::temperature)) &&
341 ((variable2 == Variable::capillary_pressure) ||
342 (variable2 == Variable::temperature)) &&
343 "SaturationLuMcCartney::d2Value is implemented for derivatives "
344 "with respect to capillary pressure only.");
345
346 const double p_cap = variable_array.capillary_pressure;
347 const double T = variable_array.temperature;
348
349 auto const& medium = *std::get<Medium*>(scale_);
350 auto const& solid_phase = medium.phase(PhaseName::Solid);
351 auto const& porosity_property = medium[PropertyType::porosity];
352 auto const& solid_denisty_property = solid_phase[PropertyType::density];
353
354 auto const phi =
355 std::get<double>(porosity_property.value(variable_array, pos, t, dt));
356 auto const rho_s = std::get<double>(
357 solid_denisty_property.value(variable_array, pos, t, dt));
358 VariableArray rho_d_and_phi;
359 rho_d_and_phi.density = rho_s * (1.0 - phi);
360 rho_d_and_phi.porosity = phi;
361
362 auto const p_cap_max = psi_max(T);
363 auto const p_cut = 1e-10;
364
365 if (p_cap >= p_cap_max)
366 {
367 return 0.;
368 }
369 if (p_cap <= p_cut && (variable1 == Variable::capillary_pressure ||
370 variable2 == Variable::capillary_pressure))
371 {
372 return 0.;
373 }
374
375 auto const variables = make_ftuple<double, 2, 2>(p_cap, T);
376 auto const& v = std::get<0>(variables); // Up to Nw derivatives at w=11
377 auto const& w = std::get<1>(variables);
378 auto const y = water_content(v, w, rho_d_and_phi);
379 double DDS = 0.0;
380 auto returnVariableTuple = [](const Variable v1,
381 const Variable v2) -> std::tuple<int, int>
382 {
385 {
386 return {2, 0};
387 }
389 {
390 return {0, 2};
391 }
392 return {1, 1};
393 };
394 auto [i, j] = returnVariableTuple(variable1, variable2);
395 if (j > 0)
396 {
397 if (p_cap <= p_cut)
398 {
399 auto const variables_b2 = make_ftuple<double, 2, 2>(p_cut, T);
400 auto const& v_b2 = std::get<0>(variables_b2);
401 auto const& w_b2 = std::get<1>(variables_b2);
402 auto const y_b2 = water_content(v_b2, w_b2, rho_d_and_phi);
403 return y_b2.derivative(i, j) / phi;
404 }
405 }
406 DDS = y.derivative(i, j);
407 return DDS / phi;
408}
409} // namespace MaterialPropertyLib
#define OGS_FATAL(...)
Definition Error.h:19
virtual PropertyDataType value() const
std::variant< Medium *, Phase *, Component * > scale_
boost::math::differentiation::promote< TemperatureType > delta_h(TemperatureType T) const
boost::math::differentiation::promote< TemperatureType > CEC(TemperatureType T) const
double const B
fitting parameter for cation exchange capacity
boost::math::differentiation::promote< TemperatureType > c(TemperatureType T) const
double const T2
parameter for temperature effect of CEC in K
double N
exponent for capillary water content
double const nu_w
<-— Lu and McCartney 2024 parameters ---->
double const A
fitting parameter for cation exchange capacity
SaturationLuMcCartney(std::string name, std::string const &material)
double SSA
material parameters for capillary water content
double n_s
refractive index of soil particles
boost::math::differentiation::promote< TemperatureType > A_H(TemperatureType T) const
boost::math::differentiation::promote< TemperatureType > theta_a_max(TemperatureType T, VariableArray const &variable_array) const
double epsilon_s
dielectic constant of soil particles
const MaterialLib::Fluid::DimensionLessGibbsFreeEnergyRegion1 gibbs_free_energy_
boost::math::differentiation::promote< PressureType, TemperatureType > capillary_water_content(PressureType p_cap, TemperatureType T, VariableArray const &variable_array) const
double const C1
parameter for temperature correction
boost::math::differentiation::promote< PressureType, TemperatureType > water_content(PressureType p_cap, TemperatureType T, VariableArray const &variable_array) const
double const Tr
reference temperature in K
boost::math::differentiation::promote< PressureType, TemperatureType > adsorptive_water_content(PressureType p_cap, TemperatureType T, VariableArray const &variable_array) const
boost::math::differentiation::promote< TemperatureType > chi(TemperatureType T) const
static constexpr double ref_T_
reference temperature in K.
double alpha_0_inv
air entry suction at reference temperature Tr in Pa
double const T1
parameter for temperature effect of CEC in K
PropertyDataType dValue(VariableArray const &variable_array, Variable const variable, ParameterLib::SpatialPosition const &, double const, double const) const override
double M
exponent for adsorptive water content
double const nu_e
main absorption frequency of UV light in Hz
boost::math::differentiation::promote< TemperatureType > rho_w(TemperatureType T) const
boost::math::differentiation::promote< TemperatureType > psi_c(TemperatureType T, VariableArray const &variable_array) const
boost::math::differentiation::promote< TemperatureType > psi_max(TemperatureType T) const
double CEC_max
maximum cation exchange capacity in meq/g
PropertyDataType d2Value(VariableArray const &variable_array, Variable const variable1, Variable const variable2, ParameterLib::SpatialPosition const &, double const, double const) const override
Default implementation: 2nd derivative of any constant property is zero.
boost::math::differentiation::promote< TemperatureType > epsilon_w(TemperatureType T) const
double E1_minus_EL
material parameters for adsorptive water content
boost::math::differentiation::promote< TemperatureType > alpha(TemperatureType T) const
boost::math::differentiation::promote< TemperatureType > zeta_s(TemperatureType T) const
constexpr double WaterVapour
Specific gas constant for water vapour.
constexpr double BoltzmannConstant
J/K.
constexpr double PlanckConstant
J s.
std::variant< double, Eigen::Matrix< double, 2, 1 >, Eigen::Matrix< double, 3, 1 >, Eigen::Matrix< double, 2, 2 >, Eigen::Matrix< double, 3, 3 >, Eigen::Matrix< double, 4, 1 >, Eigen::Matrix< double, 6, 1 >, Eigen::MatrixXd > PropertyDataType