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

The boundary value problem for the Poisson equation is defined as follows:

\begin{eqnarray*} -\Delta u &=& f \quad \text{in} \quad \Omega, \\ u &=& g \quad \text{on} \quad \Gamma_D, \\ \mathbf{n} \cdot \nabla u &=& h \quad \text{on} \quad \Gamma_N, \end{eqnarray*}

where \(\mathbf{n}\) is the outward pointing unit normal vector, \(\Gamma_D\) and \(\Gamma_N\) are the parts of the boundary where Dirichlet (essential) and Neumann (natural) boundary conditions are prescribed, respectively. The corresponding weak formulation is:

\begin{eqnarray*} \text{Find }u\in\Sigma_{h}\text{ such that}\\ \int_\Omega \nabla u \cdot \nabla \psi \text{d}\Omega = \int_\Omega f\psi \text{d}\Omega + \int_{\Gamma_N} h\psi \text{d}\Gamma\\ \forall\psi\in\Sigma_{h} \end{eqnarray*}

Where \(\Sigma_{h}\) is the function space where the Dirichlet boundary conditions according to function \(h\) are applied and \(\psi\) are the basis functions.

The system to assemble a system of equations based on this weak formulation is constructed in poisson2_example.cpp using the gsExprAssembler. This file takes as input any domain \(\Omega\), any function \(g\) and any function \(h\). In this example, we take the xml file filedata/pde/poisson2d_bvp.xml for the input.

First we include the library and use the gismo namespace.

#include <gismo.h>
using namespace gismo;
Main header to be included by clients using the G+Smo library.
The G+Smo namespace, containing all definitions for the library.

Then we define our command line options. For example, we use the option -f or --file to set the path to the file that contains our geometry and our boundary conditions.

bool plot = false;
index_t numRefine = 5;
index_t numElevate = 0;
index_t numSplit = 0;
bool last{false}, export_b64{false};
std::string fn("pde/poisson2d_bvp.xml");
gsCmdLine cmd("Tutorial on solving a Poisson problem.");
cmd.addInt( "e", "degreeElevation",
"Number of degree elevation steps to perform before solving (0: equalize degree in all directions)", numElevate );
cmd.addInt( "r", "uniformRefine", "Number of Uniform h-refinement loops", numRefine );
cmd.addInt( "s", "uniformSplit", "Subdivide the input pathes this many time", numSplit );
cmd.addString( "f", "file", "Input XML file", fn );
cmd.addSwitch("last", "Solve solely for the last level of h-refinement",
last);
cmd.addSwitch(
"plot", "Create a ParaView visualization file with the solution", plot);
cmd.addSwitch("binary", "Use B64 encoding for Paraview", export_b64);
try { cmd.getValues(argc,argv); } catch (int rv) { return rv; }
Class for command-line argument parsing.
Definition gsCmdLine.h:57
#define index_t
Definition gsConfig.h:32

Then we read the input from the file that contains the boundary value problem. In this example, we use the function gsFileData::getId() to read input from an xml file. In this function, the first argument specifies the ID of the object in the xml file (the id flag) and the second argument is the object to which the file info is written. Note that for the boundary conditions, we immedeately set the geometry map (based on the gsMultiPatch class).

gsFileData<> fd(fn);
gsInfo << "Loaded file "<< fd.lastPath() <<"\n";
fd.getId(0, mp); // id=0: Multipatch domain
for (index_t i = 0; i<numSplit;++i)
mp = mp.uniformSplit();
fd.getId(1, f); // id=1: source function
gsInfo<<"Source function "<< f << "\n";
fd.getId(2, bc); // id=2: boundary conditions
bc.setGeoMap(mp);
gsInfo<<"Boundary conditions:\n"<< bc <<"\n";
fd.getId(3, ms); // id=3: reference solution
fd.getId(4, Aopt); // id=4: assembler options
Class containing a set of boundary conditions.
Definition gsBoundaryConditions.h:342
void setGeoMap(const gsFunctionSet< T > &gm)
Set the geometry map to evaluate boundary conditions.
Definition gsBoundaryConditions.h:916
This class represents an XML data tree which can be read from or written to a (file) stream.
Definition gsFileData.h:34
Class defining a multivariate (real or vector) function given by a string mathematical expression.
Definition gsFunctionExpr.h:52
Container class for a set of geometry patches and their topology, that is, the interface connections ...
Definition gsMultiPatch.h:100
Class which holds a list of parameters/options, and provides easy access to them.
Definition gsOptionList.h:33
#define gsInfo
Definition gsDebug.h:43

