The following explains how to solve the Poisson equation using dune-functions. The full example poisson-pq2.cc can be found in the examples/ subdirectory.
Local assemblers
The program first includes the required headers and then defines free functions for assembling the Poisson problem. The function getLocalMatrix() implements the assembler of the local stiffness matrix for the bilinear form \((u,v) \mapsto \int_\Omega \nabla u(x)\nabla v(x)dx\).
template <class LocalView, class MatrixType>
void getLocalMatrix(const LocalView& localView, MatrixType& elementMatrix)
{
typedef typename LocalView::Element Element;
const Element& element = localView.element();
const int dim = Element::dimension;
auto geometry = element.geometry();
const auto& localFiniteElement = localView.tree().finiteElement();
elementMatrix.setSize(localFiniteElement.localBasis().size(),localFiniteElement.localBasis().size());
elementMatrix = 0;
int order = 2*(
dim*localFiniteElement.localBasis().order()-1);
const QuadratureRule<double, dim>& quad = QuadratureRules<double, dim>::rule(element.type(), order);
for (size_t pt=0; pt < quad.size(); pt++) {
const FieldVector<double,dim>& quadPos = quad[pt].position();
const auto& jacobianInverse = geometry.jacobianInverse(quadPos);
const double integrationElement = geometry.integrationElement(quadPos);
localFiniteElement.localBasis().evaluateJacobian(quadPos, referenceJacobians);
for (size_t i=0; i<jacobians.size(); i++)
jacobians[i] = referenceJacobians[i] * jacobianInverse;
for (size_t i=0; i<elementMatrix.N(); i++)
for (size_t j=0; j<elementMatrix.M(); j++ )
elementMatrix[i][j] += (jacobians[i] * transpose(jacobians[j]))[0][0] * quad[pt].weight() * integrationElement;
}
}
The getVolumeTerm() functions implements the local assembler for the volume right hand side term \(\int_\Omega f(x)v(x)dx\).
template <class LocalView, class LocalVolumeTerm>
void getVolumeTerm( const LocalView& localView,
BlockVector<double>& localRhs,
LocalVolumeTerm&& localVolumeTerm)
{
typedef typename LocalView::Element Element;
const Element& element = localView.element();
const int dim = Element::dimension;
const auto& localFiniteElement = localView.tree().finiteElement();
localRhs.resize(localFiniteElement.localBasis().size());
localRhs = 0;
int order =
dim*localFiniteElement.localBasis().order();
const QuadratureRule<double, dim>& quad = QuadratureRules<double, dim>::rule(element.type(), order);
for ( size_t pt=0; pt < quad.size(); pt++ ) {
const FieldVector<double,dim>& quadPos = quad[pt].position();
const double integrationElement = element.geometry().integrationElement(quadPos);
double functionValue = localVolumeTerm(quadPos);
localFiniteElement.localBasis().evaluateFunction(quadPos, shapeFunctionValues);
for (size_t i=0; i<localRhs.size(); i++)
localRhs[i] += shapeFunctionValues[i] * functionValue * quad[pt].weight() * integrationElement;
}
}
Global assembler
Assemble the global matrix pattern.
template <class FEBasis>
void getOccupationPattern(const FEBasis& feBasis, MatrixIndexSet& nb)
{
auto n = feBasis.size();
nb.resize(n, n);
auto localView = feBasis.localView();
for(const auto& e : elements(feBasis.gridView()))
{
localView.bind(e);
for (
size_t i=0; i<localView.tree().
size(); i++) {
for (
size_t j=0; j<localView.tree().
size(); j++) {
auto iIdx = localView.index(i);
auto jIdx = localView.index(j);
nb.add(iIdx, jIdx);
}
}
}
}
Assembly of matrix and right-hand-side.
template <class FEBasis, class VolumeTerm>
void assembleLaplaceMatrix(const FEBasis& feBasis,
BCRSMatrix<double>& matrix,
BlockVector<double>&
rhs,
VolumeTerm&& volumeTerm)
{
typedef typename FEBasis::GridView GridView;
GridView gridView = feBasis.gridView();
auto localVolumeTerm = localFunction(Functions::makeGridViewFunction(volumeTerm, gridView));
MatrixIndexSet occupationPattern;
getOccupationPattern(feBasis, occupationPattern);
occupationPattern.exportIdx(matrix);
rhs.resize(feBasis.size());
matrix = 0;
auto localView = feBasis.localView();
for(const auto& e : elements(gridView))
{
localView.bind(e);
Matrix<double> elementMatrix;
getLocalMatrix(localView, elementMatrix);
for (size_t i=0; i<elementMatrix.N(); i++) {
auto row = localView.index(i);
for (size_t j=0; j<elementMatrix.M(); j++ ) {
auto col = localView.index(j);
matrix[row][
col] += elementMatrix[i][j];
}
}
BlockVector<double> localRhs;
localVolumeTerm.bind(e);
getVolumeTerm(localView, localRhs, localVolumeTerm);
for (size_t i=0; i<localRhs.size(); i++) {
auto row = localView.index(i);
}
}
}
Helper functions
Treatment of boundary condition.
template <class FEBasis>
{
dirichletNodes.
resize(feBasis.size(),
false);
Functions::forEachBoundaryDOF(feBasis, [&] (
auto&&
index) {
dirichletNodes[
index] =
true;
});
}
std::ptrdiff_t index() const
Create a mixed grid containing triangles and quadrilaterals.
auto createMixedGrid()
{
for(
unsigned int k :
Dune::range(9))
factory.insertVertex({0.5*(k%3), 0.5*(k/3)});
factory.
insertElement(Dune::GeometryTypes::cube(2), {0, 1, 3, 4});
factory.
insertElement(Dune::GeometryTypes::cube(2), {1, 2, 4, 5});
factory.
insertElement(Dune::GeometryTypes::simplex(2), {3, 4, 6});
factory.
insertElement(Dune::GeometryTypes::simplex(2), {4, 7, 6});
factory.
insertElement(Dune::GeometryTypes::simplex(2), {4, 5, 7});
factory.
insertElement(Dune::GeometryTypes::simplex(2), {5, 8, 7});
}
virtual void insertElement(const GeometryType &type, const std::vector< unsigned int > &vertices)
virtual std::unique_ptr< GridType > createGrid()
Setup and initialization
Initialize MPI
int main (
int argc,
char *argv[])
try
{
MPIHelper::instance(argc, argv);
int main(int argc, char **argv)
Create grid and finite element basis
Create a mixed grid and obtain a leaf grid view.
- Note
- A
GridView is a view to a subset of the grid's elements, vertices, ... that should be stored by value and can be copied cheaply. Grids in dune are in general hierarchical and composed by elements on several levels. The discretization usually lives on the set of most refined elements that is denote the leaf GridView in dune.
auto grid = createMixedGrid();
using GridView = decltype(gridView);
LeafGridView leafGridView() const
void globalRefine(int refCount)
As a next step the program creates a global finite element basis on the GridView.
typedef Functions::LagrangeBasis<GridView,2> FEBasis;
FEBasis feBasis(gridView);
And here is the rest of the file:
using VectorType = BlockVector<double>;
using MatrixType = BCRSMatrix<double>;
MatrixType stiffnessMatrix;
auto rightHandSide = [] (const auto& x) { return 10;};
assembleLaplaceMatrix(feBasis, stiffnessMatrix,
rhs, rightHandSide);
VectorType x(feBasis.size());
x = 0;
boundaryTreatment(feBasis, dirichletNodes);
auto dirichletValueFunction = [pi](
const auto& x){
return std::sin(2*pi*x[0]); };
interpolate(feBasis, x, dirichletValueFunction, dirichletNodes);
stiffnessMatrix.mmv(x,
rhs);
for (size_t i=0; i<stiffnessMatrix.N(); i++) {
if (dirichletNodes[i]) {
for (auto&& [entry, idx] : sparseRange(stiffnessMatrix[i]))
entry = (i==idx) ? 1.0 : 0.0;
}
else {
for (
auto&& [entry, idx] :
sparseRange(stiffnessMatrix[i]))
if (dirichletNodes[idx])
entry = 0.0;
}
}
MatrixAdapter<MatrixType,VectorType,VectorType> op(stiffnessMatrix);
SeqILDL<MatrixType,VectorType,VectorType> ildl(stiffnessMatrix,1.0);
CGSolver<VectorType> cg(op,
ildl,
1e-4,
100,
2);
InverseOperatorResult statistics;
cg.apply(x,
rhs, statistics);
auto xFunction = Functions::makeDiscreteGlobalBasisFunction<double>(feBasis, x);
vtkWriter.addVertexData(xFunction, VTK::FieldInfo("x", VTK::FieldInfo::Type::scalar, 1));
vtkWriter.write("poisson-pq2");
}
}
RefinementIntervals refinementLevels(int levels)
auto sparseRange(Range &&range)
double elapsed() const noexcept