diff --git a/doc/handbook/dumux-handbook.bib b/doc/handbook/dumux-handbook.bib index 4bfc36d0e30c0cceb43b464df2b4b2d909ff3b25..6e14fbf946e5d1a411e815d1c485964f6865787a 100644 --- a/doc/handbook/dumux-handbook.bib +++ b/doc/handbook/dumux-handbook.bib @@ -1714,3 +1714,14 @@ url={http://dx.doi.org/10.1007/s11242-015-0599-1} doi = {10.2514/1.36541}, url = {https://doi.org/10.2514/1.36541} } + +@Book{Versteeg2009a, + title = {{An Introduction to Computational Fluid Dynamics}}, + publisher = {Pearson Education}, + year = {2009}, + author = {Versteeg, Henk and Malalasekra, Weeratunge}, + isbn = {978-0-13-127498-3}, + pages = {XII, 503}, + address = {Harlow}, + edition = {2}, +} diff --git a/dumux/freeflow/navierstokes/problem.hh b/dumux/freeflow/navierstokes/problem.hh index c1e9d532414cfee8a603a20fd1f224261d575b50..18f14136f434a88cdfd71b4a8aadc6adfe0d213c 100644 --- a/dumux/freeflow/navierstokes/problem.hh +++ b/dumux/freeflow/navierstokes/problem.hh @@ -76,6 +76,7 @@ class NavierStokesProblem : public NavierStokesParentProblem<TypeTag> using SolutionVector = typename GET_PROP_TYPE(TypeTag, SolutionVector); using PrimaryVariables = typename GET_PROP_TYPE(TypeTag, PrimaryVariables); using FacePrimaryVariables = typename GET_PROP_TYPE(TypeTag, FacePrimaryVariables); + using CellCenterPrimaryVariables = typename GET_PROP_TYPE(TypeTag, CellCenterPrimaryVariables); using Indices = typename GET_PROP_TYPE(TypeTag, ModelTraits)::Indices; enum { @@ -190,6 +191,14 @@ public: const SubControlVolumeFace& localSubFace) const { return FacePrimaryVariables(0.0); } + //! \brief Returns an additional wall function flux for cell-centered quantities (only needed for RANS models) + CellCenterPrimaryVariables wallFunction(const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementFaceVariables& elemFaceVars, + const SubControlVolumeFace& scvf) const + { return CellCenterPrimaryVariables(0.0); } + private: //! Returns the implementation of the problem (i.e. static polymorphism) diff --git a/dumux/freeflow/navierstokes/staggered/localresidual.hh b/dumux/freeflow/navierstokes/staggered/localresidual.hh index 7042b56ff4ac25ba019d9f7312218ef203781752..2a96c338f16d62dd297923b5463f529459faaa4a 100644 --- a/dumux/freeflow/navierstokes/staggered/localresidual.hh +++ b/dumux/freeflow/navierstokes/staggered/localresidual.hh @@ -79,6 +79,9 @@ class NavierStokesResidualImpl<TypeTag, DiscretizationMethod::staggered> using ModelTraits = typename GET_PROP_TYPE(TypeTag, ModelTraits); + static constexpr bool enableEnergyBalance = ModelTraits::enableEnergyBalance(); + static constexpr bool isCompositional = ModelTraits::numComponents() > 1; + public: using EnergyLocalResidual = FreeFlowEnergyLocalResidual<FVGridGeometry, FluxVariables, ModelTraits::enableEnergyBalance(), (ModelTraits::numComponents() > 1)>; @@ -222,6 +225,7 @@ protected: if (scvf.boundary()) { const auto bcTypes = problem.boundaryTypes(element, scvf); + const auto extrusionFactor = elemVolVars[scvf.insideScvIdx()].extrusionFactor(); // treat Dirichlet and outflow BCs FluxVariables fluxVars; @@ -231,19 +235,27 @@ protected: EnergyLocalResidual::heatFlux(boundaryFlux, problem, element, fvGeometry, elemVolVars, elemFaceVars, scvf); // treat Neumann BCs, i.e. overwrite certain fluxes by user-specified values + static constexpr auto numEqCellCenter = CellCenterResidual::dimension; if(bcTypes.hasNeumann()) { - static constexpr auto numEqCellCenter = CellCenterResidual::dimension; for(int eqIdx = 0; eqIdx < numEqCellCenter; ++eqIdx) { if(bcTypes.isNeumann(eqIdx + cellCenterOffset)) { - const auto extrusionFactor = elemVolVars[scvf.insideScvIdx()].extrusionFactor(); boundaryFlux[eqIdx] = problem.neumann(element, fvGeometry, elemVolVars, elemFaceVars, scvf)[eqIdx + cellCenterOffset] - * extrusionFactor * scvf.area(); + * extrusionFactor * scvf.area(); } } } + for(int eqIdx = 0; eqIdx < numEqCellCenter; ++eqIdx) + { + // use a wall function + if(problem.useWallFunction(element, scvf, eqIdx + cellCenterOffset)) + { + boundaryFlux[eqIdx] = problem.wallFunction(element, fvGeometry, elemVolVars, elemFaceVars, scvf)[eqIdx] + * extrusionFactor * scvf.area(); + } + } residual += boundaryFlux; diff --git a/dumux/freeflow/rans/twoeq/kepsilon/problem.hh b/dumux/freeflow/rans/twoeq/kepsilon/problem.hh index 5b703a38888460849c5d9a31ed7b25775080ca77..3f7c59cf577c8ee40a684308d91bb61632416dfc 100644 --- a/dumux/freeflow/rans/twoeq/kepsilon/problem.hh +++ b/dumux/freeflow/rans/twoeq/kepsilon/problem.hh @@ -68,9 +68,20 @@ class KEpsilonProblem : public RANSProblem<TypeTag> using PrimaryVariables = typename GET_PROP_TYPE(TypeTag, PrimaryVariables); using CellCenterPrimaryVariables = typename GET_PROP_TYPE(TypeTag, CellCenterPrimaryVariables); using FacePrimaryVariables = typename GET_PROP_TYPE(TypeTag, FacePrimaryVariables); - using Indices = typename GET_PROP_TYPE(TypeTag, ModelTraits)::Indices; + using FluidSystem = typename GET_PROP_TYPE(TypeTag, FluidSystem); + using ModelTraits = typename GET_PROP_TYPE(TypeTag, ModelTraits); + using Indices = typename ModelTraits::Indices; + + static constexpr bool enableEnergyBalance = ModelTraits::enableEnergyBalance(); + static constexpr bool isCompositional = ModelTraits::numComponents() > 1; + + // account for the offset of the cell center privars within the PrimaryVariables container + static constexpr auto cellCenterOffset = ModelTraits::numEq() - CellCenterPrimaryVariables::dimension; + static_assert(cellCenterOffset == ModelTraits::dim(), "cellCenterOffset must equal dim for staggered NavierStokes"); public: + static constexpr bool useMoles = GET_PROP_VALUE(TypeTag, UseMoles); + //! The constructor sets the gravity, if desired by the user. KEpsilonProblem(std::shared_ptr<const FVGridGeometry> fvGridGeometry) : ParentType(fvGridGeometry) @@ -319,6 +330,119 @@ public: * elemVolVars[scvf.insideScvIdx()].density()); } + //! \brief Returns the flux for non-isothermal and compositional RANS models + template<bool eB = enableEnergyBalance, bool compositional = isCompositional, + typename std::enable_if_t<eB && compositional, int> = 0> + CellCenterPrimaryVariables wallFunction(const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementFaceVariables& elemFaceVars, + const SubControlVolumeFace& scvf) const + { + return wallFunctionComponent(element, fvGeometry, elemVolVars, elemFaceVars, scvf) + + wallFunctionEnergy(element, fvGeometry, elemVolVars, elemFaceVars, scvf); + } + + //! \brief Returns the flux for isothermal and compositional RANS models + template<bool eB = enableEnergyBalance, bool compositional = isCompositional, + typename std::enable_if_t<!eB && compositional, int> = 0> + CellCenterPrimaryVariables wallFunction(const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementFaceVariables& elemFaceVars, + const SubControlVolumeFace& scvf) const + { return wallFunctionComponent(element, fvGeometry, elemVolVars, elemFaceVars, scvf); } + + //! \brief Returns the flux for non-isothermal RANS models + template<bool eB = enableEnergyBalance, bool compositional = isCompositional, + typename std::enable_if_t<eB && !compositional, int> = 0> + CellCenterPrimaryVariables wallFunction(const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementFaceVariables& elemFaceVars, + const SubControlVolumeFace& scvf) const + { return wallFunctionEnergy(element, fvGeometry, elemVolVars, elemFaceVars, scvf); } + + //! \brief Returns the flux for isothermal RANS models + template<bool eB = enableEnergyBalance, bool compositional = isCompositional, + typename std::enable_if_t<!eB && !compositional, int> = 0> + CellCenterPrimaryVariables wallFunction(const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementFaceVariables& elemFaceVars, + const SubControlVolumeFace& scvf) const + { return CellCenterPrimaryVariables(0.0); } + + //! \brief Returns the component wall-function flux + CellCenterPrimaryVariables wallFunctionComponent(const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementFaceVariables& elemFaceVars, + const SubControlVolumeFace& scvf) const + { + auto wallFunctionFlux = CellCenterPrimaryVariables(0.0); + unsigned int elementID = asImp_().fvGridGeometry().elementMapper().index(element); + + // component mass fluxes + for (int compIdx = 0; compIdx < ModelTraits::numComponents(); ++compIdx) + { + if (Indices::replaceCompEqIdx == compIdx) + continue; + + Scalar schmidtNumber = elemVolVars[scvf.insideScvIdx()].kinematicViscosity() + / elemVolVars[scvf.insideScvIdx()].diffusionCoefficient(compIdx); + Scalar massConversionFactor = useMoles ? 1.0 + : FluidSystem::molarMass(compIdx); + wallFunctionFlux[compIdx] += + -1.0 * (asImp_().dirichlet(element, scvf)[Indices::conti0EqIdx + compIdx] + - elemVolVars[scvf.insideScvIdx()].moleFraction(compIdx)) + * elemVolVars[scvf.insideScvIdx()].molarDensity() + * uStarNominal(elementID) + / asImp_().turbulentSchmidtNumber() + / (1. / asImp_().karmanConstant() * log(yPlusNominal(elementID) * 9.793) + + pFunction(schmidtNumber, asImp_().turbulentSchmidtNumber())); + } + + return wallFunctionFlux; + } + + //! \brief Returns the energy wall-function flux + CellCenterPrimaryVariables wallFunctionEnergy(const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementFaceVariables& elemFaceVars, + const SubControlVolumeFace& scvf) const + { + auto wallFunctionFlux = CellCenterPrimaryVariables(0.0); + unsigned int elementID = asImp_().fvGridGeometry().elementMapper().index(element); + // energy fluxes + Scalar prandtlNumber = elemVolVars[scvf.insideScvIdx()].kinematicViscosity() + * elemVolVars[scvf.insideScvIdx()].density() + * elemVolVars[scvf.insideScvIdx()].heatCapacity() + / elemVolVars[scvf.insideScvIdx()].thermalConductivity(); + wallFunctionFlux[Indices::energyBalanceIdx - cellCenterOffset] += + -1.0 * (asImp_().dirichlet(element, scvf)[Indices::temperatureIdx] + - elemVolVars[scvf.insideScvIdx()].temperature()) + * elemVolVars[scvf.insideScvIdx()].density() + * elemVolVars[scvf.insideScvIdx()].heatCapacity() + * uStarNominal(elementID) + / asImp_().turbulentPrandtlNumber() + / (1. / asImp_().karmanConstant() * log(yPlusNominal(elementID) * 9.793) + + pFunction(prandtlNumber, asImp_().turbulentPrandtlNumber())); + + return wallFunctionFlux; + } + + //! \brief Returns the value of the P-function after Jayatilleke \cite Versteeg2009a + const Scalar pFunction(Scalar molecularNumber, Scalar turbulentNumber) const + { + using std::pow; + using std::exp; + return 9.24 + * (pow(molecularNumber / turbulentNumber, 0.75) - 1.0) + * (1.0 + 0.28 * exp(-0.007 * molecularNumber / turbulentNumber)); + } + //! \brief Returns the \$f C_{\mu} \$f constant const Scalar cMu() const {