deal.II version GIT relicensing-2165-gc91f007519 2024-11-20 01:40:00+00:00
|
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 documentation group 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:
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.
More information on the topic can be found at this wikipedia page.
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):
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.
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
or
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:
typedef
named active_cell_iterator
. 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. begin_active()
which returns an iterator pointing to the first active cell. dimension
containing the dimension in which the object lives. space_dimension
containing the dimension of the object (e.g., a 2D surface in a 3D setting would have space_dimension = 2
). 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.
See the description in Polynomials and polynomial spaces for more information. In some contexts, anything that satisfies the interface resembling
may be considered as a polynomial for the sake of implementing finite elements.
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.
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:
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)\).
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.
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.
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.