Before we define our gsExprAssembler, we elevate the degree or we refine the basis. This is done in the following snippet

gsMultiBasis<> dbasis(mp, true);//true: poly-splines (not NURBS)
// Elevate and p-refine the basis to order p + numElevate
// where p is the highest degree in the bases
dbasis.setDegree( dbasis.maxCwiseDegree() + numElevate);
// h-refine each basis
if (last)
{
for (int r =0; r < numRefine; ++r)
dbasis.uniformRefine();
numRefine = 0;
}
gsInfo << "Patches: "<< mp.nPatches() <<", degree: "<< dbasis.minCwiseDegree() <<"\n";
#ifdef _OPENMP
gsInfo<< "Available threads: "<< omp_get_max_threads() <<"\n";
#endif
Holds a set of patch-wise bases and their topology information.
Definition gsMultiBasis.h:37

Now it is time to look at the setup of our boundary value problem. In the code snippet below, we first define our gsExprAssembler. The inputs (1,1) here mean that we have 1 test function space and 1 trial function space. Furthermore, few typedef s can be seen. These are just there to prevent lengthy types. The assembler is fed with the basis using the gsExprAssembler::setIntegrationElements() function. When the basis is defined, a gsExprEvaluator is constructed for later use. The geometry is connected to the gsExprAssembler via the gsExprAssembler::getMap() function and we create an expression for the space via gsExprAssembler::getSpace(). Functions for the right-hand side and the manufactured solution are constructed via gsExprAssembler::getCoef() and gsExprEvaluator::getVariable() functions, respectively. Note that the manufactured solution is defined in the gsExprEvaluator because it will not be used in assembly of the system. The problem setup finishes by defining the solution object, which is constructed via gsExprAssembler::getSolution() which requires a space and a reference to a vector that will contain the solution coefficients later.

A.setOptions(Aopt);
gsInfo<<"Active options:\n"<< A.options() <<"\n";
typedef gsExprAssembler<>::geometryMap geometryMap;
typedef gsExprAssembler<>::variable variable;
typedef gsExprAssembler<>::space space;
typedef gsExprAssembler<>::solution solution;
// Elements used for numerical integration
A.setIntegrationElements(dbasis);
// Set the geometry map
geometryMap G = A.getMap(mp);
// Set the discretization space
space u = A.getSpace(dbasis);
// Set the source term
auto ff = A.getCoeff(f, G);
// Recover manufactured solution
auto u_ex = ev.getVariable(ms, G);
// Solution vector and solution variable
gsMatrix<> solVector;
solution u_sol = A.getSolution(u, solVector);
Definition gsExpressions.h:973
Definition gsExpressions.h:928
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 matrix with arbitrary coefficient type and fixed or dynamic size.
Definition gsMatrix.h:41

The next step is to actually solve the system at hand. In this particular example, this is done within a loop of uniform refinement (unless the last option is selected) in order to study convergence behaviour of the method.

As seen in the snippet below, we first define the solver that we will use (based on the Eigen-library) and we define the vectors to store the errors. Then, within the loop, we first refine the basis uniformly and then the gsExprAssembler<>::space::setup() function is used. This function computes the basis (boundary conditions) and sets the interface continuity. Then the gsExprAssembler is initialized using gsExprAssembler::initSystem() and using gsExprAssembler::assemble() the matrix expression (first argument) and the rhs expression (second argument) are assembled to a system. We also call the function gsExprAssembler::assembleRhsBc() which is dedicated to assemble an expression over a boundary; in this case the Neumann boundaries which are called via gsBoundaryConditions::neumannSides(). When the Neumann boundary conditions are assembled, the system is factorized using gsSparseSolver::compute() and solved via gsSparseSolver::solve(). The errors are then computed via the gsExprEvaluator, where it should be noted that the solution object is appearing, which uses the solution vector and the space as defined earlier.

