OGS
Sigmoid.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 <cmath>
7#include <limits>
8#include <numbers>
9#include <optional>
10#include <utility>
11
12#include "BaseLib/Error.h"
13
14namespace MaterialPropertyLib
15{
16// Largest exponent x for which std::exp(x) does not overflow a double.
17static constexpr double max_exp_argument =
18 std::numeric_limits<double>::max_exponent * std::numbers::ln2;
19
20// value() is time-step independent; the derivatives pass this placeholder when
21// they call it to obtain the current sigmoid value.
22static constexpr double unused_dt = std::numeric_limits<double>::quiet_NaN();
23
24// Shared derivative preamble: returns (f_normalized, range) at the current
25// state, or nullopt when the sigmoid is saturated (f_normalized at 0 or range),
26// where all derivatives vanish.
27static std::optional<std::pair<double, double>> normalizedState(
28 double const f, double const lower_bound, double const upper_bound)
29{
30 double const range = upper_bound - lower_bound;
31 double const f_normalized = f - lower_bound;
32
33 if (f_normalized <= 0.0 || f_normalized >= range)
34 {
35 return std::nullopt;
36 }
37 return std::pair{f_normalized, range};
38}
39
41 double const steepness,
42 double const midpoint,
43 double const lower_bound,
44 double const upper_bound,
45 Variable const independent_variable)
46 : steepness_(steepness),
47 midpoint_(midpoint),
48 lower_bound_(lower_bound),
49 upper_bound_(upper_bound),
50 independent_variable_(independent_variable)
51{
53 {
55 "In the sigmoid property '{:s}', the lower_bound value {} must be "
56 "smaller than the upper_bound value {}.",
58 }
59
60 name_ = std::move(name);
61}
62
64 ParameterLib::SpatialPosition const& /*pos*/,
65 double const /*t*/,
66 double const /*dt*/) const
67{
68 double const X = std::get<double>(variable_array[independent_variable_]);
69
70 // Compute sigmoid: (X_u - X_l) / (1 + exp(-k * (X - X_c))) + X_l
71 double const exponent = -steepness_ * (X - midpoint_);
72
73 // Exponent large positive -> sigmoid -> lower_bound; avoid exp overflow.
74 if (exponent > max_exp_argument)
75 {
76 return lower_bound_;
77 }
78
79 double const sigmoid_value =
80 (upper_bound_ - lower_bound_) / (1.0 + std::exp(exponent)) +
82
83 return sigmoid_value;
84}
85
86bool Sigmoid::derivativeVanishes(Variable const variable) const
87{
88 // Non-zero only for the derivative w.r.t. the independent variable.
89 return variable != independent_variable_;
90}
91
93 Variable const variable,
95 double const t,
96 double const /*dt*/) const
97{
98 if (derivativeVanishes(variable))
99 {
100 return 0.0;
101 }
102
103 // Compute derivative using a numerically stable formula.
104 // From dz/dX = k * z * (1-z) where z = 1 / (1 + exp(-k*(X-Xc))),
105 // and f_norm = (upper - lower) * z, we get:
106 // f'(X) = k * f_norm / (upper - lower) * ((upper - lower) - f_norm)
107 // This avoids computing huge (1 + exp(-k*(X-Xc)))^2
108 double const f = std::get<double>(value(variable_array, pos, t, unused_dt));
109 auto const state = normalizedState(f, lower_bound_, upper_bound_);
110 if (!state)
111 {
112 return 0.0;
113 }
114 auto const [f_normalized, range] = *state;
115
116 return steepness_ * f_normalized * (range - f_normalized) / range;
117}
118
120 Variable const variable1,
121 Variable const variable2,
123 double const t,
124 double const /*dt*/) const
125{
126 // Only compute second derivative if both variables are the independent
127 // variable.
128 if (derivativeVanishes(variable1) || derivativeVanishes(variable2))
129 {
130 return 0.0;
131 }
132
133 // Compute second derivative using a numerically stable formula.
134 // From f'(X) = k * f_norm * (range - f_norm) / range, we get:
135 // f''(X) = k^2 * f_norm * (range - f_norm) * (range - 2*f_norm) / range^2
136 // This avoids overflow/underflow issues.
137 double const f = std::get<double>(value(variable_array, pos, t, unused_dt));
138 auto const state = normalizedState(f, lower_bound_, upper_bound_);
139 if (!state)
140 {
141 return 0.0;
142 }
143 auto const [f_normalized, range] = *state;
144
145 return steepness_ * steepness_ * f_normalized * (range - f_normalized) *
146 (range - 2.0 * f_normalized) / (range * range);
147}
148
149} // namespace MaterialPropertyLib
#define OGS_FATAL(...)
Definition Error.h:10
virtual PropertyDataType value() const
Sigmoid(std::string name, double const steepness, double const midpoint, double const lower_bound, double const upper_bound, Variable const independent_variable)
Definition Sigmoid.cpp:40
PropertyDataType d2Value(VariableArray const &variable_array, Variable const variable1, Variable const variable2, ParameterLib::SpatialPosition const &pos, double const t, double const dt) const override
Default implementation: 2nd derivative of any constant property is zero.
Definition Sigmoid.cpp:119
PropertyDataType dValue(VariableArray const &variable_array, Variable const variable, ParameterLib::SpatialPosition const &pos, double const t, double const dt) const override
Definition Sigmoid.cpp:92
double lower_bound_
X_l parameter (lower bound)
Definition Sigmoid.h:70
bool derivativeVanishes(Variable const variable) const
Definition Sigmoid.cpp:86
double upper_bound_
X_u parameter (upper bound)
Definition Sigmoid.h:71
static constexpr double max_exp_argument
Definition Sigmoid.cpp:17
static std::optional< std::pair< double, double > > normalizedState(double const f, double const lower_bound, double const upper_bound)
Definition Sigmoid.cpp:27
static constexpr double unused_dt
Definition Sigmoid.cpp:22
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