This tutorial, shows how to use some functions for adaptive refinement. It is based on poisson_example.cpp, see there for details.
More details on the hierarchical splines can be found in thbSplineBasis_example.cpp as well as in the Hierarchical splines module description.
Setting up the problem
As in poisson_example.cpp, we include the gismo namespace...
...and a command-line argument which allows the user to create a Paraview visualization file.
bool plot = false;
gsCmdLine cmd("Tutorial on solving a Poisson problem.");
cmd.addSwitch("plot", "Create a ParaView visualization file with the solution", plot);
cmd.addInt("r", "numLoop", "number of refinement loops", numRefinementLoops);
try { cmd.getValues(argc,argv); } catch (int rv) { return rv; }
Problem definition
We consider the Laplace problem on an L-shaped domain \( (-1,1)^2 \setminus [0,1]^2 \) which is represented by three squares.
L-shaped domain, 3 patches.
We solve the Laplace problem
\[ -\Delta u = 0~\mathrm{in}~\Omega,\quad u = u_{ex} ~\mathrm{on}~\partial \Omega, \]
where the exact solution \(u_{ex}\) is given in polar coordinates by
\[ u_{ex}(r,\varphi) = r^{\frac{2}{3}} \sin\Big( ( 2\varphi - \pi)/3 \Big). \]
We define exact solution (in Cartesian coordinates) and the (homogenous) right-hand-side of the PDE:
gsFunctionExpr<> g("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);
gsFunctionExpr<> f("0",2);
Getting geometry and basis
The gsMultiPatch object describing the geometry is read in from an .xml-file:
std::string fileSrc( "planar/lshape2d_3patches_thb.xml" );
gsMultiPatch<real_t> patches;
gsReadFile<real_t>( fileSrc, patches);
The information on patch-interfaces and/or boundaries is not necessarily contained in the .xml-file. To make sure that interfaces and boundaries are correctly set up, call gsMultiPatch::computeTopology().
patches.computeTopology();
To prescribe Dirichlet boundary conditions on all boundaries, we use the gsMultiPatch::const_biterator to iterate through all boundaries:
gsBoundaryConditions<> bcInfo;
for ( gsMultiPatch<>::const_biterator
bit = patches.bBegin(); bit != patches.bEnd(); ++bit)
{
}
Obtaining the gsMultiBasis from the gsMultiPatch is straightforward, just call the corresponding constructor:
gsMultiBasis<> bases( patches );
Note that the resulting gsMultiBasis will be composed of the bases defining the geometry. In this example, the geometry is given as a gsTHBSpline. Accordingly, the created gsMultiBasis is composed of gsTHBSplineBasis.
Get a gsTHBSplineBasis from a gsTensorBSpline
If the geometry is not given as a gsTHBSpline, but as a gsTensorBSpline...
std::string fileSrcTens( "planar/lshape2d_3patches_tens.xml" );
gsMultiPatch<real_t> patchesTens;
gsReadFile<real_t>( fileSrcTens, patchesTens);
patchesTens.computeTopology();
...one can create a gsTHBSplineBasis as follows:
gsMultiBasis<real_t> basesTens( patchesTens );
std::vector< gsBasis<real_t>* > basisContainer;
for ( size_t i = 0; i < basesTens.nBases(); i++)
basisContainer.push_back(new gsTHBSplineBasis<2,real_t>( basesTens.basis(i) ));
gsMultiBasis<real_t> basesFromTens( basisContainer, patches );
Loop
Instead of just solving the problem once, we wrap a loop around the solving process. For each computed solution, we compute the distribution of the error. Based on this error indication, we mark a certain number of cells for refinement.
Setting up adaptive refinement parameters
For adaptive refinement, we need to specify a criterion for marking cells, and a parameter. Currently implemented are PUCA, GARU, errorFraction (a.k.a. Doerfel-marking), see gsMarkElementsForRef().
Setting the parameter to 0 will result in global refinement, setting it to 1 will result in (almost) no refinement.
MarkingStrategy adaptRefCrit = PUCA;
const real_t adaptRefParam = 0.9;
Solving the problem
If desired or necessary (e.g., because the geometry is given by a very coarse representation), apply initial uniform (i.e., global) refinement steps:
int numInitUniformRefine = 2;
for (int i = 0; i < numInitUniformRefine; ++i)
bases.uniformRefine();
Wrap a loop around the solution process
for( int refLoop = 0; refLoop <= numRefinementLoops; refLoop++)
{
The part re solving the equation is the same as in poisson_example.cpp.
gsPoissonAssembler<real_t> PoissonAssembler(patches,bases,bcInfo,f);
PoissonAssembler.options().setInt("DirichletValues", dirichlet::l2Projection);
PoissonAssembler.assemble();
gsSparseSolver<>::CGDiagonal solver( PoissonAssembler.matrix() );
gsMatrix<> solVector = solver.solve( PoissonAssembler.rhs() );
gsMultiPatch<> sol;
PoissonAssembler.constructSolution(solVector, sol);
gsField<> solField(patches, sol);
Adaptive refinement
After having computed the solution, we compute the local errors. Either by some a posteriori error estimation technique, or, as in this academic example, by computation of the exact error.
gsExprEvaluator<> ev;
gsExprEvaluator<>::geometryMap Gm = ev.
getMap(patches);
gsExprEvaluator<>::variable is = ev.getVariable(sol);
auto ms = ev.getVariable(g, Gm);
ev.integralElWise( ( igrad(is,Gm) - igrad(ms)).sqNorm()*
meas(Gm) );
const std::vector<real_t> & eltErrs = ev.elementwise();
Having computed the element-wise errors, we select those that need to be marked by calling gsMarkElementsForRef(), where the previously defined parameters adaptRefCrit and adaptRefParam are used. The actual refinement is done by gsRefineMarkedElements().
std::vector<bool> elMarked;
for (
size_t k=0; k!=elMarked.size(); k++)
gsInfo<<
" "<<elMarked[k];
Note/Warning:
As of now, we cannot access a single element of a gsHTensorBasis directly (e.g., by some index). gsMarkElementsForRef() and gsRefineMarkedElements() rely on the assumption that the gsDomainIterator used in these two functions will iterate over the elements in the same order.
This is also the reason why elMarked is a vector of booleans indicating "refine!" or "don't refine!" for each element (instead of, e.g., a list of inidces of elements).
Recall from thbSplineBasis_example.cpp that the refined area must contain the support of at least one basis function. Due to this, we also refine the 1-ring of cells around marked cells.
Adaptive refinement on a gsMultiPatch geometry can result in interfaces which are no longer fully matching. Thus, it is important to call gsMultiBasis::repairInterfaces().
bases.repairInterfaces( patches.interfaces() );
Plotting final solution
In the last iteration, we export the solution to paraview files
if( plot && refLoop == numRefinementLoops )
{
gsWriteParaview<>(solField, "adaptRef", 1000, true);
}
Plot the solution in Paraview
Mesh (left, coloured by patch-index) and solution after 4 adaptive refinement steps.
Annotated source file
Here is the full file examples/adaptRefinementThb_example.cpp
. Clicking on a function or class name will lead you to its reference documentation.
using namespace gismo;
int main(
int argc,
char *argv[])
{
bool plot = false;
gsCmdLine cmd(
"Tutorial on solving a Poisson problem.");
cmd.addSwitch("plot", "Create a ParaView visualization file with the solution", plot);
cmd.addInt("r", "numLoop", "number of refinement loops", numRefinementLoops);
try { cmd.getValues(argc,argv); } catch (int rv) { return rv; }
gsFunctionExpr<> g(
"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);
gsInfo<<
"Source function " << f <<
"\n";
gsInfo<<
"Exact solution " << g <<
"\n\n";
std::string fileSrc( "planar/lshape2d_3patches_thb.xml" );
gsInfo <<
"The domain is a "<< patches <<
"\n";
patches.computeTopology();
std::string fileSrcTens( "planar/lshape2d_3patches_tens.xml" );
bit = patches.bBegin(); bit != patches.bEnd(); ++bit)
{
}
std::vector< gsBasis<real_t>* > basisContainer;
for ( size_t i = 0; i < basesTens.nBases(); i++)
int numInitUniformRefine = 2;
for (int i = 0; i < numInitUniformRefine; ++i)
bases.uniformRefine();
MarkingStrategy adaptRefCrit = PUCA;
const real_t adaptRefParam = 0.9;
for( int refLoop = 0; refLoop <= numRefinementLoops; refLoop++)
{
PoissonAssembler.options().setInt("DirichletValues", dirichlet::l2Projection);
PoissonAssembler.assemble();
gsSparseSolver<>::CGDiagonal solver( PoissonAssembler.matrix() );
gsMatrix<> solVector = solver.solve( PoissonAssembler.rhs() );
PoissonAssembler.constructSolution(solVector, sol);
gsExprEvaluator<>::geometryMap Gm = ev.
getMap(patches);
const std::vector<real_t> & eltErrs = ev.
elementwise();
std::vector<bool> elMarked;
for (
size_t k=0; k!=elMarked.size(); k++)
gsInfo<<
" "<<elMarked[k];
bases.repairInterfaces( patches.interfaces() );
if( plot && refLoop == numRefinementLoops )
{
gsWriteParaview<>(solField, "adaptRef", 1000, true);
}
}
if( plot )
{
}
else
{
gsInfo<<
"Done. No output created, re-run with --plot to get a ParaView "
"file containing Plotting image data.\n";
}
return EXIT_SUCCESS;
}