G+Smo  25.01.0
Geometry + Simulation Modules
 
Loading...
Searching...
No Matches
ieti_example.cpp

We consider the Poisson boundary value problem (for simplicity here only with Dirichlet conditions):

\begin{eqnarray*} -\Delta u &=& f \quad \text{in} \quad \Omega, \\ u &=& g \quad \text{on} \quad \partial\Omega. \end{eqnarray*}

Its variational form is: Find \( u\in V_g \) such that

\[ \int_\Omega \nabla u \nabla v d x = \int_\Omega f v dx \]

for all \( v\in V_0 \), which corresponds to the linear system

\[ A \underline u = \underline f. \]

The computational domain is a non-overlapping multi-patch domain:

\[ \overline{ \Omega } = \bigcup_{k=1}^K \overline{ \Omega_k } \]

\[ \Omega_k \cap \Omega_l = \emptyset \quad \text{ for all } k\not=l. \]

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 V \;:\; v|_{\Omega_k} \in V_k \} \]

where \( V \) is the proper continuous space.

After reading the command line arguments, we read in the geometry file and modify the geometry as desired:

gsMultiPatch<>::uPtr mpPtr = gsReadFile<>(geometry);
memory::unique_ptr< gsMultiPatch > uPtr
Unique pointer for gsMultiPatch.
Definition gsMultiPatch.h:109
gsMultiPatch<>& mp = *mpPtr;
for (index_t i=0; i<splitPatches; ++i)
{
gsInfo << "split patches uniformly... " << std::flush;
mp = mp.uniformSplit();
}
#define index_t
Definition gsConfig.h:32
#define gsInfo
Definition gsDebug.h:43

Then, we set up the right-hand-side f and a gsBoundaryConditions object based on the command line arguments.

// Right-hand-side
gsFunctionExpr<> f( "2*sin(x)*cos(y)", mp.geoDim() );
// Dirichlet function
gsFunctionExpr<> gD( "sin(x)*cos(y)", mp.geoDim() );
// Neumann
gsConstantFunction<> gN( 1.0, mp.geoDim() );
gsBoundaryConditions<> bc;

Then, we extract the basis from the multi patch object to obtain a multi basis.

gsMultiBasis<> mb(mp);

We set the spline degree and the grid size as desired.

for ( size_t i = 0; i < mb.nBases(); ++ i )
mb[i].setDegreePreservingMultiplicity(degree);
for ( index_t i = 0; i < refinements; ++i )
mb.uniformRefine();

Now, we can discuss the IETI system.

We are considering patch-wise formulations of the variational formulation:

\[ \int_{\Omega_k} \nabla u \nabla v d x = \int_{\Omega_k} f v dx \]

Each of these bilinear forms and linear forms lead to matrices and vectors

\[ A_k \qquad\text{and}\qquad \underline f_k. \]

Using the jump matrices \( B_k \), which are provided by the class gsIetiMapper, we obtain the following problem:

\[ \begin{pmatrix} A_1 & & & & B_1^\top \\ & A_2 & & & B_2^\top \\ & &\ddots& & \vdots \\ & & & A_K & B_K^\top \\ B_1 & B_2 &\cdots& B_K & 0 \\ \end{pmatrix} \begin{pmatrix} \underline u_1 \\ \underline u_2 \\ \vdots \\ \underline u_K \\ \underline \lambda \end{pmatrix} = \begin{pmatrix} \underline f_1 \\ \underline f_2 \\ \vdots \\ \underline f_K \\ 0 \end{pmatrix} \]

This is the kind of systems, the class gsIetiSystem is working with. The problem is that – in general – the matrices \( A_k \) are singular. To circumvent this problem, in FETI-dp / IETI-dp methods, we use primal degrees of freedom, like vertex values or edge averages.

These primal dofs are handled by the classes gsPrimalSystem and again gsIetiMapper. The IETI mapper provides vectors \( c_k^{(i)} \) that realize the constraint that the primal dof is set to zero. For each patch, these primal constraints build a matrix \( C_k \). The IETI mapper also provides a vector of indices that associates each of these primal constraints to a (global) index referring to the primal dof.

Finally, we obtain one subspace (the K+1st) that refers to the primal degrees of freedom.

We have the following IETI system:

