sandbox/acastillo/output_fields/vtu/output_vtu.h

    XML File Formats

    preamble: define some useful macros

    To make the routines (a little bit) easier to follow, we’ll define the following macros to deal with the reconstruction of the VOF interface

    #include "geometry.h"
    #include "fractions.h"
    
    #if dimension == 1
    coord mycs(Point point, scalar c)
    {
      coord n = {1.};
      return n;
    }
    #elif dimension == 2
    #include "myc2d.h"
    #define mfacets int m = facets(n, alpha, v);
    #else // dimension == 3
    #include "myc.h"
    #define mfacets int m = facets(n, alpha, v, 1.);
    #endif

    Macro to simplify facets calculation

    #define shortcut_facets               \
      coord n;                            \
      n = mycs(point, c);                 \
      double alpha = plane_alpha(c[], n); \
      coord v[12];                        \
      mfacets;

    Macro to simplify dealing with slices

    #define shortcut_slice(n, _alpha)                                \
      double alpha = (_alpha - n.x * x - n.y * y - n.z * z) / Delta; \
      if (fabs(alpha) > 0.87)                                        \
        continue;

    and some useful functions

    output_vtu(): Exports full 2D (or 3D) fields.

    This function writes one VTK XML file which can be read using Paraview. The file stores scalar and vector fields defined at the center points are stored at the cell center of an unstructured grid with the following structure:

    if 2D
    if 3D

    In MPI environments, each task writes its own .vtu file, linked together by a .pvtu file. Results are recorded in binary format.

    The arguments and their default values are:

    list
    pointer to the list of scalar fields to be exported.
    vlist
    pointer to the list of vector fields to be exported.
    subname
    subname to be used for the output file.

    Example Usage

    scalar * slist = {a,b};
    vector * vlist = {c,d};
    char *subname = "domain";
    output_vtu(slist, vlist, subname);

    see, also example.

    // Function prototypes
    void output_vtu_pid(scalar *list, vector *vlist, char *subname);
    #ifdef _MPI
    void output_pvtu(scalar *list, vector *vlist, char *subname);
    #endif
    
    trace    
    void output_vtu(scalar *list, vector *vlist, char *subname){
    // Check if MPI is defined
    @if _MPI
      // If MPI is defined, call output_pvtu to handle parallel VTU output
      output_pvtu(list, vlist, subname);
    @else
      // If MPI is not defined, call output_vtu_pid to handle serial VTU output
      output_vtu_pid(list, vlist, subname);
    @endif
    }

    output_vtu_pid(): writes one .vtu file for the current process

    void output_vtu_pid(scalar *list, vector *vlist, char *subname) {
    
    #if defined(_OPENMP)
      int num_omp = omp_get_max_threads(); // Get the number of OpenMP threads
      omp_set_num_threads(1);              // Set the number of OpenMP threads to 1
    #endif
    
      char name[111];                   // Buffer for file name construction
      sprintf(name, "%s.vtu", subname); // Construct the VTU filename
    
      FILE *fp = fopen(name, "w"); // Open the VTU file for writing
      if (!fp){
        fprintf(stderr, "Error opening file %s for writing.\n", name);
      }

    Define a scalar field for periodic conditions

      scalar per_mask[];
      foreach () {
        per_mask[] = 1.;
      }

    Obtain the number of points, cells, and marker used for connectivity

      vertex scalar marker[];
      long no_points = 0, no_cells = 0;
      foreach_vertex(serial, noauto){
        #if TREE
          marker[] = _k;
        #else
          #if dimension == 2
            marker[] = (point.i - 2) * ((1 << point.level) + 1) + (point.j - 2);
          #else
            marker[] = (point.i - 2) * sq((1 << point.level) + 1) + (point.j - 2) * ((1 << point.level) + 1) + (point.k - 2);
          #endif
        #endif
        no_points++; // Increment the number of points
      }
    
      foreach (serial, noauto){
        if (per_mask[]){
          no_cells++; // Increment the number of cells
        }
      }

    VTK cell types: VTK_QUAD (in 2D) or VTK_HEXAHEDRON (in 3D)

      #if dimension == 2
        char type = 9, noffset = 4;
      #elif dimension == 3
        char type = 12, noffset = 8;
      #endif

    Write the light data of the VTU file with data blocks specified by offset

      long count = 0;
      write_vtu_header(fp, no_points, no_cells);                        // Write the VTU file header
      write_scalar_light_data(fp, list, vlist, &count, no_cells);       // Write scalar data arrays
      write_points_light_data(fp, &count, no_points);                   // Write points data array
      write_cells_light_data(fp, &count, no_cells, no_cells * noffset); // Write cells data arrays
      write_vtu_appended(fp);                                           // Write the VTU appended data section

    Write the heavy data blocks

      write_scalar_heavy_data(fp, list, per_mask, no_cells);            // Write scalar field data
      write_vector_heavy_data(fp, vlist, per_mask, no_cells);           // Write vector field data
      write_points_heavy_data(fp, no_points);                           // Write points data
      write_cell_offsets(fp, no_cells, noffset);                        // Write cell offsets
      write_cell_types(fp, no_cells, type);                             // Write cell types
      write_cell_connectivity(fp, marker, per_mask, no_cells, noffset); // Write cell connectivity

    and close the file

      fputs("\t\n", fp);
      fputs("\t</AppendedData>\n", fp);
      fputs("</VTKFile>\n", fp);
      fflush(fp); // Flush the file buffer
      fclose(fp); // Close the VTU file
    
    #if defined(_OPENMP)
      omp_set_num_threads(num_omp); // Restore the original number of OpenMP threads
    #endif
    }

    output_pvtu(): if MPI, writes one .pvtu and .vtu for each process

    @ if _MPI 
    void output_pvtu(scalar *list, vector *vlist, char *subname)
    {
      char name[112]; // Buffer for file name construction
      FILE *fp;       // File pointer for file operations
    
      if (pid() == 0){
    
        sprintf(name, "%s.pvtu", subname); // Construct the PVTU filename
        fp = fopen(name, "w");             // Open the PVTU file for writing
        if (!fp){
          fprintf(stderr, "Error opening file %s for writing.\n", name);
          MPI_Abort(MPI_COMM_WORLD, 1);
        }
    
        // Write sections of the PVTU file
        write_pvtu_header(fp);                     // Write the header
        write_scalar_light_pdata(fp, list, vlist); // Write scalar data arrays
        write_points_light_pdata(fp);              // Write points data array
        write_pieces_light_pdata(fp, subname);     // Write piece references
    
        // Close the PVTU file
        fflush(fp); // Flush the file buffer
        fclose(fp); // Close the file
      }
      MPI_Barrier(MPI_COMM_WORLD);
      sprintf(name, "%s_n%3.3d", subname, pid());
      output_vtu_pid(list, vlist, name);
    }
    @endif

    output_slice_vtu(): Exports a 2D slice of a 3D field

    This function takes a slice defined by n={n_x, n_y, n_z} and _alpha as as in view.h. Only works for x, y, and/or z planes. Here, _alpha is assumed to intersect a cell face and must be a multiple of L0/1<<MINLEVEL. This also means that scalar and vector fields are written at the corresponding face values.

    Naturaly, this routine only works in 3D.

    The arguments and their default values are:

    list
    pointer to the list of scalar fields to be exported.
    vlist
    pointer to the list of vector fields to be exported.
    subname
    subname to be used for the output file.
    n
    vector \vec{n} normal to the plane.
    alpha
    coordinate \alpha intersecting the plane.

    Example Usage

    scalar * slist = {a,b};
    vector * vlist = {c,d};
    output_slice_vtu(slist, vlist, "slice_x", (coord){1,0,0}, 0);
    output_slice_vtu(slist, vlist, "slice_y", (coord){0,1,0}, 0);
    output_slice_vtu(slist, vlist, "slice_z", (coord){0,0,1}, 0);

    see, also example.

    #if dimension > 2
    
    // Function prototypes
    void output_slice_vtu_pid(scalar *list, vector *vlist, char *subname, coord n, double _alpha);
    #ifdef _MPI
    void output_slice_pvtu(scalar *list, vector *vlist, char *subname, coord n, double _alpha);
    #endif
    
    trace    
    void output_slice_vtu(scalar *list, vector *vlist, char *subname, coord n = {0, 0, 1}, double _alpha = 0){
    // Check if MPI is defined
    @ if _MPI
      // If MPI is defined, call output_pvtu to handle parallel VTU output
      output_slice_pvtu(list, vlist, subname, n, _alpha);
    @ else
      // If MPI is not defined, call output_vtu_pid to handle serial VTU output
      output_slice_vtu_pid(list, vlist, subname, n, _alpha);
    @endif
    }

    output_slice_vtu_pid(): writes one .vtu file for the current process

    void output_slice_vtu_pid(scalar *slist, vector *vlist, char *subname, coord n = {0, 0, 1}, double _alpha = 0){
    
    #if defined(_OPENMP)
      int num_omp = omp_get_max_threads();  // Get the number of OpenMP threads
      omp_set_num_threads(1);               // Set the number of OpenMP threads to 1
    #endif
    
      char name[111];                       // Buffer for file name construction
      sprintf(name, "%s.vtu", subname);     // Construct the VTU filename
    
      FILE *fp = fopen(name, "w");          // Open the VTU file for writing
      if (!fp){
        fprintf(stderr, "Error opening file %s for writing.\n", name);
      }

    Define a scalar field to deal with solids and periodic conditions

      scalar per_mask[];
      foreach (){
        per_mask[] = 0.;
        shortcut_slice(n, _alpha);
        if (alpha > 0.){
    #if EMBED
          per_mask[] = cs[];
    #else
          per_mask[] = 1.;
    #endif
        }
      }

    Obtain the number of points, cells, and marker used for connectivity

      vertex scalar marker[];
      long no_points = 0, no_cells = 0;
      foreach_vertex(serial, noauto){
        marker[] = 0.;
        shortcut_slice(n, _alpha);  // if not in the requested plane, we cycle
        marker[] = no_points;
        no_points++;                // Increment the number of points
      }
    
      foreach (serial, noauto){
        if (per_mask[]){
          no_cells++;               // Increment the number of cells
        }
      }

    Every time we write someting we increase the counter to track where the data array starts. Cells are defined as VTK_QUAD(=9) defined by 4 points.

      char type = 9, noffset = 4;
      long count = 0;

    Write the light data.

      write_vtu_header(fp, no_points, no_cells);                        // Write the VTU file header
      write_scalar_light_data(fp, slist, vlist, &count, no_cells);      // Write scalar data arrays
      write_points_light_data(fp, &count, no_points);                   // Write points data array
      write_cells_light_data(fp, &count, no_cells, no_cells * noffset); // Write cells data arrays
      write_vtu_appended(fp);                                           // Write the VTU appended data section

    Write the heavy data blocks

      write_scalar_heavy_data_slice(fp, slist, per_mask, no_cells, n, _alpha);   // Write scalar field data
      write_vector_heavy_data_slice(fp, vlist, per_mask, no_cells, n, _alpha);   // Write vector field data
      write_points_heavy_data_slice(fp, no_points, n, _alpha);                   // Write points data
      write_cell_offsets(fp, no_cells, noffset);                                 // Write cell offsets
      write_cell_types(fp, no_cells, type);                                      // Write cell types
      write_cell_connectivity_slice(fp, marker, per_mask, no_cells, noffset, n); // Write cell connectivity

    and close the file

      fputs("\t\n", fp);
      fputs("\t </AppendedData>\n", fp);
      fputs("</VTKFile>\n", fp);
      fflush(fp); // Flush the file buffer
      fclose(fp); // Close the VTU file
    
    #if defined(_OPENMP)
      omp_set_num_threads(num_omp); // Restore the original number of OpenMP threads
    #endif
    }

    output_slice_pvtu(): if MPI, writes one .pvtu and .vtu for each process

    @ if _MPI 
    void output_slice_pvtu(scalar *list, vector *vlist, char *subname, coord n = {0, 0, 1}, double _alpha = 0)
    {
      char name[112]; // Buffer for file name construction
      FILE *fp;       // File pointer for file operations
    
      if (pid() == 0){
    
        sprintf(name, "%s.pvtu", subname); // Construct the PVTU filename
        fp = fopen(name, "w");             // Open the PVTU file for writing
        if (!fp){
          fprintf(stderr, "Error opening file %s for writing.\n", name);
          MPI_Abort(MPI_COMM_WORLD, 1);
        }
    
        // Write sections of the PVTU file
        write_pvtu_header(fp);                     // Write the header
        write_scalar_light_pdata(fp, list, vlist); // Write scalar data arrays
        write_points_light_pdata(fp);              // Write points data array
        write_pieces_light_pdata(fp, subname);     // Write piece references
    
        // Close the PVTU file
        fflush(fp); // Flush the file buffer
        fclose(fp); // Close the file
      }
      MPI_Barrier(MPI_COMM_WORLD);
      sprintf(name, "%s_n%3.3d", subname, pid());
      output_slice_vtu_pid(list, vlist, name, n, _alpha);
    }
    @endif
    #endif

    output_facets_vtu(): Exports the VOF interface.

    This function writes a surface from field data using the built-in VOF PLIC surface. The file also stores a scalar field at the cell center of an unstructured grid with the following structure:

    if 2D
    if 3D

    This extends the routines in output_surfaces.h. by Oystein Lande in two points: one, this is extended to write a single .vtu file when using MPI; and two, heavy data is stored in raw binary format.

    The arguments and their default values are:

    c
    vof scalar field.
    s
    scalar field to store, for instance, curvature.
    subname
    subname to be used for the output file.

    Example Usage

    scalar kappa[];
    curvature (f, kappa);
    output_facets_vtu(f, kappa, "Interface");

    see, also example.

    // Function prototypes
    void output_facets_pid(scalar c, scalar s, char *subname);
    #ifdef _MPI
    void output_facets_pvtu(scalar c, scalar s, char *subname);
    #endif
    
    trace
    void output_facets_vtu(scalar c, scalar s, char *subname){
    // Check if MPI is defined
    @ if _MPI
      // If MPI is defined, call output_pvtu to handle parallel VTU output
      output_facets_pvtu(c, s, subname);
    @ else
      // If MPI is not defined, call output_vtu_pid to handle serial VTU output
      output_facets_pid(c, s, subname);
    @endif
    }

    output_facets_pid(): writes one .vtu file for the current process

    void output_facets_pid(scalar c, scalar kappa, char *subname)
    {
    
    #if defined(_OPENMP)
      // Save current number of OpenMP threads and set it to 1 for this function
      int num_omp = omp_get_max_threads();
      omp_set_num_threads(1);
    #endif

    Construct the filename and open the file for writing

      char name[99];
      sprintf(name, "%s.vtu", subname);
      FILE *fp = fopen(name, "w");

    Obtain the number of vertices (points) and assets (cells)

      long nverts = 0, nfacets = 0;
      count_vertices_and_facets(c, &nverts, &nfacets);

    Declare arrays to store vertex coordinates and facet offsets

      double *pt_array_x = malloc(nverts * sizeof(double));
      double *pt_array_y = malloc(nverts * sizeof(double));
      #if dimension <= 2
        double *pt_array_z = NULL;
      #else
        double *pt_array_z = malloc(nverts * sizeof(double));
      #endif
      long *offsets = malloc(nfacets * sizeof(long));

    Populate the arrays with vertex coordinates and facet offsets

      populate_vertex_and_offset_arrays(c, nverts, nfacets, pt_array_x, pt_array_y, pt_array_z, offsets);

    Populate the arrays with the curvature kappa

      double *fc_array_kappa = malloc(nfacets * sizeof(double));
      populate_facet_arrays(c, kappa, nverts, nfacets, fc_array_kappa);

    Set cell type: VTK_POLY_LINE for 2D, VTK_POLYGON for 3D

      #if dimension <= 2
        char type = 4; // VTK_POLY_LINE (=4)
      #else
        char type = 7; // VTK_POLYGON (=7)
      #endif

    Counter to keep track of the byte offsets in the VTK file

      long count = 0.;

    Write the light data

      write_vtu_header(fp, nverts, nfacets);                       // Write the VTU file header
      write_points_light_data(fp, &count, nverts);                 // Write points data array
      write_cells_light_data(fp, &count, nfacets, nverts);         // Write cells data arrays
      write_scalar_light_data(fp, {kappa}, NULL, &count, nfacets); // Write scalar data arrays
      write_vtu_appended(fp);                                      // Write the VTU appended data section

    Write the points (vertices) data to the VTK file

      write_points_heavy_data_array(fp, nverts, pt_array_x, pt_array_y, pt_array_z);
      write_cell_offsets2(fp, nfacets, offsets);                  // Write cell offsets
      write_cell_types(fp, nfacets, type);                        // Write cell types
      write_cell_offsets3(fp, nverts);                            // Write cell offsets
      write_scalar_heavy_data_array(fp, nfacets, fc_array_kappa); // Write cell curvature

    and close the file

      fputs("\t\n", fp);
      fputs("\t </AppendedData>\n", fp);
      fputs("</VTKFile>\n", fp);
    
      fflush(fp); // Flush the file buffer
      fclose(fp); // Close the VTU file
    
      free(offsets);
      free(pt_array_x);
      free(pt_array_y);
    #if dimension == 3
      free(pt_array_z);
    #endif
      free(fc_array_kappa);
    
    #if defined(_OPENMP)
      omp_set_num_threads(num_omp);
    #endif
    }

    output_facets_pvtu(): if MPI, writes one .pvtu and .vtu for each process

    @ if _MPI
    void output_facets_pvtu(scalar c, scalar kappa, char *subname){
    
    #if defined(_OPENMP)
      // Save current number of OpenMP threads and set it to 1 for this function
      int num_omp = omp_get_max_threads();
      omp_set_num_threads(1);
    #endif

    Obtain the number of vertices (points) and assets (cells)

      long nverts_long = 0, nfacets_long = 0;
      count_vertices_and_facets(c, &nverts_long, &nfacets_long);
      int nverts = (int)nverts_long, nfacets = (int)nfacets_long;

    Declare arrays to store vertex coordinates and facet offsets

      double *pt_array_x = malloc(nverts * sizeof(double));
      double *pt_array_y = malloc(nverts * sizeof(double));
      #if dimension <= 2
        double *pt_array_z = NULL;
      #else
        double *pt_array_z = malloc(nverts * sizeof(double));
      #endif
      long *offsets = malloc(nfacets * sizeof(long));

    Populate the arrays with vertex coordinates and facet offsets

      populate_vertex_and_offset_arrays(c, nverts_long, nfacets_long, pt_array_x, pt_array_y, pt_array_z, offsets);

    Populate the arrays with the curvature kappa

      double *fc_array_kappa = malloc(nfacets * sizeof(double));
      populate_facet_arrays(c, kappa, nverts, nfacets, fc_array_kappa);

    Global variables to store the total number of vertices and facets across all processes

      long glob_nverts = 0, glob_nfacets = 0;
      MPI_Allreduce(&nverts_long, &glob_nverts, 1, MPI_LONG, MPI_SUM, MPI_COMM_WORLD);
      MPI_Allreduce(&nfacets_long, &glob_nfacets, 1, MPI_LONG, MPI_SUM, MPI_COMM_WORLD);

    Arrays to store the number of vertices and facets per process

      int list_nverts[npe()], pos_nverts[npe()];
      int list_nfacets[npe()], pos_nfacets[npe()];

    Gather the number of vertices and facets from all processes

      MPI_Allgather(&nverts, 1, MPI_INT, list_nverts, 1, MPI_INT, MPI_COMM_WORLD);
      MPI_Allgather(&nfacets, 1, MPI_INT, list_nfacets, 1, MPI_INT, MPI_COMM_WORLD);

    Calculate the starting positions for vertices and facets for each process

      pos_nverts[0] = 0;
      pos_nfacets[0] = 0;
      for (int i = 1; i < npe(); i++){
        pos_nverts[i] = pos_nverts[i - 1] + list_nverts[i - 1];
        pos_nfacets[i] = pos_nfacets[i - 1] + list_nfacets[i - 1];
      }

    Adjust facet offsets for the current process

      for (int i = 0; i < nfacets; i++)
        offsets[i] = offsets[i] + pos_nverts[pid()];
    
      // Pointers for global arrays (only used by the root process) 
      long *glob_offsets = NULL;
      double *glob_pt_array_x = NULL;
      double *glob_pt_array_y = NULL;
      double *glob_pt_array_z = NULL;
      double *glob_fc_array_kappa = NULL;

    Gather the data from each process

      const int root = 0;
      if (pid() == root){
        // Allocate memory for global arrays on the root process
        glob_offsets = malloc(glob_nfacets * sizeof(long));
        glob_pt_array_x = malloc(glob_nverts * sizeof(double));
        glob_pt_array_y = malloc(glob_nverts * sizeof(double));
        #if dimension == 3
          glob_pt_array_z = malloc(glob_nverts * sizeof(double));
        #endif
        glob_fc_array_kappa = malloc(glob_nfacets * sizeof(double));
      }
    
      // Gather vertex and facet data from all processes to the root process
      MPI_Gatherv(offsets, nfacets, MPI_LONG, glob_offsets, list_nfacets, pos_nfacets, MPI_LONG, root, MPI_COMM_WORLD);
      MPI_Gatherv(pt_array_x, nverts, MPI_DOUBLE, glob_pt_array_x, list_nverts, pos_nverts, MPI_DOUBLE, root, MPI_COMM_WORLD);
      MPI_Gatherv(pt_array_y, nverts, MPI_DOUBLE, glob_pt_array_y, list_nverts, pos_nverts, MPI_DOUBLE, root, MPI_COMM_WORLD);
    #if dimension == 3
      MPI_Gatherv(pt_array_z, nverts, MPI_DOUBLE, glob_pt_array_z, list_nverts, pos_nverts, MPI_DOUBLE, root, MPI_COMM_WORLD);
    #endif
      MPI_Gatherv(fc_array_kappa, nfacets, MPI_DOUBLE, glob_fc_array_kappa, list_nfacets, pos_nfacets, MPI_DOUBLE, root, MPI_COMM_WORLD);
    
      // Free local arrays
      free(offsets);
      free(pt_array_x);
      free(pt_array_y);
      #if dimension == 3
        free(pt_array_z);
      #endif
      free(fc_array_kappa);

    Only the root process writes the data to the VTK file

      if (pid() == root){
    
        #if dimension <= 2
          char type = 4; // VTK_POLY_LINE (=4)
        #else
          char type = 7; // VTK_POLYGON (=7)
        #endif
    
        // Construct the filename and open the file for writing
        char name[99];
        sprintf(name, "%s.vtu", subname);
        FILE *fp = fopen(name, "w");
    
        // Counter to keep track of the byte offsets in the VTK file
        long count = 0;
        write_vtu_header(fp, glob_nverts, glob_nfacets);               // Write the VTU file header
        write_points_light_data(fp, &count, glob_nverts);              // Write points data array
        write_cells_light_data(fp, &count, glob_nfacets, glob_nverts); // Write cells data arrays
        write_scalar_light_data(fp, {kappa}, NULL, &count, nfacets);   // Write scalar data arrays
        write_vtu_appended(fp);                                        // Write the VTU appended data section
    
        // Write the points (vertices) data to the VTK file
        write_points_heavy_data_array(fp, glob_nverts, glob_pt_array_x, glob_pt_array_y, glob_pt_array_z);
        write_cell_offsets2(fp, glob_nfacets, glob_offsets);                  // Write cell offsets
        write_cell_types(fp, glob_nfacets, type);                             // Write cell types
        write_cell_offsets3(fp, glob_nverts);                                 // Write cell offsets
        write_scalar_heavy_data_array(fp, glob_nfacets, glob_fc_array_kappa); // Write cell curvature
    
        fputs("\t\n", fp);
        fputs("\t </AppendedData>\n", fp);
        fputs("</VTKFile>\n", fp);
        fflush(fp); // Flush the file buffer
        fclose(fp); // Close the VTU file
    
        // Free allocated memory
        free(glob_offsets);
        free(glob_pt_array_x);
        free(glob_pt_array_y);
    #if dimension == 3
        free(glob_pt_array_z);
    #endif
        free(glob_fc_array_kappa);
      }
    
    #if defined(_OPENMP)
      omp_set_num_threads(num_omp);
    #endif
    }
    @endif

    postamble: delete macros

    #undef shortcut_slice
    #undef shortcut_facets
    #undef mfacets