Meshes
Meshes in FiniteElementContainers leverage a very abstract interface. Currently, only an Exodus interface is directly supported within the main package but others could be readily supported through package extensions which we are planning on.
Mesh Infrastructure
The mesh system provides a common interface for reading, storing, manipulating, and writing finite element meshes independently of the underlying file format.
Rather than exposing Exodus, Gmsh, or Abaqus APIs directly, the library converts all mesh data into a common AbstractMesh representation that can be consumed by function spaces, assemblers, physics models, and post-processing routines.
The overall design can be viewed as
Exodus (.g/.exo)
/
Mesh File -----> FileMesh ------> UnstructuredMesh ------> FEM Application
\
Gmsh (.msh/.geo)
|
+--> FunctionSpace
+--> Boundary Conditions
+--> Initial Conditions
+--> Physics
+--> Assembly
+--> PostProcessorThe goal is that application code never depends on the mesh file format being used.
Mesh Types
The mesh system consists of three layers.
File Meshes
A FileMesh represents an open mesh file.
meshfile = FileMesh(ExodusMesh(), "mesh.g")A FileMesh is intentionally lightweight and provides only an interface for reading data from disk.
The following methods define the minimal mesh reader interface:
element_blocks(mesh)
nodal_coordinates_and_ids(mesh)
num_dimensions(mesh)Optional helper methods include
nodesets(mesh)
sidesets(mesh)
node_id_map(mesh)New mesh file formats can be added simply by implementing these methods.
For example,
struct MyMeshFormat <: AbstractMeshFileType
end
function element_blocks(mesh::FileMesh{...,MyMeshFormat})
...
endwithout modifying the rest of the FEM library.
UnstructuredMesh
Most FEM algorithms operate on an UnstructuredMesh.
This object owns all mesh connectivity and geometric information required during analysis.
mesh = UnstructuredMesh("mesh.g")Internally this stores
- nodal coordinates
- element connectivity
- element blocks
- element types
- node ID maps
- element ID maps
- nodesets
- sidesets
- optional edge connectivity
- optional face connectivity
allowing the remainder of the code base to be completely independent of Exodus or Gmsh.
For example,
mesh.nodal_coords
mesh.element_conns["block_1"]
mesh.nodeset_nodes["left"]
mesh.sideset_elems["traction"]provide direct access to commonly used mesh data structures.
Reading Meshes
The simplest way to construct a mesh is
mesh = UnstructuredMesh("mesh.g")The constructor automatically dispatches based on file extension.
.g
.e
.exo -> Exodus reader
.msh
.geo -> Gmsh reader
.inp
.i -> Abaqus reader (planned)Application code therefore remains independent of mesh format.
mesh = UnstructuredMesh(input_file)works regardless of whether the mesh originated from Exodus or Gmsh.
Mesh Summary
Meshes provide a convenient summary through Julia's display system.
println(mesh)produces output similar to
UnstructuredMesh:
Number of dimensions = 2
Number of nodes = 1245
Element Blocks:
block_1
Element type = QUAD4
Number of elements = 1024
Node sets:
left
right
Side sets:
tractionwhich is often useful when debugging new applications.
Element Blocks
Elements are grouped into blocks.
Each block has
- a name
- an element type
- a connectivity matrix
- an element ID map
For example,
conn = mesh.element_conns["solid"]
etype = mesh.element_types["solid"]returns the connectivity and element type associated with the block named "solid".
This organization makes it straightforward to assign different material models or physics objects to different regions of a mesh.
Nodesets and Sidesets
Boundary conditions are typically applied through named mesh entities.
Node sets contain collections of nodes,
mesh.nodeset_nodes["fixed"]while side sets contain element faces or edges,
mesh.sideset_elems["traction"]
mesh.sideset_sides["traction"]allowing boundary conditions to be specified symbolically rather than through explicit node lists.
For example,
DirichletBC(
"u",
x -> 0.0;
nodeset_name="fixed"
)can automatically locate the appropriate degrees of freedom.
Creating Edge and Face Connectivity
Many finite element methods require explicit edge or face connectivity.
These may be requested during mesh construction
mesh = UnstructuredMesh(
"mesh.g",
create_edges=true
)which constructs a globally unique edge numbering from the element connectivity.
Similarly,
create_faces=truecan be used for three-dimensional meshes.
These connectivity objects are useful for
- H(curl) elements
- H(div) elements
- discontinuous Galerkin methods
- mortar methods
- adaptive refinement
without requiring repeated reconstruction of edge topology.
Higher Order Mesh Generation
Linear meshes may be automatically upgraded to higher-order Lagrange meshes.
For example,
mesh2 = UnstructuredMesh(
"mesh.g",
p_order=2
)creates a quadratic mesh by inserting mid-edge nodes and interior nodes into each element.
Internally this process
- identifies unique mesh edges,
- inserts edge nodes,
- inserts element interior nodes,
- updates element connectivity.
This provides a convenient mechanism for generating higher-order discretizations from existing linear meshes.
Rigid Body Modes
The mesh infrastructure can automatically compute rigid body modes.
R = rigid_body_modes(mesh)For two-dimensional meshes this returns
- x translation
- y translation
- rotation
while three-dimensional meshes return
- Tx
- Ty
- Tz
- Rx
- Ry
- Rz
These modes are useful for
- nullspace construction,
- multigrid methods,
- constrained optimization,
- singular stiffness matrices,
- elasticity verification tests.
Writing Meshes
Meshes may be written back to disk using
write_to_file(mesh, "output.g")which currently outputs Exodus meshes.
This allows generated meshes or modified connectivity to be saved for visualization or reuse.
Copying Meshes
Existing Exodus meshes may be duplicated without reading all mesh data into memory.
copy_mesh(mesh, "new_mesh.g")This is particularly useful for creating output databases prior to writing simulation results.
Post Processing
The mesh infrastructure integrates directly with the post-processing system.
A postprocessor may be created from an existing mesh using
pp = PostProcessor(
ExodusMesh,
mesh,
"results.g"
)or from an existing Exodus file
pp = PostProcessor(
ExodusMesh,
"results.g",
displacement,
pressure
)which automatically registers nodal and element variables.
Fields can then be written directly
write_field(
pp,
step,
names(U),
U
)or
write_field(
pp,
step,
"solid",
"stress",
stress
)allowing visualization in ParaView, VisIt, or other Exodus-compatible tools.
Extending the Mesh System
Supporting a new mesh format requires only implementing the minimal FileMesh interface.
element_blocks(mesh)
nodal_coordinates_and_ids(mesh)
num_dimensions(mesh)along with optional helpers such as
nodesets(mesh)
sidesets(mesh)
node_id_map(mesh)Once these methods are provided, the entire FEM infrastructure—including function spaces, assembly, physics objects, boundary conditions, and post-processing—works automatically without modification.
This separation between mesh I/O and finite element algorithms allows new mesh formats to be integrated with minimal effort while keeping the core FEM implementation completely file-format independent.
FiniteElementContainers.AbstractMesh — Type
abstract type AbstractMeshFiniteElementContainers.FileMesh — Type
struct FileMesh{MeshObj, MeshType} <: FiniteElementContainers.AbstractMeshfile_name::Stringmesh_obj::Any
Mesh type that has a handle to an open mesh file object. This type's methods are "overridden" in extensions.
See FiniteElementContainersExodusExt for an example.
FiniteElementContainers.file_name — Method
file_name(mesh::FiniteElementContainers.AbstractMesh) -> Any
Returns file name for an mesh type
Structured Meshes
Simple structured meshes on rectangles or parallepipeds can be create through StructuredMesh mesh type.
FiniteElementContainers.StructuredMesh — Type
struct StructuredMesh{ND, RT<:Number, IT<:Integer} <: FiniteElementContainers.AbstractMeshnodal_coords::H1Field{RT, Vector{RT}, ND} where {ND, RT<:Number}element_block_names::Vector{String}element_block_names_map::Dict{Int64, String}element_types::Dict{String, String}element_conns::Dict{String, Matrix{IT}} where IT<:Integerelement_id_map::Vector{IT} where IT<:Integerelement_id_maps::Dict{String, Vector{IT}} where IT<:Integernode_id_map::Vector{IT} where IT<:Integernodeset_names::Dict{IT, String} where IT<:Integernodeset_nodes::Dict{String, Vector{IT}} where IT<:Integersideset_names::Dict{IT, String} where IT<:Integersideset_elems::Dict{String, Vector{IT}} where IT<:Integersideset_nodes::Dict{String, Vector{IT}} where IT<:Integersideset_sides::Dict{String, Vector{IT}} where IT<:Integersideset_side_nodes::Dict{String, Matrix{IT}} where IT<:Integer
Unstructured Meshes
Unstructured meshes (e.g. those read from a file created by a mesher) can be created with the following mesh type
FiniteElementContainers.UnstructuredMesh — Type
struct UnstructuredMesh{MeshObj, ND, RT<:Number, IT<:Integer, EdgeConns, FaceConns} <: FiniteElementContainers.AbstractMeshmesh_obj::Anynodal_coords::H1Field{RT, Vector{RT}, ND} where {ND, RT<:Number}element_block_names::Vector{String}element_block_names_map::Dict{IT, String} where IT<:Integerelement_types::Dict{String, String}element_conns::Dict{String, Matrix{IT}} where IT<:Integerelement_id_map::Vector{IT} where IT<:Integerelement_id_maps::Dict{String, Vector{IT}} where IT<:Integernode_id_map::Vector{IT} where IT<:Integernodeset_names::Dict{IT, String} where IT<:Integernodeset_nodes::Dict{String, Vector{IT}} where IT<:Integersideset_names::Dict{IT, String} where IT<:Integersideset_elems::Dict{String, Vector{IT}} where IT<:Integersideset_nodes::Dict{String, Vector{IT}} where IT<:Integersideset_sides::Dict{String, Vector{IT}} where IT<:Integersideset_side_nodes::Dict{String, Matrix{IT}} where IT<:Integeredge_conns::Anyface_conns::Any
FiniteElementContainers.UnstructuredMesh — Method
UnstructuredMesh(
file_type::FiniteElementContainers.AbstractMeshFileType,
file_name::String;
kwargs...
) -> UnstructuredMesh{MeshObj, ND, RT, Int64, EdgeConns, Nothing} where {MeshObj, ND, RT<:Number, EdgeConns}
FiniteElementContainers.UnstructuredMesh — Method
UnstructuredMesh(
file_name::String;
kwargs...
) -> UnstructuredMesh{MeshObj, ND, RT, Int64, EdgeConns, Nothing} where {MeshObj, ND, RT<:Number, EdgeConns}
FiniteElementContainers.UnstructuredMesh — Method
UnstructuredMesh(
file::FileMesh{T, V};
create_edges,
create_faces,
interp_type,
p_order,
space_type
) -> UnstructuredMesh{MeshObj, ND, RT, Int64, EdgeConns, Nothing} where {MeshObj, ND, RT<:Number, EdgeConns}
TODO change the interface so we don't need createedges/createfaces. We should parse the interpolation/space type and determine this change in behavior from these inputs...
Exodus interface API
FiniteElementContainers.FileMesh — Method
struct FileMesh{MeshObj, MeshType} <: FiniteElementContainers.AbstractMeshfile_name::Stringmesh_obj::Any
FiniteElementContainers.nodal_coordinates — Method
nodal_coordinates(
mesh::FileMesh{<:Exodus.ExodusDatabase, FiniteElementContainers.ExodusMesh}
) -> H1Field
FiniteElementContainers.nodal_coordinates_and_ids — Method
nodal_coordinates_and_ids(
mesh::FileMesh{<:Exodus.ExodusDatabase, FiniteElementContainers.ExodusMesh}
) -> Tuple{H1Field, Any}
FiniteElementContainers.node_cmaps — Method
node_cmaps(
mesh::FileMesh{<:Exodus.ExodusDatabase, FiniteElementContainers.ExodusMesh},
rank
) -> Vector
Gmsh interface API
FiniteElementContainers.FileMesh — Method
struct FileMesh{MeshObj, MeshType} <: FiniteElementContainers.AbstractMeshfile_name::Stringmesh_obj::Any
GmshExt.GmshFile — Type
struct FileMesh{Nothing, FiniteElementContainers.GmshMesh} <: FiniteElementContainers.AbstractMeshfile_name::Stringmesh_obj::Nothing