We consider a Stokes boundary value problem with both Dirichlet (inflow / no-slip) and Neumann boundary conditions. The computational domain is \(\Omega\subset \mathbb R^d\). The variational problem characterizing velocity \(u\) and pressure \(p\) is as follows: Find \( (u,p) \in V \times Q = [H^1_{0,D}(\Omega)]^d \times L^2(\Omega)\) such that
\[\begin{eqnarray}
\int_\Omega \nabla u : \nabla v d x & + & \int_\Omega p \nabla \cdot v d x & = & \int_{\Gamma_N} g \cdot v dx \\
& & \int_\Omega q \nabla \cdot u d x & = & 0
\end{eqnarray}\]
for all \( (v,q) \in V \times Q \). The domain \( \Omega \) is composed of non-overlapping patches \( \Omega_k \).
The theoretical framework follows the paper J. Sogn and S. Takacs. Stable discretizations and IETI-DP solvers for the Stokes system in multi-patch Isogeometric Analysis. ESAIM: Mathematical Modelling and Numerical Analysis, vol. 57 (2), p. 921 – 925, 2023.
We discretize using the Galerkin principle, i.e., by choosing \( V_h \times Q_h \subset V \times Q \). Since the Stokes equations are a saddle point problem, we have to choose a pair of spaces that is inf-sup stable.
For the individual patches \( \Omega_k \), we choose the Taylor-Hood spaces, i.e., \( V_h \) is composed of splines of degree \( p+1 \) and smoothnes \( C^{p-1} \), and \( Q_h \) is composed of splines of degree \( p \) and smoothnes \( C^{p-1} \). Both spaces use the same grid (breakpoints).
The global space is constructed as follows. Since the velocity space is in \( H^1 \), we need continuity for the velocity. On each patch, we have a isogeometric function space \( V_k \). We assume that the function spaces are fully matching. The global function space is
\[
V_h = \{ v\in [H^1_{0,D}(\Omega)]^d \;:\; v|_{\Omega_k} \in V_k \}.
\]
As primal degrees of freedom, we choose the corner values and edge averages of the velocity components. Additionally, we use the averages of the pressure (on each of the patches as primals), see paper for a corresponding motivation.
\[
A_k :=
\begin{pmatrix}
K_k & & D_{x,k}^\top \\
& K_k & D_{y,k}^\top \\
D_{x,k} & D_{y,k} &
\end{pmatrix},
\]
where \( K_k \) is a standard grad-grad-stiffness matrix (as for the Poisson problem) and \( D_{x,k} \) and \( D_{y,k} \) represent \( \int \partial_x u p dx \) and \( \int \partial_y u p dx \) respectively. If we would not have primal dofs, this matrix could be directly handed over to the gsIetiSystem.
Also the setup of the primal system is done as for the Poisson problem. More work would be necessary here if the problem only had Dirichlet conditions: In that case, we would have \( Q = L^2_0(\Omega) \), so we would need to specify that the average pressure vanishes, i.e., \( \int_\Omega p dx = 0 \). This can be formulated as a constraint in the primal system.
Since the Schur complement formulation is symmetric and positive definite, we can apply a preconditioned conjugate gradient solver.
template<class Container>
template<class Container>
std::vector<index_t> combinedPrimalDofIndices(
const Container& ietiMapper,
index_t k);
template<class Container>
std::vector<gsSparseVector<real_t>> combinedPrimalConstraints(
const Container& ietiMapper,
index_t k);
template<class Container>
std::vector<index_t> combinedSkeletonDofs(
const Container& ietiMapper,
index_t k);
template<class Container>
std::vector<std::vector<gsMatrix<>>> decomposeSolution(
const Container& ietiMapper,
const std::vector<
gsMatrix<>>& solution);
template<class T>
int main(int argc, char *argv[])
{
bool liftGeo = false;
real_t tolerance = 1.e-6;
std::string fn;
bool plot = false;
gsCmdLine cmd(
"Solves the Stokes system with an isogeometric discretization using an isogeometric tearing and interconnecting (IETI) solver.");
cmd.addSwitch("", "3D", "Lift geometry to 3D.", liftGeo);
cmd.addInt ("", "SplitPatches", "Split every patch that many times in 2^d patches", splitPatches);
cmd.addInt ("r", "Refinements", "Number of uniform h-refinement steps to perform before solving", refinements);
cmd.addInt ("p", "Degree", "Degree of the B-spline discretization space (for pressure)", degree);
cmd.addReal ("t", "Solver.Tolerance", "Stopping criterion for linear solver", tolerance);
cmd.addInt ("", "Solver.MaxIterations", "Stopping criterion for linear solver", maxIterations);
cmd.addString("" , "fn", "Write solution and used options to file", fn);
cmd.addSwitch( "plot", "Plot the result with Paraview", plot);
try { cmd.getValues(argc,argv); } catch (int rv) { return rv; }
std::string geometry("planar/rectangle_with_disk_hole.xml");
gsInfo <<
"Use geometry file planar/rectangle_with_disk_hole.xml.\n";
{
gsInfo <<
"Geometry file could not be found.\n";
return EXIT_FAILURE;
}
gsInfo <<
"Run ieti_stokes_example with options:\n" << cmd << std::endl;
gsInfo <<
"Define geometry... " << std::flush;
if (!mpPtr)
{
gsInfo <<
"No geometry found in file " << geometry <<
".\n";
return EXIT_FAILURE;
}
if (liftGeo)
mpPtr = lift3D(*mpPtr,(real_t)(10));
const std::size_t dim = mp.geoDim();
for (
index_t i=0; i<splitPatches; ++i)
{
gsInfo <<
"split patches uniformly... " << std::flush;
mp = mp.uniformSplit();
}
gsInfo <<
"Define boundary conditions... " << std::flush;
gsFunctionExpr<> inflow( dim<3 ?
"sin(pi*(2+y)/4)" :
"sin(pi*(2+y)/4)*z*(10-z)", dim );
std::vector<gsBoundaryConditions<>> bc;
bc.resize(dim+1);
index_t inlets = 0, outlets = 0, bccount = 0;
for (gsMultiPatch<>::const_biterator it = mp.bBegin(); it < mp.bEnd(); ++it)
{
if (it->patch / math::exp2(mp.geoDim()*splitPatches) == 3 && it->side() == 4)
{
for (std::size_t r=1; r<dim; ++r)
++inlets;
}
else if (it->patch / math::exp2(mp.geoDim()*splitPatches) == 10 && it->side() == 2)
{
for (std::size_t r=0; r<dim; ++r)
++outlets;
}
else
{
for (std::size_t r=0; r<dim; ++r)
}
++bccount;
}
for (std::size_t r=0; r<dim+1; ++r)
bc[r].setGeoMap(mp);
gsInfo <<
"done: "<<bccount<<
" conditions, including "
<<inlets<<" inlet and "<<outlets<<" outlet, set.\n";
std::vector<gsMultiBasis<>> mb;
for (std::size_t r=0; r<dim+1; ++r)
mb.emplace_back(mp,true);
const index_t nPatches = mp.nPatches();
gsInfo <<
"Setup bases and adjust degree (Taylor-Hood)... " << std::flush;
for (std::size_t r=0; r<dim; ++r)
{
mb[r][k].setDegreePreservingMultiplicity(degree+1);
for (
index_t i=0; i<refinements; ++i )
mb[r].uniformRefine();
mb[r][k].reduceContinuity(1);
}
mb[dim][k].setDegreePreservingMultiplicity(degree);
for (
index_t i=0; i<refinements; ++i )
mb[dim].uniformRefine();
gsInfo <<
"Setup assembler and assemble matrix for patch... " << std::flush;
std::vector<gsIetiMapper<>> ietiMapper;
ietiMapper.resize(dim+1);
for (std::size_t r=0; r<dim+1; ++r)
{
const index_t continuity = r<dim ? 0 : -1;
u.setup(bc[r], dirichlet::interpolation, continuity);
ietiMapper[r].init(mb[r], u.mapper(), u.fixedPart());
}
for (std::size_t r=0; r<dim+1; ++r)
ietiMapper[r].computeJumpMatrices(true, true);
for (std::size_t r=0; r<dim; ++r)
{
ietiMapper[r].cornersAsPrimals();
ietiMapper[r].interfaceAveragesAsPrimals(mp,dim-1);
}
ietiMapper[dim].interfaceAveragesAsPrimals(mp,dim);
for (std::size_t r=0; r<dim+1; ++r)
nPrimals += ietiMapper[r].nPrimalDofs();
primal.setEliminatePointwiseConstraints(false);
{
gsInfo <<
"[" << k <<
"] " << std::flush;
std::vector<gsMultiBasis<>> mb_local;
std::vector<gsBoundaryConditions<>> bc_local(dim+1);
mb_local.reserve(dim+1);
for (std::size_t r=0; r<dim+1; ++r)
{
mb_local.push_back(mb[r][k]);
bc[r].getConditionsForPatch(k,bc_local[r]);
bc_local[r].setGeoMap(mp_local);
}
assembler.setIntegrationElements(mb_local[0]);
geometryMap G = assembler.getMap(mp_local);
std::vector<std::remove_cv<space>::type> v;
for (std::size_t r=0; r<dim; ++r)
{
v.push_back(assembler.getSpace(mb_local[r],1,r));
v[r].setup(bc_local[r], dirichlet::interpolation, 0);
ietiMapper[r].initFeSpace(v[r],k);
}
space p = assembler.getSpace(mb_local[dim],1,dim);
p.setup(bc_local[dim], dirichlet::interpolation, 0);
ietiMapper[dim].initFeSpace(p,k);
assembler.initSystem();
for (std::size_t r=0; r<dim; ++r)
{
assembler.assemble( igrad(v[r], G) * igrad(v[r], G).tr() *
meas(G) ,
igrad(v[r],G)[r] * p.tr() *
meas(G) ,
p * igrad(v[r],G)[r].tr() *
meas(G) );
}
{
for (std::size_t r=0; r<dim; ++r)
sz += ietiMapper[r].jumpMatrix(k).cols();
gsSparseMatrix<> velocityJumpMatrix = jumpMatrix.block(0,0,jumpMatrix.rows(),sz);
std::vector<index_t> skeletonDofs = combinedSkeletonDofs(ietiMapper,k);
);
}
std::vector<index_t> primalDofIndices = combinedPrimalDofIndices(ietiMapper,k);
std::vector<gsSparseVector<real_t>> primalConstraints = combinedPrimalConstraints(ietiMapper,k);
primal.handleConstraints(primalConstraints, primalDofIndices, jumpMatrix, localMatrix, localRhs);
makeMatrixOp(localMatrix),
makeSparseLUSolver(localMatrix)
);
}
{
primal.jumpMatrix().moveToPtr(),
makeMatrixOp(primal.localMatrix().moveToPtr()),
localSolver
);
}
gsInfo <<
"Setup solver and solve... \n"
" Setup multiplicity scaling... " << std::flush;
gsInfo <<
"done.\n Setup CG solver and solve... \n" << std::flush;
PCG.setCalcEigenvalues(true);
PCG.setOptions(cmd.getGroup("Solver"));
const index_t iter = errorHistory.rows()-1;
const bool success = errorHistory(iter,0) < PCG.tolerance();
if (success)
gsInfo <<
" Reached desired tolerance after " << iter <<
" iterations:\n ";
else
gsInfo <<
" Did not reach desired tolerance after " << iter <<
" iterations:\n ";
if (errorHistory.rows() < 20)
gsInfo << errorHistory.transpose() <<
"\n\n";
else
gsInfo << errorHistory.topRows(5).transpose() <<
" ... " << errorHistory.bottomRows(5).transpose() <<
"\n\n";
gsInfo <<
" Calculated condition number is " << PCG.getConditionNumber() <<
".";
gsInfo <<
"\n Reconstruct solution from Lagrange multipliers... " << std::flush;
std::vector<std::vector<gsMatrix<>>> solutionPatches = decomposeSolution(ietiMapper,
primal.distributePrimalSolution(
)
);
std::vector<gsMatrix<>> solutions;
for (std::size_t r=0; r<ietiMapper.size(); ++r)
solutions.push_back( ietiMapper[r].constructGlobalSolutionFromLocalSolutions(solutionPatches[r]));
if (!fn.empty())
{
std::time_t time = std::time(NULL);
fd.add(cmd);
for (std::size_t r=0; r<ietiMapper.size(); ++r)
fd.add(solutions[r]);
fd.addComment(std::string("stokes_ieti_example Timestamp:")+std::ctime(&time));
fd.save(fn);
gsInfo <<
"Write solution to file " << fn <<
".\n";
}
if (plot)
{
for (std::size_t r=0; r<ietiMapper.size(); ++r)
{
const char* filenames[] = { "ieti_velocity_x", "ieti_velocity_y", "ieti_velocity_z", "ieti_pressure" };
const char* filename = filenames[(r<dim&&r<3)?r:3];
gsInfo <<
"Write Paraview data to file \"" << filename <<
".pvd\".\n";
mpsol.
addPatch( mb[r][k].makeGeometry( ietiMapper[r].incorporateFixedPart(k, solutionPatches[r][k]) ) );
gsWriteParaview<>(
gsField<>( mp, mpsol ), filename, dim < 3 ? 1000 : 10000);
}
}
if (!plot&&fn.empty())
{
gsInfo <<
"No output created, re-run with --plot to get a ParaView "
"file containing the solution or --fn to write solution to xml file.\n";
}
return success ? EXIT_SUCCESS : EXIT_FAILURE;
}
template<typename Iterator>
typename std::iterator_traits<Iterator>::value_type blockDiagonal( Iterator begin, Iterator end )
{
typedef typename std::iterator_traits<Iterator>::value_type sparseMatrix;
typedef typename sparseMatrix::Scalar T;
typedef typename sparseMatrix::Index Index;
Index nz = 0;
for (Iterator sm_it=begin; sm_it!=end; ++sm_it)
nz += sm_it->nonZeros();
se.reserve(nz);
Index rows = 0, cols = 0;
for (Iterator sm_it=begin; sm_it!=end; ++sm_it)
{
const sparseMatrix& sm = *sm_it;
for(Index j=0; j<sm.outerSize(); ++j)
for(typename sparseMatrix::InnerIterator it(sm,j); it; ++it)
se.add(rows+it.row(), cols+it.col(), it.value());
rows += sm.rows();
cols += sm.cols();
}
sparseMatrix result(rows, cols);
result.setFrom(se);
return result;
}
template<class Container>
{
std::vector<gsSparseMatrix<real_t, RowMajor>> jumpMatrices;
for (std::size_t r=0; r<ietiMapper.size(); ++r)
jumpMatrices.push_back(ietiMapper[r].jumpMatrix(k));
return blockDiagonal(jumpMatrices.begin(), jumpMatrices.end());
}
template<class Container>
std::vector<index_t> combinedPrimalDofIndices(
const Container& ietiMapper,
index_t k)
{
std::vector<index_t> primalDofIndices;
for (std::size_t i=0; i<ietiMapper.size(); ++i)
{
const std::vector<index_t>& ind = ietiMapper[i].primalDofIndices(k);
for (std::size_t j=0; j<ind.size(); ++j)
primalDofIndices.push_back(ind[j]+offset);
offset += ietiMapper[i].nPrimalDofs();
}
return primalDofIndices;
}
template<class Container>
std::vector<gsSparseVector<real_t>> combinedPrimalConstraints(
const Container& ietiMapper,
index_t k)
{
std::vector<gsSparseVector<real_t>> primalConstraints;
for (std::size_t i=0; i<ietiMapper.size(); ++i)
rows += ietiMapper[i].jumpMatrix(k).cols();
for (std::size_t i=0; i<ietiMapper.size(); ++i)
{
const std::vector<gsSparseVector<real_t>>& cond = ietiMapper[i].primalConstraints(k);
for (std::size_t j=0; j<cond.size(); ++j)
{
tmp[pre + it.row()] = it.value();
primalConstraints.push_back(
give(tmp));
}
pre += ietiMapper[i].jumpMatrix(k).cols();
}
return primalConstraints;
}
template<class Container>
std::vector<index_t> combinedSkeletonDofs(
const Container& ietiMapper,
index_t k)
{
std::vector<index_t> skeletonDofs;
for (std::size_t i=0; i<ietiMapper.size(); ++i)
{
const std::vector<index_t>& sd = ietiMapper[i].skeletonDofs(k);
for (std::size_t j = 0; j<sd.size(); ++j)
skeletonDofs.push_back(sd[j]+offset);
offset += ietiMapper[i].jumpMatrix(k).cols();
}
return skeletonDofs;
}
template<class Container>
std::vector<std::vector<gsMatrix<>>> decomposeSolution(
const Container& ietiMapper,
const std::vector<
gsMatrix<>>& solution)
{
std::vector<std::vector<gsMatrix<>>> result(ietiMapper.size());
for (std::size_t r=0; r<ietiMapper.size(); ++r)
result[r].reserve(solution.size());
for (std::size_t k=0; k<solution.size(); ++k)
{
for (std::size_t r=0; r<ietiMapper.size(); ++r)
{
const index_t sz = ietiMapper[r].jumpMatrix(k).cols();
result[r].push_back(solution[k].block(offset,0,sz,1));
offset += ietiMapper[r].jumpMatrix(k).cols();
}
}
return result;
}
template<class T>
{
std::vector<gsGeometry<T>*> geos;
geos.reserve(mp.nPatches());
for (
typename std::vector<
gsGeometry<T>*>::const_iterator it = mp.begin();
it != mp.end(); ++it)
{
if (tb)
{
continue;
}
if (tn)
{
continue;
}
GISMO_ENSURE(
false,
"lift3D only available for gsTensorBSpline<2,T> and gsTensorNurbs<2,T>.");
}
return result;
}
Definition gsExpressions.h:973
Definition gsExpressions.h:928
Class for command-line argument parsing.
Definition gsCmdLine.h:57
The conjugate gradient method.
Definition gsConjugateGradient.h:30
Class defining a globally constant function.
Definition gsConstantFunction.h:34
Definition gsExprAssembler.h:33
gsExprHelper< T >::geometryMap geometryMap
Geometry map type.
Definition gsExprAssembler.h:65
expr::gsFeSolution< T > solution
Solution type.
Definition gsExprAssembler.h:68
Generic evaluator of isogeometric expressions.
Definition gsExprEvaluator.h:39
A scalar of vector field defined on a m_parametric geometry.
Definition gsField.h:55
This class represents an XML data tree which can be read from or written to a (file) stream.
Definition gsFileData.h:34
static std::string getSearchPaths()
Get the defined search paths.
Definition gsFileManager.cpp:271
static bool fileExists(const std::string &name)
Checks if the file exists.
Definition gsFileManager.cpp:62
Class defining a multivariate (real or vector) function given by a string mathematical expression.
Definition gsFunctionExpr.h:52
Abstract base class representing a geometry map.
Definition gsGeometry.h:93
This class represents a IETI system.
Definition gsIetiSystem.h:70
OpPtr schurComplement() const
Returns gsLinearOperator that represents the Schur complement for the IETI problem.
Definition gsIetiSystem.hpp:83
void addSubdomain(JumpMatrixPtr jumpMatrix, OpPtr localMatrixOp, Matrix localRhs, OpPtr localSolverOp=OpPtr())
Definition gsIetiSystem.hpp:32
void reserve(index_t n)
Reserves the memory required to store the number of subdomains.
Definition gsIetiSystem.hpp:23
Matrix rhsForSchurComplement() const
Returns the right-hand-side that is required for the Schur complement formulation of the IETI problem...
Definition gsIetiSystem.hpp:91
std::vector< Matrix > constructSolutionFromLagrangeMultipliers(const Matrix &multipliers) const
Returns the local solutions for the individual subdomains.
Definition gsIetiSystem.hpp:107
index_t nLagrangeMultipliers() const
Returns the number of Lagrange multipliers.
Definition gsIetiSystem.h:116
A matrix with arbitrary coefficient type and fixed or dynamic size.
Definition gsMatrix.h:41
Container class for a set of geometry patches and their topology, that is, the interface connections ...
Definition gsMultiPatch.h:100
bool computeTopology(T tol=1e-4, bool cornersOnly=false, bool tjunctions=false)
Attempt to compute interfaces and boundaries automatically.
Definition gsMultiPatch.hpp:377
index_t addPatch(typename gsGeometry< T >::uPtr g)
Add a patch from a gsGeometry<T>::uPtr.
Definition gsMultiPatch.hpp:211
memory::unique_ptr< gsMultiPatch > uPtr
Unique pointer for gsMultiPatch.
Definition gsMultiPatch.h:109
This class represents the primal system for a IETI-DP algorithm.
Definition gsPrimalSystem.h:87
Reads an object from a data file, if such the requested object exists in the file.
Definition gsReadFile.h:43
This class represents the scaled Dirichlet preconditioner for a IETI problem.
Definition gsScaledDirichletPrec.h:79
OpPtr preconditioner() const
This returns the preconditioner as gsLinearOperator.
Definition gsScaledDirichletPrec.hpp:176
void addSubdomain(JumpMatrixPtr jumpMatrix, OpPtr localSchurOp)
Definition gsScaledDirichletPrec.h:107
void reserve(index_t n)
Reserves the memory required to store the given number of subdomain.
Definition gsScaledDirichletPrec.h:90
void setupMultiplicityScaling()
This sets up the member vector localScaling based on multiplicity scaling.
Definition gsScaledDirichletPrec.hpp:149
Class that provides a container for triplets (i,j,value) to be filled in a sparse matrix.
Definition gsSparseMatrix.h:34
Sparse matrix class, based on gsEigen::SparseMatrix.
Definition gsSparseMatrix.h:139
uPtr moveToPtr()
This function returns a smart pointer to the matrix. After calling it, the matrix object becomes empt...
Definition gsSparseMatrix.h:247
Sparse vector class, based on gsEigen::SparseVector.
Definition gsSparseVector.h:35
A tensor product of d B-spline functions, with arbitrary target dimension.
Definition gsTensorBSpline.h:45
A tensor product Non-Uniform Rational B-spline function (NURBS) of parametric dimension d,...
Definition gsTensorNurbs.h:41
Main header to be included by clients using the G+Smo library.
#define GISMO_ENSURE(cond, message)
Definition gsDebug.h:102
EIGEN_STRONG_INLINE meas_expr< T > meas(const gsGeometryMap< T > &G)
The measure of a geometry map.
Definition gsExpressions.h:4555
unique_ptr< T > make_unique(T *x)
Definition gsMemory.h:198
The G+Smo namespace, containing all definitions for the library.
@ dirichlet
Dirichlet type.
Definition gsBoundaryConditions.h:31
@ neumann
Neumann type.
Definition gsBoundaryConditions.h:33
Class gsNurbsCreator provides some simple examples of Nurbs Geometries.
Definition gsNurbsCreator.h:37
Definition gsScaledDirichletPrec.h:148