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 OnePTests | ||
10 | * \brief Root benchmark case Schnepf et al 2020 M3.1 doi: 10.3389/fpls.2020.00316 | ||
11 | */ | ||
12 | |||
13 | #ifndef DUMUX_ROOTBENCHMARK_TEST_PROBLEM_HH | ||
14 | #define DUMUX_ROOTBENCHMARK_TEST_PROBLEM_HH | ||
15 | |||
16 | #include <dune/common/fvector.hh> | ||
17 | #include <dune/common/fmatrix.hh> | ||
18 | |||
19 | #include <dumux/common/properties.hh> | ||
20 | #include <dumux/common/parameters.hh> | ||
21 | #include <dumux/common/boundarytypes.hh> | ||
22 | #include <dumux/common/numeqvector.hh> | ||
23 | |||
24 | #include <dumux/io/format.hh> | ||
25 | |||
26 | #include <dumux/porousmediumflow/problem.hh> | ||
27 | |||
28 | #include <dumux/material/components/constant.hh> | ||
29 | |||
30 | namespace Dumux { | ||
31 | |||
32 | /*! | ||
33 | * \ingroup OnePTests | ||
34 | * \brief Root benchmark case Schnepf et al 2020 M3.1 doi: 10.3389/fpls.2020.00316 | ||
35 | */ | ||
36 | template <class TypeTag> | ||
37 | class RootBenchmarkProblem : public PorousMediumFlowProblem<TypeTag> | ||
38 | { | ||
39 | using ParentType = PorousMediumFlowProblem<TypeTag>; | ||
40 | using Scalar = GetPropType<TypeTag, Properties::Scalar>; | ||
41 | using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>; | ||
42 | using GridView = typename GridGeometry::GridView; | ||
43 | using FVElementGeometry = typename GridGeometry::LocalView; | ||
44 | using SubControlVolume = typename FVElementGeometry::SubControlVolume; | ||
45 | using Element = typename GridView::template Codim<0>::Entity; | ||
46 | using GlobalPosition = typename Element::Geometry::GlobalCoordinate; | ||
47 | |||
48 | static constexpr int dimWorld = GridView::dimensionworld; | ||
49 | |||
50 | using PrimaryVariables = GetPropType<TypeTag, Properties::PrimaryVariables>; | ||
51 | using BoundaryTypes = Dumux::BoundaryTypes<GetPropType<TypeTag, Properties::ModelTraits>::numEq()>; | ||
52 | using NumEqVector = Dumux::NumEqVector<PrimaryVariables>; | ||
53 | |||
54 | public: | ||
55 | 12 | RootBenchmarkProblem(std::shared_ptr<const GridGeometry> gridGeometry) | |
56 |
10/28✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 12 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 12 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 12 times.
✓ Branch 15 taken 12 times.
✗ Branch 16 not taken.
✓ Branch 18 taken 12 times.
✗ Branch 19 not taken.
✓ Branch 21 taken 12 times.
✗ Branch 22 not taken.
✓ Branch 24 taken 12 times.
✗ Branch 25 not taken.
✓ Branch 27 taken 12 times.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
✗ Branch 35 not taken.
✗ Branch 36 not taken.
✗ Branch 37 not taken.
✗ Branch 38 not taken.
|
36 | : ParentType(gridGeometry) |
57 | { | ||
58 |
2/4✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 12 times.
|
12 | name_ = getParam<std::string>("Problem.Name"); |
59 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | rootCollarPressure_ = getParam<double>("Problem.RootCollarPressure"); |
60 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | soilPressure_ = getParam<double>("Problem.SoilPressure"); |
61 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | radius_ = getParam<double>("Problem.Radius"); |
62 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | density_ = getParam<double>("Component.LiquidDensity"); |
63 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | Kr_ = this->spatialParams().radialConductivity(); |
64 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | Kx_ = this->spatialParams().axialConductivity(); |
65 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | const auto enableGravity = getParam<bool>("Problem.EnableGravity"); |
66 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
|
12 | const Scalar gravity = enableGravity ? 9.81 : 0.0; |
67 | |||
68 | // cf. Schnepf et al 2020 doi: 10.3389/fpls.2020.00316 | ||
69 | // Equation is 2*pi*R*Kr(ps - p0) = d/ds(-Kx (dp/ds + rho*g)) | ||
70 | // Analytical solution is p = ps + A*exp(Cs) + B*exp(-Cs) | ||
71 | // where C = sqrt(2*pi*R*Kr/Kx) | ||
72 | // Two boundary conditions are needed to determine A and B | ||
73 | // Dirichlet at root collar (s=0, p=p0) | ||
74 | // (1) A + B = p0 - ps | ||
75 | // Neumann no-flow at tip (s=-L, dp/ds + rho*g = 0) | ||
76 | // (2) A*C*exp(-CL) - B*C*exp(CL) = -rho*g | ||
77 | // Assemble (1) and (2) and solve for A and B | ||
78 | Dune::FieldMatrix<double, 2, 2> M; | ||
79 | 12 | Dune::FieldVector<double, 2> b; | |
80 | using std::exp; using std::sqrt; | ||
81 | 12 | const auto C = sqrt(2.0*M_PI*radius_*Kr_/Kx_); C_ = C; | |
82 |
4/8✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 12 times.
✗ Branch 11 not taken.
|
48 | const auto L = this->gridGeometry().bBoxMax()[dimWorld-1] - this->gridGeometry().bBoxMin()[dimWorld-1]; |
83 |
5/10✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 12 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 12 times.
✗ Branch 14 not taken.
|
60 | M[0][0] = 1.0; M[0][1] = 1.0; b[0] = rootCollarPressure_ - soilPressure_; |
84 |
5/10✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 12 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 12 times.
✗ Branch 14 not taken.
|
60 | M[1][0] = C*exp(-C*L); M[1][1] = -C*exp(C*L); b[1] = -density_*gravity; |
85 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | M.solve(d_, b); |
86 | |||
87 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | std::cout << "Computed analytical solution with:\n"; |
88 |
3/8✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
|
36 | std::cout << Fmt::format("A or d1 = {}, B or d2 = {}, c = {}, √c = {}\n", d_[0], d_[1], C*C, C); |
89 |
3/8✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
|
12 | std::cout << Fmt::format("Kx = {}, Kr = {}\n", Kx_, Kr_); |
90 | |||
91 | 12 | const auto psiCollar = 100*(rootCollarPressure_ - 1e5)/(1000*9.81); | |
92 | 12 | const auto psiSoil = 100*(soilPressure_ - 1e5)/(1000*9.81); | |
93 | 36 | const auto psi0 = 100*(analyticalSolution(gridGeometry->bBoxMax()) - 1e5)/(1000*9.81); | |
94 | 36 | const auto psiTip = 100*(analyticalSolution(gridGeometry->bBoxMin()) - 1e5)/(1000*9.81); | |
95 |
3/10✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
|
12 | std::cout << Fmt::format("ψ(0) = {}, ψ(-0.5) = {}, ψ0 = {}, ψs = {} cm\n", psi0, psiTip, psiCollar, psiSoil); |
96 | |||
97 |
4/8✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 12 times.
✗ Branch 11 not taken.
|
36 | analyticalPressures_.resize(gridGeometry->gridView().size(0)); |
98 |
4/8✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 12 times.
✗ Branch 11 not taken.
|
36 | analyticalHead_.resize(gridGeometry->gridView().size(0)); |
99 |
4/8✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 1272 times.
✗ Branch 10 not taken.
|
2556 | for (const auto& element : elements(gridGeometry->gridView())) |
100 | { | ||
101 | 3780 | const auto eIdx = gridGeometry->elementMapper().index(element); | |
102 | 1260 | const auto& globalPos = element.geometry().center(); | |
103 | 1260 | analyticalPressures_[eIdx] = analyticalSolution(globalPos); | |
104 | 2520 | analyticalHead_[eIdx] = 100*(analyticalPressures_[eIdx] - 1e5)/(1000*9.81); | |
105 | } | ||
106 | 12 | } | |
107 | |||
108 | /*! | ||
109 | * \brief The problem name. | ||
110 | */ | ||
111 | const std::string& name() const | ||
112 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | { return name_; } |
113 | |||
114 | /*! | ||
115 | * \brief Specifies which kind of boundary condition should be | ||
116 | * used for which equation on a given boundary control volume. | ||
117 | */ | ||
118 | BoundaryTypes boundaryTypesAtPos(const GlobalPosition &globalPos) const | ||
119 | { | ||
120 |
4/6✓ Branch 0 taken 12 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 24 times.
✓ Branch 3 taken 24 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
72 | BoundaryTypes bcTypes; |
121 |
12/18✓ Branch 0 taken 12 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 12 times.
✓ Branch 4 taken 12 times.
✓ Branch 5 taken 12 times.
✓ Branch 6 taken 24 times.
✓ Branch 7 taken 24 times.
✓ Branch 8 taken 24 times.
✓ Branch 9 taken 24 times.
✓ Branch 10 taken 24 times.
✓ Branch 11 taken 24 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
|
216 | if (globalPos[dimWorld-1] > this->gridGeometry().bBoxMax()[dimWorld-1] - eps_) |
122 | bcTypes.setAllDirichlet(); // Dirichlet at root collar | ||
123 | else | ||
124 | bcTypes.setAllNeumann(); // No-flow at root tips | ||
125 | return bcTypes; | ||
126 | } | ||
127 | |||
128 | /*! | ||
129 | * \brief Evaluates the boundary conditions for a Dirichlet control volume. | ||
130 | */ | ||
131 | ✗ | PrimaryVariables dirichletAtPos(const GlobalPosition& globalPos) const | |
132 | 24 | { return PrimaryVariables(rootCollarPressure_); } | |
133 | |||
134 | /*! | ||
135 | * \brief Evaluates the source term for all phases within a given sub-control volume. | ||
136 | * Positive values mean that the conserved quantity is created, negative ones mean that it vanishes. | ||
137 | * E.g. for the mass balance that would be a mass rate in \f$ [ kg / (m^3 \cdot s)] \f$. | ||
138 | */ | ||
139 | template<class ElementVolumeVariables> | ||
140 | 2520 | NumEqVector source(const Element& element, | |
141 | const FVElementGeometry& fvGeometry, | ||
142 | const ElementVolumeVariables& elemVolVars, | ||
143 | const SubControlVolume& scv) const | ||
144 | { | ||
145 | 2520 | NumEqVector values(0.0); | |
146 | 2520 | const auto& volVars = elemVolVars[scv]; | |
147 | // source term with static soil pressure | ||
148 | 5040 | values[0] = density_*2.0*M_PI*radius_*Kr_*(soilPressure_ - volVars.pressure()); | |
149 | // make this volume-specific source | ||
150 | 2520 | values[0] /= volVars.extrusionFactor(); | |
151 | 2520 | return values; | |
152 | } | ||
153 | |||
154 | template<class SolutionVector> | ||
155 | 12 | void outputL2Norm(const SolutionVector& solution) const | |
156 | { | ||
157 | // calculate the discrete L2-Norm and maximum element size | ||
158 | 12 | Scalar l2Norm = 0.0; | |
159 | 12 | Scalar l2NormNormalization = 0.0; | |
160 | 12 | Scalar hMax = 0.0; | |
161 | |||
162 | 12 | const auto& gg = this->gridGeometry(); | |
163 |
1/2✓ Branch 2 taken 1272 times.
✗ Branch 3 not taken.
|
2544 | for (const auto& element : elements(gg.gridView())) |
164 | { | ||
165 | 2520 | const auto eIdx = gg.elementMapper().index(element); | |
166 | 1260 | const auto geo = element.geometry(); | |
167 | 1260 | const auto globalPos = geo.center(); | |
168 | 1260 | const auto pe = analyticalSolution(globalPos); | |
169 |
2/2✓ Branch 0 taken 670 times.
✓ Branch 1 taken 590 times.
|
1260 | const auto p = solution[eIdx][0]; |
170 |
2/2✓ Branch 0 taken 670 times.
✓ Branch 1 taken 590 times.
|
1260 | const auto dV = geo.volume(); |
171 | |||
172 | 1260 | l2Norm += (p - pe)*(p - pe)*dV; | |
173 | 1260 | l2NormNormalization += pe*pe*dV; | |
174 |
2/2✓ Branch 0 taken 670 times.
✓ Branch 1 taken 590 times.
|
1930 | hMax = std::max(dV, hMax); |
175 | } | ||
176 | |||
177 | 12 | l2Norm = std::sqrt(l2Norm/l2NormNormalization); | |
178 | |||
179 | // write the norm into a log file | ||
180 |
1/2✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
|
12 | std::ofstream logFile(this->name() + ".log", std::ios::app); |
181 |
3/6✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 12 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
|
36 | logFile << "[ConvergenceTest] L2-norm(pressure) = " << l2Norm << " hMax = " << hMax << std::endl; |
182 | 12 | } | |
183 | |||
184 | 2544 | Scalar analyticalSolution(const GlobalPosition& globalPos) const | |
185 | { | ||
186 | using std::exp; | ||
187 | 2544 | const auto s = globalPos[dimWorld-1]; | |
188 | 7632 | return soilPressure_ + d_[0]*exp(s*C_) + d_[1]*exp(-s*C_); | |
189 | } | ||
190 | |||
191 | const std::vector<Scalar>& analyticalPressure() const | ||
192 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | { return analyticalPressures_; } |
193 | |||
194 | const std::vector<Scalar>& analyticalHead() const | ||
195 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | { return analyticalHead_; } |
196 | |||
197 | private: | ||
198 | Scalar rootCollarPressure_, soilPressure_; | ||
199 | Scalar radius_; | ||
200 | Scalar density_; | ||
201 | Scalar Kr_, Kx_; | ||
202 | |||
203 | static constexpr Scalar eps_ = 1e-8; | ||
204 | std::string name_; | ||
205 | |||
206 | // analytical solution | ||
207 | Dune::FieldVector<Scalar, 2> d_; | ||
208 | Scalar C_; | ||
209 | |||
210 | std::vector<Scalar> analyticalPressures_; | ||
211 | std::vector<Scalar> analyticalHead_; | ||
212 | }; | ||
213 | |||
214 | } // end namespace Dumux | ||
215 | |||
216 | #endif | ||
217 |