GeMA
The GeMA main application
V - Model data 2

Meshes

The GeMA framework is not committed to any particular discretization method. Since different methods require different types of domain discretization, the framework applies a hierarchical approach in order to be flexible and try to support several of those methods. In GeMA, domain discretizations are loosely called "meshes", even if the discretization consists of a simple point cloud, as in "mesh free" methods.

For the framework, a mesh is composed by a set of nodes, each tied to its spatial position and, possibly associated with a dataset. This is a very open definition that does not imply any kind of topology.

Inheriting from a mesh, the frameworks second support level is a cell mesh. A cell defines a partition of the global spatial domain and is represented in 1D by a segment, in 2D by an area and in 3D by a volume. In general, those cells are disjoint and their union represents the whole spatial domain. Each cell is formed by an ordered set of nodes that collectively define its border geometry. The schema defining how many nodes exist in a cell and their organization defines the cell type.

The third level in GeMA's mesh hierarchy is the element mesh, supporting the finite element method. It extends the cell concept by associating each cell to an interpolation function (shape function) and to a set of integration points. Figure 10 illustrates these concepts, that can be further extended to support other needs.

gemaMeshTypes.png
Figure 10: Mesh types

A GeMA simulation can contain multiple meshes, and those meshes can store heterogeneous cell types. Figure 11 shows an example of a heterogeneous mesh and its related entities. They are:

  • Nodes: Nodes define the set of cell vertices. Their coordinate dimension is a mesh characteristic. For the framework, there is no limitation on node dimension: 1D, 2D and 3D are equally supported, provided that all nodes in a mesh have the same dimension (physics plugins, on the other hand, might limit their support to a fixed dimension set);
  • Ghost nodes: Some discretization techniques, like the XFEM method, require the ability to (dynamically) create additional element points where the solution field will be calculated (additional degrees of freedom, not tied to the element nodes). Those points are not part of the element geometry definition and are called ghost nodes in GeMA. Their existence adds support for sub-parametric elements;
  • Cells: Cells / elements are formed by an ordered set of nodes. Their number and organization should follow the schema provided by the cell / element type. The framework supports several types of 2D and 3D elements, both linear and quadratic. See the Element types documentation for a list of the available cells / elements and their node, edge and face orders.
  • Node sets: Node sets are named groups of nodes belonging to the mesh, and each mesh can contain several of them. Node sets can contain regular nodes, ghost nodes or both. They are particularly handy when imposing boundary conditions;
  • Cell groups: Mesh cells can be organized in groups, and each mesh can contain several of them. Groups can have heterogeneous elements and a cell can belong to multiple groups;
  • Borders: Mesh borders are formed by sets of cell edges in 2D or sets of cell faces in 3D. They are particularly handy when imposing boundary conditions. Each edge or face is identified by a tuple composed of the cell id and the index of the edge / face in the cell;
  • Integration rules: Integration rules are used to define the location and weight of integration points (also called Gauss points) associated with each mesh element type. There are two kinds of integration rules: element rules and border rules. The latter is used to specify integrating schemas for boundary conditions. A mesh can be associated with several integration rule sets.

gemaMeshEntities.png
Figure 11: Cell/element mesh entities

To solve our proposed heat conductivity problem, the plate will be discretized into a structured mesh composed by triangle elements. Our first mesh will be composed of 25 nodes and 32 triangles as shown in Figure 12. The Lua code to create this simple mesh is shown below.

