FEDOF denotes the finite element degree of freedom. The concept of degree of freedom depends upon the type of finite element approximation (basis functions) used for the problem.
These series of notes will present the FEDOF concept for for H1 conforming Hierarchical and Lagrange basis functions.
In this note we will focus on 2D mesh with quadrilateral.
Information about this domain is given below:
engine: GMSH 4.1 0 8
version: 4.1
nsd: 2
minNptrs: 1
maxNptrs: 9
minElemNum: 1
maxElemNum: 16
tNodes: 9
tEntitiesForNodes: 9
tEntitiesForElements: 9
tElements: 4, 8, 4, 0
total mesh of volume: 0
total mesh of surface: 1
total mesh of curve: 4
total mesh of point: 4
The information about the cell mesh is given below.
total nodes: 9
total elements: 4
tEdges: 0
tFaces: 12
For a 2D-mesh, we do not define the edges. For 2D mesh we only have Cell, faces, and vertices.
From the domain we can get the pointer to mesh of cells. In this case a cell is made of quadrilateral elements.
cellMesh => dom%GetMeshPointer()
We can get the maximum number of nodes in an element by using GetMaxNNE() method.
maxNNE = cellMesh%GetMaxNNE()
For this mesh the value of maxNNE will be 4, as the maximum number of nodes in a quadrilateral element is 4.
You can think maxNNE as the maximum number of vertex connectivity for elements of mesh.
We can get the global element number of a local element number by using following.
iel = cellMesh%GetGlobalElemNumber(localElement=1)
Global element number is what you see in the mesh file, it the element number assigned by the mesh generator. Whereas, local element number is the location of this element inside the cellMesh object. We do not hope you to know the local element number, therefore, you can convert global element number to local element by using the method GetLocalElemNumber().
In this case iel will be 13.
The vertex connectivity of this element can be obtained by using the following code.
con = cellMesh%GetConnectivity(globalElement=1, islocal=yes, opt="V")
To get vertex connectivity we use opt="V", for face connectivity we will use opt="F", for edge connectivity we will use opt="E", and for cell connectivity we will use opt="C", and for all connectivity we will use opt="A".
The vertex connectivity of global element 13 (local element 1) is given below.
1 5 9 8
Note that the vertex connectivity is returned in terms of global vertex numbers.
Now lets get the face connectivity for this element.
We can enquire about the total number of faces in the mesh by using the method GetTotalFaces() as shown below.
CALL Display(cellMesh%GetTotalFaces(), "Total faces in mesh:")
The face connectivity of global Element 13 (local element 1) can be obtained by using the following.
con = cellMesh%GetConnectivity(globalElement=1, islocal=yes, opt="F")
CALL Display(con, "Connectivity of faces for globalElement=1: ")
The result is given below.
1 2 3 4
These are local face numbers. Note that there are no global face numbers because the mesh file does not provide face number. Therefore, the concept of global or local face number is not valid here.
We can get the orientation of these faces by using GetFaceOrientation(), the face orientation is a two dimensional array, where first dimension contains the face orientation flag in two dimensions and the second dimension contains the local face number. The following code is related to getting the face orientation.
The number of rows in faceOrientation should be 3.
CALL Reallocate(orient, 3, SIZE(con))
CALL cellMesh%GetFaceOrientation(globalElement=1, islocal=yes, ans=orient, &
nrow=nrow, ncol=ncol)
CALL Display(orient(1:nrow, 1:ncol), &
"Face orientations for globalElement=1: ")
The result is given below.
Face orientations for globalElement=1:
---------------------------------------
1 1 -1 -1
Let's understand the concept of face orientation.
The vertex connectivity of global element 13 is given by [1 5 9 8]. The faces are oriented in the counter clockwise direction.
In 2D, positive orientation of a face means from small to large vertex number. Therefore,
If you try to get the edge data for 2D mesh you will get nothing because edge data is note defined for 2D mesh.
Getting vertex connectivity of the a face. To get the node numbers of a face we need to describe the globalElement and the local face number as shown below.
con = cellMesh%GetFacetConnectivity(globalElement=1, iface=1, islocal=yes)
CALL Display(con, "Node numbers of face 1 of element 1: ")
con = cellMesh%GetFacetConnectivity(globalElement=1, iface=2, islocal=yes)
CALL Display(con, "Node numbers of face 2 of element 1: ")
con = cellMesh%GetFacetConnectivity(globalElement=1, iface=3, islocal=yes)
CALL Display(con, "Node numbers of face 3 of element 1: ")
con = cellMesh%GetFacetConnectivity(globalElement=1, iface=4, islocal=yes)
CALL Display(con, "Node numbers of face 4 of element 1: ")
The result is given below.
Node numbers of face 1 of element 1: [1 5]
Node numbers of face 2 of element 1: [5 9]
Node numbers of face 3 of element 1: [9 8]
Node numbers of face 4 of element 1: [8 1]
Note these face connectivities are for local face of element.
we can get all connectivity of an element by using the following code.
con = cellMesh%GetConnectivity(globalElement=1, islocal=yes, opt='V')
CALL Display(con, "Connectivity of vertex for localElement=1: ")
Getting the degree of freedom for scalar field.
1 5 9 8
10 11 12 13
22
1 5 9 8 10 11 12 13 22
There is another way to get FEDOF connectivity.
From the cellMesh we get the connectivity with opt "V", "F", "C", and "A" for vertex, face, cell, and all DOF connectivity respectively. Then for each of these abstract node number we can call following methods on FEDOF object.
GetVertextDOF()GetFaceDOF()GetCellDOF()Read the part 1 of this series here before proceeding further.
In this note we will study the FEDOF for scalar field using H1 conforming Hierarchical basis functions. The main focus on the degree of freedom associated with vertex, face, and cell of the element.
In this note we will focus on 2D mesh with quadrilateral.
Initiate the FEDOF object and scalar field by using the following code.
CALL u%ImportFromToml(tomlName="u", fedof=fedof, dom=dom, filename=tomlFileName)
CALL fedof%Display(msg="FEDOF info: ")
The following code gets all the DOF in an element.
1 5 9 8 10 11 12 13 14 15 16 17 18 19 20 21 46 47 48 49 50 51 52 53 54
Following code gets the vertex DOF connectivity.
nrow = fedof%GetTotalDOF(globalElement=1, islocal=yes, opt="V")
CALL Display(nrow, "Total Vertex DOF for localElement=1:")
con = fedof%GetConnectivity(globalElement=1, islocal=yes, opt="V")
CALL Display(con, "Vertex DOF connectivity for localElement=1:")
1 5 9 8
These are local DOF numbers, and it has nothing to do with the mesh connectivity.
We can get the DOF of a single vertex by using GetVertexDOF() as shown below.
CALL Reallocate(con, 100)
call cellMesh%GetConnectivity_(globalElement=1, islocal=yes, opt="V", ans=con, tsize=tsize)
CALL fedof%GetVertexDOF(globalNode=con(1), islocal=.false., ans=con, tsize=tsize)
CALL Display(con(1), "DOF of vertex 1 of element 1:")
Following code gets the connectivity of all face DOF in an element. Note that these face DOF are local. In addition, they are related to the positive oriented faces only.
nrow = fedof%GetTotalDOF(globalElement=1, islocal=yes, opt="F")
CALL Display(nrow, "Total Face DOF for localElement=1:")
con = fedof%GetConnectivity(globalElement=1, islocal=yes, opt="F")
CALL Display(con, "Face DOF connectivity for localElement=1:", full=.TRUE., &
orient="R")
10 11 12 13 14 15 16 17 18 19 20 21
We can get the DOF of a face by using GetFaceDOF() as shown below.
CALL Reallocate(con, 100, isExpand=.TRUE., expandFactor=2)
CALL cellMesh%GetConnectivity_(globalElement=1, islocal=yes, opt="F", &
ans=con, tsize=tsize)
nrow = con(1)
CALL fedof%GetFaceDOF(globalFace=nrow, islocal=.TRUE., ans=con, &
tsize=tsize)
CALL Display(con(1:tsize), "DOF on face: "//ToString(nrow), full=.TRUE., &
orient="R")
10 11 12
Getting dof on local face 3 on element 1:
CALL Reallocate(con, 100, isExpand=.TRUE., expandFactor=2)
CALL cellMesh%GetConnectivity_(globalElement=1, islocal=yes, opt="F", &
ans=con, tsize=tsize)
nrow = con(3)
CALL fedof%GetFaceDOF(globalFace=nrow, islocal=.TRUE., ans=con, &
tsize=tsize)
CALL Display(con(1:tsize), "DOF on face: "//ToString(nrow), full=.TRUE., &
orient="R")
16 17 18
Following code gets the connectivity of cell in an element. Note that these DOFs are local.
nrow = fedof%GetTotalDOF(globalElement=1, islocal=yes, opt="C")
CALL Display(nrow, "Total Cell DOF for localElement=1:")
con = fedof%GetConnectivity(globalElement=1, islocal=yes, opt="C")
CALL Display(con, "Cell DOF connectivity for localElement=1:")
46 47 48 49 50 51 52 53 54
We can get the cell DOF by using GetCellDOF() as shown below.
CALL Reallocate(con, 100, isExpand=.TRUE., expandFactor=2)
CALL cellMesh%GetConnectivity_(globalElement=1, islocal=yes, opt="C", &
ans=con, tsize=tsize)
nrow = con(1)
CALL fedof%GetCellDOF(globalCell=nrow, islocal=.FALSE., ans=con, &
tsize=tsize)
CALL Display(con(1:tsize), "DOF in cell: "//ToString(nrow), full=.TRUE., &
orient="R")
46 47 48 49 50 51 52 53 54
In this note we will study the FEDOF for scalar field using H1 conforming Hierarchical basis functions. The main focus is on generating quadrature points and shape functions.
In this note we will focus on 2D mesh with quadrilateral.
Initiate the FEDOF object and scalar field by using the following code.
CALL u%ImportFromToml(tomlName="u", fedof=fedof, dom=dom, filename=tomlFileName)
CALL fedof%Display(msg="FEDOF info: ")
The following code gets the quadrature points in an element.
CALL fedof%GetQuadraturePoints(quad=qp, globalElement=1, islocal=yes)
CALL Display(qp, "Quadrature points: ")
| x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | x10 | x11 | x12 | x13 | x14 | x15 | x16 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| -0.861136 | -0.861136 | -0.861136 | -0.861136 | -0.339981 | -0.339981 | -0.339981 | -0.339981 | 0.339981 | 0.339981 | 0.339981 | 0.339981 | 0.861136 | 0.861136 | 0.861136 | 0.861136 |
| -0.861136 | -0.339981 | 0.339981 | 0.861136 | -0.861136 | -0.339981 | 0.339981 | 0.861136 | -0.861136 | -0.339981 | 0.339981 | 0.861136 | -0.861136 | -0.339981 | 0.339981 | 0.861136 |
| 0.121003 | 0.226852 | 0.226852 | 0.121003 | 0.226852 | 0.425293 | 0.425293 | 0.226852 | 0.226852 | 0.425293 | 0.425293 | 0.226852 | 0.121003 | 0.226852 | 0.226852 | 0.121003 |
To apply boundary condition in FEM computation, EASIFEM, provides a class called DirichletBC_.
DirichletBC_ is a subclass of AbstractBC.
To understand how DirichletBC works, lets consider an example of linear elasticity. Let's say we want to apply the following boundary condition.
We may think that there is only one boundary condition. But in easifem this is not the case. Actually, , has three components in 3D (and two components in 2D). Therefore, the above boundary condition is actually boundary condition for , , and . So, we have three boundary condition on a given boundary .
The second point, which is quite obvious, is that every boundary condition has two things:
To define the boundary EASIFEM employs the MeshSelection class. The value can be specified in several ways as mentioned below in this section.
Several instances of DirichletBC can have same boundary but different condition.
We can initiate an instance of DirichletBC_ by importing the information from a toml-file. To do so, we will use the method called ImportFromToml.
Let us consider the following toml file
[domain]
filename = "./mesh/square_3x3.h5"
group = "/"
totalMedium = 1
[u]
name = "u"
engine = "NATIVE_SERIAL"
fieldType = "Normal"
spaceCompo = 1
timeCompo = 1
fedofName = "space"
geofedofName = "geofedof"
[u.geofedof]
baseContinuity = "H1"
baseInterpolation = "Lagrange"
ipType = "Equidistance"
baseType = "Monomial"
[u.space]
baseContinuity = "H1"
baseInterpolation = "Hierarchical"
# ipType = "Equidistance"
# baseType = "Monomial"
order = 4
scaleForQuadOrder = 2
quadOptName = "quadOpt"
[u.space.quadOpt]
isHomogeneous = true
quadratureType = "GaussLegendre"
# order = 2
[dbc]
name = "left_bottom_fix"
idof = 1
nodalValueType = "Constant"
value = 10.0
[dbc.boundary]
isSelectionByMeshID = true
[dbc.boundary.meshID]
line = [1, 4]
We use the following code to import the boundary condition from the above toml file.
!> author: Vikas Sharma, Ph. D.
! date: 2025-10-23
! summary: Study of DirichletBC class
PROGRAM main
USE GlobalData, ONLY: DFP, I4B, LGT
USE ExceptionHandler_Class, ONLY: e, EXCEPTION_INFORMATION
USE DirichletBC_Class
USE FEDomain_Class
USE AbstractMesh_Class
USE FEDOF_Class
USE ScalarField_Class
USE Display_Method
USE ReallocateUtility
IMPLICIT NONE
!----------------------------------------------------------------------------
! Parameters
!----------------------------------------------------------------------------
CHARACTER(*), PARAMETER :: tomlFileName = "./test1.toml"
!----------------------------------------------------------------------------
! Types and variables
!----------------------------------------------------------------------------
TYPE(DirichletBC_) :: obj
TYPE(FEDomain_) :: dom
TYPE(ScalarField_) :: u
TYPE(FEDOF_) :: fedof, geofedof
INTEGER(I4B) :: tsize, iNodeOnNode, iNodeOnEdge, iNodeOnFace, nrow, ncol
!----------------------------------------------------------------------------
! Allocatables and pointers
!----------------------------------------------------------------------------
CLASS(AbstractMesh_), POINTER :: cellMesh, boundaryMesh
INTEGER(I4B), ALLOCATABLE :: nodeNum(:)
REAL(DFP), ALLOCATABLE :: nodalValue(:, :)
!----------------------------------------------------------------------------
! Setting the verbosity
! In Debug mode, there will many messages printed to the screen
! Following code suppress information-exception.
!----------------------------------------------------------------------------
CALL e%setQuietMode(EXCEPTION_INFORMATION, .TRUE.)
!----------------------------------------------------------------------------
! Domain: Initiate FEDomain and print info
!----------------------------------------------------------------------------
CALL dom%ImportFromToml(tomlName="domain", filename=tomlFileName)
! CALL dom%DisplayDomainInfo(msg="DomainInfo: ")
!----------------------------------------------------------------------------
! ScalarField and FEDOF: Initiate scalarField and fedof
!----------------------------------------------------------------------------
CALL u%ImportFromToml(tomlName="u", fedof=fedof, geofedof=geofedof, dom=dom, &
filename=tomlFileName)
! CALL fedof%Display(msg="FEDOF info: ")
!----------------------------------------------------------------------------
! DirichletBC: Import from toml
!----------------------------------------------------------------------------
CALL obj%ImportFromToml(filename=tomlFileName, dom=dom, tomlName="dbc")
! CALL obj%Display("DirichletBC Info: ")
!----------------------------------------------------------------------------
! DirichletBC: Get the total node numbers
!----------------------------------------------------------------------------
tsize = obj%GetTotalNodeNum(fedof=fedof)
CALL Display(tsize, "Total Node Num: ")
!----------------------------------------------------------------------------
! DirichletBC: Get the node numbers
!----------------------------------------------------------------------------
CALL Reallocate(nodeNum, tsize, isExpand=.TRUE., expandFactor=2)
CALL obj%Get(fedof=fedof, nodeNum=nodeNum, tsize=tsize, &
iNodeOnNode=iNodeOnNode, iNodeOnEdge=iNodeOnEdge, &
iNodeOnFace=iNodeOnFace)
CALL Display(tsize, "tsize = ")
CALL Display(nodeNum(1:tsize), "nodeNum", full=.TRUE., orient="ROW")
CALL Display(iNodeOnNode, "iNodeOnNode = ")
CALL Display(iNodeOnFace, "iNodeOnFace = ")
CALL Display(iNodeOnEdge, "iNodeOnEdge = ")
!----------------------------------------------------------------------------
! DirichletBC: Get the nodal values
!----------------------------------------------------------------------------
CALL Reallocate(nodalValue, tsize, 2, isExpand=.TRUE., expandFactor=2)
CALL obj%Get(fedof=fedof, nodeNum=nodeNum, nodalValue=nodalValue, &
nrow=nrow, ncol=ncol)
CALL Display(nodalValue(1:nrow, 1:ncol), "nodalValue: ", &
full=.TRUE.)
CALL Display([nrow, ncol], "[nrow, ncol]: ")
!----------------------------------------------------------------------------
! Cleanup
!----------------------------------------------------------------------------
CALL dom%DEALLOCATE()
END PROGRAM main
We obtain the following information about the boundary condition.
| Property | Value |
|---|---|
| isInit | TRUE |
| name | left_bottom_fix |
| idof | 1 |
| nodalValueType | CONSTANT |
| isUserFunction | FALSE |
| isUseExternal | FALSE |
| IsInitiated | TRUE |
| IsSelectionByMeshID | TRUE |
| IsSelectionByElemNum | FALSE |
| IsSelectionByNodeNum | FALSE |
| IsSelectionByBox | FALSE |
| MeshID Point | FALSE |
| MeshID Curve | TRUE |
| MeshID Surface | FALSE |
| MeshID Volume | FALSE |
| ElemNum Point | FALSE |
| ElemNum Curve | FALSE |
| ElemNum Surface | FALSE |
| ElemNum Volume | FALSE |
| NodeNum Point | FALSE |
| NodeNum Curve | FALSE |
| NodeNum Surface | FALSE |
| NodeNum Volume | FALSE |
| Box Point | FALSE |
| Box Curve | FALSE |
| Box Surface | FALSE |
| Box Volume | FALSE |
To get the total number of nodes where the boundary condition is applied, we can use the method called GetTotalNodeNum.
CALL Display(obj%GetTotalNodeNum(fedof=fedof), "Total Node Num: ")
Total Node Num: 18
There is a total of 18 nodes where the boundary condition is applied. Actually, node number 1 is counted two times because it is common to bottom and left boundaries.
To get the node numbers we call Get method. These node numbers are degrees of freedom numbers for FEDOF. You can use these indices to set and get the values in node fields.
CALL obj%Get(fedof=fedof, nodeNum=nodeNum, tsize=tsize, &
iNodeOnNode=iNodeOnNode, iNodeOnEdge=iNodeOnEdge, &
iNodeOnFace=iNodeOnFace)
CALL Display(tsize, "tsize = ")
CALL Display(nodeNum(1:tsize), "nodeNum", full=.TRUE., orient="ROW")
CALL Display(iNodeOnNode, "iNodeOnNode = ")
CALL Display(iNodeOnFace, "iNodeOnFace = ")
CALL Display(iNodeOnEdge, "iNodeOnEdge = ")
nodeNum
----------------------------------------------------------------------
1 5 2 4 8 1 10 11 12 31 32 33 28 29 30 19 20 21
iNodeOnNode = 1
iNodeOnFace = 7
iNodeOnEdge = 19
Before reading this post, it is recommended to read Handling Dirichlet Bounding Conditions in easifem (Part 1) first.
In this example, Dirichlet boundary conditions is given by a user defined function.
We can initiate an instance of DirichletBC_ by importing the information from a toml-file. To do so, we will use the method called ImportFromToml.
Let us consider the following toml file
[domain]
filename = "./mesh/square_3x3.h5"
group = "/"
totalMedium = 1
[u]
name = "u"
engine = "NATIVE_SERIAL"
fieldType = "Normal"
spaceCompo = 1
timeCompo = 1
fedofName = "space"
geofedofName = "geofedof"
[u.geofedof]
baseContinuity = "H1"
baseInterpolation = "Lagrange"
ipType = "Equidistance"
baseType = "Monomial"
[u.space]
baseContinuity = "H1"
baseInterpolation = "Hierarchical"
# ipType = "Equidistance"
# baseType = "Monomial"
order = 4
scaleForQuadOrder = 2
quadOptName = "quadOpt"
[u.space.quadOpt]
isHomogeneous = true
quadratureType = "GaussLegendre"
# order = 2
[dbc]
name = "Bottom Space UserFunction"
idof = 1
nodalValueType = "Space"
isUserFunction = true
[dbc.function]
name = "func"
returnType = "Scalar"
numReturns = 1
argType = "Space"
numArgs = 3
luaScript = "test2.lua"
luaFunctionName = "BottomFunc"
[dbc.boundary]
isSelectionByMeshID = true
[dbc.boundary.meshID]
line = [1]
We use the following code to import the boundary condition from the above toml file.
!> author: Vikas Sharma, Ph. D.
! date: 2025-10-23
! summary: Study of DirichletBC class
PROGRAM main
USE GlobalData, ONLY: DFP, I4B, LGT
USE ExceptionHandler_Class, ONLY: e, EXCEPTION_INFORMATION
USE DirichletBC_Class
USE FEDomain_Class
USE AbstractMesh_Class
USE FEDOF_Class
USE ScalarField_Class
USE Display_Method
USE ReallocateUtility
IMPLICIT NONE
!----------------------------------------------------------------------------
! Parameters
!----------------------------------------------------------------------------
CHARACTER(*), PARAMETER :: tomlFileName = "./test2.toml"
!----------------------------------------------------------------------------
! Types and variables
!----------------------------------------------------------------------------
TYPE(DirichletBC_) :: obj
TYPE(FEDomain_) :: dom
TYPE(ScalarField_) :: u
TYPE(FEDOF_) :: fedof, geofedof
INTEGER(I4B) :: tsize, iNodeOnNode, iNodeOnEdge, iNodeOnFace, nrow, ncol
!----------------------------------------------------------------------------
! Allocatables and pointers
!----------------------------------------------------------------------------
CLASS(AbstractMesh_), POINTER :: cellMesh, boundaryMesh
INTEGER(I4B), ALLOCATABLE :: nodeNum(:)
REAL(DFP), ALLOCATABLE :: nodalValue(:, :)
!----------------------------------------------------------------------------
! Setting the verbosity
! In Debug mode, there will many messages printed to the screen
! Following code suppress information-exception.
!----------------------------------------------------------------------------
! CALL e%setQuietMode(EXCEPTION_INFORMATION, .TRUE.)
!----------------------------------------------------------------------------
! Domain: Initiate FEDomain and print info
!----------------------------------------------------------------------------
CALL dom%ImportFromToml(tomlName="domain", filename=tomlFileName)
! CALL dom%DisplayDomainInfo(msg="DomainInfo: ")
!----------------------------------------------------------------------------
! ScalarField and FEDOF: Initiate scalarField and fedof
!----------------------------------------------------------------------------
CALL u%ImportFromToml(tomlName="u", fedof=fedof, geofedof=geofedof, dom=dom, &
filename=tomlFileName)
! CALL fedof%Display(msg="FEDOF info: ")
!----------------------------------------------------------------------------
! DirichletBC: Import from toml
!----------------------------------------------------------------------------
CALL obj%ImportFromToml(filename=tomlFileName, dom=dom, tomlName="dbc")
! CALL obj%Display("DirichletBC Info: ")
!----------------------------------------------------------------------------
! DirichletBC: Get the total node numbers
!----------------------------------------------------------------------------
tsize = obj%GetTotalNodeNum(fedof=fedof)
CALL Display(tsize, "Total Node Num: ")
!----------------------------------------------------------------------------
! DirichletBC: Get the node numbers
!----------------------------------------------------------------------------
CALL Reallocate(nodeNum, tsize, isExpand=.TRUE., expandFactor=2)
CALL obj%Get(fedof=fedof, nodeNum=nodeNum, tsize=tsize, &
iNodeOnNode=iNodeOnNode, iNodeOnEdge=iNodeOnEdge, &
iNodeOnFace=iNodeOnFace)
CALL Display(tsize, "tsize = ")
CALL Display(nodeNum(1:tsize), "nodeNum", full=.TRUE., orient="ROW")
CALL Display(iNodeOnNode, "iNodeOnNode = ")
CALL Display(iNodeOnFace, "iNodeOnFace = ")
CALL Display(iNodeOnEdge, "iNodeOnEdge = ")
!----------------------------------------------------------------------------
! DirichletBC: Get the nodal values
!----------------------------------------------------------------------------
CALL Reallocate(nodalValue, tsize, 2, isExpand=.TRUE., expandFactor=2)
CALL Display(SHAPE(nodalValue), "shape(nodalValue) before Get: ")
nrow = 0; ncol = 0
CALL obj%Get(fedof=fedof, geofedof=geofedof, nodeNum=nodeNum, &
nodalValue=nodalValue, nrow=nrow, ncol=ncol)
CALL Display(nodeNum(1:nrow), "nodeNum(1:nrow): ", &
full=.TRUE., advance="NO")
CALL Display(nodalValue(1:nrow, 1:ncol), "nodalValue: ", &
full=.TRUE.)
CALL Display([nrow, ncol], "[nrow, ncol]: ")
!----------------------------------------------------------------------------
! Cleanup
!----------------------------------------------------------------------------
CALL dom%DEALLOCATE()
END PROGRAM main
We obtain the following information about the boundary condition.
| Property | Value |
|---|---|
| isInit | TRUE |
| name | Bottom Space UserFunction |
| idof | 1 |
| nodalValueType | SPACE |
| isUserFunction | TRUE |
| isUseExternal | FALSE |
| Property | Value |
|---|---|
| IsInitiated | TRUE |
| IsSelectionByMeshID | TRUE |
| IsSelectionByElemNum | FALSE |
| IsSelectionByNodeNum | FALSE |
| IsSelectionByBox | FALSE |
| Element Type | Point | Curve | Surface | Volume |
|---|---|---|---|---|
| MeshID ALLOCATED | FALSE | TRUE | FALSE | FALSE |
| ElemNum ALLOCATED | FALSE | FALSE | FALSE | FALSE |
| NodeNum ALLOCATED | FALSE | FALSE | FALSE | FALSE |
| Box ALLOCATED | FALSE | FALSE | FALSE | FALSE |
To get the total number of nodes where the boundary condition is applied, we can use the method called GetTotalNodeNum.
CALL Display(obj%GetTotalNodeNum(fedof=fedof), "Total Node Num: ")
Total Node Num: 9
To get the node numbers we call Get method. These node numbers are degrees of freedom numbers for FEDOF. You can use these indices to set and get the values in node fields.
CALL obj%Get(fedof=fedof, nodeNum=nodeNum, tsize=tsize, &
iNodeOnNode=iNodeOnNode, iNodeOnEdge=iNodeOnEdge, &
iNodeOnFace=iNodeOnFace)
CALL Display(tsize, "tsize = ")
CALL Display(nodeNum(1:tsize), "nodeNum", full=.TRUE., orient="ROW")
CALL Display(iNodeOnNode, "iNodeOnNode = ")
CALL Display(iNodeOnFace, "iNodeOnFace = ")
CALL Display(iNodeOnEdge, "iNodeOnEdge = ")
nodeNum
----------------------------------
1 5 2 10 11 12 31 32 33
iNodeOnNode = 1
iNodeOnFace = 4
iNodeOnEdge = 10
To create a structured mesh of quadrangles in easifem, we will need following classes:
PROGRAM main
USE GmshStructuredMesh_Class
USE Gmsh_Class
USE FPL
USE GlobalData
USE MSHFIle_Class
USE HDF5File_Class
IMPLICIT NONE
TYPE(GmshStructuredMesh_) :: obj
TYPE(Gmsh_) :: gmsh
TYPE(ParameterList_) :: param
! CHARACTER(*), PARAMETER :: title = "small_quad4_mesh"
CHARACTER(*), PARAMETER :: title = "very_small_quad4_mesh"
REAL(DFP), PARAMETER :: pointsOnAxis1(2) = [0.0, 1.0]
REAL(DFP), PARAMETER :: pointsOnAxis2(2) = [0.0, 1.0]
INTEGER(I4B), PARAMETER :: transfinitePointsOnAxis1(1) = [3]
INTEGER(I4B), PARAMETER :: transfinitePointsOnAxis2(1) = [3]
INTEGER(I4B) :: ierr
TYPE(MSHFile_) :: mshFile
TYPE(HDF5File_) :: hdf5file
CALL FPL_Init()
CALL param%Initiate()
CALL SetGmshStructuredMeshParam(param=param, &
filename=title//".msh", &
pointsOnAxis1=pointsOnAxis1, &
pointsOnAxis2=pointsOnAxis2, &
transfinitePointsOnAxis1=transfinitePointsOnAxis1, &
transfinitePointsOnAxis2=transfinitePointsOnAxis2, &
recombineAll=.TRUE.)
CALL obj%Initiate(param)
ierr = gmsh%initialize()
ierr = gmsh%model%add("GmshStructuredMesh2D")
CALL obj%Generate(gmsh)
! ierr = gmsh%fltk%run()
ierr = gmsh%finalize()
CALL param%DEALLOCATE()
CALL obj%DEALLOCATE()
CALL mshFile%Initiate(filename=title//'.msh', STATUS="OLD", ACTION="READ")
CALL mshFile%OPEN()
CALL mshFile%READ()
CALL hdf5file%Initiate(title//'.h5', MODE="NEW")
CALL hdf5file%OPEN()
CALL mshFile%Export(hdf5=hdf5file, group="")
CALL mshFile%DEALLOCATE()
CALL hdf5file%DEALLOCATE()
END PROGRAM main
PROGRAM main
USE GmshStructuredMesh_Class
USE Gmsh_Class
USE FPL
USE GlobalData
USE MSHFile_Class
USE HDF5File_Class
IMPLICIT NONE
TYPE(GmshStructuredMesh_) :: obj
TYPE(Gmsh_) :: gmsh
TYPE(ParameterList_) :: param
CHARACTER(*), PARAMETER :: title = "very_small_quad4_mesh_two_region"
REAL(DFP), PARAMETER :: pointsOnAxis1(3) = [0.0, 1.0, 2.0]
REAL(DFP), PARAMETER :: pointsOnAxis2(2) = [0.0, 1.0]
INTEGER(I4B), PARAMETER :: transfinitePointsOnAxis1(2) = [3, 3]
INTEGER(I4B), PARAMETER :: transfinitePointsOnAxis2(1) = [2]
INTEGER(I4B) :: ierr
TYPE(MSHFile_) :: mshFile
TYPE(HDF5File_) :: hdf5file
CALL FPL_Init()
CALL param%Initiate()
CALL SetGmshStructuredMeshParam( &
param=param, &
filename=title//".msh", &
pointsOnAxis1=pointsOnAxis1, &
pointsOnAxis2=pointsOnAxis2, &
transfinitePointsOnAxis1=transfinitePointsOnAxis1, &
transfinitePointsOnAxis2=transfinitePointsOnAxis2, &
recombineAll=.TRUE.)
CALL obj%Initiate(param)
ierr = gmsh%initialize()
ierr = gmsh%model%add("GmshStructuredMesh2D")
CALL obj%Generate(gmsh)
! ierr = gmsh%fltk%run()
ierr = gmsh%finalize()
CALL param%DEALLOCATE()
CALL obj%DEALLOCATE()
CALL mshFile%Initiate(filename=title//'.msh', STATUS="OLD", ACTION="READ")
CALL mshFile%OPEN()
CALL mshFile%READ()
CALL hdf5file%Initiate(title//'.h5', MODE="NEW")
CALL hdf5file%OPEN()
CALL mshFile%Export(hdf5=hdf5file, group="")
CALL mshFile%DEALLOCATE()
CALL hdf5file%DEALLOCATE()
END PROGRAM main
PROGRAM main
USE easifemBase
USE Gmsh_Class
USE HDF5File_Class
USE MSHFile_Class
TYPE(Gmsh_) :: gmsh
CHARACTER(LEN=*), PARAMETER :: title = "small_tri3_mesh"
REAL(DFP), PARAMETER :: lx = 2.0
REAL(DFP), PARAMETER :: ly = 2.0
INTEGER(I4B), PARAMETER :: order = 1
REAL(DFP), PARAMETER :: meshSize = 1.0
REAL(DFP), PARAMETER :: corner(3) = 0.0_DFP
INTEGER(I4B) :: ierr
REAL(DFP) :: x, y, z, lc
TYPE(MSHFile_) :: mshFile
TYPE(HDF5File_) :: hdf5file
ierr = gmsh%Initialize()
ierr = gmsh%model%add(title)
x = corner(1); y = corner(2); z = corner(3); lc = meshSize
ierr = gmsh%model%geo%addPoint(x=x, y=y, z=z, meshSize=lc, tag=1)
x = x + lx; y = y; z = z; lc = lc
ierr = gmsh%model%geo%addPoint(x=x, y=y, z=z, meshSize=lc, tag=2)
x = x; y = y + ly; z = z; lc = lc
ierr = gmsh%model%geo%addPoint(x=x, y=y, z=z, meshSize=lc, tag=3)
x = corner(1); y = y; z = z; lc = lc
ierr = gmsh%model%geo%addPoint(x=x, y=y, z=z, meshSize=lc, tag=4)
ierr = gmsh%model%geo%addLine(1, 2, 1)
ierr = gmsh%model%geo%addLine(2, 3, 2)
ierr = gmsh%model%geo%addLine(3, 4, 3)
ierr = gmsh%model%geo%addLine(4, 1, 4)
ierr = gmsh%model%geo%addCurveLoop([1, 2, 3, 4], tag=1)
ierr = gmsh%model%geo%addPlaneSurface([1], 1)
ierr = gmsh%model%geo%synchronize()
ierr = gmsh%option%setNumber(VALUE=1, name="Mesh.SaveAll")
ierr = gmsh%model%mesh%generate(2)
ierr = gmsh%model%mesh%setOrder(order)
ierr = gmsh%WRITE(title//'.msh')
ierr = gmsh%Finalize()
CALL mshFile%Initiate(filename=title//'.msh', STATUS="OLD", ACTION="READ")
CALL mshFile%OPEN()
CALL mshFile%READ()
CALL hdf5file%Initiate(title//'.h5', MODE="NEW")
CALL hdf5file%OPEN()
CALL mshFile%Export(hdf5=hdf5file, group="")
CALL mshFile%DEALLOCATE()
CALL hdf5file%DEALLOCATE()
END PROGRAM main
A kernel is a computer program written in object oriented programming paradigm which attempts to solve a partial differential equation. In easifem, the term kernel is used for solving a particular PDE.
In our experience when we focus on solving a particular PDE, we can design the kernel quickly and more efficiently. This is because the kernel has specific tasks to perform with limited number of kernel parameters. We believe that this is a good balance between flexibility, efficiency and speed of development. In the past, we have tried to create a generic PDE solver type kernel but after few years the kernel became too complex and difficult to maintain by a group of developers. Therefore, we want to define a kernel for a specific PDE.
Yes, the kernel has sufficient amount of flexibility. For example, the coefficient of PDE can be constant, spatially changing or time dependent. The boundary condition can also be constant, space, time, or space-time dependent.
A kernel is a blend of procedural programming and object oriented programming. The procedural programming comes into the picture because a kernel is trying to solve a specific PDE by using a specific method. In this sense, the kernel's design is driven keeping procedure programming in mind. However, a kernel can solve several problems governed by the same PDE. These problems can have different types of boundary conditions and material properties. Also, we can use several numerical methods to solve the problem. In order to facilitate these objectives, we use object oriented programming.
Add the following .gitignore file in the root directory of your kernel. This will help you to ignore the unnecessary files which are not required for the kernel.
*.a
*.mod
*.smod
*.o
*.out
*.i90
*.if90
*.DS_Store
*.prj
*.drawio
*.log
*.eps
*.h5
*.gp
*.msh
*.vtu
*.ipynb
*.dat
*.mtx
*.eps
*.pdf
*.geo
*.ps
*.png
*.jpg
*.plt
*.csv
*/build/
**/build
build/
_packages/
__*
log.txt
test.txt
## Obsidian related
.obsidian
# VSCode related
vscode-settings
.vscode/
settings.json
## Docusaurus related
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.DS_Store
.$*
~*
Create following directory in the root directory of your kernel.
src directoryThe src directory will contain the following directories and files.
modules: This directory contains the header files of the kernel. These header files are called modules.submodules: This directory contains the submodules of the kernel. These submodules are contains the implementation of the header files.Let us say you want to develop a class called Abstract1DUVSTFEM_ (note that all user defined data types end with an underscore). Then you should create a directory called Abstract1DUVSTFEM in the src/modules directory.
Now in Abstract1DUVSTFEM directory create another directory called src which will contain the header file. In this source directory you will create Abstract1DUVSTFEM_Class.F90.
In Abstract1DUVSTFEM directory create a file called CMakeLists.txt. The content of this is given below.
set(src_path "${CMAKE_CURRENT_LIST_DIR}/src/")
target_sources(${PROJECT_NAME} PRIVATE ${src_path}/Abstract1DUVSTFEM_Class.F90)
If you follow the above steps the src directory will look like the following.
src
├── modules
│ └── Abstract1DUVSTFEM
│ ├── CMakeLists.txt
│ └── src
│ └── Abstract1DUVSTFEM_Class.F90
└── submodules
Now we will implement the methods defined in Abstract1DUVSTFEM_Class.F90 in the src/submodules directory. Create a directory called Abstract1DUVSTFEM in the src/submodules directory. In this directory create a file called CMakeLists.txt. In this directory create a directory called src which will contain the submodules of module Abstract1DUVSTFEM_Class. The structure of the directory will look like this.
The content of CMakeLists.txt file will be as follows.
set(src_path "${CMAKE_CURRENT_LIST_DIR}/src/")
target_sources(
${PROJECT_NAME}
PRIVATE ${src_path}/[email protected]
src
├── modules
│ └── Abstract1DUVSTFEM
│ ├── CMakeLists.txt
│ └── src
│ └── Abstract1DUVSTFEM_Class.F90
└── submodules
├── Abstract1DUVSTFEM
│ └── src
│ ├── [email protected]
└── CMakeLists.txt
In modules and submodules directory create a file called CMakeLists.txt. The CMake file will look like this.
# AbstractSTFEM
include(${CMAKE_CURRENT_LIST_DIR}/Abstract1DUVSTFEM/CMakeLists.txt)
# AbstractSTFEM
include(${CMAKE_CURRENT_LIST_DIR}/Abstract1DUVSTFEM/CMakeLists.txt)
Now the structure of the src directory will look like this.
src
├── modules
│ ├── Abstract1DUVSTFEM
│ │ ├── CMakeLists.txt
│ │ └── src
│ │ └── Abstract1DUVSTFEM_Class.F90
│ └── CMakeLists.txt
└── submodules
├── Abstract1DUVSTFEM
│ ├── CMakeLists.txt
│ └── src
│ ├── [email protected]
├── CMakeLists.txt
Now we need to write the main CMakeLists.txt file in the root of our project. That is, the parent directory of src directory. We will describe the content of this file in another blog post.
cmake_minimum_required(VERSION 3.20.0 FATAL_ERROR)
set(PROJECT_NAME "easifemOneDimElasticity")
project(${PROJECT_NAME})
enable_language(Fortran C CXX)
set(VERSION_MAJOR "25")
set(VERSION_MINOR "04")
set(VERSION_BugFix "1")
set(PROJECT_VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_BugFix})
set(CMAKE_PROJECT_DESCRIPTION
"${PROJECT_NAME} is part of EASIFEM platform.
EASIFEM: Expandable and Scalable Infrastructure for Finite Element Methods.
This program solves one dimensional wave propogation problems using finite element
methods including space-time finite element methods.
")
set(CMAKE_PROJECT_HOMEPAGE_URL "https://www.easifem.com")
set(TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets")
set(namespace "${PROJECT_NAME}")
include(CMakePrintHelpers)
include(FortranCInterface)
FortranCInterface_VERIFY()
list(APPEND TARGET_COMPILE_DEF "-DUSE_CMAKE")
# find my cmake modules here...
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules)
# check error
if(" ${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL " ${CMAKE_CURRENT_BINARY_DIR}")
message(
FATAL_ERROR
"[ERROR] :: Build directory and Source directory cannot be same.")
endif()
# make directories include(${PROJECT_SOURCE_DIR}/cmake/makeDirs.cmake)
#
include(GNUInstallDirs)
set(CMAKE_Fortran_MODULE_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_INCLUDEDIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
set(INSTALL_LIBDIR
${CMAKE_INSTALL_LIBDIR}
CACHE PATH "Installation location of lib")
set(INSTALL_INCLUDEDIR
${CMAKE_INSTALL_INCLUDEDIR}
CACHE PATH "Installation location of module files")
set(INSTALL_BINDIR
${CMAKE_INSTALL_BINDIR}
CACHE PATH "Installation location of binary files")
if(WIN32 AND NOT CYGWIN)
set(DEF_INSTALL_CMAKEDIR CMake)
else()
set(DEF_INSTALL_CMAKEDIR share/cmake/${PROJECT_NAME})
endif()
set(INSTALL_CMAKEDIR
${DEF_INSTALL_CMAKEDIR}
CACHE PATH "Installation directory for CMake files")
foreach(p LIB BIN INCLUDE CMAKE)
file(TO_NATIVE_PATH ${CMAKE_INSTALL_PREFIX}/${INSTALL_${p}DIR} _path)
message(STATUS "Installing ${p} componenets to ${_path}")
endforeach()
option(BUILD_SHARED_LIBS "Build shared library" ON)
if(BUILD_SHARED_LIBS)
message(STATUS "${PROJECT_NAME} will be built as a shared library.")
add_library(${PROJECT_NAME} SHARED "")
set_property(TARGET ${PROJECT_NAME} PROPERTY POSITION_INDEPENDENT_CODE TRUE)
else()
message(STATUS "${PROJECT_NAME} will be built as a static library.")
add_library(${PROJECT_NAME} STATIC "")
endif()
message(
STATUS
"[INFO] :: Is the Fortran compiler loaded? ${CMAKE_Fortran_COMPILER_LOADED}"
)
if(CMAKE_Fortran_COMPILER_LOADED)
message(STATUS "[INFO] :: Fortran compiler: ${CMAKE_Fortran_COMPILER_ID}")
message(
STATUS
"[INFO] :: Fortran compiler version is: ${CMAKE_Fortran_COMPILER_VERSION}"
)
endif()
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE
Release
CACHE STRING "Build type" FORCE)
endif()
if(${CMAKE_Fortran_COMPILER_ID} STREQUAL "GNU" OR Fortran_COMPILER_NAME MATCHES
"gfortran*")
list(
APPEND
FORTRAN_FLAGS
"-ffree-form"
"-ffree-line-length-none"
"-std=f2018"
"-fimplicit-none"
"-fno-range-check")
list(APPEND FORTRAN_FLAGS_RELEASE "-O3")
if(APPLE)
list(
APPEND
FORTRAN_FLAGS_DEBUG
"-fbounds-check"
"-g"
"-fbacktrace"
"-Wextra"
"-Wall"
# "-fprofile-arcs"
"-ftest-coverage"
"-Wimplicit-interface")
else()
list(
APPEND
FORTRAN_FLAGS_DEBUG
"-fbounds-check"
"-g"
"-fbacktrace"
"-Wextra"
"-Wall"
# "-fprofile-arcs"
"-ftest-coverage"
"-Wimplicit-interface")
endif()
elseif(${CMAKE_Fortran_COMPILER_ID} STREQUAL "Intel" OR Fortran_COMPILER_NAME
MATCHES "ifort*")
list(APPEND FORTRAN_FLAGS "-r8" "-W1")
list(APPEND FORTRAN_FLAGS_RELEASE "-O3")
list(
APPEND
FORTRAN_FLAGS_DEBUG
"-O0"
"-traceback"
"-g"
"-debug all"
"-check all"
"-ftrapuv"
"-warn"
"nointerfaces")
elseif(${CMAKE_Fortran_COMPILER_ID} STREQUAL "XL" OR Fortran_COMPILER_NAME
MATCHES "xlf*")
list(APPEND FORTRAN_FLAGS "-q64" "-qrealsize=8" "-qsuffix=f=f90:cpp=f90")
list(APPEND FORTRAN_FLAGS_RELEASE "-O3" "-qstrict")
list(APPEND FORTRAN_FLAGS_DEBUG "-O0" "-g" "-qfullpath" "-qkeepparm")
else()
message(ERROR "[ERROR] :: No optimized Fortran compiler flags are known")
endif()
cmake_print_variables(FORTRAN_FLAGS)
cmake_print_variables(FORTRAN_FLAGS_RELEASE)
cmake_print_variables(FORTRAN_FLAGS_DEBUG)
# include(${PROJECT_SOURCE_DIR}/cmake/targetCompileOpts.cmake)
target_compile_options(
${PROJECT_NAME}
PRIVATE ${TARGET_COMPILE_OPT} ${FORTRAN_FLAGS}
"$<$<CONFIG:Debug>:${FORTRAN_FLAGS_DEBUG}>"
"$<$<CONFIG:Release>:${FORTRAN_FLAGS_RELEASE}>")
# include(${PROJECT_SOURCE_DIR}/cmake/targetIncludeDirs.cmake)
target_include_directories(
${PROJECT_NAME} PUBLIC $<BUILD_INTERFACE:${CMAKE_Fortran_MODULE_DIRECTORY}>
$<INSTALL_INTERFACE:${INSTALL_INCLUDEDIR}>)
# include(${PROJECT_SOURCE_DIR}/cmake/targetProperties.cmake) target properties
set_target_properties(
${PROJECT_NAME}
PROPERTIES POSITION_INDEPENDENT_CODE 1
SOVERSION ${VERSION_MAJOR}
OUTPUT_NAME ${PROJECT_NAME}
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
MACOSX_RPATH ON
WINDOWS_EXPORT_ALL_SYMBOLS ON)
# include(${PROJECT_SOURCE_DIR}/cmake/targetCompileDefs.cmake)
option(USE_REAL32 OFF)
option(USE_REAL64 ON)
if(USE_REAL32)
list(APPEND TARGET_COMPILE_DEF "-DUSE_Real32")
endif()
if(USE_REAL64)
list(APPEND TARGET_COMPILE_DEF "-DUSE_Real64")
endif()
option(USE_INT32 ON)
if(USE_INT32)
list(APPEND TARGET_COMPILE_DEF "-DUSE_Int32")
endif()
option(USE_INT64 OFF)
if(USE_INT64)
list(APPEND TARGET_COMPILE_DEF "-DUSE_Int64")
endif()
list(APPEND TARGET_COMPILE_DEF "-D${CMAKE_HOST_SYSTEM_NAME}_SYSTEM")
# DEFINE DEBUG
if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
list(APPEND TARGET_COMPILE_DEF "-DDEBUG_VER")
endif()
# include(${PROJECT_SOURCE_DIR}/cmake/install.cmake) Installation
install(
DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY}
DESTINATION "./"
COMPONENT "${PROJECT_NAME}")
install(
EXPORT ${TARGETS_EXPORT_NAME}
FILE "${TARGETS_EXPORT_NAME}.cmake"
NAMESPACE ${namespace}::
DESTINATION ${INSTALL_CMAKEDIR}
COMPONENT "${PROJECT_NAME}")
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
VERSION "${PROJECT_VERSION}"
COMPATIBILITY AnyNewerVersion)
configure_package_config_file(
${CMAKE_CURRENT_SOURCE_DIR}/cmake/Config.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake
INSTALL_DESTINATION ${INSTALL_CMAKEDIR}
PATH_VARS INSTALL_INCLUDEDIR)
install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
DESTINATION ${INSTALL_CMAKEDIR}
COMPONENT "${PROJECT_NAME}-dev")
# linking easifemClasses,
# this easifemClasses library is already linked with easifemBase
# so no need to link with easifemBase again.
find_package(easifemClasses REQUIRED)
if(easifemBase_FOUND)
message(STATUS "FOUND easifemClasses")
else()
message(ERROR "NOT FOUND easifemClasses")
endif()
target_link_libraries(${PROJECT_NAME} PUBLIC easifemClasses::easifemClasses)
# Add source files
include(src/modules/CMakeLists.txt)
include(src/submodules/CMakeLists.txt)
# this should be in the end.
target_compile_definitions(${PROJECT_NAME} PUBLIC ${TARGET_COMPILE_DEF})
message(STATUS "[INFO] :: Compiler definition : ${TARGET_COMPILE_DEF}")
install(
TARGETS ${PROJECT_NAME} ${C_PROJECTS}
EXPORT ${TARGETS_EXPORT_NAME}
COMPONENT "${PROJECT_NAME}"
ARCHIVE DESTINATION ${INSTALL_LIBDIR}
RUNTIME DESTINATION ${INSTALL_BINDIR}
LIBRARY DESTINATION ${INSTALL_LIBDIR})
# include(${PROJECT_SOURCE_DIR}/cmake/packaging.cmake)
The above see make file needs Config.cmake.in file. The content of this file is given below.
@PACKAGE_INIT@
FIND_PACKAGE(easifemClasses REQUIRED)
set_and_check(
"@PROJECT_NAME@_INCLUDE_DIR" "@PACKAGE_INSTALL_INCLUDEDIR@")
include(
"${CMAKE_CURRENT_LIST_DIR}/@[email protected]")
check_required_components(
"@PROJECT_NAME@"
)
Now that we have added the source code and setup the CMake files. We need to specify the toml configuration files for the kernel, so that we can build and install easifemOneDimElasticity kernel using easifem CLI. To do so, create a file called oneDimElasticity.toml in the directory where easifem plugins are kept. In our case it is kept at ~/.config/easifem/plugins/oneDimElasticity.toml. The content of this file is given below.
name = "oneDimElasticity"
isActive = true
buildSystem = "cmake"
git = 'github.com/easifem/oneDimElasticity.git'
sourceDir = "${HOME}/Dropbox/easifem/oneDimElasticity"
installDir = "${HOME}/.easifem/install/oneDimElasticity/"
buildType = "Debug"
buildSharedLibs = true
buildStaticLibs = true
libName = "easifemOneDimElasticity"
targetName = "easifemOneDimElasticity"
projectName = "easifemOneDimElasticity"
runtest = true
license = "GPL3"
# buildOptions = [ "-D USE_GMSH_SDK:BOOL=ON"]
dependencies = ["classes"]
To build the kernel using easifem CLI, run the following command.
easifem dev oneDimElasticity
To install the kernel using easifem CLI, run the following command.
easifem install oneDimElasticity
To install the kernel with downloading the files from git, that is, by using the local source files, use the following command. In this case you must specify the sourceDir option in the above toml file.
easifem install oneDimElasticity --no-download
To apply boundary condition in FEM computation, EASIFEM, provides a class called DirichletBC_.
DirichletBC_ is a subclass of AbstractBC.
To understand how DirichletBC works, lets consider an example of linear elasticity. Let's say we want to apply the following boundary condition.
We may think that there is only one boundary condition. But in easifem this is not the case. Actually, , has three components in 3D (and two components in 2D). Therefore, the above boundary condition is actually boundary condition for , , and . So, we have three boundary condition on a given boundary .
The second point, which is quite obvious, is that every boundary condition has two things:
To define the boundary EASIFEM employs the MeshSelection class. The value can be specified in several ways as mentioned below in this section.
Several instances of DirichletBC can have same boundary but different condition.
We can initiate an instance of DirichletBC_ by importing the information from a toml-file. To do so, we will use the method called ImportFromToml.
Let us consider the following toml file
[domain]
filename = "./mesh/square_3x3.h5"
group = "/"
totalMedium = 1
[u]
name = "u"
engine = "NATIVE_SERIAL"
fieldType = "Normal"
spaceCompo = 1
timeCompo = 1
fedofName = "space"
geofedofName = "geofedof"
[u.geofedof]
baseContinuity = "H1"
baseInterpolation = "Lagrange"
ipType = "Equidistance"
baseType = "Monomial"
[u.space]
baseContinuity = "H1"
baseInterpolation = "Hierarchical"
# ipType = "Equidistance"
# baseType = "Monomial"
order = 4
scaleForQuadOrder = 2
quadOptName = "quadOpt"
[u.space.quadOpt]
isHomogeneous = true
quadratureType = "GaussLegendre"
# order = 2
[dbc]
name = "left_bottom_fix"
idof = 1
nodalValueType = "Constant"
value = 10.0
[dbc.boundary]
isSelectionByMeshID = true
[dbc.boundary.meshID]
line = [1, 4]
We use the following code to import the boundary condition from the above toml file.
!> author: Vikas Sharma, Ph. D.
! date: 2025-10-23
! summary: Study of DirichletBC class
PROGRAM main
USE GlobalData, ONLY: DFP, I4B, LGT
USE ExceptionHandler_Class, ONLY: e, EXCEPTION_INFORMATION
USE DirichletBC_Class
USE FEDomain_Class
USE AbstractMesh_Class
USE FEDOF_Class
USE ScalarField_Class
USE Display_Method
USE ReallocateUtility
IMPLICIT NONE
!----------------------------------------------------------------------------
! Parameters
!----------------------------------------------------------------------------
CHARACTER(*), PARAMETER :: tomlFileName = "./test1.toml"
!----------------------------------------------------------------------------
! Types and variables
!----------------------------------------------------------------------------
TYPE(DirichletBC_) :: obj
TYPE(FEDomain_) :: dom
TYPE(ScalarField_) :: u
TYPE(FEDOF_) :: fedof, geofedof
INTEGER(I4B) :: tsize, iNodeOnNode, iNodeOnEdge, iNodeOnFace, nrow, ncol
!----------------------------------------------------------------------------
! Allocatables and pointers
!----------------------------------------------------------------------------
CLASS(AbstractMesh_), POINTER :: cellMesh, boundaryMesh
INTEGER(I4B), ALLOCATABLE :: nodeNum(:)
REAL(DFP), ALLOCATABLE :: nodalValue(:, :)
!----------------------------------------------------------------------------
! Setting the verbosity
! In Debug mode, there will many messages printed to the screen
! Following code suppress information-exception.
!----------------------------------------------------------------------------
CALL e%setQuietMode(EXCEPTION_INFORMATION, .TRUE.)
!----------------------------------------------------------------------------
! Domain: Initiate FEDomain and print info
!----------------------------------------------------------------------------
CALL dom%ImportFromToml(tomlName="domain", filename=tomlFileName)
! CALL dom%DisplayDomainInfo(msg="DomainInfo: ")
!----------------------------------------------------------------------------
! ScalarField and FEDOF: Initiate scalarField and fedof
!----------------------------------------------------------------------------
CALL u%ImportFromToml(tomlName="u", fedof=fedof, geofedof=geofedof, dom=dom, &
filename=tomlFileName)
! CALL fedof%Display(msg="FEDOF info: ")
!----------------------------------------------------------------------------
! DirichletBC: Import from toml
!----------------------------------------------------------------------------
CALL obj%ImportFromToml(filename=tomlFileName, dom=dom, tomlName="dbc")
! CALL obj%Display("DirichletBC Info: ")
!----------------------------------------------------------------------------
! DirichletBC: Get the total node numbers
!----------------------------------------------------------------------------
tsize = obj%GetTotalNodeNum(fedof=fedof)
CALL Display(tsize, "Total Node Num: ")
!----------------------------------------------------------------------------
! DirichletBC: Get the node numbers
!----------------------------------------------------------------------------
CALL Reallocate(nodeNum, tsize, isExpand=.TRUE., expandFactor=2)
CALL obj%Get(fedof=fedof, nodeNum=nodeNum, tsize=tsize, &
iNodeOnNode=iNodeOnNode, iNodeOnEdge=iNodeOnEdge, &
iNodeOnFace=iNodeOnFace)
CALL Display(tsize, "tsize = ")
CALL Display(nodeNum(1:tsize), "nodeNum", full=.TRUE., orient="ROW")
CALL Display(iNodeOnNode, "iNodeOnNode = ")
CALL Display(iNodeOnFace, "iNodeOnFace = ")
CALL Display(iNodeOnEdge, "iNodeOnEdge = ")
!----------------------------------------------------------------------------
! DirichletBC: Get the nodal values
!----------------------------------------------------------------------------
CALL Reallocate(nodalValue, tsize, 2, isExpand=.TRUE., expandFactor=2)
CALL obj%Get(fedof=fedof, nodeNum=nodeNum, nodalValue=nodalValue, &
nrow=nrow, ncol=ncol)
CALL Display(nodalValue(1:nrow, 1:ncol), "nodalValue: ", &
full=.TRUE.)
CALL Display([nrow, ncol], "[nrow, ncol]: ")
!----------------------------------------------------------------------------
! Cleanup
!----------------------------------------------------------------------------
CALL dom%DEALLOCATE()
END PROGRAM main
Let's consider the following example, in which we will specify the constant boundary condition.
PROGRAM main
USE easifemBase
USE easifemClasses
IMPLICIT NONE
TYPE(DirichletBC_) :: obj
TYPE(MeshSelection_) :: boundary
TYPE(ParameterList_) :: param
TYPE(Domain_) :: dom
TYPE(HDF5File_) :: domainfile
CHARACTER(*), PARAMETER :: domainfilename = "./mesh3D.h5"
INTEGER(I4B) :: bottom = 1, top = 2, left = 3, right = 4, &
front = 5, behind = 6, nsd
INTEGER(I4B), ALLOCATABLE :: nodeNum(:)
REAL(DFP), ALLOCATABLE :: nodalValue(:, :)
CALL FPL_Init; CALL param%Initiate()
CALL domainfile%Initiate(filename=domainfilename, mode="READ")
CALL domainfile%OPEN()
CALL dom%Initiate(domainfile, group="")
nsd = dom%GetNSD()
! We call Set SetAbstractBCParam to set the parameter for boundary condition
CALL SetAbstractBCParam(param=param, prefix=obj%GetPrefix(), &
& name="ZeroBC", idof=1, nodalValueType=Constant)
! We call SetMeshSelectionParam to set the parameter for boundary condition
CALL SetMeshSelectionParam(param=param, prefix=boundary%GetPrefix(), &
& isSelectionByMeshID=.TRUE.)
CALL boundary%Initiate(param)
CALL boundary%Add(dom=dom, dim=nsd - 1, meshID=[top])
CALL boundary%Set()
CALL obj%Initiate(param=param, boundary=boundary, dom=dom)
CALL obj%Set(constantNodalValue=0.0_DFP)
CALL obj%Get(nodeNum=nodeNum, nodalValue=nodalValue)
CALL Display(nodeNum, "nodeNum", advance="NO")
CALL Display(nodalValue, "nodalValue", advance="YES")
CALL domainfile%DEALLOCATE()
CALL dom%DEALLOCATE()
CALL param%DEALLOCATE(); CALL FPL_Finalize
END PROGRAM main
In the above code, to define the boundary condition, we follow the steps given below.
We set the properties of DirichletBC_ by using the method called SetAbstractBCParam.
CALL SetAbstractBCParam(param=param, prefix=obj%GetPrefix(), &
& name="ZeroBC", idof=1, nodalValueType=Constant)
Because we are setting constant boundary condition, we used nodalValueType=Constant.
You can learn more about this method here.
To define a boundary we will use the MeshSelection. In the above code, we select the boundary by specifing the meshID.
CALL SetMeshSelectionParam(param=param, prefix=boundary%GetPrefix(), &
& isSelectionByMeshID=.TRUE.)
After setting the boundary parameter we call Initiate method.
CALL boundary%Initiate(param)
Subsequently, we call Add method to add the information of meshID.
CALL boundary%Add(dom=dom, dim=nsd - 1, meshID=[top])
CALL boundary%Set()
After adding the information of meshID we should call Set method, which means that we are done adding information to the boundary.
You can learn more about SetMeshSelectionParam here
DirichletBCAfter initiating the boundary, call Initiate. To initiate an instance of DirichletBC_ we need to pass the boundary, paramters, and domain.
CALL obj%Initiate(param=param, boundary=boundary, dom=dom)
After initiating an instance of DirichletBC_, next step is to set the boundary condition. To do so, we will use the method Set.
While setting the value we should respect the configuration used while calling SetAbstractBCParam. For example, in the above example we configure boundary condition for nodalValueType=Constant. Therefore, we should set the constantNodalValue while calling the set method.
CALL obj%Set(constantNodalValue=0.0_DFP)
To get the boundary condition we will use the method Get. The Get function can take two arguments nodeNum(:) and nodalValue(:,:). The nodeNum(:) is compulsory, whereas nodalValue can be optional.
CALL obj%Get(nodeNum=nodeNum, nodalValue=nodalValue)
On return, the size of nodeNum and SIZE(nodalValue, 1) is same.
The columns in nodalValue denotes the boundary condition at different times. You can read more about this subroutine here.
There is more to DirichletBC_, and you can learn about them from following pages. (Here DBC stands for DirichletBC_)