diff --git a/test/multidomain/boundary/stokesdarcy/1p_1p/CMakeLists.txt b/test/multidomain/boundary/stokesdarcy/1p_1p/CMakeLists.txt index 9bc324aad612457f366991aaa6da3c965ec4237b..45acb89ecd3ed8e3f2b58c3260ba52309dd9ee7f 100644 --- a/test/multidomain/boundary/stokesdarcy/1p_1p/CMakeLists.txt +++ b/test/multidomain/boundary/stokesdarcy/1p_1p/CMakeLists.txt @@ -1,3 +1,5 @@ +add_subdirectory(convergencetest) + add_input_file_links() add_executable(test_md_boundary_darcy1p_stokes1p EXCLUDE_FROM_ALL main.cc) diff --git a/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/CMakeLists.txt b/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..69e2b6d8ed08292a1949a37457adee6e5b3615c2 --- /dev/null +++ b/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/CMakeLists.txt @@ -0,0 +1,10 @@ +add_input_file_links() +dune_symlink_to_source_files(FILES "convergencetest.py") + +dune_add_test(NAME test_md_boundary_darcy1p_stokes1p_convtest + SOURCES main.cc + LABELS multidomain multidomain_boundary stokesdarcy + TIMEOUT 1000 + CMAKE_GUARD HAVE_UMFPACK + COMMAND ./convergencetest.py + CMD_ARGS test_md_boundary_darcy1p_stokes1p_convtest params.input) diff --git a/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/convergencetest.py b/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/convergencetest.py new file mode 100755 index 0000000000000000000000000000000000000000..6548362670d941409bb996c1ff91cbface471381 --- /dev/null +++ b/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/convergencetest.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python + +from math import * +import subprocess +import sys + +if len(sys.argv) < 2: + sys.stderr.write('Please provide a single argument <testname> to the script\n') + sys.exit(1) + +testname = str(sys.argv[1]) +testargs = [str(i) for i in sys.argv][2:] + +# remove the old log files +subprocess.call(['rm', testname + '_stokes.log']) +print("Removed old log file ({})!".format(testname + '_stokes.log')) +subprocess.call(['rm', testname + '_darcy.log']) +print("Removed old log file ({})!".format(testname + '_darcy.log')) + +# do the runs with different refinement +for i in [0, 1, 2]: + subprocess.call(['./' + testname] + testargs + ['-Grid.Refinement', str(i), + '-Vtk.OutputName', testname]) + +def checkRatesStokes(): + # check the rates and append them to the log file + logfile = open(testname + '_stokes.log', "r+") + + errorP = [] + errorVx = [] + errorVy = [] + for line in logfile: + line = line.strip("\n") + line = line.strip("\[ConvergenceTest\]") + line = line.split() + errorP.append(float(line[2])) + errorVx.append(float(line[5])) + errorVy.append(float(line[8])) + + resultsP = [] + resultsVx = [] + resultsVy = [] + logfile.truncate(0) + logfile.write("n\terrorP\t\trateP\t\terrorVx\t\trateVx\t\terrorVy\t\trateVy\n") + logfile.write("-"*50 + "\n") + for i in range(len(errorP)-1): + if isnan(errorP[i]) or isinf(errorP[i]): + continue + if not ((errorP[i] < 1e-12 or errorP[i+1] < 1e-12) and (errorVx[i] < 1e-12 or errorVx[i+1] < 1e-12) and (errorVy[i] < 1e-12 or errorVy[i+1] < 1e-12)): + rateP = (log(errorP[i])-log(errorP[i+1]))/log(2) + rateVx = (log(errorVx[i])-log(errorVx[i+1]))/log(2) + rateVy = (log(errorVy[i])-log(errorVy[i+1]))/log(2) + message = "{}\t{:0.4e}\t{:0.4e}\t{:0.4e}\t{:0.4e}\t{:0.4e}\t{:0.4e}\n".format(i, errorP[i], rateP, errorVx[i], rateVx, errorVy[i], rateVy) + logfile.write(message) + resultsP.append(rateP) + resultsVx.append(rateVx) + resultsVy.append(rateVy) + else: + logfile.write("error: exact solution!?") + i = len(errorP)-1 + message = "{}\t{:0.4e}\t\t{}\t{:0.4e}\t\t{}\t{:0.4e}\t\t{}\n".format(i, errorP[i], "", errorVx[i], "", errorVy[i], "") + logfile.write(message) + + logfile.close() + print("\nComputed the following convergence rates for {}:\n".format(testname)) + + subprocess.call(['cat', testname + '_stokes.log']) + + return {"p" : resultsP, "v_x" : resultsVx, "v_y" : resultsVy} + +def checkRatesDarcy(): + # check the rates and append them to the log file + logfile = open(testname + '_darcy.log', "r+") + + errorP = [] + for line in logfile: + line = line.strip("\n") + line = line.strip("\[ConvergenceTest\]") + line = line.split() + errorP.append(float(line[2])) + + resultsP = [] + logfile.truncate(0) + logfile.write("n\terrorP\t\trateP\n") + logfile.write("-"*50 + "\n") + for i in range(len(errorP)-1): + if isnan(errorP[i]) or isinf(errorP[i]): + continue + if not ((errorP[i] < 1e-12 or errorP[i+1] < 1e-12)): + rateP = (log(errorP[i])-log(errorP[i+1]))/log(2) + message = "{}\t{:0.4e}\t{:0.4e}\n".format(i, errorP[i], rateP) + logfile.write(message) + resultsP.append(rateP) + else: + logfile.write("error: exact solution!?") + i = len(errorP)-1 + message = "{}\t{:0.4e}\n".format(i, errorP[i], "") + logfile.write(message) + + logfile.close() + print("\nComputed the following convergence rates for {}:\n".format(testname)) + + subprocess.call(['cat', testname + '_darcy.log']) + + return {"p" : resultsP} + +def checkRatesStokesAndDarcy(): + resultsStokes = checkRatesStokes() + resultsDarcy = checkRatesDarcy() + + def mean(numbers): + return float(sum(numbers)) / len(numbers) + + # check the rates, we expect rates around 2 + if mean(resultsStokes["p"]) < 2.05 and mean(resultsStokes["p"]) < 1.84: + sys.stderr.write("*"*70 + "\n" + "The convergence rates for pressure were not close enough to 2! Test failed.\n" + "*"*70 + "\n") + sys.exit(1) + + if mean(resultsStokes["v_x"]) < 2.05 and mean(resultsStokes["v_x"]) < 1.95: + sys.stderr.write("*"*70 + "\n" + "The convergence rates for x-velocity were not close enough to 2! Test failed.\n" + "*"*70 + "\n") + sys.exit(1) + + if mean(resultsStokes["v_y"]) < 2.05 and mean(resultsStokes["v_y"]) < 1.95: + sys.stderr.write("*"*70 + "\n" + "The convergence rates for y-velocity were not close enough to 2! Test failed.\n" + "*"*70 + "\n") + sys.exit(1) + + if mean(resultsDarcy["p"]) < 2.05 and mean(resultsDarcy["p"]) < 1.95: + sys.stderr.write("*"*70 + "\n" + "The convergence rates for pressure were not close enough to 2! Test failed.\n" + "*"*70 + "\n") + sys.exit(1) + + +checkRatesStokesAndDarcy() diff --git a/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/main.cc b/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/main.cc new file mode 100644 index 0000000000000000000000000000000000000000..331fc2cdc2c728113d8c39cc3286c04f7206b596 --- /dev/null +++ b/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/main.cc @@ -0,0 +1,401 @@ +// -*- 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 3 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 + * \ingroup BoundaryTests + * \brief A test problem for the coupled Stokes/Darcy problem (1p). + * The analytical solution is given in Shiue et al., 2018: + * "Convergence of the MAC Scheme for the Stokes/Darcy Coupling Problem" + */ + +#include <config.h> + +#include <ctime> +#include <iostream> + +#include <dune/common/parallel/mpihelper.hh> +#include <dune/common/timer.hh> + +#include <dumux/common/properties.hh> +#include <dumux/common/parameters.hh> +#include <dumux/common/partial.hh> +#include <dumux/common/dumuxmessage.hh> +#include <dumux/linear/seqsolverbackend.hh> +#include <dumux/assembly/fvassembler.hh> +#include <dumux/assembly/diffmethod.hh> +#include <dumux/discretization/method.hh> +#include <dumux/io/vtkoutputmodule.hh> +#include <dumux/io/staggeredvtkoutputmodule.hh> +#include <dumux/io/grid/gridmanager_yasp.hh> + +#include <dumux/multidomain/staggeredtraits.hh> +#include <dumux/multidomain/fvassembler.hh> +#include <dumux/multidomain/newtonsolver.hh> +#include <test/freeflow/navierstokes/l2error.hh> + +#include <dumux/multidomain/boundary/stokesdarcy/couplingmanager.hh> + +#include "problem_darcy.hh" +#include "problem_stokes.hh" + +namespace Dumux { +namespace Properties { + +template<class TypeTag> +struct CouplingManager<TypeTag, TTag::StokesOneP> +{ + using Traits = StaggeredMultiDomainTraits<TypeTag, TypeTag, Properties::TTag::DarcyOneP>; + using type = Dumux::StokesDarcyCouplingManager<Traits>; +}; + +template<class TypeTag> +struct CouplingManager<TypeTag, TTag::DarcyOneP> +{ + using Traits = StaggeredMultiDomainTraits<Properties::TTag::StokesOneP, Properties::TTag::StokesOneP, TypeTag>; + using type = Dumux::StokesDarcyCouplingManager<Traits>; +}; + +} // end namespace Properties +} // end namespace Dumux + +/*! +* \brief Creates analytical solution. +* Returns a tuple of the analytical solution for the pressure, the velocity and the velocity at the faces +* \param problem the problem for which to evaluate the analytical solution +*/ +template<class Scalar, class Problem> +auto createStokesAnalyticalSolution(const Problem& problem) +{ + const auto& gridGeometry = problem.gridGeometry(); + using GridView = typename std::decay_t<decltype(gridGeometry)>::GridView; + + static constexpr auto dim = GridView::dimension; + static constexpr auto dimWorld = GridView::dimensionworld; + + using VelocityVector = Dune::FieldVector<Scalar, dimWorld>; + + std::vector<Scalar> analyticalPressure; + std::vector<VelocityVector> analyticalVelocity; + std::vector<Scalar> analyticalVelocityOnFace; + + analyticalPressure.resize(gridGeometry.numCellCenterDofs()); + analyticalVelocity.resize(gridGeometry.numCellCenterDofs()); + analyticalVelocityOnFace.resize(gridGeometry.numFaceDofs()); + + using Indices = typename Problem::Indices; + for (const auto& element : elements(gridGeometry.gridView())) + { + auto fvGeometry = localView(gridGeometry); + fvGeometry.bindElement(element); + for (auto&& scv : scvs(fvGeometry)) + { + auto ccDofIdx = scv.dofIndex(); + auto ccDofPosition = scv.dofPosition(); + auto analyticalSolutionAtCc = problem.analyticalSolution(ccDofPosition); + + // velocities on faces + for (auto&& scvf : scvfs(fvGeometry)) + { + const auto faceDofIdx = scvf.dofIndex(); + const auto faceDofPosition = scvf.center(); + const auto dirIdx = scvf.directionIndex(); + const auto analyticalSolutionAtFace = problem.analyticalSolution(faceDofPosition); + analyticalVelocityOnFace[faceDofIdx] = analyticalSolutionAtFace[Indices::velocity(dirIdx)]; + } + + analyticalPressure[ccDofIdx] = analyticalSolutionAtCc[Indices::pressureIdx]; + + for (int dirIdx = 0; dirIdx < dim; ++dirIdx) + analyticalVelocity[ccDofIdx][dirIdx] = analyticalSolutionAtCc[Indices::velocity(dirIdx)]; + } + } + + return std::make_tuple(analyticalPressure, analyticalVelocity, analyticalVelocityOnFace); +} + +/*! +* \brief Creates analytical solution. +* Returns a tuple of the analytical solution for the pressure, the velocity and the velocity at the faces +* \param problem the problem for which to evaluate the analytical solution +*/ +template<class Scalar, class Problem> +auto createDarcyAnalyticalSolution(const Problem& problem) +{ + const auto& gridGeometry = problem.gridGeometry(); + using GridView = typename std::decay_t<decltype(gridGeometry)>::GridView; + + static constexpr auto dim = GridView::dimension; + static constexpr auto dimWorld = GridView::dimensionworld; + + using VelocityVector = Dune::FieldVector<Scalar, dimWorld>; + + std::vector<Scalar> analyticalPressure; + std::vector<VelocityVector> analyticalVelocity; + + analyticalPressure.resize(gridGeometry.numDofs()); + analyticalVelocity.resize(gridGeometry.numDofs()); + + for (const auto& element : elements(gridGeometry.gridView())) + { + auto fvGeometry = localView(gridGeometry); + fvGeometry.bindElement(element); + for (auto&& scv : scvs(fvGeometry)) + { + const auto ccDofIdx = scv.dofIndex(); + const auto ccDofPosition = scv.dofPosition(); + const auto analyticalSolutionAtCc = problem.analyticalSolution(ccDofPosition); + analyticalPressure[ccDofIdx] = analyticalSolutionAtCc[dim]; + + for (int dirIdx = 0; dirIdx < dim; ++dirIdx) + analyticalVelocity[ccDofIdx][dirIdx] = analyticalSolutionAtCc[dirIdx]; + } + } + + return std::make_tuple(analyticalPressure, analyticalVelocity); +} + +template<class Problem, class SolutionVector> +void printStokesL2Error(const Problem& problem, const SolutionVector& x) +{ + using namespace Dumux; + using Scalar = double; + using TypeTag = Properties::TTag::StokesOneP; + using Indices = typename GetPropType<TypeTag, Properties::ModelTraits>::Indices; + using ModelTraits = GetPropType<TypeTag, Properties::ModelTraits>; + using PrimaryVariables = GetPropType<TypeTag, Properties::PrimaryVariables>; + + using L2Error = NavierStokesTestL2Error<Scalar, ModelTraits, PrimaryVariables>; + const auto l2error = L2Error::calculateL2Error(problem, x); + const int numCellCenterDofs = problem.gridGeometry().numCellCenterDofs(); + const int numFaceDofs = problem.gridGeometry().numFaceDofs(); + std::ostream tmpOutputObject(std::cout.rdbuf()); // create temporary output with fixed formatting without affecting std::cout + tmpOutputObject << std::setprecision(8) << "** L2 error (abs/rel) for " + << std::setw(6) << numCellCenterDofs << " cc dofs and " << numFaceDofs << " face dofs (total: " << numCellCenterDofs + numFaceDofs << "): " + << std::scientific + << "L2(p) = " << l2error.first[Indices::pressureIdx] << " / " << l2error.second[Indices::pressureIdx] + << " , L2(vx) = " << l2error.first[Indices::velocityXIdx] << " / " << l2error.second[Indices::velocityXIdx] + << " , L2(vy) = " << l2error.first[Indices::velocityYIdx] << " / " << l2error.second[Indices::velocityYIdx] + << std::endl; + + // write the norm into a log file + std::ofstream logFile; + logFile.open(problem.name() + ".log", std::ios::app); + logFile << "[ConvergenceTest] L2(p) = " << l2error.first[Indices::pressureIdx] << " L2(vx) = " << l2error.first[Indices::velocityXIdx] << " L2(vy) = " << l2error.first[Indices::velocityYIdx] << std::endl; + logFile.close(); +} + +template<class Problem, class SolutionVector> +void printDarcyL2Error(const Problem& problem, const SolutionVector& x) +{ + using namespace Dumux; + using Scalar = double; + + Scalar l2error = 0.0; + + for (const auto& element : elements(problem.gridGeometry().gridView())) + { + auto fvGeometry = localView(problem.gridGeometry()); + fvGeometry.bindElement(element); + + for (auto&& scv : scvs(fvGeometry)) + { + const auto dofIdx = scv.dofIndex(); + const Scalar delta = x[dofIdx] - problem.analyticalSolution(scv.center())[2/*pressureIdx*/]; + l2error += scv.volume()*(delta*delta); + } + } + using std::sqrt; + l2error = sqrt(l2error); + + const auto numDofs = problem.gridGeometry().numDofs(); + std::ostream tmp(std::cout.rdbuf()); + tmp << std::setprecision(8) << "** L2 error (abs) for " + << std::setw(6) << numDofs << " cc dofs " + << std::scientific + << "L2 error = " << l2error + << std::endl; + + // write the norm into a log file + std::ofstream logFile; + logFile.open(problem.name() + ".log", std::ios::app); + logFile << "[ConvergenceTest] L2(p) = " << l2error << std::endl; + logFile.close(); +} + +int main(int argc, char** argv) try +{ + using namespace Dumux; + + // initialize MPI, finalize is done automatically on exit + const auto& mpiHelper = Dune::MPIHelper::instance(argc, argv); + + // print dumux start message + if (mpiHelper.rank() == 0) + DumuxMessage::print(/*firstCall=*/true); + + // parse command line arguments and input file + Parameters::init(argc, argv); + + // Define the sub problem type tags + using StokesTypeTag = Properties::TTag::StokesOneP; + using DarcyTypeTag = Properties::TTag::DarcyOneP; + + // try to create a grid (from the given grid file or the input file) + // for both sub-domains + using DarcyGridManager = Dumux::GridManager<GetPropType<DarcyTypeTag, Properties::Grid>>; + DarcyGridManager darcyGridManager; + darcyGridManager.init("Darcy"); // pass parameter group + + using StokesGridManager = Dumux::GridManager<GetPropType<StokesTypeTag, Properties::Grid>>; + StokesGridManager stokesGridManager; + stokesGridManager.init("Stokes"); // pass parameter group + + // we compute on the leaf grid view + const auto& darcyGridView = darcyGridManager.grid().leafGridView(); + const auto& stokesGridView = stokesGridManager.grid().leafGridView(); + + // create the finite volume grid geometry + using StokesGridGeometry = GetPropType<StokesTypeTag, Properties::GridGeometry>; + auto stokesGridGeometry = std::make_shared<StokesGridGeometry>(stokesGridView); + stokesGridGeometry->update(); + using DarcyGridGeometry = GetPropType<DarcyTypeTag, Properties::GridGeometry>; + auto darcyGridGeometry = std::make_shared<DarcyGridGeometry>(darcyGridView); + darcyGridGeometry->update(); + + using Traits = StaggeredMultiDomainTraits<StokesTypeTag, StokesTypeTag, DarcyTypeTag>; + + // the coupling manager + using CouplingManager = StokesDarcyCouplingManager<Traits>; + auto couplingManager = std::make_shared<CouplingManager>(stokesGridGeometry, darcyGridGeometry); + + // the indices + constexpr auto stokesCellCenterIdx = CouplingManager::stokesCellCenterIdx; + constexpr auto stokesFaceIdx = CouplingManager::stokesFaceIdx; + constexpr auto darcyIdx = CouplingManager::darcyIdx; + + // the problem (initial and boundary conditions) + using StokesProblem = GetPropType<StokesTypeTag, Properties::Problem>; + auto stokesProblem = std::make_shared<StokesProblem>(stokesGridGeometry, couplingManager); + using DarcyProblem = GetPropType<DarcyTypeTag, Properties::Problem>; + auto darcyProblem = std::make_shared<DarcyProblem>(darcyGridGeometry, couplingManager); + + // the solution vector + Traits::SolutionVector sol; + sol[stokesCellCenterIdx].resize(stokesGridGeometry->numCellCenterDofs()); + sol[stokesFaceIdx].resize(stokesGridGeometry->numFaceDofs()); + sol[darcyIdx].resize(darcyGridGeometry->numDofs()); + + // get a solution vector storing references to the two Stokes solution vectors + auto stokesSol = partial(sol, stokesFaceIdx, stokesCellCenterIdx); + + couplingManager->init(stokesProblem, darcyProblem, sol); + + // the grid variables + using StokesGridVariables = GetPropType<StokesTypeTag, Properties::GridVariables>; + auto stokesGridVariables = std::make_shared<StokesGridVariables>(stokesProblem, stokesGridGeometry); + stokesGridVariables->init(stokesSol); + using DarcyGridVariables = GetPropType<DarcyTypeTag, Properties::GridVariables>; + auto darcyGridVariables = std::make_shared<DarcyGridVariables>(darcyProblem, darcyGridGeometry); + darcyGridVariables->init(sol[darcyIdx]); + + // intialize the vtk output module + using Scalar = typename Traits::Scalar; + StaggeredVtkOutputModule<StokesGridVariables, decltype(stokesSol)> stokesVtkWriter(*stokesGridVariables, stokesSol, stokesProblem->name()); + GetPropType<StokesTypeTag, Properties::IOFields>::initOutputModule(stokesVtkWriter); + const auto stokesAnalyticalSolution = createStokesAnalyticalSolution<Scalar>(*stokesProblem); + stokesVtkWriter.addField(std::get<0>(stokesAnalyticalSolution), "pressureExact"); + stokesVtkWriter.addField(std::get<1>(stokesAnalyticalSolution), "velocityExact"); + stokesVtkWriter.addFaceField(std::get<2>(stokesAnalyticalSolution), "faceVelocityExact"); + stokesVtkWriter.write(0.0); + + VtkOutputModule<DarcyGridVariables, GetPropType<DarcyTypeTag, Properties::SolutionVector>> darcyVtkWriter(*darcyGridVariables, sol[darcyIdx], darcyProblem->name()); + using DarcyVelocityOutput = GetPropType<DarcyTypeTag, Properties::VelocityOutput>; + darcyVtkWriter.addVelocityOutput(std::make_shared<DarcyVelocityOutput>(*darcyGridVariables)); + GetPropType<DarcyTypeTag, Properties::IOFields>::initOutputModule(darcyVtkWriter); + const auto darcyAnalyticalSolution = createDarcyAnalyticalSolution<Scalar>(*darcyProblem); + darcyVtkWriter.addField(std::get<0>(darcyAnalyticalSolution), "pressureExact"); + darcyVtkWriter.addField(std::get<1>(darcyAnalyticalSolution), "velocityExact"); + darcyVtkWriter.write(0.0); + + // the assembler for a stationary problem + using Assembler = MultiDomainFVAssembler<Traits, CouplingManager, DiffMethod::numeric>; + auto assembler = std::make_shared<Assembler>(std::make_tuple(stokesProblem, stokesProblem, darcyProblem), + std::make_tuple(stokesGridGeometry->faceFVGridGeometryPtr(), + stokesGridGeometry->cellCenterFVGridGeometryPtr(), + darcyGridGeometry), + std::make_tuple(stokesGridVariables->faceGridVariablesPtr(), + stokesGridVariables->cellCenterGridVariablesPtr(), + darcyGridVariables), + couplingManager); + + // the linear solver + using LinearSolver = UMFPackBackend; + auto linearSolver = std::make_shared<LinearSolver>(); + + // the non-linear solver + using NewtonSolver = MultiDomainNewtonSolver<Assembler, LinearSolver, CouplingManager>; + NewtonSolver nonLinearSolver(assembler, linearSolver, couplingManager); + + // solve the non-linear system + nonLinearSolver.solve(sol); + + // write vtk output + stokesVtkWriter.write(1.0); + darcyVtkWriter.write(1.0); + + printStokesL2Error(*stokesProblem, stokesSol); + printDarcyL2Error(*darcyProblem, sol[darcyIdx]); + + //////////////////////////////////////////////////////////// + // finalize, print dumux message to say goodbye + //////////////////////////////////////////////////////////// + + // print dumux end message + if (mpiHelper.rank() == 0) + { + Parameters::print(); + DumuxMessage::print(/*firstCall=*/false); + } + + return 0; +} // end main +catch (Dumux::ParameterException &e) +{ + std::cerr << std::endl << e << " ---> Abort!" << std::endl; + return 1; +} +catch (Dune::DGFException & e) +{ + std::cerr << "DGF exception thrown (" << e << + "). Most likely, the DGF file name is wrong " + "or the DGF file is corrupted, " + "e.g. missing hash at end of file or wrong number (dimensions) of entries." + << " ---> Abort!" << std::endl; + return 2; +} +catch (Dune::Exception &e) +{ + std::cerr << "Dune reported error: " << e << " ---> Abort!" << std::endl; + return 3; +} +catch (...) +{ + std::cerr << "Unknown exception thrown! ---> Abort!" << std::endl; + return 4; +} diff --git a/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/manufactured_solution.py b/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/manufactured_solution.py new file mode 100644 index 0000000000000000000000000000000000000000..e06ece60e357bfee1da173bd577eb9da113dd39e --- /dev/null +++ b/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/manufactured_solution.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +""" +This script determines the source terms needed for analytical solutions for coupled Stokes/Darcy problems. +Given an analytical solution, it evaluates the momentum and mass balance equations and outputs source terms +such that the balance equations are fulfilled. It furthermore calculates the Darcy velocity from a given pressure solution. +""" + +import sympy as sp +import numpy as np +from sympy import * + +# case = "Rybak" +# case = "Shiue_1" +case = "Shiue_2" + +# divergence of a matrix +def div(M): + return np.array([sp.diff(M[0,0],x) + sp.diff(M[0,1],y) , sp.diff(M[1,0],x) + sp.diff(M[1,1],y) ]) + +# gradient of a vector +def grad(v): + return np.array([[sp.diff(v[0],x), sp.diff(v[0],y)], [sp.diff(v[1],x), sp.diff(v[1],y)]]) + +# coordinates +x, y = sp.symbols("x y") + +# analytical solutions for v and p in free-flow domain (see reference given in problems) +def analyticalSolutionStokes(case): + if case == "Rybak": + vFF = np.array([-sp.cos(sp.pi*x)*sp.sin(sp.pi*y), sp.sin(sp.pi*x)*sp.cos(sp.pi*y)]) + pFF = 0.5*y*sp.sin(sp.pi*x) + elif case == "Shiue_1": + vFF = np.array([-1.0/sp.pi * sp.exp(y) * sp.sin(sp.pi*x), (sp.exp(y) - sp.exp(1)) * sp.cos(sp.pi*x)]) + pFF = 2.0*sp.exp(y) * sp.cos(sp.pi*x) + else: # Shiue_2 + vFF = np.array([(y-1.0)*(y-1.0) + x*(y-1.0) + 3.0*x - 1.0, x*(x-1.0) - 0.5*(y-1.0)*(y-1.0) - 3.0*y + 1.0]) + pFF = 2.0*x + y - 1.0 + return [vFF, pFF] + +vFF, pFF = analyticalSolutionStokes(case) + +# individual terms of the Navier-Stokes eq. +vvT = np.outer(vFF, vFF) +gradV = grad(vFF) +gradVT = grad(vFF).T +pI = np.array([[pFF, 0], [0, pFF]]) + +# complete momentum flux and its divergence +#momentumFlux = vvT - (gradV + gradVT) +pI +momentumFlux = - (gradV + gradVT) +pI # only Stokes +divMomentumFlux = div(momentumFlux) + +print("Source terms for case", case) + +# solution for source term +print(" \nStokes:") +print("Source term mass:", sp.diff(vFF[0],x) + sp.diff(vFF[1], y)) +print("Source term momentum x:", divMomentumFlux[0]) +print("Source term momentum y:", divMomentumFlux[1]) + +# analytical solutions for p in Darcy domain (see reference given in problems) +def analyticalSolutionDarcy(case): + if case == "Rybak": + pD = 0.5*y*y*sp.sin(sp.pi*x) + elif case == "Shiue_1": + pD = (sp.exp(y) - y*sp.exp(1)) * sp.cos(sp.pi*x) + else: # Shiue_2 + pD = x*(1.0-x)*(y-1.0) + pow(y-1.0, 3)/3.0 + 2.0*x + 2.0*y + 4.0 + return pD + +pD = analyticalSolutionDarcy(case) + +gradPdK = np.array([sp.diff(pD,x), sp.diff(pD,y)]) +vD = -gradPdK +divVd = sp.diff(vD[0],x) + sp.diff(vD[1],y) + +print("\nDarcy:") +print("Source term mass:", simplify(divVd)) +print("v x:", simplify(vD[0])) +print("v_y", simplify(vD[1])) diff --git a/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/params.input b/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/params.input new file mode 100644 index 0000000000000000000000000000000000000000..01107ccb8560e0ee992c7f0164e0a303f81a32fb --- /dev/null +++ b/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/params.input @@ -0,0 +1,35 @@ +[Darcy.Grid] +UpperRight = 1 1 +Cells = 40 40 + +[Stokes.Grid] +LowerLeft = 0 1 +UpperRight = 1 2 +Cells = 40 40 + +[Stokes.Problem] +Name = stokes +EnableInertiaTerms = false + +[Darcy.Problem] +Name = darcy + +[Darcy.SpatialParams] +Permeability = 1.0 +AlphaBeaversJoseph = 1.0 + +[Vtk] +OutputName = test_md_boundary_stokes1p_darcy1p_convergencetest + +[Problem] +EnableGravity = false + +[Vtk] +AddVelocity = 1 + +[Component] +LiquidDensity = 1.0 +LiquidKinematicViscosity = 1.0 + +[Assembly] +NumericDifference.BaseEpsilon = 1e-4 diff --git a/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/problem_darcy.hh b/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/problem_darcy.hh new file mode 100644 index 0000000000000000000000000000000000000000..2b92475b442861bb05e4420428d38801c8a89655 --- /dev/null +++ b/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/problem_darcy.hh @@ -0,0 +1,360 @@ +// -*- 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 3 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 + * \ingroup BoundaryTests + * \brief The Darcy sub-problem of coupled Stokes-Darcy convergence test + */ + +#ifndef DUMUX_DARCY_SUBPROBLEM_HH +#define DUMUX_DARCY_SUBPROBLEM_HH + +#include <dune/common/fvector.hh> +#include <dune/grid/yaspgrid.hh> + +#include <dumux/discretization/cctpfa.hh> + +#include <dumux/porousmediumflow/1p/model.hh> +#include <dumux/porousmediumflow/problem.hh> + +#include "../spatialparams.hh" + +#include <dumux/material/components/constant.hh> +#include <dumux/material/fluidsystems/1pliquid.hh> + +namespace Dumux { +template <class TypeTag> +class DarcySubProblem; + +namespace Properties { +// Create new type tags +namespace TTag { +struct DarcyOneP { using InheritsFrom = std::tuple<OneP, CCTpfaModel>; }; +} // end namespace TTag + +// Set the problem property +template<class TypeTag> +struct Problem<TypeTag, TTag::DarcyOneP> { using type = Dumux::DarcySubProblem<TypeTag>; }; + +// the fluid system +template<class TypeTag> +struct FluidSystem<TypeTag, TTag::DarcyOneP> +{ + using Scalar = GetPropType<TypeTag, Properties::Scalar>; + using type = FluidSystems::OnePLiquid<Scalar, Dumux::Components::Constant<1, Scalar> > ; +}; + +// Set the grid type +template<class TypeTag> +struct Grid<TypeTag, TTag::DarcyOneP> { using type = Dune::YaspGrid<2>; }; + +template<class TypeTag> +struct SpatialParams<TypeTag, TTag::DarcyOneP> +{ + using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>; + using Scalar = GetPropType<TypeTag, Properties::Scalar>; + using type = OnePSpatialParams<GridGeometry, Scalar>; +}; +} // end namespace Properties + +/*! + * \ingroup BoundaryTests + * \brief The Darcy sub-problem of coupled Stokes-Darcy convergence test + */ +template <class TypeTag> +class DarcySubProblem : public PorousMediumFlowProblem<TypeTag> +{ + using ParentType = PorousMediumFlowProblem<TypeTag>; + using GridView = GetPropType<TypeTag, Properties::GridView>; + using Scalar = GetPropType<TypeTag, Properties::Scalar>; + using PrimaryVariables = GetPropType<TypeTag, Properties::PrimaryVariables>; + using NumEqVector = GetPropType<TypeTag, Properties::NumEqVector>; + using BoundaryTypes = GetPropType<TypeTag, Properties::BoundaryTypes>; + using VolumeVariables = GetPropType<TypeTag, Properties::VolumeVariables>; + using FVElementGeometry = typename GetPropType<TypeTag, Properties::GridGeometry>::LocalView; + using SubControlVolume = typename FVElementGeometry::SubControlVolume; + using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace; + using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>; + using Element = typename GridView::template Codim<0>::Entity; + using GlobalPosition = typename Element::Geometry::GlobalCoordinate; + + using CouplingManager = GetPropType<TypeTag, Properties::CouplingManager>; + + static constexpr auto velocityXIdx = 0; + static constexpr auto velocityYIdx = 1; + static constexpr auto pressureIdx = 2; + + enum class TestCase + { + ShiueExampleOne, ShiueExampleTwo, Rybak + }; + +public: + //! export the Indices + using Indices = typename GetPropType<TypeTag, Properties::ModelTraits>::Indices; + + DarcySubProblem(std::shared_ptr<const GridGeometry> gridGeometry, + std::shared_ptr<CouplingManager> couplingManager) + : ParentType(gridGeometry, "Darcy"), couplingManager_(couplingManager) + { + problemName_ = getParam<std::string>("Vtk.OutputName") + "_" + getParamFromGroup<std::string>(this->paramGroup(), "Problem.Name"); + + const auto testCaseInput = getParamFromGroup<std::string>(this->paramGroup(), "Problem.TestCase", "ShiueExampleTwo"); + if (testCaseInput == "ShiueExampleOne") + testCase_ = TestCase::ShiueExampleOne; + else if (testCaseInput == "ShiueExampleTwo") + testCase_ = TestCase::ShiueExampleTwo; + else if (testCaseInput == "Rybak") + testCase_ = TestCase::Rybak; + else + DUNE_THROW(Dune::InvalidStateException, testCaseInput + " is not a valid test case"); + } + + /*! + * \brief The problem name. + */ + const std::string& name() const + { + return problemName_; + } + + /*! + * \name Problem parameters + */ + // \{ + + /*! + * \brief Returns the temperature within the domain in [K]. + * + */ + Scalar temperature() const + { return 273.15 + 10; } // 10°C + // \} + + /*! + * \name Boundary conditions + */ + // \{ + + /*! + * \brief Specifies which kind of boundary condition should be + * used for which equation on a given boundary control volume. + * + * \param element The element + * \param scvf The boundary sub control volume face + */ + BoundaryTypes boundaryTypes(const Element &element, const SubControlVolumeFace &scvf) const + { + BoundaryTypes values; + + if (couplingManager().isCoupledEntity(CouplingManager::darcyIdx, scvf)) + values.setAllCouplingNeumann(); + else + values.setAllDirichlet(); + + return values; + } + + /*! + * \brief Evaluates the boundary conditions for a Dirichlet control volume. + * + * \param element The element for which the Dirichlet boundary condition is set + * \param scvf The boundary subcontrolvolumeface + * + * For this method, the \a values parameter stores primary variables. + */ + PrimaryVariables dirichlet(const Element &element, const SubControlVolumeFace &scvf) const + { + const auto p = analyticalSolution(scvf.center())[pressureIdx]; + return PrimaryVariables(p); + } + + /*! + * \brief Evaluates the boundary conditions for a Neumann control volume. + * + * \param element The element for which the Neumann boundary condition is set + * \param fvGeometry The fvGeometry + * \param elemVolVars The element volume variables + * \param elemFluxVarsCache Flux variables caches for all faces in stencil + * \param scvf The boundary sub control volume face + * + * For this method, the \a values variable stores primary variables. + */ + template<class ElementVolumeVariables, class ElementFluxVarsCache> + NumEqVector neumann(const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementFluxVarsCache& elemFluxVarsCache, + const SubControlVolumeFace& scvf) const + { + NumEqVector values(0.0); + + if (couplingManager().isCoupledEntity(CouplingManager::darcyIdx, scvf)) + values[Indices::conti0EqIdx] = couplingManager().couplingData().massCouplingCondition(element, fvGeometry, elemVolVars, scvf); + + return values; + } + + // \} + + /*! + * \name Volume terms + */ + // \{ + /*! + * \brief Evaluates the source term for all phases within a given + * sub control volume. + * + * \param element The element for which the source term is set + * \param fvGeometry The fvGeometry + * \param elemVolVars The element volume variables + * \param scv The sub control volume + */ + NumEqVector sourceAtPos(const GlobalPosition& globalPos) const + { + switch (testCase_) + { + case TestCase::ShiueExampleOne: + return rhsShiueEtAlExampleOne_(globalPos); + case TestCase::ShiueExampleTwo: + return rhsShiueEtAlExampleTwo_(globalPos); + case TestCase::Rybak: + return rhsRybak_(globalPos); + default: + DUNE_THROW(Dune::InvalidStateException, "Invalid test case"); + } + } + + // \} + + /*! + * \brief Evaluates the initial value for a control volume. + * + * \param element The element + * + * For this method, the \a priVars parameter stores primary + * variables. + */ + PrimaryVariables initial(const Element &element) const + { + return PrimaryVariables(0.0); + } + + /*! + * \brief Returns the analytical solution of the problem at a given position. + * + * \param globalPos The global position + * \param time The current simulation time + */ + auto analyticalSolution(const GlobalPosition& globalPos) const + { + switch (testCase_) + { + case TestCase::ShiueExampleOne: + return analyticalSolutionShiueEtAlExampleOne_(globalPos); + case TestCase::ShiueExampleTwo: + return analyticalSolutionShiueEtAlExampleTwo_(globalPos); + case TestCase::Rybak: + return analyticalSolutionRybak_(globalPos); + default: + DUNE_THROW(Dune::InvalidStateException, "Invalid test case"); + } + } + + // \} + + //! Get the coupling manager + const CouplingManager& couplingManager() const + { return *couplingManager_; } + +private: + + // see Rybak et al., 2015: "Multirate time integration for coupled saturated/unsaturated porous medium and free flow systems" + Dune::FieldVector<Scalar, 3> analyticalSolutionRybak_(const GlobalPosition& globalPos) const + { + Dune::FieldVector<Scalar, 3> sol(0.0); + const Scalar x = globalPos[0]; + const Scalar y = globalPos[1]; + + using std::exp; using std::sin; using std::cos; + sol[velocityXIdx] = -0.5*M_PI*y*y*cos(M_PI*x); + sol[velocityYIdx] = -1.0*y*sin(M_PI*x); + sol[pressureIdx] = 0.5*y*y*sin(M_PI*x); + return sol; + } + + // see Rybak et al., 2015: "Multirate time integration for coupled saturated/unsaturated porous medium and free flow systems" + NumEqVector rhsRybak_(const GlobalPosition& globalPos) const + { + const Scalar x = globalPos[0]; + const Scalar y = globalPos[1]; + using std::sin; + return NumEqVector((0.5*M_PI*y*M_PI*y - 1)*sin(M_PI*x)); + } + + // see Shiue et al., 2018: "Convergence of the MAC Scheme for the Stokes/Darcy Coupling Problem" + Dune::FieldVector<Scalar, 3> analyticalSolutionShiueEtAlExampleOne_(const GlobalPosition& globalPos) const + { + Dune::FieldVector<Scalar, 3> sol(0.0); + const Scalar x = globalPos[0]; + const Scalar y = globalPos[1]; + + using std::exp; using std::sin; using std::cos; + sol[pressureIdx] = (exp(y) - y*exp(1)) * cos(M_PI*x); + sol[velocityXIdx] = M_PI*(-exp(1)*y + exp(y))*sin(M_PI*x); + sol[velocityYIdx] = (exp(1) - exp(y))*cos(M_PI*x); + + return sol; + } + + // see Shiue et al., 2018: "Convergence of the MAC Scheme for the Stokes/Darcy Coupling Problem" + NumEqVector rhsShiueEtAlExampleOne_(const GlobalPosition& globalPos) const + { + const Scalar x = globalPos[0]; + const Scalar y = globalPos[1]; + using std::exp; using std::sin; using std::cos; + return NumEqVector(cos(M_PI*x) * (M_PI*M_PI*(exp(y) - y*exp(1)) - exp(y))); + } + + // see Shiue et al., 2018: "Convergence of the MAC Scheme for the Stokes/Darcy Coupling Problem" + Dune::FieldVector<Scalar, 3> analyticalSolutionShiueEtAlExampleTwo_(const GlobalPosition& globalPos) const + { + Dune::FieldVector<Scalar, 3> sol(0.0); + const Scalar x = globalPos[0]; + const Scalar y = globalPos[1]; + + sol[pressureIdx] = x*(1.0-x)*(y-1.0) + (y-1.0)*(y-1.0)*(y-1.0)/3.0 + 2.0*x + 2.0*y + 4.0; + sol[velocityXIdx] = x*(y - 1.0) + (x - 1.0)*(y - 1.0) - 2.0; + sol[velocityYIdx] = x*(x - 1.0) - 1.0*(y - 1.0)*(y - 1.0) - 2.0; + + return sol; + } + + // see Shiue et al., 2018: "Convergence of the MAC Scheme for the Stokes/Darcy Coupling Problem" + NumEqVector rhsShiueEtAlExampleTwo_(const GlobalPosition& globalPos) const + { return NumEqVector(0.0); } + + static constexpr Scalar eps_ = 1e-7; + std::shared_ptr<CouplingManager> couplingManager_; + std::string problemName_; + TestCase testCase_; +}; +} // end namespace Dumux + +#endif //DUMUX_DARCY_SUBPROBLEM_HH diff --git a/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/problem_stokes.hh b/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/problem_stokes.hh new file mode 100644 index 0000000000000000000000000000000000000000..3a02f723dd0ba43181d822eda83f124e36c108d4 --- /dev/null +++ b/test/multidomain/boundary/stokesdarcy/1p_1p/convergencetest/problem_stokes.hh @@ -0,0 +1,366 @@ +// -*- 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 3 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 + * \ingroup BoundaryTests + * \brief The Stokes sub-problem of coupled Stokes-Darcy convergence test + */ + +#ifndef DUMUX_STOKES_SUBPROBLEM_HH +#define DUMUX_STOKES_SUBPROBLEM_HH + +#include <dune/common/fvector.hh> +#include <dune/grid/yaspgrid.hh> + +#include <dumux/material/fluidsystems/1pliquid.hh> +#include <dumux/material/components/constant.hh> + +#include <dumux/freeflow/navierstokes/problem.hh> +#include <dumux/discretization/staggered/freeflow/properties.hh> +#include <dumux/freeflow/navierstokes/model.hh> + +namespace Dumux { +template <class TypeTag> +class StokesSubProblem; + +namespace Properties { +// Create new type tags +namespace TTag { +struct StokesOneP { using InheritsFrom = std::tuple<NavierStokes, StaggeredFreeFlowModel>; }; +} // end namespace TTag + +// the fluid system +template<class TypeTag> +struct FluidSystem<TypeTag, TTag::StokesOneP> +{ + using Scalar = GetPropType<TypeTag, Properties::Scalar>; + using type = FluidSystems::OnePLiquid<Scalar, Dumux::Components::Constant<1, Scalar> > ; +}; + +// Set the grid type +template<class TypeTag> +struct Grid<TypeTag, TTag::StokesOneP> { using type = Dune::YaspGrid<2, Dune::EquidistantOffsetCoordinates<GetPropType<TypeTag, Properties::Scalar>, 2> >; }; + +// Set the problem property +template<class TypeTag> +struct Problem<TypeTag, TTag::StokesOneP> { using type = Dumux::StokesSubProblem<TypeTag> ; }; + +template<class TypeTag> +struct EnableGridGeometryCache<TypeTag, TTag::StokesOneP> { static constexpr bool value = true; }; +template<class TypeTag> +struct EnableGridFluxVariablesCache<TypeTag, TTag::StokesOneP> { static constexpr bool value = true; }; +template<class TypeTag> +struct EnableGridVolumeVariablesCache<TypeTag, TTag::StokesOneP> { static constexpr bool value = true; }; +} // end namespace Properties + +/*! + * \ingroup BoundaryTests + * \brief The Stokes sub-problem of coupled Stokes-Darcy convergence test + */ +template <class TypeTag> +class StokesSubProblem : public NavierStokesProblem<TypeTag> +{ + using ParentType = NavierStokesProblem<TypeTag>; + using GridView = GetPropType<TypeTag, Properties::GridView>; + using Scalar = GetPropType<TypeTag, Properties::Scalar>; + using BoundaryTypes = GetPropType<TypeTag, Properties::BoundaryTypes>; + using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>; + using FVElementGeometry = typename GridGeometry::LocalView; + using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace; + using Element = typename GridView::template Codim<0>::Entity; + using GlobalPosition = typename Element::Geometry::GlobalCoordinate; + using PrimaryVariables = GetPropType<TypeTag, Properties::PrimaryVariables>; + using NumEqVector = GetPropType<TypeTag, Properties::NumEqVector>; + using ModelTraits = GetPropType<TypeTag, Properties::ModelTraits>; + + using CouplingManager = GetPropType<TypeTag, Properties::CouplingManager>; + + enum class TestCase + { + ShiueExampleOne, ShiueExampleTwo, Rybak + }; + +public: + //! export the Indices + using Indices = typename ModelTraits::Indices; + + StokesSubProblem(std::shared_ptr<const GridGeometry> gridGeometry, std::shared_ptr<CouplingManager> couplingManager) + : ParentType(gridGeometry, "Stokes"), couplingManager_(couplingManager) + { + problemName_ = getParam<std::string>("Vtk.OutputName") + "_" + getParamFromGroup<std::string>(this->paramGroup(), "Problem.Name"); + + const auto testCaseInput = getParamFromGroup<std::string>(this->paramGroup(), "Problem.TestCase", "ShiueExampleTwo"); + if (testCaseInput == "ShiueExampleOne") + testCase_ = TestCase::ShiueExampleOne; + else if (testCaseInput == "ShiueExampleTwo") + testCase_ = TestCase::ShiueExampleTwo; + else if (testCaseInput == "Rybak") + testCase_ = TestCase::Rybak; + else + DUNE_THROW(Dune::InvalidStateException, testCaseInput + " is not a valid test case"); + } + + /*! + * \brief The problem name. + */ + const std::string& name() const + { + return problemName_; + } + + /*! + * \name Problem parameters + */ + // \{ + + /*! + * \brief Returns the temperature within the domain in [K]. + * + * This problem assumes a temperature of 10 degrees Celsius. + */ + Scalar temperature() const + { return 273.15 + 10; } // 10°C + + /*! + * \brief Returns the sources within the domain. + * + * \param globalPos The global position + */ + NumEqVector sourceAtPos(const GlobalPosition &globalPos) const + { + switch (testCase_) + { + case TestCase::ShiueExampleOne: + return rhsShiueEtAlExampleOne_(globalPos); + case TestCase::ShiueExampleTwo: + return rhsShiueEtAlExampleTwo_(globalPos); + case TestCase::Rybak: + return rhsRybak_(globalPos); + default: + DUNE_THROW(Dune::InvalidStateException, "Invalid test case"); + } + } + + // \} + + /*! + * \name Boundary conditions + */ + // \{ + + /*! + * \brief Specifies which kind of boundary condition should be + * used for which equation on a given boundary segment. + * + * \param element The finite element + * \param scvf The sub control volume face + */ + BoundaryTypes boundaryTypes(const Element& element, + const SubControlVolumeFace& scvf) const + { + BoundaryTypes values; + + values.setDirichlet(Indices::velocityXIdx); + values.setDirichlet(Indices::velocityYIdx); + + if (couplingManager().isCoupledEntity(CouplingManager::stokesIdx, scvf)) + { + values.setCouplingNeumann(Indices::conti0EqIdx); + values.setCouplingNeumann(Indices::momentumYBalanceIdx); + values.setBeaversJoseph(Indices::momentumXBalanceIdx); + } + + return values; + } + + /*! + * \brief Evaluates the boundary conditions for a Dirichlet control volume. + */ + PrimaryVariables dirichletAtPos(const GlobalPosition& globalPos) const + { + return analyticalSolution(globalPos); + } + + /*! + * \brief Evaluates the boundary conditions for a Neumann control volume. + * + * \param element The element for which the Neumann boundary condition is set + * \param fvGeometry The fvGeometry + * \param elemVolVars The element volume variables + * \param elemFaceVars The element face variables + * \param scvf The boundary sub control volume face + */ + template<class ElementVolumeVariables, class ElementFaceVariables> + NumEqVector neumann(const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementFaceVariables& elemFaceVars, + const SubControlVolumeFace& scvf) const + { + NumEqVector values(0.0); + + if(couplingManager().isCoupledEntity(CouplingManager::stokesIdx, scvf)) + { + values[Indices::conti0EqIdx] = couplingManager().couplingData().massCouplingCondition(element, fvGeometry, elemVolVars, elemFaceVars, scvf); + values[Indices::momentumYBalanceIdx] = couplingManager().couplingData().momentumCouplingCondition(element, fvGeometry, elemVolVars, elemFaceVars, scvf); + } + return values; + } + + // \} + + //! Get the coupling manager + const CouplingManager& couplingManager() const + { return *couplingManager_; } + + /*! + * \name Volume terms + */ + // \{ + + /*! + * \brief Evaluates the initial value for a control volume. + * + * \param globalPos The global position + */ + PrimaryVariables initialAtPos(const GlobalPosition& globalPos) const + { + return PrimaryVariables(0.0); + } + + /*! + * \brief Returns the intrinsic permeability of required as input parameter + for the Beavers-Joseph-Saffman boundary condition + */ + Scalar permeability(const Element& element, const SubControlVolumeFace& scvf) const + { + return couplingManager().couplingData().darcyPermeability(element, scvf); + } + + /*! + * \brief Returns the alpha value required as input parameter for the + Beavers-Joseph-Saffman boundary condition. + */ + Scalar alphaBJ(const SubControlVolumeFace& scvf) const + { + return couplingManager().problem(CouplingManager::darcyIdx).spatialParams().beaversJosephCoeffAtPos(scvf.center()); + } + + /*! + * \brief Returns the analytical solution of the problem at a given position. + * + * \param globalPos The global position + * \param time The current simulation time + */ + PrimaryVariables analyticalSolution(const GlobalPosition& globalPos) const + { + switch (testCase_) + { + case TestCase::ShiueExampleOne: + return analyticalSolutionShiueEtAlExampleOne_(globalPos); + case TestCase::ShiueExampleTwo: + return analyticalSolutionShiueEtAlExampleTwo_(globalPos); + case TestCase::Rybak: + return analyticalSolutionRybak_(globalPos); + default: + DUNE_THROW(Dune::InvalidStateException, "Invalid test case"); + } + } + + // \} + +private: + + // see Rybak et al., 2015: "Multirate time integration for coupled saturated/unsaturated porous medium and free flow systems" + PrimaryVariables analyticalSolutionRybak_(const GlobalPosition& globalPos) const + { + PrimaryVariables sol(0.0); + const Scalar x = globalPos[0]; + const Scalar y = globalPos[1]; + + using std::sin; using std::cos; + sol[Indices::velocityXIdx] = -cos(M_PI*x)*sin(M_PI*y); + sol[Indices::velocityYIdx] = sin(M_PI*x)*cos(M_PI*y); + sol[Indices::pressureIdx] = 0.5*y*sin(M_PI*x); + return sol; + } + + // see Rybak et al., 2015: "Multirate time integration for coupled saturated/unsaturated porous medium and free flow systems" + NumEqVector rhsRybak_(const GlobalPosition& globalPos) const + { + const Scalar x = globalPos[0]; + const Scalar y = globalPos[1]; + NumEqVector source(0.0); + using std::sin; using std::cos; + source[Indices::momentumXBalanceIdx] = 0.5*M_PI*y*cos(M_PI*x) - 2*M_PI*M_PI*cos(M_PI*x)*sin(M_PI*y); + source[Indices::momentumYBalanceIdx] = 0.5*sin(M_PI*x) + 2*M_PI*M_PI*sin(M_PI*x)*cos(M_PI*y); + return source; + } + + // see Shiue et al., 2018: "Convergence of the MAC Scheme for the Stokes/Darcy Coupling Problem" + PrimaryVariables analyticalSolutionShiueEtAlExampleOne_(const GlobalPosition& globalPos) const + { + PrimaryVariables sol(0.0); + const Scalar x = globalPos[0]; + const Scalar y = globalPos[1]; + + using std::exp; using std::sin; using std::cos; + sol[Indices::velocityXIdx] = -1/M_PI * exp(y) * sin(M_PI*x); + sol[Indices::velocityYIdx] = (exp(y) - exp(1)) * cos(M_PI*x); + sol[Indices::pressureIdx] = 2*exp(y) * cos(M_PI*x); + return sol; + } + + // see Shiue et al., 2018: "Convergence of the MAC Scheme for the Stokes/Darcy Coupling Problem" + NumEqVector rhsShiueEtAlExampleOne_(const GlobalPosition& globalPos) const + { + const Scalar x = globalPos[0]; + const Scalar y = globalPos[1]; + using std::exp; using std::sin; using std::cos; + NumEqVector source(0.0); + source[Indices::momentumXBalanceIdx] = exp(y)*sin(M_PI*x) * (1/M_PI -3*M_PI); + source[Indices::momentumYBalanceIdx] = cos(M_PI*x) * (M_PI*M_PI*(exp(y)- exp(1)) + exp(y)); + return source; + } + + // see Shiue et al., 2018: "Convergence of the MAC Scheme for the Stokes/Darcy Coupling Problem" + PrimaryVariables analyticalSolutionShiueEtAlExampleTwo_(const GlobalPosition& globalPos) const + { + PrimaryVariables sol(0.0); + const Scalar x = globalPos[0]; + const Scalar y = globalPos[1]; + + sol[Indices::velocityXIdx] = (y-1.0)*(y-1.0) + x*(y-1.0) + 3.0*x - 1.0; + sol[Indices::velocityYIdx] = x*(x-1.0) - 0.5*(y-1.0)*(y-1.0) - 3.0*y + 1.0; + sol[Indices::pressureIdx] = 2.0*x + y - 1.0; + return sol; + } + + // see Shiue et al., 2018: "Convergence of the MAC Scheme for the Stokes/Darcy Coupling Problem" + NumEqVector rhsShiueEtAlExampleTwo_(const GlobalPosition& globalPos) const + { return NumEqVector(0.0); } + + static constexpr Scalar eps_ = 1e-7; + std::string problemName_; + std::shared_ptr<CouplingManager> couplingManager_; + TestCase testCase_; +}; +} // end namespace Dumux + +#endif // DUMUX_STOKES_SUBPROBLEM_HH