simpleMeshDiscretization.png
Figure 12: Mesh discretization
-- Node coordinates
local MeshNodeCoordinates = {
{ 0.00, 0.00}, { 25.00, 0.00}, { 50.00, 0.00}, { 75.00, 0.00}, {100.00, 0.00},
{ 0.00, 25.00}, { 25.00, 25.00}, { 50.00, 25.00}, { 75.00, 25.00}, {100.00, 25.00},
{ 0.00, 50.00}, { 25.00, 50.00}, { 50.00, 50.00}, { 75.00, 50.00}, {100.00, 50.00},
{ 0.00, 75.00}, { 25.00, 75.00}, { 50.00, 75.00}, { 75.00, 75.00}, {100.00, 75.00},
{ 0.00, 100.00}, { 25.00, 100.00}, { 50.00, 100.00}, { 75.00, 100.00}, {100.00, 100.00},
}
-- Mesh elements
local MeshTriList = {
{ 1, 2, 6}, { 2, 7, 6}, { 2, 3, 7}, { 3, 8, 7}, { 3, 4, 8}, { 4, 9, 8}, { 4, 5, 9}, { 5, 10, 9},
{ 6, 7, 11}, { 7, 12, 11}, { 7, 8, 12}, { 8, 13, 12}, { 8, 9, 13}, { 9, 14, 13}, { 9, 10, 14}, {10, 15, 14},
{11, 12, 16}, {12, 17, 16}, {12, 13, 17}, {13, 18, 17}, {13, 14, 18}, {14, 19, 18}, {14, 15, 19}, {15, 20, 19},
{16, 17, 21}, {17, 22, 21}, {17, 18, 22}, {18, 23, 22}, {18, 19, 23}, {19, 24, 23}, {19, 20, 24}, {20, 25, 24},
}
local MeshElements = {
{cellType = 'tri3', cellList = MeshTriList, MatProp = 1, FabProp = 1}
}
-- Mesh borders
local MeshBorders = {
{id = 'top', cellList = {{26, 2}, {28, 2}, {30, 2}, {32, 2}}},
{id = 'bottom and sides', cellList = {{ 1, 3}, { 9, 3}, {17, 3}, {25, 3}, -- Left
{ 1, 1}, { 3, 1}, { 5, 1}, { 7, 1}, -- Base
{ 8, 1}, {16, 1}, {24, 1}, {32, 1}}}, -- Right
}
Mesh
{
-- General mesh attributes
id = 'mesh',
typeName = 'GemaMesh.elem',
description = 'Plate mesh discretization',
-- Mesh dimensions
coordinateDim = 2,
coordinateUnit = 'cm',
-- State vars stored in this mesh (per node)
stateVars = {'T'},
-- Node attributes
nodeAttributes = NodeAttributes,
-- Mesh node coordinates
nodeData = MeshNodeCoordinates,
-- Element data
cellProperties = {'MatProp', 'FabProp'},
cellData = MeshElements,
-- Boundary data
boundaryEdgeData = MeshBorders,
}

Looking at the mesh typeName field, we can see that it is implemented by the standard mesh plugin, identified by its name "GemaMesh". The ".elem" part of the typeName field is used to tell the mesh plugin that the desired mesh is an element mesh. Other options are ".nodes" for node only meshes and "cell" for cell meshes.

The coordinateDim and coordinateUnit fields are used to inform the mesh that this is a 2D mesh and that node Cartesian coordinates are expressed in centimeters. The stateVars field ties this mesh to the previously declared temperature state variable. If the simulation involved more variables, they would be listed inside this subtable.

Node attributes and material properties are tied to the mesh by the nodeAttributes and cellProperties fields. The latter references the names of both property sets defined previously. Remember that a simulation can have several meshes, so each one could be tied to different state variables, property sets, and node attributes, making this fields necessary. If our simulation needed cell attributes or Gauss attributes, their definitions would be given in the cellAttributes and gaussAttributes fields.

The 'real' mesh definition is given by fields nodeData and cellData. The first is responsible for providing the list of mesh node coordinates while the second is responsible for defining element geometry. Node data is given by the MeshNodeCoordinates Lua table and its syntax is straightforward: each table entry contains a subtable with node coordinates.

The MeshElements Lua table is used to provide cell geometry and also to bind mesh elements to their respective materials. Since our problem mesh is homogeneous, and all cells share the same material, this table has only one entry, a subtable that basically informs that our mesh is composed by linear triangle elements (cellType = "tri3"), that the list with element geometry is given by the MeshTriList table and that all cells are tied to the first entry in the MatProp and FabProp property sets. The auxiliary MeshTriList table gives the set of nodes used to form each mesh triangle. Nodes are organized in a CCW fashion as required by GeMA tri3 elements.

