Skip to content

Step 5 Store Element Data with Mesh Handle

Vincent Schmitt edited this page Oct 14, 2026 · 3 revisions

Step 5 – Store Element Data with Mesh Handle

In this tutorial, we will learn how to associate custom data with the elements of a mesh and how to exchange data over the ghost elements of a parallel partition. You will find the code to this example in the tutorials/mesh_handle/t8_mesh_step5_element_data.cxx and it creates the executable tutorials/mesh_handle/t8_mesh_step5_element_data. In the last tutorials we learned how to create a mesh handle mesh and adapt it. In step 4 we also learned about algorithms for partitioning, balancing and creating a ghost layer. In this tutorial, we will start by performing all these operations in one step. After that, we will continue with building a data array and gathering data for the local elements of our process. Further we will exchange the data values of the ghost elements and output the volume data to our .vtu file to visualize it with Paraview.

Adapt, Partition, Balance and Ghost

As already explained in step 4, it is possible to mix the set_* functions together as you wish (see mesh.hxx). We can thus in one step adapt a mesh, balance it, partition it and create a ghost layer. The order in which the set_* functions are called is irrelevant. Let us execute the example with 4 MPI processes.

mpirun -np 4 ./t8_mesh_step5_element_data

As expected, the resulting mesh looks like the adapted mesh from step 4 before we call the adaption a second time.

Build Data Array and Gather Data for the Local Elements

With the standard t8_forest, element data was managed entirely by the user. A separate array had to be allocated with enough space for both local and ghost elements, and the user was responsible for keeping the array synchronized with the elements in the forest (see step 5 of the general t8code tutorials). With the mesh_handle interface we can do this step much simpler. Instead of managing a separate data array, the data becomes part of the mesh itself. After specifying the data type once when creating the mesh, you can directly assign data of this type to each element. Memory management and the association between elements and their data are handled automatically by our mesh. We first define the type of the data that should be stored for every element. In this example, each element stores its refinement level and volume. These are information that can of course be also accessed via the elements directly but are here stored as element data for demonstration purpose only.

struct data_per_element_type
{
	int level;
	double volume; 
};

When creating the mesh, this type is registered as the element data type by adding the corresponding data competences:

using mesh_class = t8_mesh_handle::mesh<t8_mesh_handle::data_element_competences,                                      t8_mesh_handle::data_mesh_competences<data_per_element_type>>;

(For a deeper dive into mesh handle competences and competence packs, look into step A of the mesh handle tutorials.) With this, the mesh now allocates and manages storage for a data vector of type data_per_element_type with one entry per (local) element. To assign data values, we simply iterate over the elements of the mesh. For each element, we create an instance of our data structure and store it with .set_element_data():

for (auto &elem : mesh) { 
  data_per_element_type data{ 
    elem.get_level(), 
    elem.get_volume()
  }; 
  elem.set_element_data(data); 
}

Please also note, that it is possible to transfer an already complete data vector onto the mesh with

mesh.set_element_data(std::move(example_data));;

After all local element data has been initialized, the ghost values can be synchronized with a single function call:

mesh.exchange_ghost_data();

Our mesh knows which data belongs to which element and automatically exchanges the element data associated with ghost elements. With mesh_handle there is no need to allocate additional storage, wrap data into an sc_array or manually determine which entries correspond to ghost elements. After this exchange is completed, the ghost elements contain the same data as their owning processes.

Access Stored Data

The data stored on an element can be retrieved directly from the element itself: auto data = elem.get_element_data(); Or, for a specific value of a specific element: elem.get_element_data().volume; This interface works identically for local and ghost elements. Since the mesh owns the data, the user no longer has to distinguish between local and ghost entries.

Output the volume data to .vtu

After creating the mesh and storing elements’ volumes and levels we write the mesh and the volumes of the elements out to .vtu files to visualize everything in Paraview. In the last steps we just called the function t8_mesh_handle::write_mesh_to_vtk(mesh, prefix); to write a mesh as .vtu . But to write our defined user data as well, we need an extended version of this function called write_mesh_to_vtk_ext. t8code supports writing element based data to .vtu files as long as its stored as doubles or vectors. Each of the data fields to write has to be provided in its own array of length num_local_elements. We support two types:

VTK variable type Double per element
T8_VTK_SCALAR 1
T8_VTK_VECTOR 3

Therefore, we need to allocate a new array to store the volumes on their own. This array has one entry per local element. Double *element_volumes = T8_ALLOC (double, num_elements); Other than writing user data, write_mesh_to_vtk_ext also offers more control over which properties of the mesh to write:

Parameter Description
mesh The mesh to write
fileprefix The prefix of the files
num_data Number of user-defined double valued data fields to write
data Array of t8_vtk_data_filed_t of length num_data providing the user-defined per element data
write_treeid If true, the global tree id of the underlying forest is written to each element
write_mpirank If true, the mpirank is written for each element
write_level If true, the refinement level is written for each element
write_element_id If true, the global element id is written for each element
write_ghosts If true, each process additionally writes its ghost elements
write_curved If true, write the elements as curved element types from vtk
do_not_use_API If true, do not use the VTK API, even if linked and available

For more documentation take a look into mesh_io.hxx. Note, you can store even more data into the .vtu file. The number of user defined data fields is defined by the parameter num_data. For each user defined data field, we need one t8_vtk_data_field_t variable. In our case, it is only one. t8_vtk_data_field_t vtk_data; Next, set the type of this variable. Since we only have one value per element, we can pick T8_VTK_SCALAR: vtk_data.type = T8_VTK_SCALAR; Also, the name of the field as should be written to the file. strcpy (vtk_data.description, "Element volume"); Finally, we copy the elements’ volumes from our mesh to the output array and pass it to write_mesh_to_vtk_ext We decided to only set set write_treeid, write_mpirank, write_level and write_element_id to true as it would be in the standard function. In this way, the only difference to write_mesh_to_vtk is the addition of the volume of the elements.

We can now display the volumes of the elements and work with them in Paraview:

Clone this wiki locally