Reference documentation for deal.II version Git 01d13113a1 2020-07-10 09:14:01 +0200
Concepts, or expectations on template parameters

Sometimes imposing constraints on the type of an object without requiring it to belong to a specific inheritance hierarchy is useful. These are usually referred to as concepts in the C++ community. This module lists the concepts commonly used in deal.II with brief descriptions of their intent. The convention in deal.II for listing constraints on a type is to provide the name of the concept as a typename in a template: for example, the type of a Vector depends on the type of the underlying field, and so it is defined as a template:

template <typename Number>
class Vector;

The point here is that you are creating a vector that can store elements of type Number. But there are some underlying assumptions on this. For example, the deal.II Vector class is not intended to be used just as a collection (unlike std::vector) but defines vector space operations such as addition of vectors, or the norm of vectors. Consequently, the data type users can specify for Number must satisfy certain conditions (i.e., it must conform to or "model" a "concept"): Specifically, the type must denote objects that represent the elements of what mathematically call a "field" (which you can think of as, well, "numbers": things we can add, multiply, divide, take the absolute value of, etc). The point of a concept is then to describe what conditions a type must satisfy to be a valid template argument in a given context.

This page describes these conditions for a number of concepts used throughout deal.II. Specifically, in the example above, the Number concept discussed below describes the types that could be used as argument for the Vector class.

Concepts have been proposed as a language extension to C++ for a long time already. They would allow us to describe that a class or function has certain properties in order to be a qualified template argument. For example, it would allow us to express in C++ code that the first argument to, say, GridTools::find_closest_vertex(), must have a type that represents an actual mesh – which we can currently only describe in words, see below. Using C++ concepts would allow us to describe this in code and trying to call such a function with an object as first argument that is not, in fact, a mesh would yield a compiler error that makes the mismatch clear.

Unfortunately, these proposals to C++ have never made it into any official C++ standard; they are proposed for C++20 however. We may start to use them once the vast majority of our users have compilers that support this standard.

DoFHandlerType

deal.II includes both DoFHandler and hp::DoFHandler as objects which manage degrees of freedom on a mesh. Though the two do not share any sort of inheritance relationship, they are similar enough that many functions just need something which resembles a DoFHandler to work correctly.

MatrixType

Many functions and classes in deal.II require an object which knows how to calculate matrix-vector products (the member function vmult), transposed matrix-vector products (the member function Tvmult), as well as the multiply and add' equivalents vmult_add and Tvmult_add. Some functions only require vmult and Tvmult, but an object should implement all four member functions if the template requires a MatrixType argument. Writing classes that satisfy these conditions is a sufficiently common occurrence that the LinearOperator class was written to make things easier; see Linear Operators for more information.

One way to think of MatrixType is to pretend it is a base class with the following signature (this is nearly the interface provided by SparseMatrix):

class MatrixType
{
public:
template <typename VectorType>
virtual void vmult(VectorType &u, const VectorType &v) const =0;
template <typename VectorType>
virtual void Tvmult(VectorType &u, const VectorType &v) const =0;
template <typename VectorType>
virtual void vmult_add(VectorType &u, const VectorType &v) const =0;
template <typename VectorType>
virtual void Tvmult_add(VectorType &u, const VectorType &v) const =0;
};

Template functions in C++ cannot be virtual (which is the main reason why this approach is not used in deal.II), so implementing this interface with inheritance will not work, but it is still a good way to think about this template concept. One can use the LinearOperator class to implement vmult_add and Tvmult_add instead of implementing them manually.

MeshType

Meshes can be thought of as arrays of vertices and connectivities, but a more fruitful view is to consider them as collections of cells. In C++, collections are often called containers (typical containers are std::vector, std::list, etc.) and they are characterized by the ability to iterate over the elements of the collection. The MeshType concept refers to any container which defines appropriate methods (such as DoFHandler::begin_active()) and typedefs (such as DoFHandler::active_cell_iterator) for managing collections of cells.