If the mesh had heterogeneous elements or if we desired to create named element groups, the MeshElements table would have more entries, as shown in the example below, consisting of three sets of elements. The first is a set of triangles, the second a set of quadrilateral elements named "quads", and the third a second set of triangles. If two table lines use the same group name, both element sets will be merged into the same named element group.

local MeshElements = {
{cellType = 'tri3', cellList = MeshTriList1},
{cellType = 'quad4', cellList = MeshQuadList1, cellGroup = 'quads'},
{cellType = 'tri3', cellList = MeshTriList2},
}

Notice that in the above example no material properties were tied to the cell groups. In fact, the material definitions seen previouslly on the tutorial problem mesh declaration, are really default values that are used only if a material is not associated directly with a mesh element during its geometry definition. That can be done by following the node set in the element definition with a material declaration, as in the example below where the first shown triangle is tied to the named "shale" material and to the third fabrication property, while the second triangle will be tied to the default fabrication property defined in the MeshElements table.

-- Mesh elements
local MeshTriList = {
...,
{ 3, 4, 8, MatProp = 'shale', FabProp = 3},
{ 4, 9, 8, MatProp = 'shale'},
...
}

The last important point in our mesh declaration is the definition of mesh borders. Those are needed for boundary conditions later on and are given by the boundaryEdgeData field that references the MeshBorders Lua table. If this was a 3D problem, boundary faces would have been defined by the boundaryFaceData field. The MeshBorders table follows a now familiar syntax, with one sub-table for each named border. In those subtables, the id field gives the border name while the cellList field provides a list with (cell number, border number) pairs. As an example, the first MeshBorders subtable specifies that the top border is composed by the second edge of cells 26, 28, 30 and 32, as can easily be seen in Figure 12. Please remember that edge numbering follows the order in which the cell nodes were defined. For a linear triangle, the second edge is the edge between the second and third given nodes. See the Element types documentation for a reference.

The standard mesh plugin accepts several other field options that were not covered in the previous example. Their meaning and some usage examples can be found on the standard mesh plugin reference documentation.

Initializing values

Unless explicitly given, attributes and state variables are initialized with their default value, as given by the defVal field in their respective declarations, or with 0.0 in the absence of that field.

