diff --git a/src/hyteg/gridtransferoperators/P2P1StokesToP2P1StokesProlongation.hpp b/src/hyteg/gridtransferoperators/P2P1StokesToP2P1StokesProlongation.hpp index 80beceafc6a867e2a0dad0dc890006e9dff59b88..b8063ef661119532a7c77d3927ccffa43ea8d848 100644 --- a/src/hyteg/gridtransferoperators/P2P1StokesToP2P1StokesProlongation.hpp +++ b/src/hyteg/gridtransferoperators/P2P1StokesToP2P1StokesProlongation.hpp @@ -24,7 +24,7 @@ #include "hyteg/gridtransferoperators/P1toP1LinearProlongation.hpp" #include "hyteg/gridtransferoperators/P2toP2QuadraticProlongation.hpp" #include "hyteg/gridtransferoperators/ProlongationOperator.hpp" - +#include "hyteg/p2functionspace/P2ProjectNormalOperator.hpp" namespace hyteg { class P2P1StokesToP2P1StokesProlongation : public ProlongationOperator< P2P1TaylorHoodFunction< real_t > > @@ -59,4 +59,44 @@ class P2P1StokesToP2P1StokesProlongation : public ProlongationOperator< P2P1Tayl P2toP2QuadraticProlongation quadraticProlongationOperator_; P1toP1LinearProlongation<> linearProlongationOperator_; }; + +/*************************************************************************** +NOTE: This prolongates the FE function and calls the project function on it + so that the normal components are set to zero on the FreeslipBoundary +***************************************************************************/ +class P2P1StokesToP2P1StokesProlongationWithFreeSlipProjection : public P2P1StokesToP2P1StokesProlongation +{ + public: + P2P1StokesToP2P1StokesProlongationWithFreeSlipProjection( std::shared_ptr< P2P1TaylorHoodFunction< real_t > > temp, + std::shared_ptr< P2ProjectNormalOperator > projection ) + : P2P1StokesToP2P1StokesProlongation() + , temp_( temp ) + , projection_( projection ) + {} + + void prolongate( const P2P1TaylorHoodFunction< real_t >& function, + const uint_t& sourceLevel, + const DoFType& flag ) const override + { + P2P1StokesToP2P1StokesProlongation::prolongate( function, sourceLevel, flag ); + projection_->project( function, sourceLevel + 1, FreeslipBoundary ); + vertexdof::projectMean( function.p(), sourceLevel + 1 ); + } + + // prolongateAndAdd has a different implementation than prolongate and seemingly needs internal projections + // as a quick fix we are using prolongate on a temporary function and add it manually + void prolongateAndAdd( const P2P1TaylorHoodFunction< real_t >& function, + const uint_t& sourceLevel, + const DoFType& flag ) const override + { + temp_->assign( { 1.0 }, { function }, sourceLevel, All ); + prolongate( *temp_, sourceLevel, flag ); + function.assign( { 1.0, 1.0 }, { function, *temp_ }, sourceLevel + 1, flag ); + } + + private: + std::shared_ptr< P2P1TaylorHoodFunction< real_t > > temp_; + std::shared_ptr< P2ProjectNormalOperator > projection_; +}; + } // namespace hyteg diff --git a/src/hyteg/gridtransferoperators/P2P1StokesToP2P1StokesRestriction.hpp b/src/hyteg/gridtransferoperators/P2P1StokesToP2P1StokesRestriction.hpp index 9c20b9239bf58711c7f3760000fdd6852a3ad176..535f95224ec2f76d04e7ff62b7a1ede9c34612ca 100644 --- a/src/hyteg/gridtransferoperators/P2P1StokesToP2P1StokesRestriction.hpp +++ b/src/hyteg/gridtransferoperators/P2P1StokesToP2P1StokesRestriction.hpp @@ -23,7 +23,7 @@ #include "hyteg/gridtransferoperators/P1toP1LinearRestriction.hpp" #include "hyteg/gridtransferoperators/P2toP2QuadraticRestriction.hpp" #include "hyteg/gridtransferoperators/RestrictionOperator.hpp" - +#include "hyteg/p2functionspace/P2ProjectNormalOperator.hpp" namespace hyteg { class P2P1StokesToP2P1StokesRestriction : public RestrictionOperator< P2P1TaylorHoodFunction< real_t > > @@ -61,4 +61,30 @@ class P2P1StokesToP2P1StokesRestriction : public RestrictionOperator< P2P1Taylor bool projectMeanAfterRestriction_; }; + +/*************************************************************************** +NOTE: This restricts the FE function and calls the project function on it + so that the normal components are set to zero on the FreeslipBoundary +***************************************************************************/ +class P2P1StokesToP2P1StokesRestrictionWithFreeSlipProjection : public P2P1StokesToP2P1StokesRestriction +{ + public: + P2P1StokesToP2P1StokesRestrictionWithFreeSlipProjection( std::shared_ptr< P2ProjectNormalOperator > projection ) + : P2P1StokesToP2P1StokesRestriction(false) + , projection_( projection ) + {} + + void restrict( const P2P1TaylorHoodFunction< real_t >& function, + const uint_t& sourceLevel, + const DoFType& flag ) const override + { + P2P1StokesToP2P1StokesRestriction::restrict(function, sourceLevel, flag); + projection_->project( function, sourceLevel-1, FreeslipBoundary ); + vertexdof::projectMean( function.p(), sourceLevel-1 ); + } + + private: + std::shared_ptr< P2ProjectNormalOperator > projection_; +}; + } // namespace hyteg diff --git a/src/hyteg/gridtransferoperators/P2toP2QuadraticVectorProlongation.hpp b/src/hyteg/gridtransferoperators/P2toP2QuadraticVectorProlongation.hpp index 2b03b9706086b56c23063958244425614f690320..1c396b9beb7240b11e7b0e3fbd5118ea49872c76 100644 --- a/src/hyteg/gridtransferoperators/P2toP2QuadraticVectorProlongation.hpp +++ b/src/hyteg/gridtransferoperators/P2toP2QuadraticVectorProlongation.hpp @@ -52,10 +52,14 @@ class P2toP2QuadraticVectorProlongation : public ProlongationOperator< P2VectorF P2toP2QuadraticProlongation quadraticProlongationOperator_; }; -class P2toP2QuadraticVectorProlongationWithProjection : public P2toP2QuadraticVectorProlongation +/*************************************************************************** +NOTE: This prolongates the FE function and calls the project function on it + so that the normal components are set to zero on the FreeslipBoundary +***************************************************************************/ +class P2toP2QuadraticVectorProlongationWithFreeSlipProjection : public P2toP2QuadraticVectorProlongation { public: - P2toP2QuadraticVectorProlongationWithProjection( std::shared_ptr< P2VectorFunction< real_t > > temp, + P2toP2QuadraticVectorProlongationWithFreeSlipProjection( std::shared_ptr< P2VectorFunction< real_t > > temp, std::shared_ptr< P2ProjectNormalOperator > projection ) : P2toP2QuadraticVectorProlongation() , temp_( temp ) diff --git a/src/hyteg/gridtransferoperators/P2toP2QuadraticVectorRestriction.hpp b/src/hyteg/gridtransferoperators/P2toP2QuadraticVectorRestriction.hpp index 4a1081db45b9ff98f0eeba7079f29d6f6612d5de..e2290afaeb2ce80e78a16fb47cbe062e771c3678 100644 --- a/src/hyteg/gridtransferoperators/P2toP2QuadraticVectorRestriction.hpp +++ b/src/hyteg/gridtransferoperators/P2toP2QuadraticVectorRestriction.hpp @@ -46,10 +46,14 @@ class P2toP2QuadraticVectorRestriction : public RestrictionOperator< P2VectorFun P2toP2QuadraticRestriction quadraticRestrictionOperator_; }; -class P2toP2QuadraticVectorRestrictionWithProjection : public P2toP2QuadraticVectorRestriction +/*************************************************************************** +NOTE: This restricts the FE function and calls the project function on it + so that the normal components are set to zero on the FreeslipBoundary +***************************************************************************/ +class P2toP2QuadraticVectorRestrictionWithFreeSlipProjection : public P2toP2QuadraticVectorRestriction { public: - P2toP2QuadraticVectorRestrictionWithProjection( std::shared_ptr< P2ProjectNormalOperator > projection ) + P2toP2QuadraticVectorRestrictionWithFreeSlipProjection( std::shared_ptr< P2ProjectNormalOperator > projection ) : P2toP2QuadraticVectorRestriction() , projection_( projection ) {} diff --git a/src/hyteg/solvers/ChebyshevSmoother.hpp b/src/hyteg/solvers/ChebyshevSmoother.hpp index 1fd3a6c18ba50dcab99377afe85664fed14fcc5b..01797e8c6e6c65abffe76bfb49f3bdf8840f2fef 100644 --- a/src/hyteg/solvers/ChebyshevSmoother.hpp +++ b/src/hyteg/solvers/ChebyshevSmoother.hpp @@ -25,6 +25,7 @@ #include "hyteg/operators/Operator.hpp" #include "hyteg/primitivestorage/PrimitiveStorage.hpp" #include "hyteg/solvers/Smoothables.hpp" +#include "hyteg/p2functionspace/P2ProjectNormalOperator.hpp" #include "Solver.hpp" @@ -41,14 +42,14 @@ class ChebyshevSmoother : public Solver< OperatorType > : coefficients{} , tmp1_( "cheb_tmp1", storage, minLevel, maxLevel ) , tmp2_( "cheb_tmp2", storage, minLevel, maxLevel ) - , flag_( Inner | NeumannBoundary ) + , flag_( Inner | NeumannBoundary | FreeslipBoundary ) {} ChebyshevSmoother( const FunctionType& tmp1, const FunctionType& tmp2 ) : coefficients{} , tmp1_( tmp1 ) , tmp2_( tmp2 ) - , flag_( Inner | NeumannBoundary ) + , flag_( Inner | NeumannBoundary | FreeslipBoundary ) {} /// Executes an iteration step of the smoother. @@ -196,13 +197,95 @@ class ChebyshevSmoother : public Solver< OperatorType > } } - private: + protected: std::vector< real_t > coefficients; FunctionType tmp1_; FunctionType tmp2_; DoFType flag_; }; +/*************************************************************************** +NOTE: This is similar to the Chebyshev smoother except that projection is + applied to set normal components to zero at the FreeslipBoundary at + every step of working +***************************************************************************/ +template < typename OperatorType > +class ChebyshevSmootherWithFreeSlipProjection : public ChebyshevSmoother< OperatorType > +{ + public: + using FunctionType = typename OperatorType::srcType; + using ChebyshevSmoother< OperatorType >::tmp1_; + using ChebyshevSmoother< OperatorType >::tmp2_; + using ChebyshevSmoother< OperatorType >::flag_; + using ChebyshevSmoother< OperatorType >::coefficients; + + ChebyshevSmootherWithFreeSlipProjection( const std::shared_ptr< PrimitiveStorage >& storage, + size_t minLevel, + size_t maxLevel, + std::shared_ptr< P2ProjectNormalOperator > projection ) + : ChebyshevSmoother< OperatorType >( storage, minLevel, maxLevel ) + , projection_( projection ) + {} + + /// Executes an iteration step of the smoother. + void solve( const OperatorType& A, const FunctionType& x, const FunctionType& b, const uint_t level ) override + { + std::shared_ptr< typename OperatorType::srcType > inverseDiagonalValues = nullptr; + + if ( const auto* A_with_inv_diag = + dynamic_cast< const OperatorWithInverseDiagonal< typename OperatorType::srcType >* >( &(A.viscousOperator) ) ) + { + inverseDiagonalValues = A_with_inv_diag->getInverseDiagonalValues(); + } + else + { + throw std::runtime_error( "The Chebyshev-Smoother requires the OperatorWithInverseDiagonal interface." ); + } + + tmp1_.copyBoundaryConditionFromFunction( x ); + tmp2_.copyBoundaryConditionFromFunction( x ); + + WALBERLA_DEBUG_SECTION() + { + WALBERLA_ASSERT( coefficients.size() > 0, "coefficients must be setup" ); + WALBERLA_ASSERT_NOT_NULLPTR( inverseDiagonalValues, "diagonal not initialized" ); + + const real_t localNormSqr = inverseDiagonalValues->dotLocal( *inverseDiagonalValues, level, flag_ ); + WALBERLA_UNUSED( localNormSqr ); + WALBERLA_ASSERT_GREATER( localNormSqr, 0.0, "diagonal not set" ); + } + + // tmp1_ := Ax + A.apply( x, tmp1_, level, flag_ ); + // tmp1_ := b-Ax + tmp1_.assign( { real_t( 1. ), real_t( -1. ) }, { b, tmp1_ }, level, flag_ ); + // tmp1_ := D^{-1} (b-Ax) + tmp1_.multElementwise( { *inverseDiagonalValues, tmp1_ }, level, flag_ ); + projection_->project( tmp1_, level, FreeslipBoundary ); + // x := x + omega_0 D^{-1} (b-Ax) + x.assign( { 1., coefficients[0] }, { x, tmp1_ }, level, flag_ ); + + // Loop preconditions before the kth iteration: + // 1.) x := x + sum_{i<k} omega_i (D^{-1}A)^{i} D^{-1} (b-Ax) + // 2.) tmp1_ := (D^{-1}A)^{k-1} D^{-1} (b-Ax) + for ( uint_t k = 1; k < coefficients.size(); k += 1 ) + { + // tmp2_ := A (D^{-1}A)^{k-1} D^{-1} (b-Ax) + A.apply( tmp1_, tmp2_, level, flag_ ); + // tmp1_ := (D^{-1}A)^{k} D^{-1} (b-Ax) + tmp1_.multElementwise( { *inverseDiagonalValues, tmp2_ }, level, flag_ ); + projection_->project( tmp1_, level, FreeslipBoundary ); + // x := x + sum_{i<k+1} omega_i (D^{-1}A)^{i} D^{-1} (b-Ax) + x.assign( { 1, coefficients[k] }, { x, tmp1_ }, level, flag_ ); + } + + projection_->project( tmp1_, level, FreeslipBoundary ); + } + + private: + std::shared_ptr< P2ProjectNormalOperator > projection_; +}; + /// Namespace for utility functions of the Chebyshev-Smoother namespace chebyshev { diff --git a/src/hyteg/solvers/solvertemplates/StokesFSGMGSolverTemplate.hpp b/src/hyteg/solvers/solvertemplates/StokesFSGMGSolverTemplate.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7d974e37eadcddc95e39c51056ef944496cfeb4e --- /dev/null +++ b/src/hyteg/solvers/solvertemplates/StokesFSGMGSolverTemplate.hpp @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2024 Andreas Burkhart, Ponsuganth Ilangovan P. + * + * This file is part of HyTeG + * (see https://i10git.cs.fau.de/hyteg/hyteg). + * + * 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/>. + */ + +#pragma once + +#include "hyteg/gridtransferoperators/P2P1StokesToP2P1StokesProlongation.hpp" +#include "hyteg/gridtransferoperators/P2P1StokesToP2P1StokesRestriction.hpp" +#include "hyteg/gridtransferoperators/P2toP2QuadraticVectorProlongation.hpp" +#include "hyteg/gridtransferoperators/P2toP2QuadraticVectorRestriction.hpp" +#include "hyteg/solvers/CGSolver.hpp" +#include "hyteg/solvers/ChebyshevSmoother.hpp" +#include "hyteg/solvers/FGMRESSolver.hpp" +#include "hyteg/solvers/GeometricMultigridSolver.hpp" +#include "hyteg/solvers/Solver.hpp" +#include "hyteg/solvers/preconditioners/stokes/StokesBlockPreconditioners.hpp" + +namespace hyteg { +namespace solvertemplates { + +// clang-format off + +enum class StokesGMGFSSolverParamKey +{ + NUM_POWER_ITERATIONS_SPECTRUM, + FGMRES_UZAWA_PRECONDITIONED_OUTER_ITER, + FGMRES_UZAWA_PRECONDITIONED_OUTER_TOLERANCE, + + INEXACT_UZAWA_VELOCITY_ITER, + INEXACT_UZAWA_OMEGA, + + ABLOCK_CG_SOLVER_MG_PRECONDITIONED_ITER, + ABLOCK_CG_SOLVER_MG_PRECONDITIONED_TOLERANCE, + ABLOCK_MG_PRESMOOTH, + ABLOCK_MG_POSTSMOOTH, + ABLOCK_COARSE_ITER, + ABLOCK_COARSE_TOLERANCE, + + SCHUR_CG_SOLVER_MG_PRECONDITIONED_ITER, + SCHUR_CG_SOLVER_MG_PRECONDITIONED_TOLERANCE, + SCHUR_MG_PRESMOOTH, + SCHUR_MG_POSTSMOOTH, + SCHUR_COARSE_GRID_CG_ITER, + SCHUR_COARSE_GRID_CG_TOLERANCE +}; + +// clang-format on + +// Things are still restricted to P2-P1 space +template < typename StokesOperatorType, typename ProjectionType > +inline std::shared_ptr< Solver< StokesOperatorType > > + stokesGMGFSSolver( const std::shared_ptr< PrimitiveStorage >& storage, + const uint_t& minLevel, + const uint_t& maxLevel, + const std::shared_ptr< StokesOperatorType >& stokesOperatorFSSelf, + const std::shared_ptr< ProjectionType >& projectionOperator, + BoundaryCondition bcVelocity, + bool estimateUzawaOmegaValue = false, + bool verbose = false, + std::map< StokesGMGFSSolverParamKey, real_t > extraParams = {} ) +{ + std::map< StokesGMGFSSolverParamKey, real_t > defaultParams = { + { StokesGMGFSSolverParamKey::NUM_POWER_ITERATIONS_SPECTRUM, 25.0 }, + { StokesGMGFSSolverParamKey::FGMRES_UZAWA_PRECONDITIONED_OUTER_ITER, 5.0 }, + { StokesGMGFSSolverParamKey::FGMRES_UZAWA_PRECONDITIONED_OUTER_TOLERANCE, 1e-6 }, + { StokesGMGFSSolverParamKey::INEXACT_UZAWA_VELOCITY_ITER, 1.0 }, + { StokesGMGFSSolverParamKey::INEXACT_UZAWA_OMEGA, 1.0 }, + { StokesGMGFSSolverParamKey::ABLOCK_CG_SOLVER_MG_PRECONDITIONED_ITER, 3 }, + { StokesGMGFSSolverParamKey::ABLOCK_CG_SOLVER_MG_PRECONDITIONED_TOLERANCE, 1e-6 }, + { StokesGMGFSSolverParamKey::ABLOCK_MG_PRESMOOTH, 3 }, + { StokesGMGFSSolverParamKey::ABLOCK_MG_POSTSMOOTH, 3 }, + { StokesGMGFSSolverParamKey::ABLOCK_COARSE_ITER, 10 }, + { StokesGMGFSSolverParamKey::ABLOCK_COARSE_TOLERANCE, 1e-8 }, + { StokesGMGFSSolverParamKey::SCHUR_CG_SOLVER_MG_PRECONDITIONED_ITER, 1 }, + { StokesGMGFSSolverParamKey::SCHUR_CG_SOLVER_MG_PRECONDITIONED_TOLERANCE, 1e-6 }, + { StokesGMGFSSolverParamKey::SCHUR_MG_PRESMOOTH, 3 }, + { StokesGMGFSSolverParamKey::SCHUR_MG_POSTSMOOTH, 3 }, + { StokesGMGFSSolverParamKey::SCHUR_COARSE_GRID_CG_ITER, 1 }, + { StokesGMGFSSolverParamKey::SCHUR_COARSE_GRID_CG_TOLERANCE, 1e-6 }, + }; + + for (auto const& param : extraParams) + { + if(defaultParams.find(param.first) == defaultParams.end()) + { + WALBERLA_ABORT("Passed parameter key not found"); + } + else + { + defaultParams[param.first] = real_c(param.second); + } + } + + uint_t numPowerIteration = uint_c(defaultParams[StokesGMGFSSolverParamKey::NUM_POWER_ITERATIONS_SPECTRUM]); + + uint_t fGMRESOuterIter = uint_c(defaultParams[StokesGMGFSSolverParamKey::FGMRES_UZAWA_PRECONDITIONED_OUTER_ITER]); + real_t fGMRESTol = real_c(defaultParams[StokesGMGFSSolverParamKey::FGMRES_UZAWA_PRECONDITIONED_OUTER_TOLERANCE]); + + uint_t uzawaVelocityIter = uint_c(defaultParams[StokesGMGFSSolverParamKey::INEXACT_UZAWA_VELOCITY_ITER]); + real_t uzawaSmootherOmega = real_c(defaultParams[StokesGMGFSSolverParamKey::INEXACT_UZAWA_OMEGA]); + + uint_t ABlockCGOuterIter = uint_c(defaultParams[StokesGMGFSSolverParamKey::ABLOCK_CG_SOLVER_MG_PRECONDITIONED_ITER]); + real_t ABlockCGOuterTol = real_c(defaultParams[StokesGMGFSSolverParamKey::ABLOCK_CG_SOLVER_MG_PRECONDITIONED_TOLERANCE]); + + uint_t ABlockCGPreSmooth = uint_c(defaultParams[StokesGMGFSSolverParamKey::ABLOCK_MG_PRESMOOTH]); + uint_t ABlockCGPostSmooth = uint_c(defaultParams[StokesGMGFSSolverParamKey::ABLOCK_MG_POSTSMOOTH]); + + uint_t ABlockCGCoarseIter = uint_c(defaultParams[StokesGMGFSSolverParamKey::ABLOCK_COARSE_ITER]); + real_t ABlockCGCoarseTol = real_c(defaultParams[StokesGMGFSSolverParamKey::ABLOCK_COARSE_TOLERANCE]); + + uint_t SchurCGOuterIter = uint_c(defaultParams[StokesGMGFSSolverParamKey::SCHUR_CG_SOLVER_MG_PRECONDITIONED_ITER]); + real_t SchurCGOuterTol = real_c(defaultParams[StokesGMGFSSolverParamKey::SCHUR_CG_SOLVER_MG_PRECONDITIONED_TOLERANCE]); + + uint_t SchurCGPreSmooth = uint_c(defaultParams[StokesGMGFSSolverParamKey::SCHUR_MG_PRESMOOTH]); + uint_t SchurCGPostSmooth = uint_c(defaultParams[StokesGMGFSSolverParamKey::SCHUR_MG_POSTSMOOTH]); + + uint_t SchurCGCoarseIter = uint_c(defaultParams[StokesGMGFSSolverParamKey::SCHUR_COARSE_GRID_CG_ITER]); + real_t SchurCGCoarseTol = real_c(defaultParams[StokesGMGFSSolverParamKey::SCHUR_COARSE_GRID_CG_TOLERANCE]); + + auto uTmp = std::make_shared< P2P1TaylorHoodFunction< real_t > >( + "uTmp_stokesGMGFSSolver_solvertemplate", storage, minLevel, maxLevel, bcVelocity ); + + auto tempFct = std::make_shared< P2VectorFunction< real_t > >( + "tempFct_stokesGMGFSSolver_solvertemplate", storage, minLevel, maxLevel, bcVelocity ); + + auto uSpec = std::make_shared< P2P1TaylorHoodFunction< real_t > >( + "uSpec_stokesGMGFSSolver_solvertemplate", storage, minLevel, maxLevel, bcVelocity ); + + auto uTmpSpec = std::make_shared< P2P1TaylorHoodFunction< real_t > >( + "uTmpSpec_stokesGMGFSSolver_solvertemplate", storage, minLevel, maxLevel, bcVelocity ); + + auto prolongationOperator = std::make_shared< P2P1StokesToP2P1StokesProlongationWithFreeSlipProjection >( uTmp, projectionOperator ); + auto restrictionOperator = std::make_shared< P2P1StokesToP2P1StokesRestrictionWithFreeSlipProjection >( projectionOperator ); + + // Multigridsolver for A + typedef typename StokesOperatorType::ViscousOperatorFS_T SubstAType; + typedef StokesOperatorType StokesOperatorFS; + + auto APrecOperator = stokesOperatorFSSelf->getA(); + + APrecOperator.computeInverseDiagonalOperatorValues(); + + auto ABlockProlongationOperator = + std::make_shared< P2toP2QuadraticVectorProlongationWithFreeSlipProjection >( tempFct, projectionOperator ); + auto ABlockRestrictionOperator = std::make_shared< P2toP2QuadraticVectorRestrictionWithFreeSlipProjection >( projectionOperator ); + + // TODO: TO CHECK THIS! + // auto Jacobi = std::make_shared< WeightedJacobiSmoother< SubstAType > >( storage, minLevel, maxLevel, 2.0 / 3.0 ); + + // auto ABlockCoarseGridSolver = std::make_shared< PETScLUSolver< SubstAType > >( storage, minLevel ); + + auto ABlockCoarseGridSolver = std::make_shared< hyteg::CGSolver< SubstAType > >( storage, minLevel, maxLevel, 10, 1e-8 ); + + // auto ABlockCoarseGridSolver = + // std::make_shared< hyteg::MinResSolver< SubstAType > >( storage, minLevel, maxLevel, 100, 1e-8 ); + ABlockCoarseGridSolver->setPrintInfo( verbose ); + // ABlockCoarseGridSolver->setSolverName("ABlockCoarseGridSolver"); + + auto ABlockSmoother = + std::make_shared< ChebyshevSmootherWithFreeSlipProjection< SubstAType > >( storage, minLevel, maxLevel, projectionOperator ); + + std::function< real_t( const Point3D& ) > randFuncA = []( const Point3D& ) { + return walberla::math::realRandom( real_c( -1 ), real_c( 1 ) ); + }; + + WALBERLA_LOG_INFO_ON_ROOT( "Estimate spectral radius!" ); + // avoid that the startpoint of our poweriteration is in the kernel of the operator + uSpec->uvw().interpolate( randFuncA, maxLevel, All ); + + auto spectralRadiusA = chebyshev::estimateRadius( + APrecOperator.viscousOperator, maxLevel, numPowerIteration, storage, uSpec->uvw(), uTmpSpec->uvw() ); + uSpec->uvw().interpolate( 0, maxLevel, All ); + uTmpSpec->interpolate( 0, maxLevel, All ); + + WALBERLA_LOG_INFO_ON_ROOT( "Estimated spectral radius: " << spectralRadiusA ); + + ABlockSmoother->setupCoefficients( 3, spectralRadiusA ); + + auto ABlockMultigridSolver = std::make_shared< GeometricMultigridSolver< SubstAType > >( storage, + ABlockSmoother, + ABlockCoarseGridSolver, + ABlockRestrictionOperator, + ABlockProlongationOperator, + minLevel, + maxLevel, + 3, + 3, + 0, + CycleType::VCYCLE ); + + auto ABlockSolver = + std::make_shared< hyteg::CGSolver< SubstAType > >( storage, minLevel, maxLevel, 3, 1e-6, ABlockMultigridSolver ); + ABlockSolver->setPrintInfo( verbose ); + // ABlockSolver->setSolverName("ABlockSolverOuter"); + + // estimate sigma + real_t estimatedSigma = uzawaSmootherOmega; + if ( estimateUzawaOmegaValue ) + { + estimatedSigma = estimateUzawaSigma< StokesOperatorFS, SubstAType >( storage, + minLevel, + maxLevel, + *stokesOperatorFSSelf, + ABlockSolver, + 5, + 1, + bcVelocity, + bcVelocity, + bcVelocity, + 42, + projectionOperator ); + } + + WALBERLA_LOG_INFO_ON_ROOT( "Estimated sigma: " << estimatedSigma ); + + // Multigridsolver for Schur + typedef typename StokesOperatorFS::SchurOperator_T SubstSType; + + auto SchurProlongationOperator = std::make_shared< P1toP1LinearProlongation< real_t > >(); + auto SchurRestrictionOperator = std::make_shared< P1toP1LinearRestriction< real_t > >(); + + auto SchurCoarseGridSolver = std::make_shared< hyteg::CGSolver< SubstSType > >( storage, minLevel, maxLevel, 1, 1e-6 ); + SchurCoarseGridSolver->setPrintInfo( verbose ); + // SchurCoarseGridSolver->setSolverName("SchurCoarseGridSolver"); + + auto SchurSmoother = std::make_shared< ChebyshevSmoother< SubstSType > >( storage, minLevel, maxLevel ); + + std::function< real_t( const Point3D& ) > randFuncS = []( const Point3D& ) { + return walberla::math::realRandom( real_c( -1 ), real_c( 1 ) ); + }; + + // avoid that the startpoint of our poweriteration is in the kernel of the operator + uSpec->p().interpolate( randFuncS, maxLevel, All ); + auto spectralRadiusSchur = chebyshev::estimateRadius( + stokesOperatorFSSelf->getSchur(), maxLevel, numPowerIteration, storage, uSpec->p(), uTmpSpec->p() ); + uSpec->p().interpolate( 0, maxLevel, All ); + uTmpSpec->interpolate( 0, maxLevel, All ); + + WALBERLA_LOG_INFO_ON_ROOT( "Estimated spectral radius Schur: " << spectralRadiusSchur ); + + SchurSmoother->setupCoefficients( 2, spectralRadiusSchur ); + + auto SchurMultigridSolver_ = std::make_shared< GeometricMultigridSolver< SubstSType > >( storage, + SchurSmoother, + SchurCoarseGridSolver, + SchurRestrictionOperator, + SchurProlongationOperator, + minLevel, + maxLevel, + 3, + 3, + 0, + CycleType::VCYCLE ); + + auto SchurSolver = std::make_shared< CGSolver< SubstSType > >( storage, minLevel, maxLevel, 10, 1e-8, SchurMultigridSolver_ ); + SchurSolver->setPrintInfo( verbose ); + // SchurSolver->setSolverName("SchurSolver"); + + real_t estimatedOmega = uzawaSmootherOmega; + if ( estimateUzawaOmegaValue ) + { + ABlockCoarseGridSolver->setPrintInfo( verbose ); + estimatedOmega = estimateUzawaOmega< StokesOperatorFS, SubstAType, SubstSType >( storage, + minLevel, + maxLevel, + *stokesOperatorFSSelf, + stokesOperatorFSSelf->getSchur(), + ABlockCoarseGridSolver, + SchurSolver, + 15, + 1, + bcVelocity, + bcVelocity, + bcVelocity, + 42, + projectionOperator ); + ABlockCoarseGridSolver->setPrintInfo( false ); + } + + WALBERLA_LOG_INFO_ON_ROOT( "Estimated omega: " << estimatedOmega ); + + WALBERLA_LOG_INFO_ON_ROOT( "Sigma: " << estimatedSigma << " ; " + << "Omega: " << estimatedOmega ); + + auto uzawaSmoother = std::make_shared< InexactUzawaPreconditioner< StokesOperatorFS, SubstAType, SubstSType > >( + storage, + minLevel, + maxLevel, + stokesOperatorFSSelf->getSchur(), + ABlockSolver, + SchurSolver, + estimatedSigma, + estimatedOmega, + uzawaVelocityIter, + projectionOperator ); + + auto finalStokesSolver = std::make_shared< FGMRESSolver< StokesOperatorFS > >( + storage, minLevel, maxLevel, fGMRESOuterIter, 50, fGMRESTol, fGMRESTol, 0, uzawaSmoother ); + finalStokesSolver->setPrintInfo( true ); + + return finalStokesSolver; +} + +} // namespace solvertemplates +} // namespace hyteg \ No newline at end of file diff --git a/src/hyteg_operators b/src/hyteg_operators index fef1be8fc53894338764200ec277c3cb3f34b873..382dabbf1b7d13789fda1f688ec07ef02b380857 160000 --- a/src/hyteg_operators +++ b/src/hyteg_operators @@ -1 +1 @@ -Subproject commit fef1be8fc53894338764200ec277c3cb3f34b873 +Subproject commit 382dabbf1b7d13789fda1f688ec07ef02b380857 diff --git a/src/terraneo/operators/P2P1StokesOperatorWithProjection.hpp b/src/terraneo/operators/P2P1StokesOperatorWithProjection.hpp new file mode 100644 index 0000000000000000000000000000000000000000..091ce7e05fb2b32170ffb1c560be0b1bc4b7bb01 --- /dev/null +++ b/src/terraneo/operators/P2P1StokesOperatorWithProjection.hpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024 Ponsuganth Ilangovan P, Andreas Burkhart. + * + * This file is part of HyTeG + * (see https://i10git.cs.fau.de/hyteg/hyteg). + * + * 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/>. + */ + +#pragma once + +#include "hyteg/p2functionspace/P2ProjectNormalOperator.hpp" +#include "hyteg_operators/operators/k_mass/P1ElementwiseKMassIcosahedralShellMap.hpp" +#include "hyteg_operators_composites/stokes/P2P1StokesFullOperator.hpp" +#include "terraneo/operators/P2StokesABlockWithProjection.hpp" + +namespace hyteg { + +/*************************************************************************************************** +NOTE: Here FS denotes FreeSlip, Normal Stokes operator is wrapped with a FreeSlip Projection Wrapper + Changes the linear system from $Ku = f $ + to $PKP^T = Pf$ +***************************************************************************************************/ +class P2P1StokesFullIcosahedralShellMapOperatorFS +: public Operator< P2P1TaylorHoodFunction< real_t >, P2P1TaylorHoodFunction< real_t > > +{ + public: + typedef operatorgeneration::P2P1StokesFullIcosahedralShellMapOperator::ViscousOperator_T ViscousOperator_T; + typedef P2ViscousIcosahedralShellMapOperatorFS ViscousOperatorFS_T; + typedef operatorgeneration::P2P1StokesFullIcosahedralShellMapOperator::DivergenceOperator_T DivOperator_T; + typedef operatorgeneration::P2P1StokesFullIcosahedralShellMapOperator::GradientOperator_T GradOperator_T; + typedef operatorgeneration::P2P1StokesFullIcosahedralShellMapOperator::StabilizationOperator_T StabOperator_T; + + typedef operatorgeneration::P1ElementwiseKMassIcosahedralShellMap SchurOperator_T; + + P2P1StokesFullIcosahedralShellMapOperatorFS( const std::shared_ptr< PrimitiveStorage >& storage, + uint_t minLevel, + uint_t maxLevel, + const P2Function< real_t >& mu, + const P1Function< real_t >& muInv, + P2ProjectNormalOperator& projectNormal, + BoundaryCondition bcVelocity ) + : Operator< P2P1TaylorHoodFunction< real_t >, P2P1TaylorHoodFunction< real_t > >( storage, minLevel, maxLevel ) + , tmp_( "tmp__P2P1StokesFullIcosahedralShellMapOperatorFS", storage, minLevel, maxLevel, bcVelocity ) + , tmp_Vec( "tmp_Vec_P2P1StokesFullIcosahedralShellMapOperatorFS", storage, minLevel, maxLevel ) + , tmp_P2( "tmp_P2_P2P1StokesFullIcosahedralShellMapOperatorFS", storage, minLevel, maxLevel ) + , StokesOp( storage, minLevel, maxLevel, mu ) + , schurOperator( storage, minLevel, maxLevel, muInv ) + , projectNormal_( projectNormal ) + , viscousFSOp( storage, minLevel, maxLevel, mu, projectNormal_, bcVelocity ) + , massOperator( storage, minLevel, maxLevel ) + { + StokesOp.getA().computeInverseDiagonalOperatorValues(); + viscousFSOp.computeInverseDiagonalOperatorValues(); + schurOperator.computeInverseDiagonalOperatorValues(); + } + + void apply( const P2P1TaylorHoodFunction< real_t >& src, + const P2P1TaylorHoodFunction< real_t >& dst, + const uint_t level, + const DoFType flag, + const UpdateType updateType = Replace ) const + { + // hyteg::removeRotationalModes( massOperator, src.uvw(), tmp_Vec, tmp_P2, level ); + // vertexdof::projectMean( src.p(), level ); + + tmp_.assign( { 1 }, { src }, level, All ); + projectNormal_.project( tmp_, level, FreeslipBoundary ); + + StokesOp.apply( tmp_, dst, level, flag, updateType ); + projectNormal_.project( dst, level, FreeslipBoundary ); + } + + P2P1TaylorHoodFunction< real_t > tmp_; + + P2VectorFunction< real_t > tmp_Vec; + P2Function< real_t > tmp_P2; + + operatorgeneration::P2P1StokesFullIcosahedralShellMapOperator StokesOp; + SchurOperator_T schurOperator; + P2ProjectNormalOperator& projectNormal_; + ViscousOperatorFS_T viscousFSOp; + + P2ElementwiseBlendingMassOperator massOperator; + + const ViscousOperatorFS_T& getA() const { return viscousFSOp; } + const DivOperator_T& getB() const { return StokesOp.getB(); } + const GradOperator_T& getBT() const { return StokesOp.getBT(); } + const SchurOperator_T& getSchur() const { return schurOperator; } + const StabOperator_T& getStab() const { return StokesOp.getStab(); } + + ViscousOperatorFS_T& getA() { return viscousFSOp; } +}; +} // namespace hyteg \ No newline at end of file diff --git a/src/terraneo/operators/P2StokesABlockWithProjection.hpp b/src/terraneo/operators/P2StokesABlockWithProjection.hpp new file mode 100644 index 0000000000000000000000000000000000000000..11212317e447cfd539baa838574a85026aa972b5 --- /dev/null +++ b/src/terraneo/operators/P2StokesABlockWithProjection.hpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024 Ponsuganth Ilangovan P, Andreas Burkhart. + * + * This file is part of HyTeG + * (see https://i10git.cs.fau.de/hyteg/hyteg). + * + * 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/>. + */ + +#pragma once + +#include "hyteg/p2functionspace/P2ProjectNormalOperator.hpp" +#include "hyteg_operators/operators/k_mass/P1ElementwiseKMassIcosahedralShellMap.hpp" +#include "hyteg_operators_composites/stokes/P2P1StokesFullOperator.hpp" + +namespace hyteg { + +/*************************************************************************************************** +NOTE: Here FS denotes FreeSlip, Stokes A block operator is wrapped with a FreeSlip Projection Wrapper + Changes the linear system from $Ku = f $ + to $PKP^T = Pf$ +***************************************************************************************************/ +class P2ViscousIcosahedralShellMapOperatorFS : public Operator< P2VectorFunction< real_t >, P2VectorFunction< real_t > > +{ + public: + typedef operatorgeneration::P2P1StokesFullIcosahedralShellMapOperator::ViscousOperator_T ViscousOperator_T; + + P2ViscousIcosahedralShellMapOperatorFS( const std::shared_ptr< PrimitiveStorage >& storage, + uint_t minLevel, + uint_t maxLevel, + const P2Function< real_t >& mu, + P2ProjectNormalOperator& projectNormal, + BoundaryCondition bcVelocity ) + : Operator< P2VectorFunction< real_t >, P2VectorFunction< real_t > >( storage, minLevel, maxLevel ) + , tmp_( "tmp__P2ViscousIcosahedralShellMapOperatorFS", storage, minLevel, maxLevel, bcVelocity ) + , viscousOperator( storage, minLevel, maxLevel, mu ) + , projectNormal_( projectNormal ) + {} + + void apply( const P2VectorFunction< real_t >& src, + const P2VectorFunction< real_t >& dst, + const uint_t level, + const DoFType flag, + const UpdateType updateType = Replace ) const + { + tmp_.assign( { 1 }, { src }, level, All ); + projectNormal_.project( tmp_, level, FreeslipBoundary ); + + viscousOperator.apply( tmp_, dst, level, flag, updateType ); + projectNormal_.project( dst, level, FreeslipBoundary ); + } + + void computeInverseDiagonalOperatorValues() { viscousOperator.computeInverseDiagonalOperatorValues(); } + + P2VectorFunction< real_t > tmp_; + + ViscousOperator_T viscousOperator; + P2ProjectNormalOperator& projectNormal_; +}; +} // namespace hyteg \ No newline at end of file diff --git a/tests/terraneo/CMakeLists.txt b/tests/terraneo/CMakeLists.txt index 4ffe92eeb1d3dab902078b0faefa0964eba6ef8b..e1351c4c375b6c9653da747adc2392247eaa46f4 100644 --- a/tests/terraneo/CMakeLists.txt +++ b/tests/terraneo/CMakeLists.txt @@ -9,3 +9,5 @@ waLBerla_execute_test(NAME InitialisationTest) waLBerla_compile_test(FILES parameter/TerraNeoParameterTest.cpp DEPENDS terraneo hyteg core ) waLBerla_execute_test(NAME TerraNeoParameterTest) + +add_subdirectory( solvers ) diff --git a/tests/terraneo/solvers/CMakeLists.txt b/tests/terraneo/solvers/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..31aa5810a970cac24bbb25884d301a8c57fdd288 --- /dev/null +++ b/tests/terraneo/solvers/CMakeLists.txt @@ -0,0 +1,2 @@ +waLBerla_compile_test(FILES P2P1StokesFSGMGSolverTest.cpp DEPENDS hyteg terraneo core mixed_operator opgen-k_mass opgen-mass opgen-composites-stokes) +waLBerla_execute_test(NAME P2P1StokesFSGMGSolverTest COMMAND $<TARGET_FILE:P2P1StokesFSGMGSolverTest> LABELS longrun) diff --git a/tests/terraneo/solvers/P2P1StokesFSGMGSolverTest.cpp b/tests/terraneo/solvers/P2P1StokesFSGMGSolverTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9b4000801bab02e5e89edce0bd0670294f80c39d --- /dev/null +++ b/tests/terraneo/solvers/P2P1StokesFSGMGSolverTest.cpp @@ -0,0 +1,139 @@ +/* +* Copyright (c) 2017-2024 Andreas Burkhart, Nils Kohl. +* +* This file is part of HyTeG +* (see https://i10git.cs.fau.de/hyteg/hyteg). +* +* 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/>. +*/ + +#include <core/Environment.h> +#include <core/config/Config.h> +#include <core/math/Constants.h> +#include <core/mpi/MPIManager.h> + +#include "hyteg/composites/P2P1TaylorHoodFunction.hpp" +#include "hyteg/dataexport/ADIOS2/AdiosWriter.hpp" +#include "hyteg/geometry/IcosahedralShellMap.hpp" +#include "hyteg/mesh/MeshInfo.hpp" +#include "hyteg/numerictools/L2Space.hpp" +#include "hyteg/p1functionspace/P1Function.hpp" +#include "hyteg/p2functionspace/P2Function.hpp" +#include "hyteg/p2functionspace/P2ProjectNormalOperator.hpp" +#include "hyteg/primitivestorage/PrimitiveStorage.hpp" +#include "hyteg/primitivestorage/SetupPrimitiveStorage.hpp" +#include "hyteg/solvers/solvertemplates/StokesFSGMGSolverTemplate.hpp" + +#include "mixed_operator/VectorMassOperator.hpp" +#include "terraneo/operators/P2P1StokesOperatorWithProjection.hpp" +#include "terraneo/operators/P2StokesABlockWithProjection.hpp" +#include "terraneo/sphericalharmonics/SphericalHarmonicsTool.hpp" + +using namespace hyteg; + +using walberla::real_t; + +int main( int argc, char* argv[] ) +{ + walberla::MPIManager::instance()->initializeMPI( &argc, &argv ); + walberla::MPIManager::instance()->useWorldComm(); + + real_t rMin = 0.5, rMax = 1.0; + MeshInfo meshInfo = MeshInfo::meshSphericalShell( 3, 2, 0.5, 1.0 ); + + SetupPrimitiveStorage setupStorage( meshInfo, walberla::uint_c( walberla::mpi::MPIManager::instance()->numProcesses() ) ); + IcosahedralShellMap::setMap( setupStorage ); + auto storage = std::make_shared< PrimitiveStorage >( setupStorage ); + + BoundaryCondition bcVelocity; + bcVelocity.createDirichletBC( "DirichletOuter", { MeshInfo::hollowFlag::flagOuterBoundary } ); + // bcVelocity.createDirichletBC( "DirichletInner", {MeshInfo::hollowFlag::flagInnerBoundary} ); + bcVelocity.createFreeslipBC( "FreeslipInner", { MeshInfo::hollowFlag::flagInnerBoundary } ); + + uint_t minLevel = 2U, maxLevel = 3U; + + P2P1TaylorHoodFunction< real_t > u( "u", storage, minLevel, maxLevel, bcVelocity ); + P2P1TaylorHoodFunction< real_t > fStrong( "fStrong", storage, minLevel, maxLevel, bcVelocity ); + P2P1TaylorHoodFunction< real_t > f( "f", storage, minLevel, maxLevel, bcVelocity ); + P2P1TaylorHoodFunction< real_t > res( "res", storage, minLevel, maxLevel, bcVelocity ); + + P2Function< real_t > mu( "mu", storage, minLevel, maxLevel ); + P1Function< real_t > muInv( "muInv", storage, minLevel, maxLevel ); + + uint_t lMax = 5U; + + terraneo::SphericalHarmonicsTool sphTool( lMax ); + + // Some coefficient and its inverse. + auto visc = [&]( const Point3D& x ) { return 2 + std::sin( x[0] ) * std::cos( x[1] ); }; + // auto visc = [&]( const Point3D& x ) { return 1.0; }; + auto viscInv = [&]( const Point3D& x ) { return 1.0 / visc( x ); }; + for ( uint_t level = minLevel; level <= maxLevel; level++ ) + { + mu.interpolate( visc, level, All ); + muInv.interpolate( viscInv, level, All ); + } + + std::function< void( const Point3D&, Point3D& ) > normalsFS = []( const Point3D& x, Point3D& n ) { n = -x / x.norm(); }; + + real_t rhsScale = 1.0; + std::function< real_t( const Point3D& ) > fRHS = [&]( const Point3D& x ) { + real_t r = x.norm(); + return rhsScale * std::sin( walberla::math::pi * ( r - rMin ) / ( rMax - rMin ) ) * + sphTool.shconvert_eval( 4, 2, x[0], x[1], x[2] ); + }; + + fStrong.uvw().interpolate( fRHS, maxLevel, All ); + + auto projectionOperator = std::make_shared< P2ProjectNormalOperator >( storage, minLevel, maxLevel, normalsFS ); + + auto stokesOperatorFS = std::make_shared< P2P1StokesFullIcosahedralShellMapOperatorFS >( + storage, minLevel, maxLevel, mu, muInv, *projectionOperator, bcVelocity ); + + P2ElementwiseBlendingVectorMassOperator vecMassOperator( storage, minLevel, maxLevel ); + + vecMassOperator.apply( fStrong.uvw(), f.uvw(), maxLevel, All ); + + auto stokesSolverTest = + solvertemplates::stokesGMGFSSolver< P2P1StokesFullIcosahedralShellMapOperatorFS, P2ProjectNormalOperator >( + storage, + minLevel, + maxLevel, + stokesOperatorFS, + projectionOperator, + bcVelocity, + false, + false, + { { solvertemplates::StokesGMGFSSolverParamKey::FGMRES_UZAWA_PRECONDITIONED_OUTER_ITER, 10 } } ); + + projectionOperator->project( f, maxLevel, FreeslipBoundary ); + stokesSolverTest->solve( *stokesOperatorFS, u, f, maxLevel ); + + stokesOperatorFS->apply( u, res, maxLevel, Inner | NeumannBoundary | FreeslipBoundary ); + res.assign( { 1.0, -1.0 }, { res, f }, maxLevel ); + + real_t unscaledFinalResiduum = std::sqrt( res.dotGlobal( res, maxLevel, Inner | FreeslipBoundary ) ); + + real_t unscaledResidualEpsilon = 2e-4; + + WALBERLA_LOG_INFO_ON_ROOT( "Final residual: " << unscaledFinalResiduum ); + WALBERLA_CHECK_LESS( unscaledFinalResiduum, unscaledResidualEpsilon ); + + // AdiosWriter adiosWriter( "../../output", "solverTest", storage ); + // adiosWriter.add( u ); + // adiosWriter.add( f ); + // adiosWriter.write( maxLevel, 0U ); + + return EXIT_SUCCESS; +}