After reading the command line arguments, we define the problem specification (domain, right-hand side, function for boundary conditions, functions for CDR equation) for the chosen benchmark problem (option –Benchmark
).
gsFunctionExpr<> sol_exact, rhs_exact, coeff_diff, coeff_conv, coeff_reac;
gsMultiPatch<> mp;
gsInfo << "|| Benchmark information ||\n";
switch (numBenchmark)
{
case 1:
gsInfo << "1) CDR-equation the unit square\n";
mp = gsMultiPatch<>(*gsNurbsCreator<>::BSplineSquare(1.0, 0.0, 0.0));
sol_exact = gsFunctionExpr<>("sin(pi*x)*sin(pi*y)",2);
rhs_exact = gsFunctionExpr<>("1.2*pi*pi*sin(pi*x)*sin(pi*y)+0.9*pi*pi*sin(pi*x)*sin(pi*y)+0.7*pi*pi*cos(pi*x)*cos(pi*y)"
" + 0.4*pi*pi*cos(pi*x)*cos(pi*y) +0.4*pi*cos(pi*x)*sin(pi*y)-0.2*pi*sin(pi*x)*cos(pi*y)+0.3*sin(pi*x)*sin(pi*y)", 2);
coeff_diff = gsFunctionExpr<>("1.2","-0.7","-0.4","0.9",2);
coeff_conv = gsFunctionExpr<>("0.4","-0.2",2);
coeff_reac = gsFunctionExpr<>("0.3",2);
break;
case 2:
gsInfo << "2) Poisson equation on the quarter annulus\n";
sol_exact = gsFunctionExpr<>( "-(x*x+y*y-1)*(x*x+y*y-4)*x*y*y", 2);
rhs_exact = gsFunctionExpr<>( "2*x*(22*x*x*y*y+21*y*y*y*y-45*y*y+x*x*x*x-5*x*x+4)", 2);
coeff_diff = gsFunctionExpr<>("1","0","0","1",2);
coeff_conv = gsFunctionExpr<>("0","0",2);
coeff_reac = gsFunctionExpr<>("0",2);
break;
case 3:
gsInfo << "3) Poisson equation on the quarter annulus\n";
sol_exact = gsFunctionExpr<>( "(x^2+y^2-3*sqrt(x^2+y^2)+2)*sin(2*atan(y/x))", 2);
rhs_exact = gsFunctionExpr<>( "(8-9*sqrt(x^2 + y^2))*sin(2*atan(y/x))/(x^2+y^2)", 2);
coeff_diff = gsFunctionExpr<>("1","0","0","1",2);
coeff_conv = gsFunctionExpr<>("0","0",2);
coeff_reac = gsFunctionExpr<>("0",2);
break;
case 4:
gsInfo << "4) Poisson equation on an L-shaped domain\n";
sol_exact = gsFunctionExpr<>( "if ( y>0, ( (x^2+y^2)^(1.0/3.0) )*sin( (2*atan2(y,x) - pi)/3.0 ), ( (x^2+y^2)^(1.0/3.0) )*sin( (2*atan2(y,x) +3*pi)/3.0 ) )", 2);
rhs_exact = gsFunctionExpr<>( "0", 2);
coeff_diff = gsFunctionExpr<>("1","0","0","1",2);
coeff_conv = gsFunctionExpr<>("0","0",2);
coeff_reac = gsFunctionExpr<>("0",2);
break;
case 5:
gsInfo << "5) Poisson equation on the unit cube\n";
sol_exact = gsFunctionExpr<>( "sin(pi*x)*sin(pi*y)*sin(pi*z)", 3);
rhs_exact = gsFunctionExpr<>( "(3*pi^2)*sin(pi*x)*sin(pi*y)*sin(pi*z)", 3);
coeff_diff = gsFunctionExpr<>("1","0","0","0","1","0","0","0","1",3);
coeff_conv = gsFunctionExpr<>("0","0","0",3);
coeff_reac = gsFunctionExpr<>("0",3);
break;
case 6:
gsInfo << "6) Poisson's equation on Yeti footprint\n";
mp = *static_cast<gsMultiPatch<>::uPtr>(gsReadFile<>("domain2d/yeti_mp2.xml"));
sol_exact = gsFunctionExpr<>( "sin(5*pi*x)*sin(5*pi*y)", 2);
rhs_exact = gsFunctionExpr<>( "(50*pi^2)*sin(5*pi*x)*sin(5*pi*y)", 2);
coeff_diff = gsFunctionExpr<>("1","0","0","1",2);
coeff_conv = gsFunctionExpr<>("0","0",2);
coeff_reac = gsFunctionExpr<>("0",2);
break;
default:
gsInfo << "Unknown benchmark case chosen.\n";
return EXIT_FAILURE;
}
The command line option –PatchSplits
\(i\) allows to apply \(i\) uniform splits of each patch into \( 2^d \) sub-patches.
for (
index_t i=0; i<numSplits; ++i)
mp = mp.uniformSplit();
We define the boundary conditions object with the function containing the corresponding Dirichlet data.
gsBoundaryConditions<> bcInfo;
gsFunctionExpr<> zero("0", mp.geoDim());
for (gsMultiPatch<>::const_biterator bit = mp.bBegin(); bit != mp.bEnd(); ++bit)
bcInfo.setGeoMap(mp);
The setup of the multigrid hierarchy is given by the command line parameters –Levels
, –Projection
and –Coarsening
.
With –Levels
, the number of levels \(L\) is specified.
Using –Coarsening
, one can specify \(L-1\) values for the coarsening (the leftmost entry corresponds to the coarsest level): h
: standard grid coarsening (as by uniform grid refinement; increase grid size by factor 2) p
: reduce spline degree and smoothness (keep grid as-is) z
: reduce spline degree and smoothness and grid coarsening
The choice –Projection 1
means that p
and z
refer to a direct coarsening from degree \(p\) to degree 1. The choice –Projection 2
means that p
and z
refer to a reduction of the spline degree by 1, like from \(p\) to \(p-1\).
If –Projection 1
, there can only be one single p
or z
coarsening.
If –Coarsening
is not specified: in case of –Projection 1
, the default is h...hz
, meaning that we have degree 1 for the levels \(\ell=1,\ldots,L-1\) and the degree specified by –Degree
for the finest level \(L\). All levels refer to different grid sizes. in case of –Projection 2
, the default is h...hz...z
, meaning that we have the degree specified by –Degree
for the finest level \(L\). On each next coarser level, the degree is reduced by 1. After reaching degree 1, further coarsening (if still necessary) is only of type h
. All levels refer to different grid sizes.
if (typeCoarsening.empty())
{
if (typeProjection==1)
{
typeCoarsening = std::string(numLevels-2, 'h') + "z";
}
else
{
if (numLevels>numDegree)
typeCoarsening = std::string(numLevels-numDegree, 'h') + std::string(numDegree-1, 'z');
else
typeCoarsening = std::string(numLevels-1, 'z');
}
}
We now compute the parameters for the coarsest grid such that the finest grid is as prescribed using the parameters –Degree
and –Refinement
.
std::vector<gsMultiBasis<>> bases;
bases.emplace_back(mp);
const index_t degreeOffset = numDegree - (numRefP+numRefZ) * (typeProjection==1?(numDegree-1):1) - bases[0].degree();
if (degreeOffset>0)
bases[0].degreeIncrease(degreeOffset);
else if (degreeOffset<0)
bases[0].degreeReduce(-degreeOffset);
for (
index_t i = 0; i < numRefine - numRefH - numRefZ; ++i)
bases[0].uniformRefine();
The finer grids are then constructed as indicated using the parameters –Coarsening
and –Projection
.
for (
index_t i = 1; i < numLevels; i++)
{
bases.push_back(bases.back());
if (typeCoarsening[i-1] == 'p')
{
if (typeProjection == 1)
bases.back().degreeIncrease(numDegree-1);
else
bases.back().degreeIncrease();
}
else if (typeCoarsening[i-1] == 'h')
{
bases.back().uniformRefine();
}
else
{
bases.back().uniformRefine();
if (typeProjection == 1)
bases.back().degreeIncrease(numDegree-1);
else
bases.back().degreeIncrease();
}
}
The next step is the setup of the refinement and prolongation operators. In case of h-coarsening, the prolongation is the canonical embedding matrix and the restriction is its transpose.
In case of p- and z-coarsening, the method depends on the lumping type specified by –Lumping
.
In case of lumping type 1, the restriction and the prolongation are the corresponding \(L_2\)-orthogonal projectors, given by
\[ M_{\ell-1}^{-1} M_{\ell-1,\ell} \quad\mbox{and}\quad M_{\ell}^{-1} M_{\ell,\ell-1} \]
respectively. Here, \( M_{\ell} \) and \( M_{\ell-1} \) are the mass matrices on those grid levels (assembleMass). \( M_{\ell-1,\ell} = M_{\ell,\ell-1}^\top \) is obtained by deriving a mass matrix with the basis functions for the level \(\ell-1\) as trial functions and the basis functions for the level \(\ell\) as test functions (assembleMixedMass). The realization of the inverses is done by applying a conjugate gradient solver.
In case of lumping type 2, the matrices \( M_{\ell} \) and \( M_{\ell-1} \) are replaced by diagonal matrices, where the diagonal entries coincide with the row-sum of the \( M_{\ell} \) or \( M_{\ell-1} \), respectively. Using the partition-of-unity property, these diagonal entries are obtained by evaluating \( (\cdot,1)_{L_2(\Omega)} \) for the basis functions (assembleLumpedMass).
gsInfo << "|| Multigrid hierarchy ||\n";
for (
index_t i=1; i<numLevels; i++)
{
if (typeCoarsening[i-1] != 'h')
{
if (typeLumping == 1)
{
gsInfo << "Restriction and prolongation between levels " << i << " and " << i-1 << ": L2-projection with lumped mass.\n";
gsSparseMatrix<> mixedMass = assembleMixedMass(mp, bases[i], bases[i-1], bcInfo, opt);
gsSparseMatrix<real_t> prolongationMatrix
= assembleLumpedMass(mp, bases[i], bcInfo, opt).asDiagonal().inverse()
* mixedMass;
gsSparseMatrix<real_t> restrictionMatrix
= assembleLumpedMass(mp, bases[i-1], bcInfo, opt).asDiagonal().inverse()
* mixedMass.transpose();
prolongation[i-1] = makeMatrixOp(prolongationMatrix.moveToPtr());
restriction[i-1] = makeMatrixOp(restrictionMatrix.moveToPtr());
}
else
{
gsInfo << "Restriction and prolongation between levels " << i << " and " << i-1 << ": exact L2-projection.\n";
gsSparseMatrix<> prolongationP = assembleMixedMass(mp, bases[i], bases[i-1], bcInfo, opt);
gsSparseMatrix<> restrictionP = prolongationP.transpose();
gsSparseMatrix<> prolongationM = assembleMass(mp, bases[i], bcInfo, opt);
gsSparseMatrix<> restrictionM = assembleMass(mp, bases[i-1], bcInfo, opt);
prolongation[i-1] = makeLinearOp(
[prolongationP=
give(prolongationP),prolongationM=
give(prolongationM)]
(const gsMatrix<>& Xcoarse, gsMatrix<>& Xfine)
{
gsConjugateGradient<> CGSolver(prolongationM);
CGSolver.setTolerance(1e-12);
CGSolver.solve(prolongationP*Xcoarse,Xfine);
},
prolongationP.rows(), prolongationP.cols()
);
restriction[i-1] = makeLinearOp(
[restrictionP=
give(restrictionP),restrictionM=
give(restrictionM)]
(const gsMatrix<>& Xfine, gsMatrix<>& Xcoarse)
{
gsConjugateGradient<> CGSolver(restrictionM);
CGSolver.setTolerance(1e-12);
CGSolver.solve(restrictionP*Xfine,Xcoarse);
},
restrictionP.rows(), restrictionP.cols()
);
}
}
else
{
gsInfo << "Restriction and prolongation between levels " << i << " and " << i-1 << ": canonical embedding and its transpose.\n";
bases[i].clone()->uniformCoarsen_withTransfer(prolongation_H[i-1],bcInfo,opt);
prolongation[i-1] = makeMatrixOp(prolongation_H[i-1]);
restriction[i-1] = makeMatrixOp(prolongation_H[i-1].transpose());
}
}
For a p-multigrid method, one has to assemble the problem for the individual levels. The assembler is used to derive the stiffness matrix for the finest level, the spline degree on the corresponding level is different to the level on the next finer level, or rediscretization is explicitly requested (using –CoarseOperator 1
). Otherwise, Galerkin projections are used.
for (
index_t i=numLevels-1; i>=0; --i)
{
if (typeCoarseOperator == 1 || i == numLevels-1 || typeCoarsening[i] != 'h')
{
gsInfo << "Assemble on level " << i << ": ";
gsCDRAssembler<real_t> assembler(
mp,
bases[i],
bcInfo,
rhs_exact,
coeff_diff, coeff_conv, coeff_reac,
(dirichlet::strategy)opt.
getInt(
"DirichletStrategy"),
iFace::glue
);
matrices[i] = assembler.
matrix();
if (i==numLevels-1)
Time_Assembly += clock.
stop();
gsInfo << "Degree: " << bases[i].degree() << ", Ndof: " << matrices[i].rows() << "\n";
}
else
{
gsInfo << "Apply Galerkin projection on level " << i << ": ";
matrices[i] = prolongation_H[i].transpose() * matrices[i+1] * prolongation_H[i];
Time_Galerkin += clock.
stop();
gsInfo << "Ndof: " << matrices[i].rows() << "\n";
}
operators[i] = makeMatrixOp(matrices[i]);
}
gsSparseMatrix<>& matrix = matrices.back();
Now, we are able to setup the gsMultiGrid object. This class has two kinds of constructors: stiffness matrix on the finest grid and vector of prolongation matrices (all as gsSparseMatrix) vectors of all the stiffness matrices, the restriction operators and the prolongation operators (all as gsLinearOperator::Ptr).
In the first case, the stiffness matrices on the finer grids are derived automatically using a Galerkin projection.
Since we have a vector of stiffness matrices and since our restrictions and prolongations might not be just matrices, we need to use the second option.
We setup the coarse solver exactly since the "matrix-free" version of gsMultiGridOp does not setup the coarse solver automatically.
mg->setCoarseSolver(makeSparseLUSolver(matrices[0]));
Next, we choose the type of the cycle (1=V-cycle, 2=W-cycle). Here, we can use different options, depending if the coarsening is a h-coarsening or a coarsening which involves a change of the degree (p- or z-coarsening).
if (typeCycle_p == typeCycle_h)
gsInfo << "Multigrid cycle type: " << typeCycle_h << "\n";
else
gsInfo << "Multigrid cycle type: " << typeCycle_h << " for h-refinement and " << typeCycle_p << " for p- and z-refinement\n";
for (
index_t i=1; i<numLevels-1; i++)
{
if (typeCoarsening[i] != 'h')
mg->setNumCycles(i,typeCycle_p);
else
mg->setNumCycles(i,typeCycle_h);
}
The next step is the setup of the smoothers. As smoothers, one can choose an incomplete LU smoother (–Smoother 1
), a blockwise incomplete LU smoother (–Smoother 4
), the subspace corrected mass smoother (–Smoother 3
; see multiGrid_example.cpp) the Gauss-Seidel smoother (–Smoother 2
). For the levels with spline degree 1, always the Gauss-Seidel smoother is used. For the setup, we use a for loop over the levels 1,...,numLevels. On level 0, we use a direct solver.
Moreover, we specify the number of pre and post smoothing steps (default: 1), as given via command –Smoothing
.
If a conjugate gradient solver (–Solver 3
) is used, the post smoothing operator is the transpose of the pre smoothing. Otherwise, we choose the post smoothing to be equal to the pre smoothing. This is obtained with by setting setSymmSmooth to false.
for (
index_t i=1; i<numLevels; i++)
{
if (bases[i].degree()==1)
{
mg->setSmoother(i,makeGaussSeidelOp(matrices[i]));
gsInfo << "Smoother for level " << i << ": Gauss-Seidel\n";
}
else switch (typeSmoother)
{
case 1:
mg->setSmoother(i,makeIncompleteLUOp(matrices[i]));
gsInfo << "Smoother for level " << i << ": Incomplete LU\n";
break;
case 2:
mg->setSmoother(i,makeGaussSeidelOp(matrices[i]));
gsInfo << "Smoother for level " << i << ": Gauss-Seidel\n";
break;
case 3:
mg->setSmoother(i,setupSubspaceCorrectedMassSmoother(matrices[i], bases[i], bcInfo, opt));
gsInfo << "Smoother for level " << i << ": Subspace corrected mass smoother (damping = "<<dampingSCMS<<")\n";
break;
case 4:
mg->setSmoother(i,setupBlockILUT(matrices[i], bases[i], bcInfo, opt));
gsInfo << "Smoother for level " << i << ": Blockwise Incomplete LU\n";
break;
default:
gsInfo << "Unknown smoother chosen.\n";
return EXIT_FAILURE;
}
}
gsInfo << "Number of smoothing steps: " << numSmoothing << "\n";
mg->setNumPreSmooth(numSmoothing);
mg->setNumPostSmooth(numSmoothing);
gsInfo << "Post smoothing is transpose of pre smoothing: " << (typeSolver == 3?"yes":"no") << "\n";
mg->setSymmSmooth(typeSolver == 3);
Next, we initialize the chosen iterative solver.
gsIterativeSolver<>::Ptr solver;
switch (typeSolver)
{
case 1:
gsInfo << "\n|| Solver information ||\np-multigrid is applied as stand-alone solver\n";
break;
case 2:
gsInfo << "\n|| Solver information ||\nBiCGStab is applied as solver, p-multigrid as a preconditioner\n";
break;
case 3:
gsInfo << "\n|| Solver information ||\nCG is applied as solver, p-multigrid as a preconditioner\n";
break;
default:
gsInfo << "Unknown iterative solver chosen.\n";
return EXIT_FAILURE;
}
We choose a random initial guess.
gsMatrix<> x = gsMatrix<>::Random(matrix.rows(),1);
Finally, we use the iterative solver to solve the problem. We use solveDetailed with an errorHistory
in order to be able to give the convergence behavior.
solver->solveDetailed( rhs, x, error_history );
Annotated source file
Here is the full file examples/pMultiGrid_example.cpp
. Clicking on a function or class name will lead you to its reference documentation.
#include <string>
using namespace gismo;
);
);
);
);
);
int main(
int argc,
char* argv[])
{
std::string typeCoarsening;
real_t dampingSCMS = 1;
real_t tol = 1e-8;
gsCmdLine cmd(
"This program solves the CDR-equation with a p-multigrid or h-multigrid method");
cmd.addInt("b", "Benchmark", "Number of the benchmark problem", numBenchmark);
cmd.addInt("P", "PatchSplits", "Number of patch splittings: split each patch that many times into 2^d patches", numSplits);
cmd.addInt("p", "Degree", "Spline degree for finest grid", numDegree);
cmd.addInt("r", "Refinement", "Number of global refinements to obtain finest grid", numRefine);
cmd.addInt("d", "BCHandling", "Handle Dirichlet BC's by (1) elimination or (2) Nitsche's method", typeBCHandling);
cmd.addInt("l", "Levels", "Number of levels for multigrid", numLevels);
cmd.addInt("D", "Projection", "A p or z coarsening refers to (1) changing the degree directly to 1 or (2) reducing the degree by 1", typeProjection);
cmd.addString("z", "Coarsening", "Coarsening strategy for each of the levels: (h) coarser grid, (p) reduced spline degree or (z) both", typeCoarsening);
cmd.addInt("L", "Lumping", "Restriction and prolongation performed with the (1) lumped or (2) full mass matrix", typeLumping);
cmd.addInt("G", "CoarseOperator", "Derive coarse stiffness matrix (1) by rediscretization or (2) using Galerkin projection if possible", typeCoarseOperator);
cmd.addInt("m", "Cycle_p", "Type of cycle where p or z-coarsening is applied: (1) V-cycle or (2) W-cycle", typeCycle_p);
cmd.addInt("M", "Cycle_h", "Type of cycle where h-coarsening is applied: (1) V-cycle or (2) W-cycle", typeCycle_h);
cmd.addInt("S", "Smoother", "Smoother: (1) ILUT, (2) Gauss-Seidel, (3) subspace corrected mass smoother or (4) Block ILUT", typeSmoother);
cmd.addInt("v", "Smoothing", "Number of pre and post smoothing steps", numSmoothing);
cmd.addReal("", "DampingSCMS", "Damping for subspace corrected mass smoother (otherwise ignored)", dampingSCMS);
cmd.addInt("s", "Solver", "Solver: (1) mg as stand-alone solver, (2) BiCGStab prec. with mg or (3) CG prec. with mg", typeSolver);
cmd.addReal("", "Tolerance", "Threshold for iterative solver", tol);
cmd.addInt("", "MaxIter", "Maximum number of iterations for iterative solver", maxIter);
try { cmd.getValues(argc,argv); } catch (int rv) { return rv; }
if (numDegree<=0)
{
gsInfo << "--Degree has to be a positive value.\n";
return EXIT_FAILURE;
}
if (numRefine<0)
{
gsInfo << "--Refinement has to be a non-negative value.\n";
return EXIT_FAILURE;
}
if (numSmoothing<0)
{
gsInfo << "--Smoothing has to be a non-negative value.\n";
return EXIT_FAILURE;
}
if (numLevels<=0)
{
gsInfo << "--Levels has to be a positive value.\n";
return EXIT_FAILURE;
}
if (numSplits<0)
{
gsInfo << "--PatchSplits has to be a non-negative value.\n";
return EXIT_FAILURE;
}
if (typeCycle_p<=0 || typeCycle_h<=0)
{
gsInfo << "--Cycle_p and Cycle_h have to be a positvie value.\n";
return EXIT_FAILURE;
}
switch (typeBCHandling)
{
case 1:
opt.
addInt(
"DirichletStrategy",
"",dirichlet::elimination);
break;
case 2:
opt.
addInt(
"DirichletStrategy",
"",dirichlet::nitsche );
break;
default:
gsInfo << "Unknown boundary condition handling type chosen.\n";
return EXIT_FAILURE;
}
if (typeLumping < 1 || typeLumping > 2)
{
gsInfo << "Unknown lumping type chosen.\n";
return -1;
}
if (typeProjection < 1 || typeProjection > 2)
{
gsInfo << "Unknown projection type chosen.\n";
return -1;
}
if (typeCoarseOperator < 1 || typeCoarseOperator > 2)
{
gsInfo << "Unknown coarse operator chosen.\n";
return -1;
}
gsInfo << "|| Benchmark information ||\n";
switch (numBenchmark)
{
case 1:
gsInfo << "1) CDR-equation the unit square\n";
rhs_exact =
gsFunctionExpr<>(
"1.2*pi*pi*sin(pi*x)*sin(pi*y)+0.9*pi*pi*sin(pi*x)*sin(pi*y)+0.7*pi*pi*cos(pi*x)*cos(pi*y)"
" + 0.4*pi*pi*cos(pi*x)*cos(pi*y) +0.4*pi*cos(pi*x)*sin(pi*y)-0.2*pi*sin(pi*x)*cos(pi*y)+0.3*sin(pi*x)*sin(pi*y)", 2);
break;
case 2:
gsInfo << "2) Poisson equation on the quarter annulus\n";
rhs_exact =
gsFunctionExpr<>(
"2*x*(22*x*x*y*y+21*y*y*y*y-45*y*y+x*x*x*x-5*x*x+4)", 2);
break;
case 3:
gsInfo << "3) Poisson equation on the quarter annulus\n";
sol_exact =
gsFunctionExpr<>(
"(x^2+y^2-3*sqrt(x^2+y^2)+2)*sin(2*atan(y/x))", 2);
rhs_exact =
gsFunctionExpr<>(
"(8-9*sqrt(x^2 + y^2))*sin(2*atan(y/x))/(x^2+y^2)", 2);
break;
case 4:
gsInfo << "4) Poisson equation on an L-shaped domain\n";
sol_exact =
gsFunctionExpr<>(
"if ( y>0, ( (x^2+y^2)^(1.0/3.0) )*sin( (2*atan2(y,x) - pi)/3.0 ), ( (x^2+y^2)^(1.0/3.0) )*sin( (2*atan2(y,x) +3*pi)/3.0 ) )", 2);
break;
case 5:
gsInfo << "5) Poisson equation on the unit cube\n";
break;
case 6:
gsInfo << "6) Poisson's equation on Yeti footprint\n";
break;
default:
gsInfo << "Unknown benchmark case chosen.\n";
return EXIT_FAILURE;
}
gsInfo << "Exact solution: " << sol_exact << "\n";
gsInfo << "Right hand side: " << rhs_exact << "\n";
gsInfo << "Handling of boundary conditions: " << (typeBCHandling==1 ? "elimination" : "Nitsche") << "\n";
for (
index_t i=0; i<numSplits; ++i)
gsInfo <<
"Number of patches: " << mp.
nPatches() <<
"\n\n";
if (typeCoarsening.empty())
{
if (typeProjection==1)
{
typeCoarsening = std::string(numLevels-2, 'h') + "z";
}
else
{
if (numLevels>numDegree)
typeCoarsening = std::string(numLevels-numDegree, 'h') + std::string(numDegree-1, 'z');
else
typeCoarsening = std::string(numLevels-1, 'z');
}
}
if (typeCoarsening.size() != (size_t)numLevels-1)
{
gsInfo << "The string provided to --Coarsening should have length " << numLevels-1 << "\n";
return EXIT_FAILURE;
}
index_t numRefH = 0, numRefP = 0, numRefZ = 0;
for (
index_t i = 0; i < numLevels-1; ++i)
{
if ( typeCoarsening[i] == 'h')
numRefH++;
else if ( typeCoarsening[i] == 'p')
numRefP++;
else if ( typeCoarsening[i] == 'z')
numRefZ++;
else
{
gsInfo << "Unknown type given in --Coarsening.\n";
return EXIT_FAILURE;
}
}
if (typeProjection==1 && numRefP+numRefZ > 1)
{
gsInfo << "If --Projection 1, there should be only one coarsening of type p or z in --Coarsening.\n";
return EXIT_FAILURE;
}
if (numRefine - numRefH - numRefZ<0)
{
gsInfo << "Cannot have more h or z refinements in --Coarsening than overall number of refinement steps.\n";
return EXIT_FAILURE;
}
std::vector<gsMultiBasis<>> bases;
bases.emplace_back(mp);
const index_t degreeOffset = numDegree - (numRefP+numRefZ) * (typeProjection==1?(numDegree-1):1) - bases[0].degree();
if (degreeOffset>0)
bases[0].degreeIncrease(degreeOffset);
else if (degreeOffset<0)
bases[0].degreeReduce(-degreeOffset);
for (
index_t i = 0; i < numRefine - numRefH - numRefZ; ++i)
bases[0].uniformRefine();
for (
index_t i = 1; i < numLevels; i++)
{
bases.push_back(bases.back());
if (typeCoarsening[i-1] == 'p')
{
if (typeProjection == 1)
bases.back().degreeIncrease(numDegree-1);
else
bases.back().degreeIncrease();
}
else if (typeCoarsening[i-1] == 'h')
{
bases.back().uniformRefine();
}
else
{
bases.back().uniformRefine();
if (typeProjection == 1)
bases.back().degreeIncrease(numDegree-1);
else
bases.back().degreeIncrease();
}
}
std::vector<gsSparseMatrix<real_t,RowMajor>> prolongation_H(numLevels-1);
std::vector<gsLinearOperator<>::Ptr> restriction(numLevels-1);
std::vector<gsLinearOperator<>::Ptr> prolongation(numLevels-1);
gsInfo << "|| Multigrid hierarchy ||\n";
for (
index_t i=1; i<numLevels; i++)
{
if (typeCoarsening[i-1] != 'h')
{
if (typeLumping == 1)
{
gsInfo << "Restriction and prolongation between levels " << i << " and " << i-1 << ": L2-projection with lumped mass.\n";
gsSparseMatrix<> mixedMass = assembleMixedMass(mp, bases[i], bases[i-1], bcInfo, opt);
= assembleLumpedMass(mp, bases[i], bcInfo, opt).asDiagonal().inverse()
* mixedMass;
= assembleLumpedMass(mp, bases[i-1], bcInfo, opt).asDiagonal().inverse()
* mixedMass.transpose();
prolongation[i-1] = makeMatrixOp(prolongationMatrix.
moveToPtr());
restriction[i-1] = makeMatrixOp(restrictionMatrix.
moveToPtr());
}
else
{
gsInfo << "Restriction and prolongation between levels " << i << " and " << i-1 << ": exact L2-projection.\n";
gsSparseMatrix<> prolongationP = assembleMixedMass(mp, bases[i], bases[i-1], bcInfo, opt);
prolongation[i-1] = makeLinearOp(
[prolongationP=
give(prolongationP),prolongationM=
give(prolongationM)]
{
CGSolver.setTolerance(1e-12);
CGSolver.solve(prolongationP*Xcoarse,Xfine);
},
prolongationP.rows(), prolongationP.cols()
);
restriction[i-1] = makeLinearOp(
[restrictionP=
give(restrictionP),restrictionM=
give(restrictionM)]
{
CGSolver.setTolerance(1e-12);
CGSolver.solve(restrictionP*Xfine,Xcoarse);
},
restrictionP.rows(), restrictionP.cols()
);
}
}
else
{
gsInfo << "Restriction and prolongation between levels " << i << " and " << i-1 << ": canonical embedding and its transpose.\n";
bases[i].clone()->uniformCoarsen_withTransfer(prolongation_H[i-1],bcInfo,opt);
prolongation[i-1] = makeMatrixOp(prolongation_H[i-1]);
restriction[i-1] = makeMatrixOp(prolongation_H[i-1].transpose());
}
}
double Time_Transfer = clock.
stop();
gsInfo << "\n|| Assembling ||\n";
std::vector<gsSparseMatrix<>> matrices(numLevels);
std::vector<gsLinearOperator<>::Ptr> operators(numLevels);
double Time_Assembly = 0, Time_Galerkin = 0;
for (
index_t i=numLevels-1; i>=0; --i)
{
if (typeCoarseOperator == 1 || i == numLevels-1 || typeCoarsening[i] != 'h')
{
gsInfo << "Assemble on level " << i << ": ";
mp,
bases[i],
bcInfo,
rhs_exact,
coeff_diff, coeff_conv, coeff_reac,
(dirichlet::strategy)opt.
getInt(
"DirichletStrategy"),
iFace::glue
);
matrices[i] = assembler.
matrix();
if (i==numLevels-1)
Time_Assembly += clock.
stop();
gsInfo << "Degree: " << bases[i].degree() << ", Ndof: " << matrices[i].rows() << "\n";
}
else
{
gsInfo << "Apply Galerkin projection on level " << i << ": ";
matrices[i] = prolongation_H[i].transpose() * matrices[i+1] * prolongation_H[i];
Time_Galerkin += clock.
stop();
gsInfo << "Ndof: " << matrices[i].rows() << "\n";
}
operators[i] = makeMatrixOp(matrices[i]);
}
double Time_Coarse_Solver_Setup = clock.
stop();
if (typeCycle_p == typeCycle_h)
gsInfo << "Multigrid cycle type: " << typeCycle_h << "\n";
else
gsInfo << "Multigrid cycle type: " << typeCycle_h << " for h-refinement and " << typeCycle_p << " for p- and z-refinement\n";
for (
index_t i=1; i<numLevels-1; i++)
{
if (typeCoarsening[i] != 'h')
else
}
gsInfo << "\n|| Smoother ||\n";
opt.
addReal(
"Damping",
"",dampingSCMS);
for (
index_t i=1; i<numLevels; i++)
{
if (bases[i].degree()==1)
{
gsInfo << "Smoother for level " << i << ": Gauss-Seidel\n";
}
else switch (typeSmoother)
{
case 1:
gsInfo << "Smoother for level " << i << ": Incomplete LU\n";
break;
case 2:
gsInfo << "Smoother for level " << i << ": Gauss-Seidel\n";
break;
case 3:
mg->
setSmoother(i,setupSubspaceCorrectedMassSmoother(matrices[i], bases[i], bcInfo, opt));
gsInfo << "Smoother for level " << i << ": Subspace corrected mass smoother (damping = "<<dampingSCMS<<")\n";
break;
case 4:
mg->
setSmoother(i,setupBlockILUT(matrices[i], bases[i], bcInfo, opt));
gsInfo << "Smoother for level " << i << ": Blockwise Incomplete LU\n";
break;
default:
gsInfo << "Unknown smoother chosen.\n";
return EXIT_FAILURE;
}
}
gsInfo << "Number of smoothing steps: " << numSmoothing << "\n";
gsInfo << "Post smoothing is transpose of pre smoothing: " << (typeSolver == 3?"yes":"no") << "\n";
double Time_Smoother_Setup = clock.
stop();
gsInfo << "\n|| Setup Timings || \n";
gsInfo << "Total transfer setup time: " << Time_Transfer << "\n";
gsInfo << "Total Assembly time: " << Time_Assembly << "\n";
gsInfo << "Total Galerkin projections: " << Time_Galerkin << "\n";
gsInfo << "Coarse solver setup time: " << Time_Coarse_Solver_Setup << "\n";
gsInfo << "Smoother setup time: " << Time_Smoother_Setup << "\n";
gsInfo << "Total setup time: " << Time_Assembly + Time_Galerkin + Time_Transfer
+ Time_Coarse_Solver_Setup + Time_Smoother_Setup << "\n";
gsIterativeSolver<>::Ptr solver;
switch (typeSolver)
{
case 1:
gsInfo << "\n|| Solver information ||\np-multigrid is applied as stand-alone solver\n";
break;
case 2:
gsInfo << "\n|| Solver information ||\nBiCGStab is applied as solver, p-multigrid as a preconditioner\n";
break;
case 3:
gsInfo << "\n|| Solver information ||\nCG is applied as solver, p-multigrid as a preconditioner\n";
break;
default:
gsInfo << "Unknown iterative solver chosen.\n";
return EXIT_FAILURE;
}
const real_t rhs_norm = rhs.norm();
solver->
setTolerance( tol * (rhs-matrix*x).norm() / rhs_norm );
double Time_Solve = clock.
stop();
gsInfo << " 0 | Residual norm: " << std::setprecision(6) << (error_history(0,0) * rhs_norm) << "\n";
for (
index_t i=1; i<error_history.rows(); ++i)
gsInfo << std::right << std::setw(4) << i
<< " | Residual norm: "
<< std::setprecision(6) << std::left << std::setw(15) << (error_history(i,0) * rhs_norm)
<< " reduction: 1 / "
<< std::setprecision(3) << (error_history(i-1,0)/error_history(i,0))
<< "\n";
if (success)
gsInfo <<
"Solver reached accuracy goal after " << solver->
iterations() <<
" iterations.\n";
else
gsInfo <<
"Solver did not reach accuracy goal within " << solver->
iterations() <<
" iterations.\n";
gsInfo << "The iteration took " << Time_Solve << " seconds.\n";
gsInfo << "Residual after solving: " << (rhs-matrix*x).norm() << "\n";
return success ? EXIT_SUCCESS : EXIT_FAILURE;
}
)
{
mb.getMapper(
(dirichlet::strategy)opt.
getInt(
"DirichletStrategy"),
iFace::glue,
bc,
dm,
0
);
const index_t nTotalDofs = dm.freeSize();
std::vector< std::vector<patchComponent> > components = mb.topology().
allComponents(
true);
const index_t nr_components = components.size();
for (
index_t ps=0; ps < 2*dim; ++ps )
std::vector< gsSparseMatrix<real_t,RowMajor> > transfers;
transfers.reserve(nr_components);
std::vector< gsLinearOperator<>::Ptr > ops;
ops.reserve(nr_components);
for (
index_t i=0; i<nr_components; ++i)
{
se.reserve(sz);
se.add(indices(l,0),l,(real_t)(1));
transfer.setFrom(se);
if (sz>0)
{
if (bases[0]->dim() == dim)
{
GISMO_ASSERT ( bases.size() == 1,
"Only one basis is expected for each patch." );
ops.push_back(
*(bases[0]),
dir_bc,
)
);
}
else
{
ops.push_back( makeSparseCholeskySolver(mat) );
}
transfers.push_back(
give(transfer));
}
}
}
)
{
mb.getMapper(
(dirichlet::strategy)opt.
getInt(
"DirichletStrategy"),
iFace::glue,
bc,
dm,
0
);
std::vector<index_t> sizes(nPatches+1);
std::vector<index_t> shifts(nPatches+1);
shifts[0] = 0;
{
sizes[k] = dm.findFreeUncoupled(k).rows();
shifts[k+1] = shifts[k] + sizes[k];
}
sizes[nPatches] = A.rows() - shifts[nPatches];
std::vector< gsEigen::PermutationMatrix<Dynamic,Dynamic,index_t> > P(nPatches+1);
std::vector< gsEigen::PermutationMatrix<Dynamic,Dynamic,index_t> > Pinv(nPatches+1);
A_aprox.setZero();
gsSparseMatrix<> S = A.block(shifts[nPatches],shifts[nPatches],sizes[nPatches],sizes[nPatches]);
for (
index_t k=0 ; k<nPatches; k++)
{
gsEigen::IncompleteLUT<real_t> ilu;
ilu.setFillfactor(1);
ilu.compute(block);
P[k] = ilu.fillReducingPermutation();
Pinv[k] = ilu.inversePermutation();
A_aprox.block(shifts[k],shifts[k],sizes[k],sizes[k]) = ilu.factors();
if (sizes[nPatches]>0)
{
gsMatrix<> ddB = A.block(shifts[nPatches],shifts[k],sizes[nPatches],sizes[k]);
gsMatrix<> ddC = A.block(shifts[k],shifts[nPatches],sizes[k],sizes[nPatches]);
gsMatrix<> ddBtilde = ilu.factors().triangularView<gsEigen::Upper>().transpose().solve((ddB*P[k]).transpose()).transpose();
gsMatrix<> ddCtilde = ilu.factors().triangularView<gsEigen::UnitLower>().solve(Pinv[k]*ddC);
S -= (ddBtilde*ddCtilde).sparseView();
A_aprox.block(shifts[k],shifts[nPatches],sizes[k],sizes[nPatches]) = ddCtilde;
A_aprox.block(shifts[nPatches],shifts[k],sizes[nPatches],sizes[k]) = ddBtilde;
}
}
if (sizes[nPatches]>0)
{
gsEigen::IncompleteLUT<real_t> ilu;
ilu.setFillfactor(1);
ilu.compute(S);
P[nPatches] = ilu.fillReducingPermutation();
Pinv[nPatches] = ilu.inversePermutation();
A_aprox.block(shifts[nPatches],shifts[nPatches],sizes[nPatches],sizes[nPatches]) = ilu.factors();
}
makeMatrixOp(A),
makeLinearOp(
{
x.setZero(rhs.rows(),rhs.cols());
for (
index_t k=0; k<=m_nPatches; ++k)
x.block(m_shifts[k],0,m_sizes[k],1) = m_Pinv[k]*rhs.block(m_shifts[k],0,m_sizes[k],1);
x = m_A_aprox.template triangularView<gsEigen::UnitLower>().solve(x);
x = m_A_aprox.template triangularView<gsEigen::Upper>().solve(x);
for (
index_t k=0; k<=m_nPatches; ++k)
x.block(m_shifts[k],0,m_sizes[k],1) = m_P[k]*x.block(m_shifts[k],0,m_sizes[k],1);
},
A.rows(),
A.cols()
)
);
}
)
{
geometryMap G = ex.getMap(mp);
space w_n = ex.getSpace(basis, 1, 0);
w_n.setInterfaceCont(0);
if ((dirichlet::strategy)opt.
getInt(
"DirichletStrategy") == dirichlet::elimination)
{
w_n.setup(bcInfo, dirichlet::interpolation, 0);
}
ex.setIntegrationElements(basis);
ex.initSystem();
ex.assemble(w_n *
meas(G));
return ex.rhs();
}
)
{
geometryMap G = ex.getMap(mp);
space w_n = ex.getSpace(basis, 1, 0);
if ((dirichlet::strategy)opt.
getInt(
"DirichletStrategy") == dirichlet::elimination)
{
w_n.setup(bcInfo, dirichlet::interpolation, 0);
}
ex.setIntegrationElements(basis);
ex.initSystem();
ex.assemble(w_n *
meas(G) * w_n.tr());
return ex.matrix();
}
)
{
geometryMap G = ex.getMap(mp);
space v_n = ex.getSpace(basisU, 1, 0);
space u_n = ex.getTestSpace(v_n, basisV);
if ((dirichlet::strategy)opt.
getInt(
"DirichletStrategy") == dirichlet::elimination)
{
v_n.setup(bcInfo, dirichlet::interpolation, 0);
u_n.setup(bcInfo, dirichlet::interpolation, 0);
}
ex.setIntegrationElements(basisU);
ex.initSystem();
ex.assemble(u_n *
meas(G) * v_n.tr());
return ex.matrix().transpose();
}