In our heat conduction simulation, we must properly initialize the Tana attribute with the analytically calculated temperature for each node (Err doesn't need special initialization since all nodes will reference the same user function given by defVal).

This initialization can be done in the same table used to define node coordinates by following them with initial values for node attributes and state variables, in that order (and in their respective declaration orders). It is not necessary to provide initialization values for all attributes and state variables, but if you want to give an initial value to the second attribute and not to the first, the former must be replaced by a nil value. Vector and matrix values are represented by subtables with the initial values for each dimension. Functions are represented by a string with the function name. Strings can also be used if the initialized value has a constMap used for translating strings into values. The same technique can be used to initialize cell attributes: just add the initial values after the geometry node list.

To add initial values for Tana, we begin by creating a Lua function that calculates the analytical temperature solution at a given coordinate following the equation defined in section II - Example problem, then we add a call to that function in each of the node coordinate table entries, as seen below.

-- Some constants to allow for easy changing the model
local Tt = 500 -- Top temperature in degc
local Ts = 100 -- Bottom and side temperatures in degC
local w = 100 -- Width of the plate in cm
local h = 100 -- Height of the plate in cm
-- Analytical solution
local function T(x, y)
local sum = 0
for i=1, 200 do
local u = i*math.pi/w
local num = ((-1)^(i+1) + 1) * math.sin(u*x) * math.sinh(u*y)
local den = i * math.sinh(u*h)
sum = sum + num/den
end
return Ts + (Tt-Ts) * (2/math.pi) * sum
end
-- Node coordinates + initial value for Tana
local MeshNodeCoordinates = {
{ 0.00, 0.00, T( 0.00, 0.00)},
{ 25.00, 0.00, T( 25.00, 0.00)},
{ 50.00, 0.00, T( 50.00, 0.00)},
{ 75.00, 0.00, T( 75.00, 0.00)},
{100.00, 0.00, T(100.00, 0.00)},
{ 0.00, 25.00, T( 0.00, 25.00)},
{ 25.00, 25.00, T( 25.00, 25.00)},
{ 50.00, 25.00, T( 50.00, 25.00)},
{ 75.00, 25.00, T( 75.00, 25.00)},
{100.00, 25.00, T(100.00, 25.00)},
{ 0.00, 50.00, T( 0.00, 50.00)},
{ 25.00, 50.00, T( 25.00, 50.00)},
{ 50.00, 50.00, T( 50.00, 50.00)},
{ 75.00, 50.00, T( 75.00, 50.00)},
{100.00, 50.00, T(100.00, 50.00)},
{ 0.00, 75.00, T( 0.00, 75.00)},
{ 25.00, 75.00, T( 25.00, 75.00)},
{ 50.00, 75.00, T( 50.00, 75.00)},
{ 75.00, 75.00, T( 75.00, 75.00)},
{100.00, 75.00, T(100.00, 75.00)},
{ 0.00, 100.00, T( 0.00, 100.00)},
{ 25.00, 100.00, T( 25.00, 100.00)},
{ 50.00, 100.00, T( 50.00, 100.00)},
{ 75.00, 100.00, T( 75.00, 100.00)},
{100.00, 100.00, T(100.00, 100.00)},
}

An easier alternative, that uses the fact that the simulation model is really a Lua script, can be seen in the code below that uses a loop to add values to the original node coordinates table MeshNodeCoordinates. The Lua construct "for i=1, #MeshNodeCoordinates do" is a loop that runs from i = 1 to the number of entries in the MeshNodeCoordinates table, increasing i by one at each step.

for i=1, #MeshNodeCoordinates do
local x = MeshNodeCoordinates[i][1]
local y = MeshNodeCoordinates[i][2]
MeshNodeCoordinates[i][3] = T(x, y)
end

Parameterizing our simulation

Since our example problem is laid over a regular domain and our desired simulation mesh is a structured mesh, instead of manually creating our node and cell lists, as we have done earlier in a boring and error prone way, we can create Lua functions capable of building the contents of the MeshNodeCoordinates, MeshTriList and MeshBorders tables. As an additional bonus, since those functions can be made to receive as parameters the desired number of mesh nodes in each direction, we end up being able to easily parameterize the size of the mesh used to solve the problem. The mesh creation functions are given below.

local nx = 5 -- Number of nodes in the x direction
local ny = 5 -- Number of nodes in the y direction
local w = 100 -- Width of the plate in cm
local h = 100 -- Height of the plate in cm
--
-- Creates a square mesh with nx/ny nodes in each direction
-- and dx/dy separation between nodes. The first node is
-- at coordinate 0.0. Returns a table with mesh nodes and another
-- with triangle definitions
--
local function SquareDomain(dx, nx, dy, ny)
-- Node list
local nodes = {}
for y=0, ny-1 do
for x=0, nx-1 do
nodes[#nodes+1] = {x*dx, y*dy, T(x*dx, y*dy)}
end
end
-- element list
local cells = {}
local N = function (r,c) return (r-1)*nx + c end
for y=1, ny-1 do
for x=1, nx-1 do
cells[#cells+1] = {N(y, x), N(y, x+1), N(y+1, x)}
cells[#cells+1] = {N(y, x+1), N(y+1, x+1), N(y+1, x)}
end
end
return nodes, cells
end
-- Node coordinates + Mesh elements
local MeshNodeCoordinates, MeshTriList = SquareDomain(w/(nx-1), nx, h/(ny-1), ny)
local MeshElements = {
{cellType = 'tri3', cellList = MeshTriList, MatProp = 1, FabProp = 1}
}
-- Mesh borders
local function TopBorder(nx, ny)
local list = {}
local nelem = (nx-1)*(ny-1)
for i=nelem-nx+2, nelem do
list[#list+1] = {i*2, 2}
end
return list
end
local function OtherBorders(nx, ny)
local list = {}
-- left
for i=1, ny-1 do
list[#list+1] = {(i-1)*(nx-1)*2+1, 3}
end
-- bottom
for i=1, nx-1 do
list[#list+1] = {i*2-1, 1}
end
-- right
for i=1, ny-1 do
list[#list+1] = {i*(nx-1)*2, 1}
end
return list
end
local MeshBorders = {
{id = 'top', cellList = TopBorder(nx, ny) },
{id = 'bottom and sides', cellList = OtherBorders(nx, ny)},
}

In fact, since creating regular meshes is so common, the Mesh Lib set of auxilliary functions provides an out of the box alternative for the above code (see the heat conduction example for further explanations and a working code):

dofile('$SCRIPTS/meshLib.lua') -- Loads the 'meshLib' auxiliary functions
local xpoints = meshLib.regularSpacing(0.0, w, nx-1)
local ypoints = meshLib.regularSpacing(0.0, h, ny-1)
local MeshNodeCoordinates, MeshElements, MeshBorders = meshLib.build2DGrid('tri3', xpoints, ypoints, nil,
{MatProp = 1, FabProp = 1})

When running the GeMA application, it is possible for the user to send parameters to the simulation (see section VII - Running your simulation). Those parameters are accessed in the simulation file by using the global variable userParams. By using this variable, instead of a constant value, the mesh size can be chosen by the user when running the simulation. For that, we just need to replace the first two lines of the previous script with:

local n = userParams or 5 -- Use the size given as parameter or 5 if no parameter was passed
local nx = n -- Number of nodes in the x direction
local ny = n -- Number of nodes in the y direction

Boundary conditions

Finally, the last missing part in our model data are the boundary conditions. For the GeMA framework, a boundary condition object stores a set of boundary conditions, all sharing the same type and mesh. It also stores a set of properties with values for each single condition. Those properties are described in the same way as any attribute and their values can also be user functions, allowing for an easy way to represent boundary conditions that change over time.

The meaning of each of those properties is collectively identified by the object type field, which is interpreted by the physics object that will ultimately make use of this boundary condition. When creating a model, the physics plugin is the authority that defines which are the available boundary condition types and their required properties. The plugin options documentation can be found here.

A condition also stores where it should be applied. All conditions in the object are tied to the same kind of application point. Available options are nodes, cells, cell edges or cell faces. When applying a boundary condition to nodes, edges or faces, border names declared inside the mesh definition can be used as the application point.

Our heat conduction problem requires a Dirichlet boundary condition that prescribes fixed temperature values over the plate border nodes. For that, the thermo physics plugin recognizes the "node temperature" boundary condition type, which has a single property "T" storing the prescribed node temperature. The thermo physics also supports other boundary conditions types, as can be seen in its documentation.

BoundaryCondition {
id = 'Border temperature',
type = 'node temperature',
mesh = 'mesh',
properties = {
{id = 'T', description = 'External temperature applied on the node', unit = 'degC'},
},
nodeValues = {
{'bottom and sides', Ts},
{'top', Tt},
}
}

The code above shows the needed boundary condition object to complete our simulation model data. The mesh field ties this boundary condition to the problem mesh (remember that a generic GeMA simulation can have several meshes). The properties field defines the set of properties used by the boundary condition using the now familiar syntax used on attribute definitions.

The nodeValues field is used to tie property values to mesh nodes. Each of its entries is a subtable that begins with a node number or a border name, followed by values for the declared properties. When the node is given by a border name, the value set is applied to all nodes in that border.

For boundary conditions tied to cells, the cellValues field should be used. For this kind of condition, the application point, given as the first value in each table line, should be a cell number. For edges and faces, the edgeValues and faceValues fields should be used. For them, the application point must be a border name.


Go to the next section, previous section or return to the index.