Output meshes defined by user-defined functions
More advanced mesh element selection is possible using cs_post_define_volume_mesh_by_func or cs_post_define_surface_mesh_by_func, which allow defining volume or surface meshes using user-defined element lists.
The possibility to modify a mesh over time is limited by the most restrictive writer which is associated with. For instance, if writer 1 allows the modification of the mesh topology (argument time_dep
= FVM_WRITER_TRANSIENT_CONNECT in the call to cs_post_define_writer) and writer 2 allows no modification (time_dep
= FVM_WRITER_FIXED_MESH), a user post-processing mesh associated with writers 1 and 2 will not be modifiable, but a mesh associated with writer 1 only will be modifiable. The modification can be done by using the advanced cs_post_define_volume_mesh_by_func or cs_post_define_surface_mesh_by_func, associated with a user-defined selection function based on time-varying criteria (such as field values being above a given threshold). If the time_dep
argument is set to true
, the mesh will be redefined using the selection function at each output time step for every modifiable mesh.
Example: surface mesh with complex selection criteria
In the following example, we build a surface mesh containing interior faces separating cells of group "2" from those of group "3", (assuming no cell has both colors), as well as boundary faces of group "4".
This is done by first defining 2 selection functions, whose arguments and behavior match the cs_post_elt_select_t type.
The function for selection of interior faces separating cells of two groups also illustrates the usage of the cs_selector_get_family_list function to build a mask allowing direct checking of this criterion when comparing cells adjacent to a given face:
static void
_i_faces_select_example(void *input,
{
int n_families = 0;
int *family_list = NULL;
int *family_mask = NULL;
family_mask[i] = 0;
for (i = 0; i < n_families; i++)
family_mask[family_list[i] - 1] += 1;
for (i = 0; i < n_families; i++)
family_mask[family_list[i] - 1] += 2;
for (face_id = 0; face_id < m->
n_i_faces; face_id++) {
if ((iflag1 == 1 && iflag2 == 2) || (iflag1 == 2 && iflag2 == 1)) {
i_face_ids[n_i_faces] = face_id;
n_i_faces += 1;
}
}
*n_faces = n_i_faces;
*face_ids = i_face_ids;
}
The function for selection of boundary faces is simpler, as it simply needs to apply the selection criterion for boundary faces:
static void
_i_faces_select_example(void *input,
{
int n_families = 0;
int *family_list = NULL;
int *family_mask = NULL;
family_mask[i] = 0;
for (i = 0; i < n_families; i++)
family_mask[family_list[i] - 1] += 1;
for (i = 0; i < n_families; i++)
family_mask[family_list[i] - 1] += 2;
for (face_id = 0; face_id < m->
n_i_faces; face_id++) {
if ((iflag1 == 1 && iflag2 == 2) || (iflag1 == 2 && iflag2 == 1)) {
i_face_ids[n_i_faces] = face_id;
n_i_faces += 1;
}
}
*n_faces = n_i_faces;
*face_ids = i_face_ids;
}
Given these tow functions, the mesh can be defined using the cs_post_define_surface_mesh_by_func function, passing it the user-defined selection functions (actually, function pointers):
{
const int n_writers = 1;
const int writer_ids[] = {1};
"Mixed surface",
_i_faces_select_example,
_b_faces_select_example,
NULL,
NULL,
false,
false,
false,
n_writers,
writer_ids);
}
Example: time-varying mesh
A mesh defined through the advanced cs_post_define_surface_mesh_by_func, cs_post_define_volume_mesh_by_func, or cs_post_define_particles_mesh_by_func may vary in time, as long as the matching time_varying
argument is set to true
, and the mesh (or aliases thereof) id only associated to writers defined with the FVM_WRITER_TRANSIENT_CONNECT option. In the case of particles, which always vary in time, this allows also varying the selection (filter) function with time.
In the following example, we build a volume mesh containing cells with values of field named "He_fraction" greater than 0.05.
First, we define the selection function:
static void
_he_fraction_05_select(void *input,
{
if (f == NULL)
"No field with name \"He_fraction\" defined");
_cell_ids[_n_cells] = i;
_n_cells += 1;
}
}
}
*n_cells = _n_cells;
*cell_ids = _cell_ids;
}
Then, we simply define matching volume mesh passing the associated selection function pointer:
{
const int n_writers = 1;
const int writer_ids[] = {2};
"He_fraction_05",
_he_fraction_05_select,
NULL,
true,
false,
false,
n_writers,
writer_ids);
}
The matching function will be called at all time steps requiring output of this mesh.
- Warning
- some mesh formats do not allow changing meshes (or the implemented output functions do not allow them yet) and some may not allow empty meshes, even if this is only transient.
Other advanced mesh types
Example: edges mesh
In cases where a mesh containing polygonal elements is output through a writer configured to divide polygons into triangles (for example when visualization tools do not support polygons, or when highly non convex faces lead to visualization artifacts), it may be useful to extract a mesh containing the edges of the original mesh so as to view the polygon boundaries as an overlay.
In the following example, we build such a mesh (with id 5), based on the faces of a mesh with id 1:
{
const int n_writers = 1;
const int writer_ids[] = {4};
1,
n_writers,
writer_ids);
}
Output of various space-filling curves
In the below example, edge meshes illustrating various space-filling curves possibilities are output.
First the below functions write the various space-filling curves either in serial or parallel.
static void
_cs_post_write_sfc_serial(fvm_writer_t *writer)
{
double *coords = NULL, *val = NULL;
fvm_nodal_t *nm = NULL;
const double *var_ptr[1] = {NULL};
sfc_id++) {
3,
sfc_id);
j = order[i];
for (k = 0; k < 3; k++)
coords[i*3 + k] = mq->
cell_cen[j*3 + k];
val[i] = i+1;
}
for (i = 0; i < n_edges; i++) {
connect[i*2] = i+1;
connect[i*2+1] = i+2;
}
cell_gnum = NULL;
NULL,
NULL,
NULL,
connect,
NULL);
var_ptr[0] = val;
nm,
1,
0,
0,
-1,
0.0,
(const void * *)var_ptr);
}
}
#if defined(HAVE_MPI)
static void
_cs_post_write_sfc_parall(fvm_writer_t *writer)
{
cs_gnum_t *vtx_gnum = NULL, *edge_gnum = NULL;
double *val = NULL;
fvm_nodal_t *nm = NULL;
const double *var_ptr[1] = {NULL};
sfc_id++) {
3,
sfc_id);
0,
0,
bi,
cell_gnum);
if (block_size > 0) {
}
3,
coords);
cell_gnum = NULL;
if (block_size > 0) {
MPI_Status status;
if (prev_rank < 0)
prev_rank = MPI_PROC_NULL;
next_rank = MPI_PROC_NULL;
MPI_Sendrecv(coords, 3, MPI_DOUBLE, prev_rank, 0,
coords + 3*block_size, 3, MPI_DOUBLE, next_rank, 0,
}
for (i = 0; i < block_size; i++) {
connect[n_edges*2] = i+1;
connect[n_edges*2+1] = i+2;
edge_gnum[n_edges] = vtx_gnum[i];
n_edges++;
}
val[i] = vtx_gnum[i];
}
if (block_size > 0) {
vtx_gnum[block_size] = bi.
gnum_range[0] + block_size;
val[block_size] = vtx_gnum[block_size];
}
n_edges,
NULL,
NULL,
NULL,
connect,
NULL);
connect = NULL;
var_ptr[0] = val;
nm,
1,
0,
0,
-1,
0.0,
(const void * *)var_ptr);
if (block_size > 0) {
}
}
}
#endif
Then a fake cell selection function is used to call the writing of the space-filling curves at the correct step.
static void
_sfc_cell_select(void *input,
{
*n_cells = 0;
*cell_ids = NULL;
fvm_writer_t *w = NULL;
"postprocessing",
#if defined(HAVE_MPI)
_cs_post_write_sfc_parall(w);
#endif
_cs_post_write_sfc_serial(w);
}
Finally edge meshes are defined to illustrate the various possibilities.
const int n_writers = 1;
"SFC",
_sfc_cell_select,
NULL,
false,
false,
false,
n_writers,
writer_ids);