\[ \begin{pmatrix} \tilde A_1 & & & & \tilde B_1^\top \\ & \tilde A_2 & & & \tilde B_2^\top \\ & & \ddots & & \vdots \\ & & & \tilde A_{K+1} & \tilde B_{K+1}^\top \\ \tilde B_1 & \tilde B_2 & \cdots & \tilde B_{K+1} & 0 \\ \end{pmatrix} \begin{pmatrix} \underline{ \tilde u}_1 \\ \underline{ \tilde u}_2 \\ \vdots \\ \underline{ \tilde u}_{K+1} \\ \underline \lambda \end{pmatrix} = \begin{pmatrix} \underline{ \tilde f}_1 \\ \underline{ \tilde f}_2 \\ \vdots \\ \underline{ \tilde f}_{K+1} \\ 0 \end{pmatrix}, \qquad (*) \]

which is passed to the class gsIetiSystem. For \( k=1,\ldots,K \), we have the local saddle point system

\[ \tilde{A}_k = \begin{pmatrix} A_k & C_k^\top \\ C_k & 0 \end{pmatrix}, \]

the extended jump matrix

\[ \tilde{B}_k = B_k (P_0^{(k)})^\top = \begin{pmatrix} B_k & 0_k \end{pmatrix} \]

and the extended right-hand side

\[ \underline{\tilde f}_k = P_0^{(k)} \underline f_k = \begin{pmatrix} \underline f_k \\ 0 \end{pmatrix}. \]

These modifications are provided by gsPrimalSystem. The class gsPrimalSystem moreover computes the energy minimizing basis functions and represents them as matrices \( \Psi_k = (P_c^{(k)})^\top \tilde{A}_k^{-1} R_c \), where \( R_c=(0,I)^\top \) and \( P_c=(I,0) \). It then computes

\[ \tilde A_{K+1} = \sum_{k=1}^K \Psi_k A_k \Psi_k^\top \quad\text{and}\quad \tilde B_{K+1} = \sum_{k=1}^K B_k \Psi_k^\top \quad\text{and}\quad \underline{\tilde f}_{K+1} = \sum_{k=1}^K \Psi_k \underline f_k. \]

By setting a corresponding flag, it is possible to eliminate the pointwise constraints. A constraint is pointwise the correspoding row of \( C_k \) has only one non-zero entries. Typically, the corner values are pointwise constraints.

Instead of directly solving the IETI-saddle point system (*), we solve the corresponding Schur complement system

\[ F \underline \lambda = \underline g, \]

where

\[ F = \sum_{k=1}^{K+1} \tilde B_k \tilde A_k^{-1} \tilde B_k^\top \quad\text{and}\quad \underline g = \sum_{k=1}^{K+1} \tilde B_k \tilde A_k^{-1} \underline{\tilde f}_k. \]

The Schur complement system is preconditioned with the scaled Dirichlet preconditioner

\[ M_{sD} = \sum_{k=1}^{K} \hat B_k D_k^{-1} S_k D_k^{-1} \hat B_k^\top, \]

where \( S_k \) is the Schur complement of \( A_k \) (not: \(\tilde A_k\)) with respect to the dofs on the interfaces. \( \hat B_k \) is the restriction of \( B_k \) to the dofs on the interfaces. \( D_k \) is a scaling matrix, e.g., based on multiplicity scaling. The class gsScaledDirichletPrec not only collects the contributions of the preconditioner; it also helps with restricting to the skeleton and computing the Schur complement.

In the example file ieti2_example.cpp, we solve the saddle point system (*) with a MINRES solver, preconditioned with a block-diagonal preconditioner, where the last block is \( M_{sD} \).

First, we set up the gsIetiMapper object, which helps us with assembling and keeping track of the dof indices.

gsIetiMapper<> ietiMapper;

For initializing the IETI mapper, it is necessary to provide a gsDofMapper for the global problem and the function values for the eliminated dofs (typically the Dirichlet conditions). If systems are considered, the method it is expected to have one IETI mapper per variable.

For obtaining a gsDofMapper, we use the assembler:

{
typedef gsExprAssembler<>::space space;
gsExprAssembler<> assembler;
space u = assembler.getSpace(mb);
bc.setGeoMap(mp);
u.setup(bc, dirichlet::interpolation, 0);
ietiMapper.init( mb, u.mapper(), u.fixedPart() );
}
gsExprHelper< T >::space space
Space type.
Definition gsExprAssembler.h:67