Instances of Triangulation, DoFHandler, and hp::DoFHandler may all be considered as containers of cells. In fact, the most important parts of the public interface of these classes consists simply of the ability to get iterators to their elements. Since these parts of the interface are generic, i.e., the functions have the same name in all classes, it is possible to write operations that do not actually care whether they work on a triangulation or a DoF handler object. Examples abound, for example, in the GridTools namespace, underlining the power of the abstraction that meshes and DoF handlers can all be considered simply as collections (containers) of cells.

On the other hand, meshes are non-standard containers unlike std::vector or std::list in that they can be sliced several ways. For example, one can iterate over the subset of active cells or over all cells; likewise, cells are organized into levels and one can get iterator ranges for only the cells on one level. Generally, however, all classes that implement the containers-of-cells concept use the same function names to provide the same functionality.

Functions that may be called with either class indicate this by accepting a template parameter like

template <template <int, int> class MeshType>

or

template <typename MeshType>

The classes that satisfy this concept are collectively referred to as mesh classes. The exact definition of MeshType relies a lot on library internals, but it can be summarized as any class with the following properties:

1. A typedef named active_cell_iterator.
2. A method get_triangulation() which returns a reference to the underlying geometrical description (one of the Triangulation classes) of the collection of cells. If the mesh happens to be a Triangulation, then the mesh just returns a reference to itself.
3. A method begin_active() which returns an iterator pointing to the first active cell.
4. A static member value dimension containing the dimension in which the object lives.
5. A static member value space_dimension containing the dimension of the object (e.g., a 2D surface in a 3D setting would have space_dimension = 2).

Number

This concept describes scalars which make sense as vector or matrix entries, which is usually some finite precision approximation of a field element. The canonical examples are double and float, but deal.II supports std::complex<T> for floating point type T in many places as well.

PolynomialType

See the description in Polynomials and polynomial spaces for more information. In some contexts, anything that satisfies the interface resembling

template <int dim>
class PolynomialType
{
virtual void compute (const Point<dim> &unit_point,
std::vector<Tensor<1,dim> > &values,
}

may be considered as a polynomial for the sake of implementing finite elements.

PreconditionerType

This is essentially a synonym for MatrixType, but usually only requires that vmult() and Tvmult() be defined. Most of the time defining Tvmult() is not necessary. One should think of vmult() as applying some approximation of the inverse of a linear operator to a vector, instead of the action of a linear operator to a vector, for the preconditioner classes.

RelaxationType

This is an object capable of relaxation for multigrid methods. One can think of an object satisfying this constraint as having the following interface as well as the constraints required by MatrixType:

class RelaxationType
{
public:
template <typename VectorType>
virtual void step(VectorType &u, const VectorType &v) const =0;
template <typename VectorType>
virtual void Tstep(VectorType &u, const VectorType &v) const =0;
};

where these two member functions perform one step (or the transpose of such a step) of the smoothing scheme. In other words, the operations performed by these functions are $$u = u - P^{-1} (A u - v)$$ and $$u = u - P^{-T} (A u - v)$$.

SparsityPatternType

Almost all functions (with the notable exception of SparsityTools::distribute_sparsity_pattern) which take a sparsity pattern as an argument can take either a regular SparsityPattern or a DynamicSparsityPattern, or even one of the block sparsity patterns. See Sparsity patterns for more information.

StreamType

Deriving new stream classes in C++ is well-known to be difficult. To get around this, some functions accept a parameter which defines operator<<, which allows for easy output to any kind of output stream.

VectorType

deal.II supports many different vector classes, including bindings to vectors in other libraries. These are similar to standard library vectors (i.e., they define begin(), end(), operator[], and size()) but also define numerical operations like add()`. Some examples of VectorType include Vector, TrilinosWrappers::MPI::Vector, and BlockVector.