#ifdef GISMO_WITH_PARDISO
gsSparseSolver<>::PardisoLDLT solver;
#else
gsSparseSolver<>::CGDiagonal solver;
#endif
gsVector<> l2err(numRefine+1), h1err(numRefine+1);
gsInfo<< "(dot1=assembled, dot2=solved, dot3=got_error)\n"
"\nDoFs: ";
double setup_time(0), ma_time(0), slv_time(0), err_time(0);
gsStopwatch timer;
for (int r=0; r<=numRefine; ++r)
{
timer.restart();
dbasis.uniformRefine();
// Setup the space \a u with strongly imposed Dirichlet part
//u.setup(bc, dirichlet::interpolation, 0);
u.setup(bc, dirichlet::l2Projection, 0);
// Initialize the system
A.initSystem();
// Compute sparsity patter: this is done automatically - but
// is needed if assemble(.) is called twice
A.computePattern( igrad(u) * igrad(u).tr() );
setup_time += timer.stop();
gsInfo<< A.numDofs() <<std::flush;
timer.restart();
// Compute the system matrix and right-hand side
A.assemble(
igrad(u, G) * igrad(u, G).tr() * meas(G) //matrix
,
u * ff * meas(G) //rhs vector
);
// Compute the Neumann terms defined on physical space
auto g_N = A.getBdrFunction(G);
A.assembleBdr(bc.get("Neumann"), u * g_N.tr() * nv(G) );
ma_time += timer.stop();
// gsDebugVar(A.matrix().toDense());
// gsDebugVar(A.rhs().transpose() );
gsInfo<< "." <<std::flush;// Assemblying done
timer.restart();
solver.compute( A.matrix() );
solVector = solver.solve(A.rhs());
slv_time += timer.stop();
gsInfo<< "." <<std::flush; // Linear solving done
timer.restart();
// Compute the L2 and H1 error, based on manufactured solution
l2err[r]= math::sqrt( ev.integral( (u_ex - u_sol).sqNorm() * meas(G) ) );
h1err[r]= l2err[r] +
math::sqrt(ev.integral( ( igrad(u_ex) - igrad(u_sol,G) ).sqNorm() * meas(G) ));
err_time += timer.stop();
gsInfo<< ". " <<std::flush; // Error computations done
} //for loop
A Stopwatch object can be used to measure execution time of code, algorithms, etc.
Definition gsStopwatch.h:73
double stop()
Return elapsed time in seconds.
Definition gsStopwatch.h:83
void restart()
Start taking the time.
Definition gsStopwatch.h:80
A vector with arbitrary coefficient type and fixed or dynamic size.
Definition gsVector.h:37

Error norms are computed in the following snippet:

gsInfo<< "\nL2 error: "<<std::scientific<<std::setprecision(3)<<l2err.transpose()<<"\n";
gsInfo<< "H1 error: "<<std::scientific<<h1err.transpose()<<"\n";
if (!last && numRefine>0)
{
gsInfo<< "\nEoC (L2): " << std::fixed<<std::setprecision(2)
<< ( l2err.head(numRefine).array() /
l2err.tail(numRefine).array() ).log().transpose() / std::log(2.0)
<<"\n";
gsInfo<< "EoC (H1): "<< std::fixed<<std::setprecision(2)
<<( h1err.head(numRefine).array() /
h1err.tail(numRefine).array() ).log().transpose() / std::log(2.0) <<"\n";
}

Lastly, the export to ParaView is done in the snipped below. Note that this does not directly use the gsWriteParaview class, but instead the gsExprEvaluator::writeParaview() function is employed.

