Line | Branch | Exec | Source |
---|---|---|---|
1 | // -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- | ||
2 | // vi: set et ts=4 sw=4 sts=4: | ||
3 | // | ||
4 | // SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder | ||
5 | // SPDX-License-Identifier: GPL-3.0-or-later | ||
6 | // | ||
7 | /*! | ||
8 | * \file | ||
9 | * \ingroup EmbeddedCoupling | ||
10 | * \brief Coupling reconstruction for 1d-3d mixed-dimension method with distributed sources | ||
11 | */ | ||
12 | #ifndef DUMUX_MULTIDOMAIN_EMBEDDED_COUPLING_RECONSTRUCTION_1D3D_KERNEL_ROOTSOIL_HH | ||
13 | #define DUMUX_MULTIDOMAIN_EMBEDDED_COUPLING_RECONSTRUCTION_1D3D_KERNEL_ROOTSOIL_HH | ||
14 | |||
15 | #include <cmath> | ||
16 | #include <algorithm> | ||
17 | #include <memory> | ||
18 | #include <vector> | ||
19 | |||
20 | #include <dune/common/exceptions.hh> | ||
21 | |||
22 | #include <dumux/common/integrate.hh> | ||
23 | #include <dumux/common/parameters.hh> | ||
24 | #include <dumux/common/math.hh> | ||
25 | |||
26 | #include <dumux/nonlinear/findscalarroot.hh> | ||
27 | |||
28 | namespace Dumux::Detail::RootSoil { | ||
29 | |||
30 | /*! | ||
31 | * \file | ||
32 | * \ingroup EmbeddedCoupling | ||
33 | * \brief Kirchhoff transformation for the Richards equation | ||
34 | */ | ||
35 | class KirchhoffTransformation | ||
36 | { | ||
37 | double alpha, n, m, l; | ||
38 | public: | ||
39 | 1 | KirchhoffTransformation() | |
40 | 1 | { | |
41 | 1 | alpha = getParam<double>("Soil.SpatialParams.VanGenuchtenAlpha"); | |
42 | 1 | n = getParam<double>("Soil.SpatialParams.VanGenuchtenN"); | |
43 | 1 | l = getParam<double>("Soil.SpatialParams.VanGenuchtenL", 0.5); | |
44 | 1 | m = 1.0 - 1.0/n; | |
45 | 1 | } | |
46 | |||
47 | 38472785 | inline double kr(const double pw) const | |
48 | { | ||
49 | 38472785 | using std::pow; | |
50 | 38472785 | const auto pc = 1.0e5-pw; | |
51 | 38472785 | const auto swe = pow(pow(alpha*pc, n) + 1.0, -m); | |
52 | 38472785 | const auto r = 1.0 - pow(1.0 - pow(swe, 1.0/m), m); | |
53 | 38472785 | return pow(swe, l)*r*r; | |
54 | } | ||
55 | |||
56 | //! Kirchhoff transform | ||
57 | inline double computeTransformed(const double p) const | ||
58 | 19286393 | { return integrateScalarFunction([this](const auto& p){ return kr(p); }, 0.0, p, 1e-15); } | |
59 | |||
60 | //! Inverse Kirchhoff transform | ||
61 | inline double computeInverseTransformed(const double psi) const | ||
62 | { | ||
63 | const auto residual = [this, psi](const auto& p){ return psi - computeTransformed(p); }; | ||
64 | return findScalarRootBrent(-2.6e7, 1.0e5, residual, 1e-15, 200000); | ||
65 | } | ||
66 | }; | ||
67 | |||
68 | /*! | ||
69 | * \file | ||
70 | * \ingroup EmbeddedCoupling | ||
71 | * \brief Kirchhoff transformation for the Richards equation with values cached in a lookup table | ||
72 | */ | ||
73 | class KirchhoffTransformationCached | ||
74 | { | ||
75 | double pMin, pMax; | ||
76 | std::size_t samples; | ||
77 | KirchhoffTransformation transformation_; | ||
78 | std::vector<double> p_, psi_; | ||
79 | public: | ||
80 | 1 | KirchhoffTransformationCached() | |
81 | 1 | { | |
82 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | pMin = getParam<double>("MixedDimension.Reconstruction.PressureMin", -2.6e7); |
83 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | pMax = getParam<double>("MixedDimension.Reconstruction.PressureMax", 1.0e5); |
84 |
1/4✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
1 | samples = getParam<std::size_t>("MixedDimension.Reconstruction.Samples", 10000); |
85 | |||
86 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | p_.resize(samples); |
87 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | psi_.resize(samples); |
88 | 1 | const double pStep = (pMax-pMin)/samples; | |
89 |
2/2✓ Branch 0 taken 100001 times.
✓ Branch 1 taken 1 times.
|
100002 | for (std::size_t i = 0; i <= samples; ++i) |
90 | { | ||
91 | 100001 | const auto pValue = pMin + i*pStep; | |
92 | 100001 | p_[i] = pValue; | |
93 | 100001 | psi_[i] = transformation_.computeTransformed(pValue); | |
94 | } | ||
95 | |||
96 | 1 | std::cout << "[coupling] Presampled Kirchhoff transformation with " | |
97 |
2/4✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
|
2 | << samples << " samples (step size: " << pStep << " Pa)." |
98 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | << std::endl; |
99 | 1 | } | |
100 | |||
101 | //! Kirchhoff transform | ||
102 | inline double computeTransformed(const double p) const | ||
103 | 102574630 | { return interpolate<InterpolationPolicy::LinearTable>(p, p_, psi_); } | |
104 | |||
105 | //! Inverse Kirchhoff transform | ||
106 | inline double computeInverseTransformed(const double psi) const | ||
107 | 14 | { return interpolate<InterpolationPolicy::LinearTable>(psi, psi_, p_); } | |
108 | }; | ||
109 | |||
110 | /*! | ||
111 | * \file | ||
112 | * \ingroup EmbeddedCoupling | ||
113 | * \brief Caching the source computation to speed up Jacobian approximation during assembly | ||
114 | */ | ||
115 | struct SourceCache | ||
116 | { | ||
117 | SourceCache() | ||
118 | 1329 | : q_{} | |
119 | { | ||
120 | 1329 | p0_.fill(1e20); | |
121 | 1329 | pRoot_.fill(1e20); | |
122 | } | ||
123 | |||
124 | template<class F> | ||
125 | 154753281 | double operator() (const double p0, const double pRoot, const F& f) | |
126 | { | ||
127 |
2/2✓ Branch 0 taken 323218140 times.
✓ Branch 1 taken 2438772 times.
|
325656912 | for (std::size_t i = 0; i < size; ++i) |
128 |
12/12✓ Branch 0 taken 244076898 times.
✓ Branch 1 taken 79141242 times.
✓ Branch 2 taken 244076898 times.
✓ Branch 3 taken 79141242 times.
✓ Branch 4 taken 244076898 times.
✓ Branch 5 taken 79141242 times.
✓ Branch 6 taken 152314509 times.
✓ Branch 7 taken 91762389 times.
✓ Branch 8 taken 152314509 times.
✓ Branch 9 taken 91762389 times.
✓ Branch 10 taken 152314509 times.
✓ Branch 11 taken 91762389 times.
|
969654420 | if (std::abs(p0-p0_[i]) < 1e-14 && std::abs(pRoot-pRoot_[i]) < 1e-14) |
129 | 304629018 | return q_[i]; | |
130 | |||
131 | 2438772 | const auto q = f(p0, pRoot); | |
132 |
2/2✓ Branch 0 taken 1626696 times.
✓ Branch 1 taken 812075 times.
|
2438771 | p0_[writePos] = p0; |
133 |
2/2✓ Branch 0 taken 1626696 times.
✓ Branch 1 taken 812075 times.
|
2438771 | pRoot_[writePos] = pRoot; |
134 |
2/2✓ Branch 0 taken 1626696 times.
✓ Branch 1 taken 812075 times.
|
2438771 | q_[writePos] = q; |
135 |
2/2✓ Branch 0 taken 1626696 times.
✓ Branch 1 taken 812075 times.
|
2438771 | writePos = (writePos >= size-1) ? 0 : writePos+1; |
136 | 2438771 | return q; | |
137 | } | ||
138 | |||
139 | private: | ||
140 | static constexpr std::size_t size = 3; // original and each variable deflected | ||
141 | std::array<double, size> q_, p0_, pRoot_; | ||
142 | std::size_t writePos = 0; | ||
143 | }; | ||
144 | |||
145 | } // end namespace Dumux::Detail::RootSoil | ||
146 | |||
147 | namespace Dumux::RootSoil { | ||
148 | |||
149 | /*! | ||
150 | * \file | ||
151 | * \ingroup EmbeddedCoupling | ||
152 | * \brief Coupling reconstruction as derived in Koch et al (2022), https://doi.org/10.1016/j.jcp.2021.110823 | ||
153 | */ | ||
154 | template<class PcKrSwCurve> | ||
155 | class CouplingReconstruction | ||
156 | { | ||
157 | using CacheStorage = std::vector<std::vector<std::pair<std::size_t, Detail::RootSoil::SourceCache>>>; | ||
158 | public: | ||
159 | 1 | CouplingReconstruction() | |
160 | 2 | { | |
161 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | K_ = getParam<double>("Soil.SpatialParams.Permeability"); |
162 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | porosity_ = getParam<double>("Soil.SpatialParams.Porosity"); |
163 | // relative tolerance in term of the unknown approximation (psBar) | ||
164 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | nonlinearSolverTolerance_ = getParam<double>("MixedDimension.Reconstruction.RelativeTolerance", 1e-4); |
165 | |||
166 |
1/2✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | std::cout << "\n" << "[coupling] Test Kirchhoff transform:\n"; |
167 | const auto& tf = transformation_; | ||
168 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 times.
|
8 | for (const auto p : {0.9e5, 0.5e5, 0.1e5, 0.0, -1.0e5, -5.0e5, -1.5e6}) |
169 | { | ||
170 | 7 | const auto psi = tf.computeTransformed(p); | |
171 | 7 | const auto pNum = tf.computeInverseTransformed(psi); | |
172 |
4/8✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 7 times.
✗ Branch 7 not taken.
✓ Branch 10 taken 7 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 7 times.
✗ Branch 14 not taken.
|
28 | std::cout << "pw: " << p << ", psi: " << psi << ", pw (inverse): " << pNum << '\n'; |
173 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
|
14 | if (std::abs(p-pNum) > 100) |
174 | ✗ | std::cerr << "Transformation is not precise enough. Maybe decrease the tolerances!" << std::endl; | |
175 | } | ||
176 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | std::cout << std::endl; |
177 | 1 | } | |
178 | |||
179 | 2438772 | double computePsBar(const double p0, const double pRoot, | |
180 | const double kernelRadius, const double rootRadius, | ||
181 | const double Kr, const double density, const double viscosity, | ||
182 | const std::size_t sourceId, | ||
183 | const double delta) const | ||
184 | { | ||
185 | 2438772 | const auto& tf = transformation_; | |
186 | 2438772 | const auto psi0 = tf.computeTransformed(p0); | |
187 | 2438772 | const auto sourceFactor = (std::log(kernelRadius/rootRadius) - 0.5 + 0.5*delta*delta/kernelRadius/kernelRadius)*viscosity/(2.0*M_PI*K_); | |
188 | |||
189 | 295529988 | const auto residual = [&](const double psBar){ | |
190 | 195394144 | const auto sourceTerm = sourceFactor*2.0*M_PI*rootRadius*Kr*(psBar - pRoot); | |
191 | 48848536 | return psi0 - tf.computeTransformed(psBar) - sourceTerm; | |
192 | }; | ||
193 | |||
194 | 2438772 | return findScalarRootBrent(-2.5e7, 1.0e5, residual, nonlinearSolverTolerance_, 20000); | |
195 | } | ||
196 | |||
197 | double computeSource(const double p0, const double pRoot, | ||
198 | const double kernelRadius, const double rootRadius, | ||
199 | const double Kr, const double density, const double viscosity, | ||
200 | const std::size_t sourceId, | ||
201 | const double delta) const | ||
202 | { | ||
203 | ✗ | const auto psBar = computePsBar(p0, pRoot, kernelRadius, rootRadius, Kr, density, viscosity, sourceId, delta); | |
204 | ✗ | return -2.0*M_PI*rootRadius*Kr*(psBar - pRoot)*density; | |
205 | } | ||
206 | |||
207 | // try to be smart and use a cache to reduce nonlinear solves | ||
208 | 154753281 | double computeSource(std::size_t bEIdx, std::size_t lEIdx, | |
209 | const double p0, const double pRoot, | ||
210 | const double kernelRadius, const double rootRadius, | ||
211 | const double Kr, const double density, const double viscosity, | ||
212 | const std::size_t sourceId, | ||
213 | const double delta) const | ||
214 | { | ||
215 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 154753281 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 154753281 times.
|
309506562 | if (!sourceCache_) |
216 | ✗ | return computeSource(p0, pRoot, kernelRadius, rootRadius, Kr, density, viscosity, sourceId, delta); | |
217 | |||
218 | 309506562 | auto& cacheLine = (*sourceCache_)[lEIdx]; | |
219 |
5/8✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 1388802 times.
✓ Branch 3 taken 2722467 times.
✓ Branch 4 taken 21466530 times.
✓ Branch 5 taken 21489217 times.
✓ Branch 6 taken 131897949 times.
✗ Branch 7 not taken.
|
643224808 | auto bIt = std::find_if(cacheLine.begin(), cacheLine.end(), [&](const auto& p){ return p.first == bEIdx; }); |
220 |
3/6✗ Branch 0 not taken.
✓ Branch 1 taken 154753281 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 154753281 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 154753281 times.
|
464259843 | if (bIt == cacheLine.end()) |
221 | ✗ | DUNE_THROW(Dune::InvalidStateException, "Source cache does not exist for index pair (l,b): " << lEIdx << "," << bEIdx); | |
222 | |||
223 | 157192053 | return bIt->second(p0, pRoot, [&](const auto p0_, const auto pRoot_) | |
224 | { | ||
225 | 19510175 | const auto psBar = computePsBar(p0_, pRoot_, kernelRadius, rootRadius, Kr, density, viscosity, sourceId, delta); | |
226 | 7316313 | return -2.0*M_PI*rootRadius*Kr*(psBar - pRoot_)*density; | |
227 | 157192053 | }); | |
228 | } | ||
229 | |||
230 | const Dumux::Detail::RootSoil::KirchhoffTransformationCached& transformation() const | ||
231 | { return transformation_; } | ||
232 | |||
233 | template<class CouplingManager> | ||
234 | 1 | void enableCache(const CouplingManager& couplingManager) | |
235 | { | ||
236 | // create a cache vector for each low dim element | ||
237 | // the vector shall contain one element for each coupled bulk element (where p0 is measured) | ||
238 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | const auto& lowDimGG = couplingManager.problem(CouplingManager::lowDimIdx).gridGeometry(); |
239 |
2/4✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
|
1 | sourceCache_ = std::make_unique<CacheStorage>(lowDimGG.numDofs()); |
240 | // lets use the stencil to find these coupled elements (probably only works for tpfa) | ||
241 |
6/6✓ Branch 0 taken 1170 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1170 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1170 times.
✓ Branch 5 taken 1 times.
|
1171 | for (std::size_t lowDimElementIdx = 0; lowDimElementIdx < sourceCache_->size(); ++lowDimElementIdx) |
242 | { | ||
243 | 2340 | const auto& couplingStencil = couplingManager.couplingStencils(CouplingManager::lowDimIdx).at(lowDimElementIdx); | |
244 | 4680 | (*sourceCache_)[lowDimElementIdx].reserve(couplingStencil.size()); | |
245 |
4/4✓ Branch 0 taken 1329 times.
✓ Branch 1 taken 1170 times.
✓ Branch 2 taken 1329 times.
✓ Branch 3 taken 1170 times.
|
6168 | for (std::size_t bulkElementIdx : couplingStencil) |
246 | 5316 | (*sourceCache_)[lowDimElementIdx].emplace_back(bulkElementIdx, Detail::RootSoil::SourceCache{}); | |
247 | } | ||
248 | 1 | } | |
249 | private: | ||
250 | std::unique_ptr<CacheStorage> sourceCache_; | ||
251 | |||
252 | Detail::RootSoil::KirchhoffTransformationCached transformation_; | ||
253 | double K_, porosity_; | ||
254 | double nonlinearSolverTolerance_; | ||
255 | }; | ||
256 | |||
257 | } // end namespace Dumux::RootSoil | ||
258 | |||
259 | #endif | ||
260 |