Import a mesh in FEniCS
@ Lorenzo Fiore | Monday, Mar 6, 2023 | 6 minutes read

Import a mesh in FEniCS

Motivation and context

  • When I first approached the software library FEniCS I was overwhelmed by the possibilities. This collection of software allows the user to define whatever Partial Differential Equation on a geometrical domain and automate all the process from the definition of finite elements to solution of the equation, it’s simply brilliant! Wanna solve a thermal diffusion problem? Just type the heat equation in integral form:
ET = ( rho*cspec*(T_trl-T_old)/dt*T_tst + \
     + Kcond*fe.dot(fe.grad(T_trl), fe.grad(T_tst)) )*fe.dx
  • Wanna add convection through one of the boundaries? Just add that part to the equation:
ET = ( rho*cspec*(T_trl-T_old)/dt*T_tst +\
     + Kcond*fe.dot(fe.grad(T_trl), fe.grad(T_tst)) )*fe.dx +\
	 + ( outer_hconv*(T_trl - outer_Temp)*T_tst )*ds_outer
  • But here comes the catch, how to indicate to the software what part of the domain is ds_outer? Seems like this is a limitation of the script like interface in FEniCS, using the provided functionalities (such as CompiledSubDomain), you can only define the geometry if you can type the equation for it.
  • Well I refused to stop there, so I spent too many hours googling for a solution and I ended up collection enough pieces of code from the internet (mainly from Jørgen S. Dokken) to reach my goal: import mesh boundary definition from an external pre-processor to FEniCS.

Challenges I faced

  • Before coming to FEniCS, the input mesh, coming from a pre-processor, has to be translated in a suitable format, I chose xdmf and this can be achieved using the handy library meshio.
  • In FEniCS a portion of the domain or of the boundary can be identified, or “marked”, with an integer identifier and called for the definition of a subset with specific material or a boundary condition.
  • Before the “marking”, domain boundary and the materials subsets have to be created and it’s also a good idea to pre-set them to a null value:
# import the mesh in FEniCS
with fe.XDMFFile( meshfolder + meshfilename + ".xdmf") as xdmf:
	mesh = fe.Mesh()
	xdmf.read(mesh)
	mesh.init()

# Define the whole boundary
boundaries = fe.MeshFunction("size_t", mesh, mesh.topology().dim()-1)

# initialize marker for the whole boundary
boundaries.set_all(0)

# define the whole domain
materials = fe.MeshFunction("size_t", mesh, mesh.topology().dim())

# initialize marker for the whole domain
materials.set_all(0)
  • The geometrical subdomains can then be marked by looping over the cells and using the correspondence between the cell identification number and its property… which we still need to assign!
  • The assignment has to be made in parallel to the translation operation from whatever format to xdmf and stored in a separate object, a matrix is a good candidate, to be called when needed.
  • Once all of this is done we get a nice table with the correspondence between the sets we defined in the pre-processor and the integers we can use in FEniCS for identification of geometrical features:
# import the mesh
mesh, boundaries, materials = importMesh.readMesh(meshfolder, meshfile)

# create the boundary integral measure
ds_inner = fe.Measure("ds", subdomain_id=1, subdomain_data=boundaries)

Useful lessons I learned

  • This project didn’t involve much math or scientific thinking but it was critical to allow me to play with real parts coming from CAD into FEniCS!
  • That’s it! I hope this information would turn out useful to anyone, I add the script I use for the conversion at the end of this page.
# -----------------------------------------------------------------70
# Import necessary packages
# -----------------------------------------------------------------70
import fenics as fe
import numpy as np
import meshio
import os


#%% ---------------------------------------------------------------70
# Auxiliary functions
# -----------------------------------------------------------------70

def fileprint(string, meshfolder, meshfilename):
    print(string)
    with open( meshfolder + meshfilename + ".md", "a") as file:
        file.writelines( "\n" + string)


def getMeshInfo(meshfolder, meshfile, identifier = "cell", cell_geom = "triangle"):
    """Import the mesh from pre-processor and convert the mesh with Meshio
    
    - use a specific keywork to identify cell sets in the mesh creation
      and pass this keywork to the function as identifier

    - specify whether to parse for triangle or tetra:
        - 2D case --> cell_geom = "triangle"
        - 3D case --> cell_geom = "tetra"
    
    reference:
    https://fenicsproject.discourse.group/t/definition-of-subdomain-by-element-sets/6440
    """
    
    # Take the mesh file name without extension
    meshfilename = meshfile.rsplit(".")[0]

    # read the mesh whatever the format
    pre_mesh = meshio.read(meshfolder + meshfile)

    # import the cells sets
    cell_sets = pre_mesh.cell_sets_dict

    # import the cells
    pre_cells = pre_mesh.get_cells_type(cell_geom)

    # number of cells
    n_cells = len(pre_cells)

    # initialize a matrix to store sets marks for cell data (materials)
    cell_data = np.zeros(n_cells)
    
    # initialize a matrix to store sets marks for facet data (boundaries)
    boundaries_data = np.zeros(n_cells)
    
    # initialize marker counter
    mrk = 0

    # initialize marker dictionary
    mrk_dict = {}
    
    # loop through the dictionary items of cell_sets
    # the key is the set name, the value is the sets cells (type and coordinates)
    for marker, set in sorted(cell_sets.items()):
        if "mesh" not in marker:
            # Increse the marker for every set
            mrk += 1

            # Store set name and corresponding marker
            mrk_dict[marker] = mrk
            
            # loop through the types and values for a specific key
            for type, entities in set.items():
                # check for cell type
                if type == cell_geom:
                    # Store in cell_data a marker with increasing int to be visualized in Paraview
                    boundaries_data[entities] = int(mrk)
                    
                    # identify cell markers and store them in a list
                    if identifier in marker:
                        cell_data[entities] = int(mrk)

    return pre_mesh, cell_data, boundaries_data, mrk, mrk_dict


