Tekigo : the GatherScatter utilities

Additional utilities to control the mesh refinement with Tekigo are available and are grouped in the GatherScatter class. They are, for instance, useful in removing undesired artefacts due to metric (criterion) definitions. A brief overview of the different possibilities will now be given with demonstrations on an academic geometry.

Reading time: 9 min


  1. The effects of some utilties will depend on whether a metric field or a criterion is used (see also a general introduction of tekigo). Specifically, an interchangeability between a pair of keywords will result in the same result. This will be explained in the examples below. A metric, which only holds positive values, is selected as the basis of the present post.
  2. The utilities require that the grids consists of tetrahedrons.

A basic set of tekigo imports to get the different parts in this post to work are:

from tekigo import (TekigoSolution, 


A first functionality is a smoothing operation which consists of a number of gather - scatter sequences. Gather-scatter operations are commonly adopted in unstructured finite-volume CFD solvers as information is stored at the cell nodes (for memory reasons) and computations performed with information at the cell centers. In brief, for one sequence, information from the nodes is gathered at cell centers and subsequently scattered back to the nodes. Doing so on a metric will result in its smoothing. Let’s illustrate this with a simple example.

The setup

A simple rectangular domain is generated and consists of a two dimensional plane extruded over one cell width. The latter extrusion is required in order to enable the introduction of tetrahedral mesh elements as shown in Fig. 1. A local hot spot (900 K) is generated in a uniform temperature field (300 K). Such a setup can be created with pyAVBP for instance. Subsequently, the refinement metric is defined as metric = np.where(temp >= 800.0,0.5,1.0), and results in a target of max refinement (half of current edge length) in the hot spot and leaving the rest as is. The idea is to generate a possible representation of a strong gradation in the metric definition which could arise.

Fig.1 Base setup for smoothing.

The GatherScatter object

In order to use the different functionalities, such as smoot, erode, dilate etc., a GatherScatter object must be instantiated. It requires information on the connectivity between the tetrahedral elements. Important: the connectivity index must start at 0 for successful operations. Moreover, an operation type must be specified, with options being either “smooth” or “morph”. Depending on the type, some functionalities are enabled and others not. A snippet on how to do this is shown below.

# Collect connectivity information
elem_type = 'tet->node'
connect = tekigo_sol.load_current_mesh()[elem_type].astype('int64')
# Define GatherScatter object
gs_obj = GatherScatter(connect-1, operation_type ='smooth')
# or
gs_obj = GatherScatter(connect-1, operation_type ='morph')

The smoothing result

The metric defined in Fig. 1 has a sudden jump between the hotspot and its surroundings. Because of this, the resulting refinement with hip / MMG could not be as expected as a maximum gradation in edge lengths between adjacent cells is enforced. A solution would be to smooth the metric prior to the adaptation and is done with the following lines:

# Define GatherScatter object
gs_obj = GatherScatter(connect-1, operation_type ='smooth')
# Smooth the metric
smoothed_metric = gs_obj.smooth(metric, passes=5)
# Perform adaptation
raw_adapt(tekigo_sol, smoothed_metric, dry_run=False)

After the definition of a tekigo solution object (tekigo_sol) and a metric, the GatherScatter object is instantiated. The operation type must be set to “smooth”. Afterwards, the actual smoothing operation is performed on the metric with a number of passes. The number of passes is simply the number of repetitions of the gather-scatter operation. The smoothed metric is then used to perform the adaptation step, i.e. the raw adapt in the present case. The results of the refinement without and with smoothing for different number of passes are shown below.

Fig.2 Smoothing a metric with different number of passes. Left, from top to bottom: no smoothing, 1 pass, 5 passes. Right, from top to bottom: 10 passes, 20 passes, 30 passes

The baseline refinement solution without any smoothing results in a strong cluster with very small cells which could have nefast effects on a simulation by limiting the maximum time step through the CFL criterion. After 5 passes, the metric has already considerably smoothed and the clustering has spread to the cells originally outside the hot spot. Note that the rest of the domain (except close vicinity of the hotspot) has not been impacted by the smoothing operation.

Approximate max gradients

Another utility available to the user is the maximum gradients approximation. The estimation requires the user to specify the edge lengths of the current grid. If you don’t have this readiliy available, no worries, we have a function for you: approx_edge_from_vol_node. It requires the volume of the current grid elements and returns an estimates of the edge lengths. To access the option, a GatherScatter object of ‘morph’ type must be instantiated. The approx_gradmag can then be called. Note that it does modify the input field, hence the reason for a deepcopy in the present example.

# Estimate edge lengths
edge_approx = approx_edge_from_vol_node(tekigo_sol.load_current_mesh()['volume'])
# Define GatherScatter object
gs_obj = GatherScatter(connect-1, 'morph')
# Obtain an estimate of the maximum gradients
metric_copy = copy.deepcopy(metric)

Erode and Dilate

The erode and dilate functions have a different impact whether a metric field or criterion field is used. Specifically, the opposite keyword must be used in order to obtain the same results. This is simply a consequence of the fact that metric field values between 0.5 and 1 imply a refinement of the current cells while the same is achieved through values between -1 and 0 for a criterion field. The terminology erode and dilate, as well as open and close discussed later on, is based on the criterion.

As the name indicates, erode mimics the erosion process and translates in using information (presently a metric) from the surroundings to adapt the internal information (again a metric). The dilate procedure is the opposite, where local information is “radiated” on the surroundings. Both concepts are best understood through examples.

Because we rely on the raw adapt refinement which requires a metric, the erode keyword will result in a dilatation while the dilate keyword will result in an erosion.


We will apply the functionality on the same setup as in Fig. 1 with the same metric definition. A GatherScatter object of type “morph” is instantiated following by the morph function call with the metric as an argument. Similarily to the smoothing, a number of passes can be defined.

# Define GatherScatter object
gs_obj = GatherScatter(connect-1, 'morph')
# Obtain an estimate of the maximum gradients
eroded_metric = gs_obj.morph(metric, "erode", passes = 5)
# Perform adaptation
raw_adapt(tekigo_sol, eroded_metric, dry_run=False)

The result is shown in Fig 3 where the top image is the baseline refinement without any erosion (dilatation). With increasing number of passes, the metric defined in the hot spot spreads (dilates) to its surroundings. The key difference with the smoothing is that the original metric field values will not change, i.e. the metric (=0.5) defined in the hotspot will be copied to its surroundings which had previously a metric of 1. Smoothing will ensure a gradation in metric value and will therefore introduce new values which differ from the original 0.5 and 1.

Fig.3 Erode function applied to a metric field. From top to bottom: baseline , 1 erosion pass, 2 erosion passes, 5 erosion passes.


The working of dilate is analogous to erode, except for the keyword “dilate” in the morph function call.

# Define GatherScatter object
gs_obj = GatherScatter(connect-1, 'morph')
# Obtain an estimate of the maximum gradients
dilated_metric = gs_obj.morph(metric, "dilate", passes = 2)
# Perform adaptation
raw_adapt(tekigo_sol, dilated_metric, dry_run=False)

As can be seen in Fig. 4, after 5 dilation passes the hot spot has completely disappeared.

Fig.4 Dilate function applied to a metric field. From top to bottom: baseline , 1 dilation pass, 2 dilation passes, 5 dilation passes.

Open and Close

Similarily to erode and dilate, the open and close functions have the opposite effect whether used on a metric or a criterion field. Both options rely on dilate and erode:

  • open: erode followed by dilate
  • close: dilate followed by erode


To clearly see the impact of these functions, an updated setup with respect to Fig.1 is considered. In addition to the hot spot, a shock is introduced as well as some noise. The shock is ideal to illustrate what happens when a discontinuity is present in the metric definition and the noise is something that can easily be present but is undesirable.

Fig.5 Setup with a hot spot, a shock and noise.


In analogy to erode and dilate, a morph type GatherScatter object must be defined and the function call open is invoked.

# Define GatherScatter object
gs_obj = GatherScatter(connect-1, 'morph')
# Obtain an estimate of the maximum gradients
open_metric = gs_obj.morph(metric, "open", passes = 2)
# Perform adaptation
raw_adapt(tekigo_sol, open_metric, dry_run=False)

The application of open, even with multiple passes, does not impact much our refinement. Small differences can be seen at the shock front in Fig. 6.

Fig.6 Open applied to a metric field. Top to bottom: baseline, 1 pass open, 5 passes open.


Like open, the same procedure is used except for the “close” keyword in the morph function call.

# Define GatherScatter object
gs_obj = GatherScatter(connect-1, 'morph')
# Obtain an estimate of the maximum gradients
close_metric = gs_obj.morph(metric, "close", passes = 2)
# Perform adaptation
raw_adapt(tekigo_sol, close_metric, dry_run=False)

The application of close removes the undesirable noise that was present in our initial metric as shown by Fig. 7. Other than that, no significant effect is observed on the refinement.

Fig.7 Close applied to a metric field. Top to bottom: baseline, 1 pass close, 2 passes close, 5 passes close.

A side by side representation of close and open is given in Fig. 8., where it is more clearly shown that the former affects the noise, and the latter the discontinuity.

Fig.8 Close and open applied to a metric field. Top to bottom: baseline, 5 passes close, 5 passes open.

Final comments

The GatherScatter tools are available to the user, but it remains its task to use them appropriately. In the above examples the functionalities were applied to the whole field, however, it can be done locally too. Up to you to use the versatility of python in the control of your refinement metric (criterion).


The herein presented utilities wouldn’t be what they are without the hard work of Théo Defontaine.

Like this post? Share on: TwitterFacebookEmail

Jimmy-John Hoste is a postdoctoral researcher in computer science engineering with a focus on CFD related topics.

Keep Reading



Work In Progress


Stay in Touch