diff --git a/configure.ac b/configure.ac index 2a60a0717d0f873c4e32fd81b2773ae2aa97df99..1445dfef0bf5f03200ff8cd0640861b8a956083f 100644 --- a/configure.ac +++ b/configure.ac @@ -83,6 +83,7 @@ AC_CONFIG_FILES([dumux.pc dumux/material/eos/Makefile dumux/multidomain/Makefile dumux/multidomain/common/Makefile + dumux/multidomain/2cstokes2p2c/Makefile dumux/nonlinear/Makefile dumux/parallel/Makefile m4/Makefile diff --git a/dumux/multidomain/2cstokes2p2c/2cstokes2p2clocaloperator.hh b/dumux/multidomain/2cstokes2p2c/2cstokes2p2clocaloperator.hh new file mode 100644 index 0000000000000000000000000000000000000000..965d567d05b61f0f4db827a02a49d5197d842e83 --- /dev/null +++ b/dumux/multidomain/2cstokes2p2c/2cstokes2p2clocaloperator.hh @@ -0,0 +1,739 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/***************************************************************************** + * See the file COPYING for full copying permissions. * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see <http://www.gnu.org/licenses/>. * + *****************************************************************************/ +/*! + * \file + * \brief The local operator for the coupling of a two-component Stokes model + * and a two-phase two-component porous-medium model under isothermal conditions. + */ +#ifndef DUMUX_2CSTOKES_2P2C_LOCALOPERATOR_HH +#define DUMUX_2CSTOKES_2P2C_LOCALOPERATOR_HH + +#include <iostream> + +#include <dune/pdelab/multidomain/couplingutilities.hh> +#include <dune/pdelab/localoperator/pattern.hh> +#include <dune/pdelab/localoperator/idefault.hh> + +#include <dumux/multidomain/common/multidomainproperties.hh> +#include <dumux/freeflow/stokesnc/stokesncmodel.hh> +#include <dumux/implicit/2p2c/2p2cmodel.hh> + +namespace Dumux { + +/*! + * \brief The local operator for the coupling of a two-component Stokes model + * and a two-phase two-component porous-medium model under isothermal conditions. + */ +template<class TypeTag> +class TwoCStokesTwoPTwoCLocalOperator : + public Dune::PDELab::MultiDomain::CouplingOperatorDefaultFlags, + public Dune::PDELab::MultiDomain::NumericalJacobianCoupling<TwoCStokesTwoPTwoCLocalOperator<TypeTag>>, + public Dune::PDELab::MultiDomain::FullCouplingPattern, + public Dune::PDELab::InstationaryLocalOperatorDefaultMethods<double> +{ + public: + typedef typename GET_PROP_TYPE(TypeTag, Problem) GlobalProblem; + typedef typename GET_PROP_TYPE(TypeTag, MultiDomainCouplingLocalOperator) Implementation; + + typedef typename GET_PROP_TYPE(TypeTag, SubDomain1TypeTag) Stokes2cTypeTag; + typedef typename GET_PROP_TYPE(TypeTag, SubDomain2TypeTag) TwoPTwoCTypeTag; + + typedef typename GET_PROP_TYPE(TwoPTwoCTypeTag, FluidSystem) FluidSystem; + + typedef typename GET_PROP_TYPE(Stokes2cTypeTag, ElementVolumeVariables) ElementVolumeVariables1; + typedef typename GET_PROP_TYPE(TwoPTwoCTypeTag, ElementVolumeVariables) ElementVolumeVariables2; + + typedef typename GET_PROP_TYPE(Stokes2cTypeTag, FluxVariables) BoundaryVariables1; + typedef typename GET_PROP_TYPE(TwoPTwoCTypeTag, FluxVariables) BoundaryVariables2; + + typedef typename GET_PROP_TYPE(Stokes2cTypeTag, BoundaryTypes) BoundaryTypes1; + typedef typename GET_PROP_TYPE(TwoPTwoCTypeTag, BoundaryTypes) BoundaryTypes2; + + typedef typename GET_PROP_TYPE(Stokes2cTypeTag, FVElementGeometry) FVElementGeometry1; + typedef typename GET_PROP_TYPE(TwoPTwoCTypeTag, FVElementGeometry) FVElementGeometry2; + + // Multidomain Grid and Subgrid types + typedef typename GET_PROP_TYPE(TypeTag, MultiDomainGrid) MDGrid; + typedef typename MDGrid::Traits::template Codim<0>::Entity MDElement; + typedef typename MDGrid::SubDomainGrid SDGrid; + + typedef typename GET_PROP_TYPE(Stokes2cTypeTag, GridView) Stokes2cGridView; + typedef typename GET_PROP_TYPE(TwoPTwoCTypeTag, GridView) TwoPTwoCGridView; + typedef typename Stokes2cGridView::template Codim<0>::Entity SDElement1; + typedef typename TwoPTwoCGridView::template Codim<0>::Entity SDElement2; + + typedef typename GET_PROP_TYPE(Stokes2cTypeTag, Indices) Stokes2cIndices; + typedef typename GET_PROP_TYPE(TwoPTwoCTypeTag, Indices) TwoPTwoCIndices; + + enum { dim = MDGrid::dimension }; + + // FREE FLOW + enum { numEq1 = GET_PROP_VALUE(Stokes2cTypeTag, NumEq) }; + enum { nPhaseIdx1 = Stokes2cIndices::phaseIdx }; //!< Index of the free-flow phase of the fluidsystem + enum { // equation indices in the Stokes domain + momentumXIdx1 = Stokes2cIndices::momentumXIdx, //!< Index of the x-component of the momentum balance + momentumYIdx1 = Stokes2cIndices::momentumYIdx, //!< Index of the y-component of the momentum balance + momentumZIdx1 = Stokes2cIndices::momentumZIdx, //!< Index of the z-component of the momentum balance + lastMomentumIdx1 = Stokes2cIndices::lastMomentumIdx, //!< Index of the last component of the momentum balance + massBalanceIdx1 = Stokes2cIndices::massBalanceIdx, //!< Index of the mass balance + transportEqIdx1 = Stokes2cIndices::transportEqIdx //!< Index of the transport equation + }; + enum { // indices of the components + transportCompIdx1 = Stokes2cIndices::transportCompIdx, //!< Index of transported component + phaseCompIdx1 = Stokes2cIndices::phaseCompIdx //!< Index of main component of the phase + }; + + // POROUS MEDIUM + enum { numEq2 = GET_PROP_VALUE(TwoPTwoCTypeTag, NumEq) }; + enum { numPhases2 = GET_PROP_VALUE(TwoPTwoCTypeTag, NumPhases) }; + enum { // equation indices in the Darcy domain + contiWEqIdx2 = TwoPTwoCIndices::contiWEqIdx, //!< Index of the continuity equation for water component + massBalanceIdx2 = TwoPTwoCIndices::contiNEqIdx //!< Index of the total mass balance (if one comopnent balance is replaced) + }; + enum { // component indices + wCompIdx2 = TwoPTwoCIndices::wCompIdx, //!< Index of the liquids main component + nCompIdx2 = TwoPTwoCIndices::nCompIdx //!< Index of the main component of the gas + }; + enum { // phase indices + wPhaseIdx2 = TwoPTwoCIndices::wPhaseIdx, //!< Index for the liquid phase + nPhaseIdx2 = TwoPTwoCIndices::nPhaseIdx //!< Index for the gas phase + }; + + typedef typename GET_PROP(TypeTag, ParameterTree) ParameterTree; + typedef typename GET_PROP_TYPE(TypeTag, Scalar) Scalar; + typedef Dune::FieldVector<Scalar, dim> DimVector; + + typedef typename Stokes2cGridView::template Codim<dim>::EntityPointer VertexPointer1; + typedef typename TwoPTwoCGridView::template Codim<dim>::EntityPointer VertexPointer2; + typedef typename MDGrid::Traits::template Codim<0>::EntityPointer MDElementPointer; + + TwoCStokesTwoPTwoCLocalOperator(GlobalProblem& globalProblem) + : globalProblem_(globalProblem) + { + try + { + blModel_ = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, int, FreeFlow, UseBoundaryLayerModel); + massTransferModel_ = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, int, FreeFlow, MassTransferModel); + + if (massTransferModel_ == 1) + std::cout << "Using power law for mass transfer coefficient\n"; + if (massTransferModel_ == 2){ + std::cout << "Using Schluender model\n"; + } + } + catch (Dumux::ParameterException &e) { + std::cerr << e << ". Abort!\n"; + exit(1) ; + } + catch (...) { + std::cerr << "Unknown exception thrown!\n"; + exit(1); + } + } + + // multidomain flags + static const bool doAlphaCoupling = true; + static const bool doPatternCoupling = true; + + /*! + * \brief Do the coupling. The unknowns are transferred from dune-multidomain. + * Based on them, a coupling residual is calculated and added at the + * respective positions in the matrix. + * + * \param intersectionGeometry the geometry of the intersection + * \param lfsu_s local function space of the Stokes domain + * \param unknowns1 the unknowns vector of the Stokes element (formatted according to PDELab) + * \param lfsv_s docme + * \param lfsu_n local function space of the Darcy domain + * \param unknowns2 the unknowns vector of the Darcy element (formatted according to PDELab) + * \param lfsv_n docme + * \param couplingRes1 the coupling residual from the Stokes domain + * \param couplingRes2 the coupling residual from the Darcy domain + * + */ + template<typename IntersectionGeom, typename LFSU1, typename LFSU2, + typename X, typename LFSV1, typename LFSV2,typename RES> + void alpha_coupling (const IntersectionGeom& intersectionGeometry, + const LFSU1& lfsu_s, const X& unknowns1, const LFSV1& lfsv_s, + const LFSU2& lfsu_n, const X& unknowns2, const LFSV2& lfsv_n, + RES& couplingRes1, RES& couplingRes2) const + { + const MDElementPointer mdElementPointer1 = intersectionGeometry.inside(); + const MDElementPointer mdElementPointer2 = intersectionGeometry.outside(); + + const MDElement& mdElement1 = *mdElementPointer1; + const MDElement& mdElement2 = *mdElementPointer2; + + // the subodmain elements + const SDElement1& sdElement1 = *(globalProblem_.sdElementPointer1(mdElement1)); + const SDElement2& sdElement2 = *(globalProblem_.sdElementPointer2(mdElement2)); + + // a container for the parameters on each side of the coupling interface (see below) + CParams cParams; + + // update fvElementGeometry and the element volume variables + updateElemVolVars(lfsu_s, lfsu_n, + unknowns1, unknowns2, + sdElement1, sdElement2, + cParams); + + // first element + const int faceIdx1 = intersectionGeometry.indexInInside(); + const Dune::GenericReferenceElement<typename MDGrid::ctype,dim>& referenceElement1 = + Dune::GenericReferenceElements<typename MDGrid::ctype,dim>::general(mdElement1.type()); + const int numVerticesOfFace = referenceElement1.size(faceIdx1, 1, dim); + + // second element + const int faceIdx2 = intersectionGeometry.indexInOutside(); + const Dune::GenericReferenceElement<typename MDGrid::ctype,dim>& referenceElement2 = + Dune::GenericReferenceElements<typename MDGrid::ctype,dim>::general(mdElement2.type()); + + // TODO: assumes same number of vertices on a coupling face + for (int vertexInFace = 0; vertexInFace < numVerticesOfFace; ++vertexInFace) + { + const int vertInElem1 = referenceElement1.subEntity(faceIdx1, 1, vertexInFace, dim); + const int vertInElem2 = referenceElement2.subEntity(faceIdx2, 1, vertexInFace, dim); + + const int boundaryFaceIdx1 = cParams.fvGeometry1.boundaryFaceIndex(faceIdx1, vertexInFace); + const int boundaryFaceIdx2 = cParams.fvGeometry2.boundaryFaceIndex(faceIdx2, vertexInFace); + + // obtain the boundary types + const VertexPointer1 vPtr1 = sdElement1.template subEntity<dim>(vertInElem1); + const VertexPointer2 vPtr2 = sdElement2.template subEntity<dim>(vertInElem2); + + globalProblem_.sdProblem1().boundaryTypes(cParams.boundaryTypes1, *vPtr1); + globalProblem_.sdProblem2().boundaryTypes(cParams.boundaryTypes2, *vPtr2); + + const BoundaryVariables1 boundaryVars1(globalProblem_.sdProblem1(), + sdElement1, + cParams.fvGeometry1, + boundaryFaceIdx1, + cParams.elemVolVarsCur1, + /*onBoundary=*/true); + const BoundaryVariables2 boundaryVars2(globalProblem_.sdProblem2(), + sdElement2, + cParams.fvGeometry2, + boundaryFaceIdx2, + cParams.elemVolVarsCur2, + /*onBoundary=*/true); + + asImp_()->evalCoupling12(lfsu_s, lfsu_n, // local function spaces + vertInElem1, vertInElem2, + sdElement1, sdElement2, + boundaryVars1, boundaryVars2, + cParams, + couplingRes1, couplingRes2); + asImp_()->evalCoupling21(lfsu_s, lfsu_n, // local function spaces + vertInElem1, vertInElem2, + sdElement1, sdElement2, + boundaryVars1, boundaryVars2, + cParams, + couplingRes1, couplingRes2); + } + } + + /*! + * \brief Update the volume variables of the element and extract the unknowns from dune-pdelab vectors + * and bring them into a form which fits to dumux. + * + * \param lfsu_s local function space of the Stokes domain + * \param lfsu_n local function space of the Darcy domain + * \param unknowns1 the unknowns vector of the Stokes element (formatted according to PDELab) + * \param unknowns2 the unknowns vector of the Darcy element (formatted according to PDELab) + * \param sdElement1 the element in the Stokes domain + * \param sdElement2 the element in the Darcy domain + * \param cParams a parameter container + * + */ + template<typename LFSU1, typename LFSU2, typename X, typename CParams> + void updateElemVolVars (const LFSU1& lfsu_s, const LFSU2& lfsu_n, + const X& unknowns1, const X& unknowns2, + const SDElement1& sdElement1, const SDElement2& sdElement2, + CParams &cParams) const + { + cParams.fvGeometry1.update(globalProblem_.sdGridView1(), sdElement1); + cParams.fvGeometry2.update(globalProblem_.sdGridView2(), sdElement2); + + const int numVertsOfElem1 = sdElement1.template count<dim>(); + const int numVertsOfElem2 = sdElement2.template count<dim>(); + + //bring the local unknowns x_s into a form that can be passed to elemVolVarsCur.update() + Dune::BlockVector<Dune::FieldVector<Scalar,1>> elementSol1(0.); + Dune::BlockVector<Dune::FieldVector<Scalar,1>> elementSol2(0.); + elementSol1.resize(unknowns1.size()); + elementSol2.resize(unknowns2.size()); + + for (int idx=0; idx<numVertsOfElem1; ++idx) + { + for (int eqIdx1=0; eqIdx1<numEq1; ++eqIdx1) + elementSol1[eqIdx1*numVertsOfElem1+idx] = unknowns1(lfsu_s.child(eqIdx1),idx); + for (int eqIdx2=0; eqIdx2<numEq2; ++eqIdx2) + elementSol2[eqIdx2*numVertsOfElem2+idx] = unknowns2(lfsu_n.child(eqIdx2),idx); + } +#if HAVE_VALGRIND + for (unsigned int i = 0; i < elementSol1.size(); i++) + Valgrind::CheckDefined(elementSol1[i]); + for (unsigned int i = 0; i < elementSol2.size(); i++) + Valgrind::CheckDefined(elementSol2[i]); +#endif // HAVE_VALGRIND + + + // evaluate the local residual with the PDELab solution + globalProblem_.localResidual1().evalPDELab(sdElement1, cParams.fvGeometry1, elementSol1, + cParams.elemVolVarsPrev1, cParams.elemVolVarsCur1); + globalProblem_.localResidual2().evalPDELab(sdElement2, cParams.fvGeometry2, elementSol2, + cParams.elemVolVarsPrev2, cParams.elemVolVarsCur2); + + } + + /*! + * \brief Evaluation of the coupling from Stokes (1 or s) to Darcy (2 or n). + * + * \param lfsu_s local function space of the Stokes domain + * \param lfsu_n local function space of the Darcy domain + * \param vertInElem1 local vertex index in element1 + * \param vertInElem2 local vertex index in element2 + * \param sdElement1 the element in the Stokes domain + * \param sdElement2 the element in the Darcy domain + * \param boundaryVars1 the boundary variables at the interface of the Stokes domain + * \param boundaryVars2 the boundary variables at the interface of the Darcy domain + * \param cParams a parameter container + * \param couplingRes1 the coupling residual from the Stokes domain + * \param couplingRes2 the coupling residual from the Darcy domain + */ + template<typename LFSU1, typename LFSU2, typename RES1, typename RES2, typename CParams> + void evalCoupling12(const LFSU1& lfsu_s, const LFSU2& lfsu_n, + const int vertInElem1, const int vertInElem2, + const SDElement1& sdElement1, const SDElement2& sdElement2, + const BoundaryVariables1& boundaryVars1, const BoundaryVariables2& boundaryVars2, + const CParams &cParams, + RES1& couplingRes1, RES2& couplingRes2) const + { + const DimVector& globalPos1 = cParams.fvGeometry1.subContVol[vertInElem1].global; + const DimVector& bfNormal1 = boundaryVars1.face().normal; + const Scalar normalMassFlux = boundaryVars1.normalVelocity() * + cParams.elemVolVarsCur1[vertInElem1].density(); + + //rho*v*n as NEUMANN condition for porous medium (set, if B&J defined as NEUMANN condition) + if (cParams.boundaryTypes2.isCouplingInflow(massBalanceIdx2)) + { + if (globalProblem_.sdProblem1().isCornerPoint(globalPos1)) + { + couplingRes2.accumulate(lfsu_n.child(massBalanceIdx2), vertInElem2, + -normalMassFlux); + } + else + { + couplingRes2.accumulate(lfsu_n.child(massBalanceIdx2), vertInElem2, + globalProblem_.localResidual1().residual(vertInElem1)[massBalanceIdx1]); + } + } + if (cParams.boundaryTypes2.isCouplingOutflow(massBalanceIdx2)) + { + + //TODO what happens at the corners? Idea: set only pressure in corners, so that residual is not needed there + //pi-tau + // if (globalProblem_.sdProblem1().isCornerPoint(globalPos1)) + // { + // couplingRes2[getIndex_<LFSU2,massBalanceIdx2> (lfsu_n,vertInElem2)] -= cParams.elemVolVarsCur1[vertInElem1].pressure; + // } + // else + // { + // // n.(pI-tau)n as dirichlet condition for Darcy p (set, if B&J defined as Dirichlet condition) + // set residualDarcy[massBalance] = p in 2p2clocalresidual.hh + couplingRes2.accumulate(lfsu_n.child(massBalanceIdx2), vertInElem2, + globalProblem_.localResidual1().residual(vertInElem1)[momentumYIdx1] + -cParams.elemVolVarsCur1[vertInElem1].pressure()); + } + + if (cParams.boundaryTypes2.isCouplingInflow(contiWEqIdx2)) + { + // only enter here, if a BOUNDARY LAYER MODEL is used for the computation of the diffusive fluxes + if (blModel_) + { + const Scalar massFractionOut = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, Scalar, FreeFlow, RefMassfrac); + const Scalar M1 = FluidSystem::molarMass(transportCompIdx1); + const Scalar M2 = FluidSystem::molarMass(phaseCompIdx1); + const Scalar X2 = 1.0 - massFractionOut; + const Scalar massToMoleDenominator = M2 + X2*(M1 - M2); + const Scalar moleFractionOut = massFractionOut * M2 /massToMoleDenominator; + + const Scalar advectiveFlux = + normalMassFlux * + cParams.elemVolVarsCur1[vertInElem1].massFraction(transportCompIdx1); + Scalar normalMoleFracGrad = + cParams.elemVolVarsCur1[vertInElem1].moleFraction(transportCompIdx1) - + moleFractionOut; + + // multiplied by the Schmidt number^(1/3) + const Scalar deltaMassBL = computeBoundaryLayerThickness(cParams, globalPos1, vertInElem1); + normalMoleFracGrad /= deltaMassBL; + + const Scalar diffusiveFlux = + bfNormal1.two_norm() * + normalMoleFracGrad * + boundaryVars1.diffusionCoeff(transportCompIdx1) * + boundaryVars1.molarDensity() * + FluidSystem::molarMass(transportCompIdx1); + + if (massTransferModel_ == 0) + { + couplingRes2.accumulate(lfsu_n.child(contiWEqIdx2), vertInElem2, + -(advectiveFlux - diffusiveFlux)); + } + // transition from the mass transfer coefficient concept to the coupling via the local residual, + // when saturations become small; only diffusive fluxes are scaled! + else + { + const Scalar massTransferCoeff = massTransferCoefficient(cParams, sdElement1, sdElement2, globalPos1, + vertInElem1, vertInElem2); + if (massTransferCoeff > 1.0 || massTransferCoeff < 0.0) + std::cout << "MTC out of bounds! >>> " << massTransferCoeff << std::endl; + + + if (globalProblem_.sdProblem1().isCornerPoint(globalPos1)) + { + const Scalar diffusiveFluxAtCorner = + bfNormal1 * + boundaryVars1.moleFractionGrad(transportCompIdx1) * + boundaryVars1.diffusionCoeff(transportCompIdx1) * + boundaryVars1.molarDensity() * + FluidSystem::molarMass(transportCompIdx1); + + couplingRes2.accumulate(lfsu_n.child(contiWEqIdx2), vertInElem2, + -massTransferCoeff*(advectiveFlux - diffusiveFlux) - + (1.-massTransferCoeff)*(advectiveFlux - diffusiveFluxAtCorner)); + } + else + { + couplingRes2.accumulate(lfsu_n.child(contiWEqIdx2), vertInElem2, + -massTransferCoeff*(advectiveFlux - diffusiveFlux) + + (1.-massTransferCoeff)*globalProblem_.localResidual1().residual(vertInElem1)[transportEqIdx1]); + } + } + } + else + { + // compute fluxes explicitly at corner points - only quarter control volume + if (globalProblem_.sdProblem1().isCornerPoint(globalPos1)) + { + const Scalar advectiveFlux = + normalMassFlux * + cParams.elemVolVarsCur1[vertInElem1].massFraction(transportCompIdx1); + const Scalar diffusiveFlux = + bfNormal1 * + boundaryVars1.moleFractionGrad(transportCompIdx1) * + boundaryVars1.diffusionCoeff(transportCompIdx1) * + boundaryVars1.molarDensity() * + FluidSystem::molarMass(transportCompIdx1); + + couplingRes2.accumulate(lfsu_n.child(contiWEqIdx2), vertInElem2, + -(advectiveFlux - diffusiveFlux)); + } + // coupling via the defect + else + { + // the component mass flux from the stokes domain + couplingRes2.accumulate(lfsu_n.child(contiWEqIdx2), vertInElem2, + globalProblem_.localResidual1().residual(vertInElem1)[transportEqIdx1]); + } + } + } + if (cParams.boundaryTypes2.isCouplingOutflow(contiWEqIdx2)) + { + // set residualDarcy[contiLEqIdx2] = X in 2p2clocalresidual.hh + couplingRes2.accumulate(lfsu_n.child(contiWEqIdx2), vertInElem2, + -cParams.elemVolVarsCur1[vertInElem1].massFraction(transportCompIdx1)); + } + //artificial Dirichlet conditions for testing reasons (residual of the subproblem has to be set to zero) + // globalProblem_.sdProblem2().model().localResidual().residualReference(vertInElem1)[massBalanceIdx2] = 0; + // couplingRes2[comp2 + massBalanceIdx2] = cParams.elemVolVarsCur2[vertInElem2].pressure() - 1.001; + } + + /*! + * \brief Evaluation of the coupling from Darcy (2 or n) to Stokes (1 or s). + * + * \param lfsu_s local function space of the Stokes domain + * \param lfsu_n local function space of the Darcy domain + * \param vertInElem1 local vertex index in element1 + * \param vertInElem2 local vertex index in element2 + * \param sdElement1 the element in the Stokes domain + * \param sdElement2 the element in the Darcy domain + * \param boundaryVars1 the boundary variables at the interface of the Stokes domain + * \param boundaryVars2 the boundary variables at the interface of the Darcy domain + * \param cParams a parameter container + * \param couplingRes1 the coupling residual from the Stokes domain + * \param couplingRes2 the coupling residual from the Darcy domain + */ + template<typename LFSU1, typename LFSU2, typename RES1, typename RES2, typename CParams> + void evalCoupling21(const LFSU1& lfsu_s, const LFSU2& lfsu_n, + const int vertInElem1, const int vertInElem2, + const SDElement1& sdElement1, const SDElement2& sdElement2, + const BoundaryVariables1& boundaryVars1, const BoundaryVariables2& boundaryVars2, + const CParams &cParams, + RES1& couplingRes1, RES2& couplingRes2) const + { + const DimVector& globalPos2 = cParams.fvGeometry2.subContVol[vertInElem2].global; + DimVector normalFlux2(0.); + + // velocity*normal*area*rho + for (int phaseIdx=0; phaseIdx<numPhases2; ++phaseIdx) + normalFlux2[phaseIdx] = -boundaryVars2.volumeFlux(phaseIdx)* + cParams.elemVolVarsCur2[vertInElem2].density(phaseIdx); + + //p*n as NEUMANN condition for free flow (set, if B&J defined as NEUMANN condition) + if (cParams.boundaryTypes1.isCouplingOutflow(momentumYIdx1)) + { + //p*A*n as NEUMANN condition for free flow (set, if B&J defined as NEUMANN condition) + //pressure correction in stokeslocalresidual.hh + couplingRes1.accumulate(lfsu_s.child(momentumYIdx1), vertInElem1, + cParams.elemVolVarsCur2[vertInElem2].pressure(nPhaseIdx2) * + boundaryVars2.face().area); + } + if (cParams.boundaryTypes1.isCouplingInflow(momentumYIdx1)) + { + + // v.n as Dirichlet condition for the Stokes domain + // set residualStokes[momentumYIdx1] = vy in stokeslocalresidual.hh + if (globalProblem_.sdProblem2().isCornerPoint(globalPos2)) + { + couplingRes1.accumulate(lfsu_s.child(momentumYIdx1), vertInElem1, + -((normalFlux2[nPhaseIdx2] + normalFlux2[wPhaseIdx2]) + / cParams.elemVolVarsCur1[vertInElem1].density())); + } + else + { + // v.n as DIRICHLET condition for the Stokes domain (negative sign!) + couplingRes1.accumulate(lfsu_s.child(momentumYIdx1), vertInElem1, + globalProblem_.localResidual2().residual(vertInElem2)[massBalanceIdx2] + / cParams.elemVolVarsCur1[vertInElem1].density()); + // TODO: * bfNormal2.two_norm()); + } + } + + //coupling residual is added to "real" residual + //here each node is passed twice, hence only half of the dirichlet condition has to be set + //TODO what to do at boundary nodes which appear only once?(vertInElem1)[transportEqIdx1]; + if (cParams.boundaryTypes1.isCouplingOutflow(transportEqIdx1)) + { + // set residualStokes[transportEqIdx1] = x in stokes2clocalresidual.hh + couplingRes1.accumulate(lfsu_s.child(transportEqIdx1), vertInElem1, + -cParams.elemVolVarsCur2[vertInElem2].massFraction(nPhaseIdx2, wCompIdx2)); + } + // Be careful: this upwind direction won't work for transport in case of two phases + if (cParams.boundaryTypes1.isCouplingInflow(transportEqIdx1)) + { + if (globalProblem_.sdProblem2().isCornerPoint(globalPos2)) + { + const Scalar advectiveFlux = normalFlux2[nPhaseIdx2] * cParams.elemVolVarsCur2[vertInElem2].massFraction(nPhaseIdx2, wCompIdx2) + + normalFlux2[wPhaseIdx2] * cParams.elemVolVarsCur2[vertInElem2].massFraction(nPhaseIdx2, wCompIdx2); + const Scalar diffusiveFlux = boundaryVars2.moleFractionGrad(nPhaseIdx2) // grad X^w_g + * boundaryVars2.face().normal + * boundaryVars2.porousDiffCoeff(nPhaseIdx2) // D^w_g + * boundaryVars2.molarDensity(nPhaseIdx2) + * FluidSystem::molarMass(wCompIdx2); + + + couplingRes1.accumulate(lfsu_s.child(transportEqIdx1), vertInElem1, + -(advectiveFlux - diffusiveFlux)); + } + else + { + couplingRes1.accumulate(lfsu_s.child(transportEqIdx1), vertInElem1, + globalProblem_.localResidual2().residual(vertInElem2)[contiWEqIdx2]); + } + } + + // vy as Dirichlet condition for free flow (set, if B&J defined as Dirichlet condition) + // (residual of the subproblem has to be set to zero) + // globalProblem_.sdProblem1().model().localResidual().residualReference(vertInElem1)[momentumYIdx] = 0; + // Scalar normalVel = 0.0;//cParams.elemVolVarsCur1[vertInElem1].velocity[1]; + // couplingRes1[comp1+momentumYIdx1] = cParams.elemVolVarsCur1[vertInElem1].velocity[1] - normalVel;//1*bfArea1; + } + + protected: + /*! + * \brief allows to choose the approximation of the boundary layer thickness: + * 1) Blasius solution + * 2) and 3) approximations of a turbulent boundary layer + * 9) constant boundary layer thickness, which can be set in the parameter file + */ + template<typename CParams> + const Scalar computeBoundaryLayerThickness(const CParams& cParams, + const DimVector& globalPos, + const int vertexIdx) const + { + if (blModel_ == 1) + { + const Scalar vxmax = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, Scalar, FreeFlow, VxMax); + Scalar reynoldsX = vxmax * globalPos[0] * + cParams.elemVolVarsCur1[vertexIdx].fluidState().density(nPhaseIdx1); + reynoldsX /= cParams.elemVolVarsCur1[vertexIdx].fluidState().viscosity(nPhaseIdx1); + const Scalar boundaryLayerOffset = + GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, Scalar, FreeFlow, BoundaryLayerOffset); + + return 5*(globalPos[0]+boundaryLayerOffset) / sqrt(reynoldsX); + } + if (blModel_ == 2) + { + const Scalar vxmax = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, Scalar, FreeFlow, VxMax); + Scalar reynoldsX = vxmax * globalPos[0] * + cParams.elemVolVarsCur1[vertexIdx].fluidState().density(nPhaseIdx1); + reynoldsX /= cParams.elemVolVarsCur1[vertexIdx].fluidState().viscosity(nPhaseIdx1); + const Scalar boundaryLayerOffset = + GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, Scalar, FreeFlow, BoundaryLayerOffset); + + return 0.37*(globalPos[0]+boundaryLayerOffset) / pow(reynoldsX, 0.2); + } + if (blModel_ == 3) + { + const Scalar vxmax = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, Scalar, FreeFlow, VxMax); + Scalar reynoldsX = vxmax * globalPos[0] * + cParams.elemVolVarsCur1[vertexIdx].fluidState().density(nPhaseIdx1); + reynoldsX /= cParams.elemVolVarsCur1[vertexIdx].fluidState().viscosity(nPhaseIdx1); + const Scalar boundaryLayerOffset = + GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, Scalar, FreeFlow, BoundaryLayerOffset); + + const Scalar cf = 2*pow(0.41*1.5/log(reynoldsX),2); + + return 50*(globalPos[0]+boundaryLayerOffset)/(reynoldsX*sqrt(cf/2)); + } + if (blModel_ == 9) + { + if (ParameterTree::tree().hasKey("FreeFlow.ConstThickness")) + return GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, Scalar, FreeFlow, ConstThickness); + else + std::cerr << "FreeFlow.ConstThickness not defined\n"; + } + else + std::cerr << "invalid boundary layer model\n"; + + return 0; + } + + /*! + * \brief Provides different options for the computations of the mass transfer coefficient: + * 1) exponential law with a saturation as base and a mass transfer coefficient as exponent + * 2) the conventional Schlünder model with one characteristic pore size + * 3) the Schlünder model with a variable characteristic pore size deduced from the + * two-phase relations (Van Genuchten curve) + */ + template<typename CParams> + const Scalar massTransferCoefficient(const CParams &cParams, + const SDElement1& sdElement1, const SDElement2& sdElement2, + const DimVector& globalPos, + const int vertInElem1, const int vertInElem2) const + { + if (massTransferModel_ == 1){ + Scalar exponentMTC = 0; + if (ParameterTree::tree().hasKey("FreeFlow.ExponentMTC")) + exponentMTC = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, Scalar, FreeFlow, ExponentMTC); + else + std::cerr << "FreeFlow.ExponentMTC not defined\n"; + return pow(cParams.elemVolVarsCur2[vertInElem2].saturation(wPhaseIdx2), exponentMTC); + } + // Schlünder model (Schlünder, CES 1988) + if (massTransferModel_ == 2){ + Scalar charPoreRadius = 0; + if (ParameterTree::tree().hasKey("PorousMedium.CharPoreDiameter")) + charPoreRadius = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, Scalar, PorousMedium, CharPoreDiameter); + else + std::cerr << "PorousMedium.PoreDiameter not defined\n"; + + const Scalar blThickness = computeBoundaryLayerThickness(cParams, globalPos, vertInElem1); + const Scalar moistureContent = cParams.elemVolVarsCur2[vertInElem2].saturation(wPhaseIdx2) * + globalProblem_.sdProblem2().spatialParams().porosity(sdElement2, cParams.fvGeometry2, vertInElem2); + + Scalar massTransferCoeff = 1. + 2./M_PI * charPoreRadius/blThickness + * sqrt(M_PI/(4*moistureContent)) * (sqrt(M_PI/(4*moistureContent)) - 1.); + + return 1./massTransferCoeff; + } + // Schlünder model (Schlünder, CES 1988) with variable char. pore diameter depending on Pc + if (massTransferModel_ == 3){ + const Scalar surfaceTension = 72.85e-3; // source: Wikipedia + const Scalar charPoreRadius = 2*surfaceTension/cParams.elemVolVarsCur2[vertInElem2].capillaryPressure(); + + const Scalar blThickness = computeBoundaryLayerThickness(cParams, globalPos, vertInElem1); + const Scalar moistureContent = cParams.elemVolVarsCur2[vertInElem2].saturation(wPhaseIdx2) * + globalProblem_.sdProblem2().spatialParams().porosity(sdElement2, cParams.fvGeometry2, vertInElem2); + + Scalar massTransferCoeff = 1. + 2./M_PI * charPoreRadius/blThickness + * sqrt(M_PI/(4*moistureContent)) * (sqrt(M_PI/(4*moistureContent)) - 1.); + + return 1./massTransferCoeff; + } + // modified Schlünder model + if (massTransferModel_ == 4){ + // const Scalar surfaceTension = 72.85e-3; // source: Wikipedia + // const Scalar charPoreRadius = 2*surfaceTension/cParams.elemVolVarsCur2[vertInElem2].capillaryPressure(); + Scalar charPoreRadius = 0; + if (ParameterTree::tree().hasKey("PorousMedium.CharPoreRadius")) + charPoreRadius = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, Scalar, PorousMedium, CharPoreDiameter); + else + std::cerr << "PorousMedium.CharPoreRadius not defined\n"; + + const Scalar blThickness = computeBoundaryLayerThickness(cParams, globalPos, vertInElem1); + const Scalar moistureContent = cParams.elemVolVarsCur2[vertInElem2].saturation(wPhaseIdx2) * + globalProblem_.sdProblem2().spatialParams().porosity(sdElement2, cParams.fvGeometry2, vertInElem2); + + Scalar massTransferCoeff = 1. + 2./M_PI * charPoreRadius/blThickness + * (1./moistureContent) * (1./moistureContent - 1.); + + return 1./massTransferCoeff; + } + return 1.; + } + + GlobalProblem& globalProblem() const + { return globalProblem_; } + + Implementation *asImp_() + { return static_cast<Implementation *> (this); } + const Implementation *asImp_() const + { return static_cast<const Implementation *> (this); } + + private: + /*! + * \brief A struct that contains data of the FF and PM + * including boundary types, volume variables in both subdomains + * and geometric information + */ + struct CParams + { + BoundaryTypes1 boundaryTypes1; + BoundaryTypes2 boundaryTypes2; + ElementVolumeVariables1 elemVolVarsPrev1; + ElementVolumeVariables1 elemVolVarsCur1; + ElementVolumeVariables2 elemVolVarsPrev2; + ElementVolumeVariables2 elemVolVarsCur2; + FVElementGeometry1 fvGeometry1; + FVElementGeometry2 fvGeometry2; + }; + + unsigned blModel_; + unsigned massTransferModel_; + Scalar exponentMTC_; + + GlobalProblem& globalProblem_; +}; + +} // end namespace Dumux + +#endif // DUMUX_2CSTOKES_2P2C_LOCALOPERATOR_HH diff --git a/dumux/multidomain/2cstokes2p2c/2cstokes2p2cnewtoncontroller.hh b/dumux/multidomain/2cstokes2p2c/2cstokes2p2cnewtoncontroller.hh new file mode 100644 index 0000000000000000000000000000000000000000..612aec729bfcae9e0e8e698e3beabb6e34505e2d --- /dev/null +++ b/dumux/multidomain/2cstokes2p2c/2cstokes2p2cnewtoncontroller.hh @@ -0,0 +1,67 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/***************************************************************************** + * See the file COPYING for full copying permissions. * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see <http://www.gnu.org/licenses/>. * + *****************************************************************************/ +/*! + * \file + * \brief Reference implementation of a Newton controller for coupled 2cStokes - 2p2c problems + */ +#ifndef DUMUX_2CSTOKES_2P2C_NEWTON_CONTROLLER_HH +#define DUMUX_2CSTOKES_2P2C_NEWTON_CONTROLLER_HH + +#include <dumux/multidomain/common/multidomainnewtoncontroller.hh> + +/*! + * \file + */ +namespace Dumux +{ + +/*! + * \brief Implementation of a Newton controller for coupled 2cStokes - 2p2c problems + * + * The Newton controller ensures that the updateStaticData routine is called + * in the porous-medium sub-problem + */ +template <class TypeTag> +class TwoCStokesTwoPTwoCNewtonController : public MultiDomainNewtonController<TypeTag> +{ + typedef MultiDomainNewtonController<TypeTag> ParentType; + + typedef typename GET_PROP_TYPE(TypeTag, Problem) Problem; + typedef typename GET_PROP_TYPE(TypeTag, SolutionVector) SolutionVector; + +public: + TwoCStokesTwoPTwoCNewtonController(const Problem &problem) + : ParentType(problem) + { } + + /*! + * \brief Indicates that one Newton iteration was finished. + */ + void newtonEndStep(SolutionVector &uCurrentIter, SolutionVector &uLastIter) + { + ParentType::newtonEndStep(uCurrentIter, uLastIter); + + this->model_().sdModel2().updateStaticData(this->model_().sdModel2().curSol(), + this->model_().sdModel2().prevSol()); + } +}; + +} // namespace Dumux + +#endif // DUMUX_2CSTOKES_2P2C_NEWTON_CONTROLLER_HH diff --git a/dumux/multidomain/2cstokes2p2c/Makefile.am b/dumux/multidomain/2cstokes2p2c/Makefile.am new file mode 100644 index 0000000000000000000000000000000000000000..1a0b3372869717cfffcf42b5a12dea68609681aa --- /dev/null +++ b/dumux/multidomain/2cstokes2p2c/Makefile.am @@ -0,0 +1,4 @@ +2cstokes2p2cdir = $(includedir)/dumux/multidomain/2cstokes2p2c +2cstokes2p2c_HEADERS = *.hh + +include $(top_srcdir)/am/global-rules diff --git a/dumux/multidomain/Makefile.am b/dumux/multidomain/Makefile.am index f1de35a999ec8752a2b014375be2c176d35212e7..4b6c342f0c47e220af49cb3ddf73c66a44eda26c 100644 --- a/dumux/multidomain/Makefile.am +++ b/dumux/multidomain/Makefile.am @@ -1,4 +1,6 @@ -SUBDIRS = common +SUBDIRS = common \ + 2cstokes2p2c + multidomaindir = $(includedir)/dumux/multidomain include $(top_srcdir)/am/global-rules