def convertMesh(meshfolder, meshfile, identifier = "cell", cell_geom = "triangle"):

    (pre_mesh, cell_data, boundaries_data, mrk, mrk_dict) = getMeshInfo(meshfolder, meshfile, identifier, cell_geom)

    # import the cells
    pre_cells = pre_mesh.get_cells_type(cell_geom)

    # number of cells
    n_cells = len(pre_cells)
    
    # Take the mesh file name without extension
    meshfilename = meshfile.rsplit(".")[0]
    
    # Print the total number of cells
    fileprint(f"Total number of cells in mesh = {n_cells}", meshfolder, meshfilename)

    # Print the total number of markers
    fileprint(f"Max marker index: {mrk:2d}", meshfolder, meshfilename)
    
    # Print set name and corresponding marker
    for key in mrk_dict.keys():
        fileprint(f"Set: {key} -> marker {mrk_dict[key]}", meshfolder, meshfilename)

    # Translate the mesh in xdmf format bringing the marker info
    post_mesh = meshio.Mesh(points=pre_mesh.points,
                            cells={cell_geom: pre_cells},
                            cell_data={"name_to_read": [cell_data]} )

    # Take the mesh file name without extension
    meshfilename = meshfile.rsplit(".")[0]

    # Adjust the filename and output the mesh file in xdmf format
    meshio.write( meshfolder + meshfilename + ".xdmf", post_mesh)


def readMesh(meshfolder, meshfile, identifier = "cell", cell_geom = "triangle"):
    # -----------------------------------------------------------------70
    # Convert solid Element sets in surface boundary Facets Sets
    # reference
    # https://fenicsproject.discourse.group/t/how-to-define-boundary-conditions-on-an-irregular-geometry/2240/18
    # -----------------------------------------------------------------70

    # Take the mesh file name without extension
    meshfilename = meshfile.rsplit(".")[0]

    # check if mesh has already been converted in xdmf
    xdmf_exists_flag = 0
    for file in os.listdir(meshfolder):
        if file == meshfilename + ".xdmf":
            xdmf_exists_flag = 1
    
    # convert the mesh if necessary
    if xdmf_exists_flag == 0:
        convertMesh(meshfolder, meshfile, cell_geom = cell_geom)
    
    # import the mesh in FEniCS
    with fe.XDMFFile( meshfolder + meshfilename + ".xdmf") as xdmf:
        mesh = fe.Mesh()
        xdmf.read(mesh)
        mesh.init()
      
    # Define the whole boundary
    boundaries = fe.MeshFunction("size_t", mesh, mesh.topology().dim()-1)

    # initialize marker for the whole boundary
    boundaries.set_all(0)

    # define the whole domain
    materials = fe.MeshFunction("size_t", mesh, mesh.topology().dim())
    
    # initialize marker for the whole domain
    materials.set_all(0)
    
    # initialize mesh to ask for facets
    cell_to_facet = mesh.topology()(mesh.topology().dim(), mesh.topology().dim()-1)

    # get cell_data from meshio
    (pre_mesh, cell_data, boundaries_data, mrk, mrk_dict) = getMeshInfo(meshfolder, meshfile, identifier, cell_geom)


    # loop through cells
    for cell in fe.cells(mesh):
        
        # mark the cell with info stored in cell_data
        materials.set_value(cell.index(), int(cell_data[cell.index()]))
    
        # loop through the facets of a cell
        for face in cell_to_facet(cell.index()):
            # extract Facet info with Dolfin internal method
            facet = fe.Facet(mesh, face)
            # mark the facet with the info stored in boundaries_data
            if facet.exterior():
                boundaries.set_value(facet.index(), int(boundaries_data[cell.index()]))

    # https://www.karlin.mff.cuni.cz/~hron/fenics-tutorial/elasticity/doc.html
    return mesh, boundaries, materials
    

#%% ---------------------------------------------------------------70
# Debugging check
# -----------------------------------------------------------------70

if __name__ == "__main__":
        
    meshfolder = "./Meshes/"

    meshfile = "WST.inp"
    
    mesh, boundaries, materials = readMesh(meshfolder, meshfile, cell_geom = "triangle")

	fe.XDMFFile( meshfolder + "check_boundaries.xdmf" ).write(boundaries)
	fe.XDMFFile( meshfolder + "check_materials.xdmf" ).write(materials)

© 2021 - 2024 MechEngrLorenzoFiore

Powered by Hugo with theme Dream.

avatar

Lorenzo Fiore's blog
Leave the world a better place than you find it

Once a Scout, always a Scout!

I’m a proud member of the World Wide Scout Movement.

Try and leave this world a little better than you found it, and when your turn comes to die, you can die happy in feeling that at any rate, you have not wasted your time but have done your best.

Robert Baden-Powell, fouder of the Scout Movement

About me

If you came up here you want to know a little bit more about myself. That’s awesome!

I’m a Mechanical Engineer, a curious person and a maker! I don’t waste any time trying to learn everything I can about the topic I love the most, be it mechanical design, programming, old school hand abilities, music, languages, you name it!

This websites purpose is to share my knowledge and experience hoping this would be of any help for anyone facing the challenges I face. Enjoy and let’s stay in contact!

Social Links