v0.8.13

In this tutorial basic ideas about MoFEM::Simple interface and programming with user data operators (UDO) are presented.
MoFEM is designed for quick development of finite element application for multiphysics problems. We designed MoFEM to enable decomposition of the complex problem into small subproblems, wherein each individual subproblem can be independently tested and debugged or used in different context. That is achieved by pipelines of users data operators (UDO), see figure 1.
In MoFEM, there is a clear separation of declaration, definition and implementation of a field, finite element and problem. Such approach allows to use the same implementation in different problems, e.g. use the same implementation of an elastic finite element in a mechanical problem and thermomechanical problem without the need to introduce changes into a code and maximising code reusability. On the other hand for the same problem declaration and definition, one can test various formulations and implementations. In this example problem declaration and definition is managed by MoFEM::Simple interface, in which we focus attention only on field declaration and implementation of finite elements, in particular, UDO.
We initialize PETSc internal variables and register MoFEM discrete manager in PETSc. Next MoAB instance is created and assigned to it. Similarly, MoFEM instance is created and assigned. MoAB and MoFEM interfaces are abstract classes used to access data in database.
Next, we get access to database by MoFEM::Simple interface
and get options from the command line and load mesh file. Default mesh file has name mesh.h5m. Particular name of file can be given in command line using option file_name my_file.h5m
The only indication that MoFEM database has been initialized is the following on the terminal
lib version 0.5.76 git commit id 253b1dd833ada49fe779e7f5fb60e52236ba51a9
where current MoFEM version and commit id is printed.
We add fields to the database. In MoFEM::Simple interface, three groups of fields can be declared. Fields defined in the domain, fields defined on boundary and fields defined on the skeleton. The same field can be defined on domain, boundary and skeleton. Fields are declared by giving its name, approximation space, base and number of coefficients. See for details here MoFEM::Simple::addDomainField.
Next, we set approximation order to those fields
For more details see MoFEM::Simple::setFieldOrder. If needed function MoFEM::Simple::setFieldOrder can be supplemented by the additional parameter to set order to particular field entities, enabling heterogeneous approximation base.
As result the following is printed on terminal
add: name U BitFieldId 1 bit number 1 space H1 approximation base AINSWORTH_LEGENDRE_BASE rank 3 meshset 12682136550675316768 add: name L BitFieldId 2 bit number 2 space H1 approximation base AINSWORTH_LEGENDRE_BASE rank 3 meshset 12682136550675316769 add: name S BitFieldId 4 bit number 3 space H1 approximation base AINSWORTH_LEGENDRE_BASE rank 3 meshset 12682136550675316770
where at the end entity handle to meshset is printed. This meshset consisting all entities to which field is set. The effect of the setting of approximation order will be viable when fields are constructed during setup stage.
The fields, finite elements and problems and all other data structures are build with the following code
As results following lines are printed on the terminal
add finite element: dFE add finite element: bFE add finite element: sFE
add problem: SimpleProblem
Build Field U (rank 0) nb added dofs (vertices) 27 (inactive 0) nb added dofs (edges) 234 (inactive 0) nb added dofs (triangles) 270 (inactive 0) nb added dofs (tets) 36 (inactive 0) nb added dofs 567 (number of inactive dofs 0) Build Field L (rank 0) nb added dofs (vertices) 24 (inactive 0) nb added dofs (edges) 108 (inactive 0) nb added dofs (triangles) 36 (inactive 0) nb added dofs 168 (number of inactive dofs 0) Build Field S (rank 0) nb added dofs (vertices) 24 (inactive 0) nb added dofs (edges) 108 (inactive 0) nb added dofs (triangles) 36 (inactive 0) nb added dofs 168 (number of inactive dofs 0) Nb. dofs 903
Build Finite Elements dFE id 00000000000000000000000000000001 name dFE f_id_row 00000000000000000000000000000001 f_id_col 00000000000000000000000000000001 BitFEId_data 00000000000000000000000000000001 Nb. FEs 12 Build Finite Elements bFE id 00000000000000000000000000000010 name bFE f_id_row 00000000000000000000000000000111 f_id_col 00000000000000000000000000000111 BitFEId_data 00000000000000000000000000000111 Nb. FEs 12 Build Finite Elements sFE id 00000000000000000000000000000100 name sFE f_id_row 00000000000000000000000000000111 f_id_col 00000000000000000000000000000111 BitFEId_data 00000000000000000000000000000111 Nb. FEs 30
Nb. entFEAdjacencies 918 partition_problem: rank = 0 FEs row ghost dofs problem id 00000000000000000000000000000001 FiniteElement id 00000000000000000000000000000111 name SimpleProblem Nb. local dof 903 nb global row dofs 903 partition_problem: rank = 0 FEs col ghost dofs problem id 00000000000000000000000000000001 FiniteElement id 00000000000000000000000000000111 name SimpleProblem Nb. local dof 903 nb global col dofs 903 problem id 00000000000000000000000000000001 FiniteElement id 00000000000000000000000000000111 name SimpleProblem Nb. elems 54 on proc 0 partition_ghost_col_dofs: rank = 0 FEs col ghost dofs problem id 00000000000000000000000000000001 FiniteElement id 00000000000000000000000000000111 name SimpleProblem Nb. col ghost dof 0 Nb. local dof 903 partition_ghost_row_dofs: rank = 0 FEs row ghost dofs problem id 00000000000000000000000000000001 FiniteElement id 00000000000000000000000000000111 name SimpleProblem Nb. row ghost dof 0 Nb. local dof 903
Once the problem is defined and set up, we create finite element instances. We are using generic implementation of finite elements which is tailored to the dimension of finite element entities. Note that in the following code we use MoFEM::VolumeElementForcesAndSourcesCore for domain elements and MoFEM::FaceElementForcesAndSourcesCore for boundary element and skeleton elements, since we use 3d mesh, which boundary and skeleton are 2d entities emerged in 3d space
Once finite elements objects are created we add operators to them according to figure 1. Starting from domain_fe
next boundary_fe
and finally skeleton_fe
To be more explicit, we can write for example for domain_fe, we have
Note that boost::ptr_vector (see link) is container of pointers, once vector is destroyed memory pointed by pointers is released.
We have to discuss integration over the skeleton. This functionality is used for a class of PetrovDiscontinuous Galerkin methods, or when Nitsche method is applied. Those methods involve of integration of traces of approximation functions on faces. That enforces the evaluation of derivatives of base functions on finite elements adjacent to the face. In MoFEM that is realised in a way, that generic element instance is iterated over adjacent entities to faces. We start with allocation memory for "side" finite element as before code boost::shared_ptr<VolumeElementForcesAndSourcesCoreOnSide> side_fe(new VolumeElementForcesAndSourcesCoreOnSide(m_field)); Note that this time finite element class is of type MoFEM::VolumeElementForcesAndSourcesCoreOnSide, since this element has integration points which are associated with face adjacent face. Next, to that element we add user data operators, in this particular case only one
Note that pointer to finite element instance is passed in the constructor of user data operator for skeleton element, what is discussed in detail in next section. Finally, we add operator to skeleton face element itself
Following line gives access to the discrete manager (DM). DM is the interface developed for PETSc to manage complexities associated with topology (mesh) and algebra and several implementation of this interface are available in native PETSc library. Here we are using the particular implementation developed for MoFEM. To get interface the following lines of the code are needed
When DM interface is no longer needed, it needs to be destroyed by user in the standard way wellknown from PETSc interface
Finally, we get to the point when we can put our machine in motion, we iterate over finite elements and run sequences of users data operator for each of them. We start with domain_fe
as results we get
**** 0 **** **** Operators **** Hello Operator OpRow: field name U side 0 type Vertex nb dofs on entity 12 ... Hello Operator OpRow: field name U side 0 type Tetrahedra nb dofs on entity 3 Hello Operator OpRowCol: row field name U row side 0 row type Vertex nb dofs on row entity12 : col field name U col side 0 col type Vertex nb dofs on col entity12 ... Hello Operator OpRowCol: row field name U row side 0 row type Tetrahedra nb dofs on row entity3 : col field name U col side 0 col type Tetrahedra nb dofs on col entity3 Hello Operator OpVolume: volume 0.0834851 **** 2 **** **** Operators **** ... **** 11 **** **** Operators **** ...
where dots are added for the abbreviation of output. Note that operators are called in the order we pushed them to finite element operators vector. Since we have twelve volume (Tetrahedra) elements, iteration ends on eleven, since in MoFEM we always start counting from zero.
To iterate over boundary element, analogically we do,
and we get similar output to the one shown before, with one difference being that the last operator does not print volume of the element but that is normal since entity of boundary finite element in this particular case is the triangle.
The same procedure is applied to iterate over skeleton finite elements entities
We have thirty skeleton elements and output looks as follows
**** 0 **** **** Operators **** Hello Operator OpRow: field name S side 0 type Vertex nb dofs on entity 9 Hello Operator OpRow: field name S side 0 type Edge nb dofs on entity 6 Hello Operator OpRow: field name S side 1 type Edge nb dofs on entity 6 Hello Operator OpRow: field name S side 2 type Edge nb dofs on entity 6 Hello Operator OpRow: field name S side 0 type Triangle nb dofs on entity 3 Hello Operator OpSideFace Hello Operator OpVolumeSide: volume 0.0782402 normal [3](0,0,1) ... **** 13 **** **** Operators **** Hello Operator OpRow: field name S side 0 type Vertex nb dofs on entity 9 Hello Operator OpRow: field name S side 0 type Edge nb dofs on entity 6 Hello Operator OpRow: field name S side 1 type Edge nb dofs on entity 0 Hello Operator OpRow: field name S side 2 type Edge nb dofs on entity 0 Hello Operator OpRow: field name S side 0 type Triangle nb dofs on entity 0 Hello Operator OpSideFace Hello Operator OpVolumeSide: volume 0.0834851 normal [3](0,0.530559,0.50091) Hello Operator OpVolumeSide: volume 0.0884264 normal [3](0,0.530559,0.50091) ...
Note that first operator is OpRow, the second operator is OpFaceSide, this operator prints its name and runs integration over adjacent to given face elements, which is side_fe. Once this element is run for each adjacent finite element entity, users data operators are run on it, i.e. OpVolumeSide which prints volume of the adjacent entity and normal of the face. Note that first element has only one run of OpVolumeSide, since skeleton finite element "0" is on the body boundary, while skeleton finite element "13" is in body volume and it has two volume neighbours.
Now we focus attention on the implementation of users data operator. The first operator has the structure
This users data operator class is derived from MoFEM::ForcesAndSourcesCore::UserDataOperator which can be used with any type of entity. It is the type of OPROW, which indicates that it only iterates lower dimension entities on the element. On each lower entity overload method is called
which as arguments takes entity side number (local entity number on the finite element), entity type (e.g. MBVERTEX, MBEDGE, MBTET) and reference to structure MoFEM::DataForcesAndSourcesCore::EntData, which keeps information on DOFs, approximation on given entity. This type of entity is usually used to integrate the righthand side vector.
Another type of users data operator is implemented here
This user data operator is of type OPROWCOL, which takes an additional parameter in constructor, i.e. symm, which is used to set symmetry of operator. Operator of this type iterates over all unique pairs of entities. If a symmetric operator pair is set of two elements (i.e. entities), thus order of entities is not important. If an operator is not symmetric, then pairs are the sequence of two elements and all variations of entities pairs are considered. This type of operator is used to integrate matrices. Note that this time function is overloaded, which takes as argument data for rows and columns, respectively.
Performing calculations on entity of specific dimension additional data like volume, normal need to be attained, for such case derived users data operator class can be used, e.g.
and for case of operator working on adjacent to face volume entity
where from members of this class information about face normal and adjacent entity volume can be accessed.
The easiest installation is with Docker containers; please see youtube video how to run installation link. You can also look here Installation with Docker (Linux, Mac OS X and some versions of Windows). Open terminal and run Docker container
You are now in Docker container, go to directory where Hello Problem problem is located, and compile code
It can take some time when you compile it for the first time since all essential functionality is compiled
Once code is compiled, we can run test