Now we set the primal degrees of freedom. The following commands compute the (vectors that make up the) constraint matrices \( C_k \) and store them in the gsIetiMapper object.

if (cornersAsPrimals)
ietiMapper.cornersAsPrimals();
if (edgesAsPrimals)
ietiMapper.interfaceAveragesAsPrimals(mp,1);
if (facesAsPrimals)
ietiMapper.interfaceAveragesAsPrimals(mp,2);

The following command computes the jump matrices \( B_k \) and stores them in the gsIetiMapper object.

ietiMapper.computeJumpMatrices(fullyRedundant, noLagrangeMultipliersForCorners);

Now, we set up the classes for the IETI system, the primal system, and the scaled Dirichlet preconditioner.

// The ieti system does not have a special treatment for the
// primal dofs. They are just one more subdomain
gsIetiSystem<> ieti;
ieti.reserve(nPatches+1);
// The scaled Dirichlet preconditioner is independent of the
// primal dofs.
gsScaledDirichletPrec<> prec;
prec.reserve(nPatches);
// Setup the primal system, which needs to know the number of primal dofs.
gsPrimalSystem<> primal(ietiMapper.nPrimalDofs());
if (eliminateCorners)
primal.setEliminatePointwiseConstraints(true);

Now, we assemble the linear system and the right-hand side on each patch using a for loop. We use local versions of the geometry (gsMultiPatch), the basis (gsMultiBasis) and the boundary conditions (gsBoundaryConditions).

The function initFeSpace provides a new dof mapper and the Dirichlet data. This is necessary since it might happen that a 2d-patch touches the Dirichlet boundary just with a corner or that a 3d-patch touches the Dirichlet boundary with a corner or an edge. These cases are not covered by bc.getConditionsForPatch

The example file ieti2_example.cpp shows how to assemble with a classical assembler.

for (index_t k=0; k<nPatches; ++k)
{
// We use the local variants of everything
gsBoundaryConditions<> bc_local;
bc.getConditionsForPatch(k,bc_local);
gsMultiPatch<> mp_local = mp[k];
gsMultiBasis<> mb_local = mb[k];
// The usual stuff for the expression assembler
typedef gsExprAssembler<>::geometryMap geometryMap;
typedef gsExprAssembler<>::variable variable;
typedef gsExprAssembler<>::space space;
typedef gsExprAssembler<>::solution solution;
// We set up the assembler
gsExprAssembler<> assembler(1,1);
// Elements used for numerical integration
assembler.setIntegrationElements(mb_local);
gsExprEvaluator<> ev(assembler);
// Set the geometry map
geometryMap G = assembler.getMap(mp_local);
// Set the discretization space
space u = assembler.getSpace(mb_local);
// Incorporate Dirichlet BC
bc_local.setGeoMap(mp_local);
u.setup(bc_local, dirichlet::interpolation, 0);
// This function provides a new dof mapper and the Dirichlet data
// This is necessary since it might happen that a 2d-patch touches the
// Dirichlet boundary just with a corner or that a 3d-patch touches the
// Dirichlet boundary with a corner or an edge. These cases are not
// covered by bc.getConditionsForPatch
ietiMapper.initFeSpace(u,k);
// Set the source term
auto ff = assembler.getCoeff(f, G);
// Initialize the system
assembler.initSystem();
// Compute the system matrix and right-hand side
assembler.assemble( igrad(u, G) * igrad(u, G).tr() * meas(G), u * ff * meas(G) );
// Add contributions from Neumann conditions to right-hand side
variable g_N = assembler.getBdrFunction();
assembler.assembleBdr(bc_local.get("Neumann"), u * g_N.val() * nv(G).norm() );
// Fetch data
gsSparseMatrix<real_t, RowMajor> jumpMatrix = ietiMapper.jumpMatrix(k);
gsSparseMatrix<> localMatrix = assembler.matrix();
gsMatrix<> localRhs = assembler.rhs();
gsExprHelper< T >::geometryMap geometryMap
Geometry map type.
Definition gsExprAssembler.h:65
gsExprHelper< T >::variable variable
Variable type.
Definition gsExprAssembler.h:66
expr::gsFeSolution< T > solution
Solution type.
Definition gsExprAssembler.h:68

Now, we tell the preconditioner about the local matrix \( A_k \), the local vector \( \underline f_k \) and the matrix \( B_k \). This is done before the class gsPrimalSystem modifies these objects. The following command does all necessary steps:

prec.addSubdomain(
jumpMatrix,
localMatrix,
ietiMapper.skeletonDofs(k)
)
);
static std::pair< JumpMatrix, OpPtr > restrictToSkeleton(const JumpMatrix &jumpMatrix, const SparseMatrix &localMatrix, const std::vector< index_t > &dofs)
Definition gsScaledDirichletPrec.h:195

