# 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.

#### NOTE:

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,
GatherScatter,
approx_edge_from_vol_node,
)
```

## Smooth

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.

### 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'
# 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)
```

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.

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.

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
# 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.

#### Erode

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)
```

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.

#### Dilate

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)
```

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

## 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

#### Setup

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.

#### Open

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)
```

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.

#### Close

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)
```

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.

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.

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).

### Acknowledgements

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

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