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 EmbeddedTests | ||
10 | * \brief The root problem for the benchmark cases C1.2a/b from Schnepf et al (2020) https://doi.org/10.3389/fpls.2020.00316 | ||
11 | */ | ||
12 | #ifndef DUMUX_TEST_ROOT_SOIL_BENCHMARK_ROOT_PROBLEM_HH | ||
13 | #define DUMUX_TEST_ROOT_SOIL_BENCHMARK_ROOT_PROBLEM_HH | ||
14 | |||
15 | #include <dumux/common/parameters.hh> | ||
16 | #include <dumux/common/properties.hh> | ||
17 | #include <dumux/common/boundarytypes.hh> | ||
18 | #include <dumux/common/numeqvector.hh> | ||
19 | #include <dumux/porousmediumflow/problem.hh> | ||
20 | #include <dumux/discretization/evalsolution.hh> | ||
21 | #include <dumux/discretization/elementsolution.hh> | ||
22 | |||
23 | #include "couplingreconstruction.hh" | ||
24 | |||
25 | namespace Dumux { | ||
26 | |||
27 | /*! | ||
28 | * \ingroup EmbeddedTests | ||
29 | * \brief The root problem for the benchmark cases C1.2a/b from Schnepf et al (2020) https://doi.org/10.3389/fpls.2020.00316 | ||
30 | */ | ||
31 | template <class TypeTag> | ||
32 | class RootProblem : public PorousMediumFlowProblem<TypeTag> | ||
33 | { | ||
34 | using ParentType = PorousMediumFlowProblem<TypeTag>; | ||
35 | using Scalar = GetPropType<TypeTag, Properties::Scalar>; | ||
36 | using PointSource = GetPropType<TypeTag, Properties::PointSource>; | ||
37 | using Indices = typename GetPropType<TypeTag, Properties::ModelTraits>::Indices; | ||
38 | using PrimaryVariables = GetPropType<TypeTag, Properties::PrimaryVariables>; | ||
39 | using NumEqVector = Dumux::NumEqVector<PrimaryVariables>; | ||
40 | using BoundaryTypes = Dumux::BoundaryTypes<PrimaryVariables::size()>; | ||
41 | using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>; | ||
42 | using GridView = typename GridGeometry::GridView; | ||
43 | using FVElementGeometry = typename GridGeometry::LocalView; | ||
44 | using SubControlVolume = typename GridGeometry::SubControlVolume; | ||
45 | using SubControlVolumeFace = typename GridGeometry::SubControlVolumeFace; | ||
46 | using GlobalPosition = typename GridGeometry::GlobalCoordinate; | ||
47 | using SolutionVector = GetPropType<TypeTag, Properties::SolutionVector>; | ||
48 | using GridVariables = GetPropType<TypeTag, Properties::GridVariables>; | ||
49 | using Element = typename GridView::template Codim<0>::Entity; | ||
50 | using CouplingManager = GetPropType<TypeTag, Properties::CouplingManager>; | ||
51 | |||
52 | public: | ||
53 | enum BCType | ||
54 | { constCollarPressure, constTranspiration, cyclicTranspiration }; | ||
55 | |||
56 | template<class SpatialParams> | ||
57 | 2 | RootProblem(std::shared_ptr<const GridGeometry> gridGeometry, | |
58 | std::shared_ptr<SpatialParams> spatialParams, | ||
59 | std::shared_ptr<CouplingManager> couplingManager, | ||
60 | const GlobalPosition& domainSize) | ||
61 | : ParentType(gridGeometry, spatialParams, "Root") | ||
62 |
6/20✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 9 taken 2 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 2 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 2 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✓ Branch 16 taken 2 times.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
|
8 | , couplingManager_(couplingManager) |
63 | { | ||
64 | // read parameters from input file | ||
65 |
3/6✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
|
4 | name_ = getParamFromGroup<std::string>(this->paramGroup(), "Problem.Name"); |
66 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | criticalCollarPressure_ = getParam<Scalar>("BoundaryConditions.CriticalCollarPressure", -1.4e6); // in Pa |
67 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | initialRootPressure_ = getParam<Scalar>("BoundaryConditions.InitialRootPressure", -1e5); // in Pa |
68 |
1/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
2 | dailyTranspirationRate_ = getParam<Scalar>("BoundaryConditions.DailyTranspirationRate", 1.0); // mm/day |
69 |
2/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
4 | dailyTranspirationRate_ *= domainSize[0]*domainSize[1]/86400.0; // kg/s (domain size in m, kg->g=mm->m) |
70 | |||
71 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | const auto bcType = getParam<std::string>("BoundaryConditions.BoundaryType", "CyclicTranspiration"); |
72 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | if (bcType == "CyclicTranspiration") bcType_ = BCType::cyclicTranspiration; |
73 | ✗ | else if (bcType == "ConstCollarPressure") bcType_ = BCType::constCollarPressure; | |
74 | ✗ | else if (bcType == "ConstTranspiration") bcType_ = BCType::constTranspiration; | |
75 | ✗ | else DUNE_THROW(Dune::InvalidStateException, "Unknown BCType: " << bcType); | |
76 | |||
77 |
1/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
2 | bcSmoothingParam_ = getParam<Scalar>("BoundaryConditions.SmoothingK", 0.1); |
78 |
2/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
|
2 | kernelWidthFactor_ = getParam<Scalar>("MixedDimension.KernelWidthFactor"); |
79 | 2 | } | |
80 | |||
81 | const std::string& name() const | ||
82 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | { return name_; } |
83 | |||
84 | BoundaryTypes boundaryTypesAtPos(const GlobalPosition &globalPos) const | ||
85 | { | ||
86 |
2/6✗ Branch 0 not taken.
✓ Branch 1 taken 116919 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 78969 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
195888 | BoundaryTypes values; |
87 | 195888 | values.setAllNeumann(); | |
88 | |||
89 |
2/6✗ Branch 0 not taken.
✓ Branch 1 taken 116919 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 78969 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
195888 | if (bcType_ == BCType::constCollarPressure) |
90 | ✗ | if (globalPos[2] + eps_ > this->gridGeometry().bBoxMax()[2]) | |
91 | values.setAllDirichlet(); | ||
92 | |||
93 | return values; | ||
94 | } | ||
95 | |||
96 | ✗ | PrimaryVariables dirichletAtPos(const GlobalPosition& globalPos) const | |
97 | ✗ | { return PrimaryVariables(criticalCollarPressure_); } | |
98 | |||
99 | template<class ElementVolumeVariables, class ElementFluxVarsCache> | ||
100 | 262482 | NumEqVector neumann(const Element& element, | |
101 | const FVElementGeometry& fvGeometry, | ||
102 | const ElementVolumeVariables& elemVolVars, | ||
103 | const ElementFluxVarsCache& elemFluxVarsCache, | ||
104 | const SubControlVolumeFace& scvf) const | ||
105 | { | ||
106 |
2/2✓ Branch 0 taken 3977 times.
✓ Branch 1 taken 127264 times.
|
262482 | NumEqVector values(0.0); |
107 | |||
108 | // set transpiration rate at the root collar | ||
109 | 262482 | const auto globalPos = scvf.center(); | |
110 |
10/10✓ Branch 0 taken 3977 times.
✓ Branch 1 taken 127264 times.
✓ Branch 2 taken 3977 times.
✓ Branch 3 taken 127264 times.
✓ Branch 4 taken 3977 times.
✓ Branch 5 taken 127264 times.
✓ Branch 6 taken 3977 times.
✓ Branch 7 taken 127264 times.
✓ Branch 8 taken 3977 times.
✓ Branch 9 taken 127264 times.
|
1312410 | if (globalPos[2] + eps_ > this->gridGeometry().bBoxMax()[2]) |
111 | { | ||
112 | // if the plant has water stress reduce the transpiration rate (imposing Dirichlet boundary condition weakly) | ||
113 | 15908 | const auto& volVars = elemVolVars[scvf.insideScvIdx()]; | |
114 | 15908 | const auto dist = fvGeometry.scv(scvf.insideScvIdx()).volume(); | |
115 | 7954 | const auto elemSol = elementSolution(element, elemVolVars, fvGeometry); | |
116 | 7954 | const auto p = evalSolution(element, element.geometry(), elemSol, globalPos)[0]; | |
117 | 23862 | const auto eIdx = this->gridGeometry().elementMapper().index(element); | |
118 |
4/4✓ Branch 0 taken 3648 times.
✓ Branch 1 taken 329 times.
✓ Branch 2 taken 3648 times.
✓ Branch 3 taken 329 times.
|
15908 | const Scalar Kx = this->spatialParams().Kx(eIdx); |
119 |
2/2✓ Branch 0 taken 3648 times.
✓ Branch 1 taken 329 times.
|
7954 | const auto rho = volVars.density(0); |
120 | 7954 | const Scalar criticalTranspiration = rho*Kx*(p - criticalCollarPressure_ - rho*9.81*dist)/dist; | |
121 | 15908 | values[Indices::conti0EqIdx] = smoothMin(potentialTranspirationRate(), criticalTranspiration, dailyTranspirationRate_*bcSmoothingParam_); | |
122 | 7954 | values /= volVars.extrusionFactor() * scvf.area(); // convert from kg/s to kg/(s*m^2) | |
123 | } | ||
124 | |||
125 | 262482 | return values; | |
126 | } | ||
127 | |||
128 | //! Smooth minimum function | ||
129 | ✗ | Scalar smoothMin(const Scalar a, const Scalar b, const Scalar k) const | |
130 | { | ||
131 | using std::max; using std::min; using std::abs; | ||
132 |
8/12✓ Branch 0 taken 3250 times.
✓ Branch 1 taken 293 times.
✓ Branch 2 taken 3250 times.
✓ Branch 3 taken 293 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 398 times.
✓ Branch 9 taken 36 times.
✓ Branch 10 taken 398 times.
✓ Branch 11 taken 36 times.
|
7954 | const auto h = max(k-abs(a-b), 0.0 )/k; |
133 |
4/6✓ Branch 0 taken 2209 times.
✓ Branch 1 taken 1334 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 328 times.
✓ Branch 5 taken 106 times.
|
6514 | return min(a, b) - h*h*h*k*(1.0/6.0); |
134 | } | ||
135 | |||
136 | //! Compute potential transpiration rate in kg/s | ||
137 | 5279 | Scalar potentialTranspirationRate() const | |
138 | { | ||
139 | // possibly make the transpiration rate dependent on the current root length for growth | ||
140 |
1/2✓ Branch 0 taken 5279 times.
✗ Branch 1 not taken.
|
5279 | if (bcType_ == BCType::cyclicTranspiration) |
141 | 5279 | return dailyTranspirationRate_*std::sin(time_*2.0*M_PI / 86400.0 - M_PI/2.0) + dailyTranspirationRate_; | |
142 | ✗ | else if (bcType_ == BCType::constTranspiration) | |
143 | ✗ | return dailyTranspirationRate_; | |
144 | else | ||
145 | return std::numeric_limits<Scalar>::max(); | ||
146 | } | ||
147 | |||
148 | void addPointSources(std::vector<PointSource>& pointSources) const | ||
149 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | { pointSources = this->couplingManager().lowDimPointSources(); } |
150 | |||
151 | template<class ElementVolumeVariables> | ||
152 | 9148794 | void pointSource(PointSource& source, | |
153 | const Element &element, | ||
154 | const FVElementGeometry& fvGeometry, | ||
155 | const ElementVolumeVariables& elemVolVars, | ||
156 | const SubControlVolume &scv) const | ||
157 | { | ||
158 | // compute source at every integration point | ||
159 | 9148794 | const auto id = source.id(); | |
160 | |||
161 | 27446382 | const Scalar p0 = this->couplingManager().bulkPriVars(id)[Indices::pressureIdx]; | |
162 | 27446382 | const Scalar pRoot = this->couplingManager().lowDimPriVars(id)[Indices::pressureIdx]; | |
163 | 27446382 | const Scalar radius = this->couplingManager().radius(id); | |
164 | 27446382 | const auto lEIdx = this->couplingManager().pointSourceData(id).lowDimElementIdx(); | |
165 | 27446382 | const auto bEIdx = this->couplingManager().pointSourceData(id).bulkElementIdx(); | |
166 | 18297588 | const Scalar Kr = this->spatialParams().Kr(lEIdx); | |
167 | 9148794 | const Scalar kernelRadius = kernelWidthFactor_*radius; | |
168 | 9148794 | const Scalar density = 1000; | |
169 | 9148794 | const Scalar viscosity = 1e-3; | |
170 | 27446382 | const Scalar delta = this->couplingManager().averageDistance(id); | |
171 | |||
172 | // sink defined as radial flow Jr * density [m^2 s-1]* [kg m-3] | ||
173 | 9148794 | const auto sourceValue = -1.0*this->couplingManager().reconstruction().computeSource( | |
174 | bEIdx, lEIdx, p0, pRoot, kernelRadius, radius, Kr, density, viscosity, id, delta | ||
175 | ); | ||
176 | 18297588 | source = sourceValue*source.quadratureWeight()*source.integrationElement(); | |
177 | 9148794 | } | |
178 | |||
179 | ✗ | PrimaryVariables initialAtPos(const GlobalPosition &globalPos) const | |
180 | { | ||
181 |
1/4✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2340 times.
|
2340 | if (bcType_ == BCType::constCollarPressure) |
182 | ✗ | return PrimaryVariables(criticalCollarPressure_); | |
183 | else | ||
184 | 4680 | return PrimaryVariables(initialRootPressure_); | |
185 | } | ||
186 | |||
187 | //! Called after every time step | ||
188 | //! Output the total global exchange term | ||
189 | //! Output the individual source terms into sourceTerms | ||
190 | 451 | void computeSourceIntegral(const SolutionVector& sol, const GridVariables& gridVars, | |
191 | std::vector<Scalar>& sourceTerms) | ||
192 | { | ||
193 | 451 | PrimaryVariables source(0.0); | |
194 |
4/4✓ Branch 3 taken 527670 times.
✓ Branch 4 taken 451 times.
✓ Branch 5 taken 527670 times.
✓ Branch 6 taken 451 times.
|
529023 | for (const auto& element : elements(this->gridGeometry().gridView())) |
195 | { | ||
196 | 1055340 | auto fvGeometry = localView(this->gridGeometry()); | |
197 | 527670 | fvGeometry.bindElement(element); | |
198 | |||
199 | 1055340 | auto elemVolVars = localView(gridVars.curGridVolVars()); | |
200 | 527670 | elemVolVars.bindElement(element, fvGeometry, sol); | |
201 | |||
202 | 527670 | PrimaryVariables localSource(0.0); | |
203 |
5/6✓ Branch 0 taken 527670 times.
✓ Branch 1 taken 527670 times.
✓ Branch 2 taken 527670 times.
✓ Branch 3 taken 527670 times.
✓ Branch 5 taken 527670 times.
✗ Branch 6 not taken.
|
2110680 | for (const auto& scv : scvs(fvGeometry)) |
204 | { | ||
205 |
1/2✓ Branch 1 taken 527670 times.
✗ Branch 2 not taken.
|
527670 | auto pointSources = this->scvPointSources(element, fvGeometry, elemVolVars, scv); |
206 | 527670 | pointSources *= scv.volume()*elemVolVars[scv].extrusionFactor(); | |
207 | 527670 | localSource += pointSources; | |
208 | } | ||
209 | |||
210 | 1583010 | const auto eIdx = this->gridGeometry().elementMapper().index(element); | |
211 | 527670 | sourceTerms[eIdx] = localSource[0]; | |
212 | 1055340 | source += localSource; | |
213 | } | ||
214 | |||
215 | 902 | std::cout << "Global integrated source (root): " << source << " (kg/s) / " | |
216 | 902 | << source*3600*24*1000 << " (g/day)" << '\n'; | |
217 | 451 | } | |
218 | |||
219 | //! compute the actual transpiration rate | ||
220 | 434 | Scalar computeActualTranspirationRate(const SolutionVector& sol, const GridVariables& gridVars, bool verbose = true) const | |
221 | { | ||
222 | 434 | NumEqVector transpirationRate(0.0); | |
223 |
4/4✓ Branch 3 taken 507780 times.
✓ Branch 4 taken 434 times.
✓ Branch 5 taken 507780 times.
✓ Branch 6 taken 434 times.
|
509082 | for (const auto& element : elements(this->gridGeometry().gridView())) |
224 | { | ||
225 | 1015560 | auto fvGeometry = localView(this->gridGeometry()); | |
226 | 507780 | fvGeometry.bindElement(element); | |
227 | |||
228 | 1523340 | auto elemVolVars = localView(gridVars.curGridVolVars()); | |
229 | 507780 | elemVolVars.bindElement(element, fvGeometry, sol); | |
230 | |||
231 |
6/6✓ Branch 0 taken 1015560 times.
✓ Branch 1 taken 507780 times.
✓ Branch 2 taken 1015560 times.
✓ Branch 3 taken 507780 times.
✓ Branch 4 taken 14322 times.
✓ Branch 5 taken 1001238 times.
|
2031120 | for (const auto& scvf : scvfs(fvGeometry)) |
232 |
2/2✓ Branch 0 taken 14322 times.
✓ Branch 1 taken 1001238 times.
|
1015560 | if (scvf.boundary()) |
233 | ✗ | transpirationRate += this->neumann(element, fvGeometry, elemVolVars, 0.0, scvf) | |
234 |
1/2✓ Branch 3 taken 14322 times.
✗ Branch 4 not taken.
|
28644 | *scvf.area()*elemVolVars[scvf.insideScvIdx()].extrusionFactor(); |
235 | } | ||
236 | |||
237 |
1/2✓ Branch 0 taken 434 times.
✗ Branch 1 not taken.
|
434 | if (verbose) |
238 | { | ||
239 | 868 | std::cout << "Actual transpiration rate: " << transpirationRate << " (kg/s) / " | |
240 | 434 | << transpirationRate[0]*86400*1000 << " (g/day)"; | |
241 |
1/2✓ Branch 0 taken 434 times.
✗ Branch 1 not taken.
|
434 | if (bcType_ != BCType::constCollarPressure) |
242 | 868 | std::cout << " / Potential transpiration rate: " << potentialTranspirationRate() << " (kg/s) / " | |
243 | 434 | << potentialTranspirationRate()*86400*1000 << " (g/day) / "; | |
244 | 434 | std::cout << std::endl; | |
245 | } | ||
246 | |||
247 | 434 | return transpirationRate[0]; | |
248 | } | ||
249 | |||
250 | //! Get the coupling manager | ||
251 | const CouplingManager& couplingManager() const | ||
252 |
2/4✓ Branch 15 taken 2 times.
✗ Branch 16 not taken.
✓ Branch 18 taken 2 times.
✗ Branch 19 not taken.
|
54892768 | { return *couplingManager_; } |
253 | |||
254 | //! set the current time for evaluation of time-dependent boundary conditions | ||
255 | ✗ | void setTime(const Scalar t) | |
256 |
2/2✓ Branch 1 taken 451 times.
✓ Branch 2 taken 4 times.
|
455 | { time_= t; } |
257 | |||
258 | //! get the boundary condition type | ||
259 | ✗ | BCType bcType() const | |
260 | ✗ | { return bcType_; } | |
261 | |||
262 | private: | ||
263 | Scalar initialRootPressure_, criticalCollarPressure_, dailyTranspirationRate_; | ||
264 | Scalar time_; | ||
265 | Scalar kernelWidthFactor_; | ||
266 | |||
267 | BCType bcType_; | ||
268 | Scalar bcSmoothingParam_; | ||
269 | |||
270 | static constexpr Scalar eps_ = 1.5e-7; | ||
271 | std::string name_; | ||
272 | |||
273 | std::shared_ptr<CouplingManager> couplingManager_; | ||
274 | }; | ||
275 | |||
276 | } // end namespace Dumux | ||
277 | |||
278 | #endif | ||
279 |