This can also be done in a step-by-step way, for example if one does not want to use a sparse Cholesky solver (which requires symmetric positive definite problems and would be done by default). This is shown in ieti2_example.cpp.

As next step, we treat the primal dofs. We first compute energy minimizing primal basis functions. Then the following commands amends the variables jumpMatrix, localMatrix, and localRhs, which hold \( B_k \), \( A_k \) and \( \underline f_k \) before calling the function and \( \tilde B_k \), \( \tilde A_k \), and \( \underline{\tilde f}_k \) thereafter. Finally, this function collects the contributions for \( \tilde B_{K+1} \), \( \tilde A_{K+1} \), and \( \underline{\tilde f}_{K+1} \) in the object.

primal.handleConstraints(
ietiMapper.primalConstraints(k),
ietiMapper.primalDofIndices(k),
jumpMatrix,
localMatrix,
localRhs
);

This can also be done in a step-by-step way, for example if one does not want to use a sparse LU solver (which would be done by default). This is shown in the file ieti2_example.cpp.

Now, we add \( \tilde B_k \), \( \tilde A_k \), and \( \underline{\tilde f}_k \) to the IETI-system:

ieti.addSubdomain(
jumpMatrix.moveToPtr(),
makeMatrixOp(localMatrix.moveToPtr()),
give(localRhs)
);
S give(S &x)
Definition gsMemory.h:266

Additionally, one can also provide a solver object that realizes \( \tilde A_k^{-1} \), possibly the one which has already been used for the setup of the bases for the primal doefs. By default, again, a LU solver would be used.

} // end for

Finally, we add \( \tilde B_{K+1} \), \( \tilde A_{K+1} \), and \( \underline{\tilde f}_{K+1} \) to the IETI-system:

if (ietiMapper.nPrimalDofs()>0)
{
// It is not required to provide a local solver to .addSubdomain,
// since a sparse LU solver would be set up on the fly if required.
// Here, we make use of the fact that we can use a Cholesky solver
// because the primal problem is symmetric and positive definite:
= makeSparseCholeskySolver(primal.localMatrix());
ieti.addSubdomain(
primal.jumpMatrix().moveToPtr(),
makeMatrixOp(primal.localMatrix().moveToPtr()),
give(primal.localRhs()),
localSolver
);
}
memory::shared_ptr< gsLinearOperator > Ptr
Shared pointer for gsLinearOperator.
Definition gsLinearOperator.h:33

The gsScaledDirichletPrec can automatically compute the multiplicity scaling. Other scaling matrices can be manually defined.

prec.setupMultiplicityScaling();

Now, we compute the right-hand-side

\[ \underline g = \sum_{k=1}^{K+1} \tilde B_k \tilde A_k^{-1} \underline{\tilde f}_k. \]

gsMatrix<> rhsForSchur = ieti.rhsForSchurComplement();

We choose a random initial guess.

gsMatrix<> lambda;
lambda.setRandom( ieti.nLagrangeMultipliers(), 1 );

As a next step, we solve the problem

\[ F \underline \lambda = \underline g \]

using the scaled Dirichlet preconditioner with a conjugate gradient solver.

gsConjugateGradient<> PCG( ieti.schurComplement(), prec.preconditioner() );
PCG.setOptions( cmd.getGroup("Solver") ).solveDetailed( rhsForSchur, lambda, errorHistory );

So far, we have only computed the Lagrange multipliers \( \underline \lambda \), from which we want to recover the solution.

Here, we first compute the solutions \( \underline{\tilde u}_k \) for \( k=1,\ldots,K+1 \). Here, the first K solutions refer to the patches and have homogenous values for the primal dofs. So, we distribute the primal dof values from the primal problem (= last subdomain) to the patches (=first K subdomains) and obtain the solutions \( \underline u_k \) for \( k=1,\ldots,K \). Then, finally, the IETI mapper is able to combine everything into one solution vector \( \underline u \).