if (plot)
{
gsInfo<<"Plotting in Paraview...\n";
gsParaviewCollection collection("ParaviewOutput/solution", &ev);
collection.options().setSwitch("plotElements", true);
collection.options().setSwitch("base64", export_b64);
collection.options().setInt("plotElements.resolution", 16);
collection.newTimeStep(&mp);
collection.addField(u_sol,"numerical solution");
collection.addField(u_ex, "exact solution");
collection.saveTimeStep();
collection.save();
gsFileManager::open("ParaviewOutput/solution.pvd");
}
else
gsInfo << "Done. No output created, re-run with --plot to get a ParaView "
"file containing the solution.\n";
static void open(const std::string &fn)
Opens the file fn using the preferred application of the OS.
Definition gsFileManager.cpp:688
This class is used to create a Paraview .pvd (collection) file.
Definition gsParaviewCollection.h:77

Annotated source file

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

#include <gismo.h>
using namespace gismo;
int main(int argc, char *argv[])
{
bool plot = false;
index_t numRefine = 5;
index_t numElevate = 0;
index_t numSplit = 0;
bool last{false}, export_b64{false};
std::string fn("pde/poisson2d_bvp.xml");
gsCmdLine cmd("Tutorial on solving a Poisson problem.");
cmd.addInt( "e", "degreeElevation",
"Number of degree elevation steps to perform before solving (0: equalize degree in all directions)", numElevate );
cmd.addInt( "r", "uniformRefine", "Number of Uniform h-refinement loops", numRefine );
cmd.addInt( "s", "uniformSplit", "Subdivide the input pathes this many time", numSplit );
cmd.addString( "f", "file", "Input XML file", fn );
cmd.addSwitch("last", "Solve solely for the last level of h-refinement",
last);
cmd.addSwitch(
"plot", "Create a ParaView visualization file with the solution", plot);
cmd.addSwitch("binary", "Use B64 encoding for Paraview", export_b64);
try { cmd.getValues(argc,argv); } catch (int rv) { return rv; }
gsFileData<> fd(fn);
gsInfo << "Loaded file "<< fd.lastPath() <<"\n";
fd.getId(0, mp); // id=0: Multipatch domain
for (index_t i = 0; i<numSplit;++i)
mp = mp.uniformSplit();
fd.getId(1, f); // id=1: source function
gsInfo<<"Source function "<< f << "\n";
fd.getId(2, bc); // id=2: boundary conditions
bc.setGeoMap(mp);
gsInfo<<"Boundary conditions:\n"<< bc <<"\n";
fd.getId(3, ms); // id=3: reference solution
fd.getId(4, Aopt); // id=4: assembler options
gsMultiBasis<> dbasis(mp, true);//true: poly-splines (not NURBS)
// Elevate and p-refine the basis to order p + numElevate
// where p is the highest degree in the bases
dbasis.setDegree( dbasis.maxCwiseDegree() + numElevate);
// h-refine each basis
if (last)
{
for (int r =0; r < numRefine; ++r)
dbasis.uniformRefine();
numRefine = 0;
}
gsInfo << "Patches: "<< mp.nPatches() <<", degree: "<< dbasis.minCwiseDegree() <<"\n";
#ifdef _OPENMP
gsInfo<< "Available threads: "<< omp_get_max_threads() <<"\n";
#endif
A.setOptions(Aopt);
gsInfo<<"Active options:\n"<< A.options() <<"\n";
typedef gsExprAssembler<>::geometryMap geometryMap;
typedef gsExprAssembler<>::variable variable;
typedef gsExprAssembler<>::space space;
typedef gsExprAssembler<>::solution solution;
// Elements used for numerical integration
A.setIntegrationElements(dbasis);
// Set the geometry map
geometryMap G = A.getMap(mp);
// Set the discretization space
space u = A.getSpace(dbasis);
// Set the source term
auto ff = A.getCoeff(f, G);
// Recover manufactured solution
auto u_ex = ev.getVariable(ms, G);
// Solution vector and solution variable
gsMatrix<> solVector;
solution u_sol = A.getSolution(u, solVector);
#ifdef GISMO_WITH_PARDISO
gsSparseSolver<>::PardisoLDLT solver;
#else
gsSparseSolver<>::CGDiagonal solver;
#endif
gsVector<> l2err(numRefine+1), h1err(numRefine+1);
gsInfo<< "(dot1=assembled, dot2=solved, dot3=got_error)\n"
"\nDoFs: ";
double setup_time(0), ma_time(0), slv_time(0), err_time(0);
gsStopwatch timer;
for (int r=0; r<=numRefine; ++r)
{
timer.restart();
dbasis.uniformRefine();
// Setup the space \a u with strongly imposed Dirichlet part
//u.setup(bc, dirichlet::interpolation, 0);
u.setup(bc, dirichlet::l2Projection, 0);
// Initialize the system
A.initSystem();
// Compute sparsity patter: this is done automatically - but
// is needed if assemble(.) is called twice
A.computePattern( igrad(u) * igrad(u).tr() );
setup_time += timer.stop();
gsInfo<< A.numDofs() <<std::flush;
timer.restart();
// Compute the system matrix and right-hand side
A.assemble(
igrad(u, G) * igrad(u, G).tr() * meas(G) //matrix
,
u * ff * meas(G) //rhs vector
);
// Compute the Neumann terms defined on physical space
auto g_N = A.getBdrFunction(G);
A.assembleBdr(bc.get("Neumann"), u * g_N.tr() * nv(G) );
ma_time += timer.stop();
// gsDebugVar(A.matrix().toDense());
// gsDebugVar(A.rhs().transpose() );
gsInfo<< "." <<std::flush;// Assemblying done
timer.restart();
solver.compute( A.matrix() );
solVector = solver.solve(A.rhs());
slv_time += timer.stop();
gsInfo<< "." <<std::flush; // Linear solving done
timer.restart();
// Compute the L2 and H1 error, based on manufactured solution
l2err[r]= math::sqrt( ev.integral( (u_ex - u_sol).sqNorm() * meas(G) ) );
h1err[r]= l2err[r] +
math::sqrt(ev.integral( ( igrad(u_ex) - igrad(u_sol,G) ).sqNorm() * meas(G) ));
err_time += timer.stop();
gsInfo<< ". " <<std::flush; // Error computations done
} //for loop
gsInfo<<"\n\nTotal time: "<< setup_time+ma_time+slv_time+err_time <<"\n";
gsInfo<<" Setup: "<< setup_time <<"\n";
gsInfo<<"Assembly: "<< ma_time <<"\n";
gsInfo<<" Solving: "<< slv_time <<"\n";
gsInfo<<" Norms: "<< err_time <<"\n";
gsInfo<< "\nL2 error: "<<std::scientific<<std::setprecision(3)<<l2err.transpose()<<"\n";
gsInfo<< "H1 error: "<<std::scientific<<h1err.transpose()<<"\n";
if (!last && numRefine>0)
{
gsInfo<< "\nEoC (L2): " << std::fixed<<std::setprecision(2)
<< ( l2err.head(numRefine).array() /
l2err.tail(numRefine).array() ).log().transpose() / std::log(2.0)
<<"\n";
gsInfo<< "EoC (H1): "<< std::fixed<<std::setprecision(2)
<<( h1err.head(numRefine).array() /
h1err.tail(numRefine).array() ).log().transpose() / std::log(2.0) <<"\n";
}
if (plot)
{
gsInfo<<"Plotting in Paraview...\n";
gsParaviewCollection collection("ParaviewOutput/solution", &ev);
collection.options().setSwitch("plotElements", true);
collection.options().setSwitch("base64", export_b64);
collection.options().setInt("plotElements.resolution", 16);
collection.newTimeStep(&mp);
collection.addField(u_sol,"numerical solution");
collection.addField(u_ex, "exact solution");
collection.saveTimeStep();
collection.save();
gsFileManager::open("ParaviewOutput/solution.pvd");
}
else
gsInfo << "Done. No output created, re-run with --plot to get a ParaView "
"file containing the solution.\n";
return EXIT_SUCCESS;
}// end main
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