std::vector<gsMatrix<>> uLocal = primal.distributePrimalSolution(
ieti.constructSolutionFromLagrangeMultipliers(lambda)
);
gsMatrix<> uGlobal = ietiMapper.constructGlobalSolutionFromLocalSolutions(uLocal);

Annotated source file

Here is the full file examples/ieti_example.cpp. Clicking on a function or class name will lead you to its reference documentation.

#include <ctime>
#include <gismo.h>
using namespace gismo;
int main(int argc, char *argv[])
{
/************** Define command line options *************/
std::string geometry("domain2d/yeti_mp2.xml");
index_t splitPatches = 1;
real_t stretchGeometry = 1;
index_t refinements = 1;
index_t degree = 2;
std::string boundaryConditions("d");
std::string primals("c");
bool eliminateCorners = false;
real_t tolerance = 1.e-8;
index_t maxIterations = 100;
bool calcEigenvalues = false;
std::string out;
bool plot = false;
gsCmdLine cmd("Solves a PDE with an isogeometric discretization using an isogeometric tearing and interconnecting (IETI) solver.");
cmd.addString("g", "Geometry", "Geometry file", geometry);
cmd.addInt ("", "SplitPatches", "Split every patch that many times in 2^d patches", splitPatches);
cmd.addReal ("", "StretchGeometry", "Stretch geometry in x-direction by the given factor", stretchGeometry);
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", degree);
cmd.addString("b", "BoundaryConditions", "Boundary conditions", boundaryConditions);
cmd.addString("c", "Primals", "Primal constraints (c=corners, e=edges, f=faces)", primals);
cmd.addSwitch("e", "EliminateCorners", "Eliminate corners (if they are primals)", eliminateCorners);
cmd.addReal ("t", "Solver.Tolerance", "Stopping criterion for linear solver", tolerance);
cmd.addInt ("", "Solver.MaxIterations", "Maximum iterations for linear solver", maxIterations);
cmd.addSwitch("", "Solver.CalcEigenvalues","Estimate eigenvalues based on Lanczos", calcEigenvalues);
cmd.addString("", "out", "Write solution and used options to file", out);
cmd.addSwitch( "plot", "Plot the result with Paraview", plot);
try { cmd.getValues(argc,argv); } catch (int rv) { return rv; }
if ( ! gsFileManager::fileExists(geometry) )
{
gsInfo << "Geometry file could not be found.\n";
gsInfo << "I was searching in the current directory and in: " << gsFileManager::getSearchPaths() << "\n";
return EXIT_FAILURE;
}
gsInfo << "Run ieti_example with options:\n" << cmd << std::endl;
/******************* Define geometry ********************/
gsInfo << "Define geometry... " << std::flush;
if (!mpPtr)
{
gsInfo << "No geometry found in file " << geometry << ".\n";
return EXIT_FAILURE;
}
gsMultiPatch<>& mp = *mpPtr;
for (index_t i=0; i<splitPatches; ++i)
{
gsInfo << "split patches uniformly... " << std::flush;
mp = mp.uniformSplit();
}
if (stretchGeometry!=1)
{
gsInfo << "and stretch it... " << std::flush;
for (size_t i=0; i!=mp.nPatches(); ++i)
const_cast<gsGeometry<>&>(mp[i]).scale(stretchGeometry,0);
// Const cast is allowed since the object itself is not const. Stretching the
// overall domain keeps its topology.
}
gsInfo << "done.\n";
/************** Define boundary conditions **************/
gsInfo << "Define right-hand-side and boundary conditions... " << std::flush;
// Right-hand-side
gsFunctionExpr<> f( "2*sin(x)*cos(y)", mp.geoDim() );
// Dirichlet function
gsFunctionExpr<> gD( "sin(x)*cos(y)", mp.geoDim() );
// Neumann
gsConstantFunction<> gN( 1.0, mp.geoDim() );
{
const index_t len = boundaryConditions.length();
index_t i = 0;
for (gsMultiPatch<>::const_biterator it = mp.bBegin(); it < mp.bEnd(); ++it)
{
char b_local;
if ( len == 1 )
b_local = boundaryConditions[0];
else if ( i < len )
b_local = boundaryConditions[i];
else
{
gsInfo << "\nNot enough boundary conditions given.\n";
return EXIT_FAILURE;
}
if ( b_local == 'd' )
else if ( b_local == 'n' )
else
{
gsInfo << "\nInvalid boundary condition given; only 'd' (Dirichlet) and 'n' (Neumann) are supported.\n";
return EXIT_FAILURE;
}
++i;
}
if ( len > i )
gsInfo << "\nToo many boundary conditions have been specified. Ignoring the remaining ones.\n";
gsInfo << "done. "<<i<<" boundary conditions set.\n";
}
/************ Setup bases and adjust degree *************/
gsInfo << "Setup bases and adjust degree... " << std::flush;
for ( size_t i = 0; i < mb.nBases(); ++ i )
mb[i].setDegreePreservingMultiplicity(degree);
for ( index_t i = 0; i < refinements; ++i )
mb.uniformRefine();
gsInfo << "done.\n";
/********* Setup assembler and assemble matrix **********/
gsInfo << "Setup assembler and assemble matrix... " << std::flush;
const index_t nPatches = mp.nPatches();
gsIetiMapper<> ietiMapper;
// We start by setting up a global FeSpace that allows us to
// obtain a dof mapper and the Dirichlet data
{
typedef gsExprAssembler<>::space space;
gsExprAssembler<> assembler;
space u = assembler.getSpace(mb);
bc.setGeoMap(mp);
u.setup(bc, dirichlet::interpolation, 0);
ietiMapper.init( mb, u.mapper(), u.fixedPart() );
}
// Which primal dofs should we choose?
bool cornersAsPrimals = false, edgesAsPrimals = false, facesAsPrimals = false;
for (size_t i=0; i<primals.length(); ++i)
switch (primals[i])
{
case 'c': cornersAsPrimals = true; break;
case 'e': edgesAsPrimals = true; break;
case 'f': facesAsPrimals = true; break;
default:
gsInfo << "\nUnkown type of primal constraint: \"" << primals[i] << "\"\n";
return EXIT_FAILURE;
}
// We tell the ieti mapper which primal constraints we want; calling
// more than one such function is possible.
if (cornersAsPrimals)
ietiMapper.cornersAsPrimals();
if (edgesAsPrimals)
ietiMapper.interfaceAveragesAsPrimals(mp,1);
if (facesAsPrimals)
ietiMapper.interfaceAveragesAsPrimals(mp,2);
// Compute the jump matrices
bool fullyRedundant = true,
noLagrangeMultipliersForCorners = cornersAsPrimals;
ietiMapper.computeJumpMatrices(fullyRedundant, noLagrangeMultipliersForCorners);
// The ieti system does not have a special treatment for the
// primal dofs. They are just one more subdomain
ieti.reserve(nPatches+1);
// The scaled Dirichlet preconditioner is independent of the
// primal dofs.
prec.reserve(nPatches);
// Setup the primal system, which needs to know the number of primal dofs.
gsPrimalSystem<> primal(ietiMapper.nPrimalDofs());
if (eliminateCorners)
primal.setEliminatePointwiseConstraints(true);
for (index_t k=0; k<nPatches; ++k)
{
// We use the local variants of everything
bc.getConditionsForPatch(k,bc_local);
gsMultiPatch<> mp_local = mp[k];
gsMultiBasis<> mb_local = mb[k];
// The usual stuff for the expression assembler
typedef gsExprAssembler<>::geometryMap geometryMap;
typedef gsExprAssembler<>::variable variable;
typedef gsExprAssembler<>::space space;
typedef gsExprAssembler<>::solution solution;
// We set up the assembler
gsExprAssembler<> assembler(1,1);
// Elements used for numerical integration
assembler.setIntegrationElements(mb_local);
gsExprEvaluator<> ev(assembler);
// Set the geometry map
geometryMap G = assembler.getMap(mp_local);
// Set the discretization space
space u = assembler.getSpace(mb_local);
// Incorporate Dirichlet BC
bc_local.setGeoMap(mp_local);
u.setup(bc_local, dirichlet::interpolation, 0);
// This function provides a new dof mapper and the Dirichlet data
// This is necessary since it might happen that a 2d-patch touches the
// Dirichlet boundary just with a corner or that a 3d-patch touches the
// Dirichlet boundary with a corner or an edge. These cases are not
// covered by bc.getConditionsForPatch
ietiMapper.initFeSpace(u,k);
// Set the source term
auto ff = assembler.getCoeff(f, G);
// Initialize the system
assembler.initSystem();
// Compute the system matrix and right-hand side
assembler.assemble( igrad(u, G) * igrad(u, G).tr() * meas(G), u * ff * meas(G) );
// Add contributions from Neumann conditions to right-hand side
variable g_N = assembler.getBdrFunction();
assembler.assembleBdr(bc_local.get("Neumann"), u * g_N.val() * nv(G).norm() );
// Fetch data
gsSparseMatrix<real_t, RowMajor> jumpMatrix = ietiMapper.jumpMatrix(k);
gsSparseMatrix<> localMatrix = assembler.matrix();
gsMatrix<> localRhs = assembler.rhs();
// Add the patch to the scaled Dirichlet preconditioner
jumpMatrix,
localMatrix,
ietiMapper.skeletonDofs(k)
)
);
// This function writes back to jumpMatrix, localMatrix, and localRhs,
// so it must be called after prec.addSubdomain().
primal.handleConstraints(
ietiMapper.primalConstraints(k),
ietiMapper.primalDofIndices(k),
jumpMatrix,
localMatrix,
localRhs
);
// Add the patch to the Ieti system
jumpMatrix.moveToPtr(),
makeMatrixOp(localMatrix.moveToPtr()),
give(localRhs)
);
} // end for
// Add the primal problem if there are primal constraints
if (ietiMapper.nPrimalDofs()>0)
{
// It is not required to provide a local solver to .addSubdomain,
// since a sparse LU solver would be set up on the fly if required.
// Here, we make use of the fact that we can use a Cholesky solver
// because the primal problem is symmetric and positive definite:
= makeSparseCholeskySolver(primal.localMatrix());
primal.jumpMatrix().moveToPtr(),
makeMatrixOp(primal.localMatrix().moveToPtr()),
give(primal.localRhs()),
localSolver
);
}
gsInfo << "done. " << ietiMapper.nPrimalDofs() << " primal dofs.\n";
/**************** Setup solver and solve ****************/
gsInfo << "Setup solver and solve... \n"
" Setup multiplicity scaling... " << std::flush;
// Tell the preconditioner to set up the scaling
gsInfo << "done.\n Setup rhs... " << std::flush;
// Compute the Schur-complement contribution for the right-hand-side
gsMatrix<> rhsForSchur = ieti.rhsForSchurComplement();
gsInfo << "done.\n Setup cg solver for Lagrange multipliers and solve... " << std::flush;
// Initial guess
gsMatrix<> lambda;
lambda.setRandom( ieti.nLagrangeMultipliers(), 1 );
gsMatrix<> errorHistory;
// This is the main cg iteration
PCG.setOptions( cmd.getGroup("Solver") ).solveDetailed( rhsForSchur, lambda, errorHistory );
gsInfo << "done.\n Reconstruct solution from Lagrange multipliers... " << std::flush;
// Now, we want to have the global solution for u
std::vector<gsMatrix<>> uLocal = primal.distributePrimalSolution(
);
gsInfo << "done.\n\n";
/******************** Print end Exit ********************/
const index_t iter = errorHistory.rows()-1;
const bool success = errorHistory(iter,0) < 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";
if (calcEigenvalues)
gsInfo << "Estimated condition number: " << PCG.getConditionNumber() << "\n";
if (!out.empty())
{
std::time_t time = std::time(NULL);
fd.add(cmd);
fd.add(uGlobal);
fd.addComment(std::string("ieti_example Timestamp:")+std::ctime(&time));
fd.save(out);
gsInfo << "Write solution to file " << out << "\n";
}
if (plot)
{
gsInfo << "Write Paraview data to file ieti_result.pvd\n";
for (index_t k=0; k<nPatches; ++k)
mpsol.addPatch( mb[k].makeGeometry( ietiMapper.incorporateFixedPart(k, uLocal[k]) ) );
gsWriteParaview<>( gsField<>( mp, mpsol ), "ieti_result", 1000);
//gsFileManager::open("ieti_result.pvd");
}
if (!plot&&out.empty())
{
gsInfo << "Done. No output created, re-run with --plot to get a ParaView "
"file containing the solution or --out to write solution to xml file.\n";
}
return success ? EXIT_SUCCESS : EXIT_FAILURE;
}
Definition gsExpressions.h:973
Definition gsExpressions.h:928
Class containing a set of boundary conditions.
Definition gsBoundaryConditions.h:342
void getConditionsForPatch(const index_t np, gsBoundaryConditions &result) const
returns the set of all boundary conditions which refer to patch np
Definition gsBoundaryConditions.h:848
void addCondition(int p, boxSide s, condition_type::type t, gsFunctionSet< T > *f, short_t unknown=0, bool parametric=false, int comp=-1)
Adds another boundary condition.
Definition gsBoundaryConditions.h:650
bcRefList get(const std::string &label, const short_t unk=0, int comp=-1) const
Definition gsBoundaryConditions.h:420
void setGeoMap(const gsFunctionSet< T > &gm)
Set the geometry map to evaluate boundary conditions.
Definition gsBoundaryConditions.h:916
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
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
void scale(T s, int coord=-1)
Apply Scaling by factor s.
Definition gsGeometry.h:413
Ieti Mapper.
Definition gsIetiMapper.h:50
Matrix incorporateFixedPart(index_t k, const gsMatrix< T > &localSolution) const
Incorporates fixedPart (eg. from eliminated Dirichlet dofs) to given patch-local solution.
Definition gsIetiMapper.hpp:400
void computeJumpMatrices(bool fullyRedundant, bool excludeCorners)
This function computes the jump matrices.
Definition gsIetiMapper.hpp:297
void interfaceAveragesAsPrimals(const gsMultiPatch< T > &geo, short_t d)
Set up interface averages as primal dofs.
Definition gsIetiMapper.hpp:218
void cornersAsPrimals()
Set up the corners as primal dofs.
Definition gsIetiMapper.hpp:126
void init(const gsMultiBasis< T > &multiBasis, gsDofMapper dofMapperGlobal, const Matrix &fixedPart)
Init the ieti mapper after default construction.
Definition gsIetiMapper.hpp:30
Matrix constructGlobalSolutionFromLocalSolutions(const std::vector< Matrix > &localContribs)
Construct the global solution from a vector of patch-local ones.
Definition gsIetiMapper.hpp:91
const std::vector< index_t > & primalDofIndices(index_t k) const
Returns the indices of the primal dofs that are associated to the primal constraints for the given pa...
Definition gsIetiMapper.h:164
index_t nPrimalDofs() const
Returns the size of the primal problem (number of primal dofs)
Definition gsIetiMapper.h:154
std::vector< index_t > skeletonDofs(index_t patch) const
Returns a list of dofs that are (on the coarse level) coupled.
Definition gsIetiMapper.hpp:282
const JumpMatrix & jumpMatrix(index_t k) const
Returns the jump matrix for the given patch Only available after computeJumpMatrices has been called...
Definition gsIetiMapper.h:168
const std::vector< SparseVector > & primalConstraints(index_t k) const
Returns the primalConstraints (as vectors) for the given patch.
Definition gsIetiMapper.h:160
void initFeSpace(typename gsExprAssembler< T >::space u, index_t k)
Apply the required changes to a space object of the expression assembler.
Definition gsIetiMapper.h:130
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
Holds a set of patch-wise bases and their topology information.
Definition gsMultiBasis.h:37
Container class for a set of geometry patches and their topology, that is, the interface connections ...
Definition gsMultiPatch.h:100
index_t addPatch(typename gsGeometry< T >::uPtr g)
Add a patch from a gsGeometry<T>::uPtr.
Definition gsMultiPatch.hpp:211
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
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
Main header to be included by clients using the G+Smo library.
EIGEN_STRONG_INLINE meas_expr< T > meas(const gsGeometryMap< T > &G)
The measure of a geometry map.
Definition gsExpressions.h:4555
EIGEN_STRONG_INLINE onormal_expr< T > nv(const gsGeometryMap< T > &u)
The (outer pointing) boundary normal of a geometry map.
Definition gsExpressions.h:4506
The G+Smo namespace, containing all definitions for the library.
@ dirichlet
Dirichlet type.
Definition gsBoundaryConditions.h:31
@ neumann
Neumann type.
Definition gsBoundaryConditions.h:33