xref: /petsc/src/dm/impls/plex/cgns/plexcgns2.c (revision d5b43468fb8780a8feea140ccd6fa3e6a50411cc)
1 #define PETSCDM_DLL
2 #include <petsc/private/dmpleximpl.h> /*I   "petscdmplex.h"   I*/
3 #include <petsc/private/viewercgnsimpl.h>
4 
5 #include <pcgnslib.h>
6 #include <cgns_io.h>
7 
8 #if !defined(CGNS_ENUMT)
9   #define CGNS_ENUMT(a) a
10 #endif
11 #if !defined(CGNS_ENUMV)
12   #define CGNS_ENUMV(a) a
13 #endif
14 
15 PetscErrorCode DMPlexCreateCGNSFromFile_Internal(MPI_Comm comm, const char filename[], PetscBool interpolate, DM *dm)
16 {
17   PetscMPIInt rank;
18   int         cgid = -1;
19 
20   PetscFunctionBegin;
21   PetscValidCharPointer(filename, 2);
22   PetscCallMPI(MPI_Comm_rank(comm, &rank));
23   if (rank == 0) {
24     PetscCallCGNS(cg_open(filename, CG_MODE_READ, &cgid));
25     PetscCheck(cgid > 0, PETSC_COMM_SELF, PETSC_ERR_LIB, "cg_open(\"%s\",...) did not return a valid file ID", filename);
26   }
27   PetscCall(DMPlexCreateCGNS(comm, cgid, interpolate, dm));
28   if (rank == 0) PetscCallCGNS(cg_close(cgid));
29   PetscFunctionReturn(0);
30 }
31 
32 PetscErrorCode DMPlexCreateCGNS_Internal(MPI_Comm comm, PetscInt cgid, PetscBool interpolate, DM *dm)
33 {
34   PetscMPIInt  num_proc, rank;
35   DM           cdm;
36   DMLabel      label;
37   PetscSection coordSection;
38   Vec          coordinates;
39   PetscScalar *coords;
40   PetscInt    *cellStart, *vertStart, v;
41   PetscInt     labelIdRange[2], labelId;
42   /* Read from file */
43   char basename[CGIO_MAX_NAME_LENGTH + 1];
44   char buffer[CGIO_MAX_NAME_LENGTH + 1];
45   int  dim = 0, physDim = 0, coordDim = 0, numVertices = 0, numCells = 0;
46   int  nzones = 0;
47 
48   PetscFunctionBegin;
49   PetscCallMPI(MPI_Comm_rank(comm, &rank));
50   PetscCallMPI(MPI_Comm_size(comm, &num_proc));
51   PetscCall(DMCreate(comm, dm));
52   PetscCall(DMSetType(*dm, DMPLEX));
53 
54   /* Open CGNS II file and read basic information on rank 0, then broadcast to all processors */
55   if (rank == 0) {
56     int nbases, z;
57 
58     PetscCallCGNS(cg_nbases(cgid, &nbases));
59     PetscCheck(nbases <= 1, PETSC_COMM_SELF, PETSC_ERR_LIB, "CGNS file must have a single base, not %d", nbases);
60     PetscCallCGNS(cg_base_read(cgid, 1, basename, &dim, &physDim));
61     PetscCallCGNS(cg_nzones(cgid, 1, &nzones));
62     PetscCall(PetscCalloc2(nzones + 1, &cellStart, nzones + 1, &vertStart));
63     for (z = 1; z <= nzones; ++z) {
64       cgsize_t sizes[3]; /* Number of vertices, number of cells, number of boundary vertices */
65 
66       PetscCallCGNS(cg_zone_read(cgid, 1, z, buffer, sizes));
67       numVertices += sizes[0];
68       numCells += sizes[1];
69       cellStart[z] += sizes[1] + cellStart[z - 1];
70       vertStart[z] += sizes[0] + vertStart[z - 1];
71     }
72     for (z = 1; z <= nzones; ++z) vertStart[z] += numCells;
73     coordDim = dim;
74   }
75   PetscCallMPI(MPI_Bcast(basename, CGIO_MAX_NAME_LENGTH + 1, MPI_CHAR, 0, comm));
76   PetscCallMPI(MPI_Bcast(&dim, 1, MPI_INT, 0, comm));
77   PetscCallMPI(MPI_Bcast(&coordDim, 1, MPI_INT, 0, comm));
78   PetscCallMPI(MPI_Bcast(&nzones, 1, MPI_INT, 0, comm));
79 
80   PetscCall(PetscObjectSetName((PetscObject)*dm, basename));
81   PetscCall(DMSetDimension(*dm, dim));
82   PetscCall(DMCreateLabel(*dm, "celltype"));
83   PetscCall(DMPlexSetChart(*dm, 0, numCells + numVertices));
84 
85   /* Read zone information */
86   if (rank == 0) {
87     int z, c, c_loc;
88 
89     /* Read the cell set connectivity table and build mesh topology
90        CGNS standard requires that cells in a zone be numbered sequentially and be pairwise disjoint. */
91     /* First set sizes */
92     for (z = 1, c = 0; z <= nzones; ++z) {
93       CGNS_ENUMT(ZoneType_t) zonetype;
94       int nsections;
95       CGNS_ENUMT(ElementType_t) cellType;
96       cgsize_t       start, end;
97       int            nbndry, parentFlag;
98       PetscInt       numCorners;
99       DMPolytopeType ctype;
100 
101       PetscCallCGNS(cg_zone_type(cgid, 1, z, &zonetype));
102       PetscCheck(zonetype != CGNS_ENUMV(Structured), PETSC_COMM_SELF, PETSC_ERR_LIB, "Can only handle Unstructured zones for CGNS");
103       PetscCallCGNS(cg_nsections(cgid, 1, z, &nsections));
104       PetscCheck(nsections <= 1, PETSC_COMM_SELF, PETSC_ERR_LIB, "CGNS file must have a single section, not %d", nsections);
105       PetscCallCGNS(cg_section_read(cgid, 1, z, 1, buffer, &cellType, &start, &end, &nbndry, &parentFlag));
106       /* This alone is reason enough to bludgeon every single CGNDS developer, this must be what they describe as the "idiocy of crowds" */
107       if (cellType == CGNS_ENUMV(MIXED)) {
108         cgsize_t elementDataSize, *elements;
109         PetscInt off;
110 
111         PetscCallCGNS(cg_ElementDataSize(cgid, 1, z, 1, &elementDataSize));
112         PetscCall(PetscMalloc1(elementDataSize, &elements));
113         PetscCallCGNS(cg_poly_elements_read(cgid, 1, z, 1, elements, NULL, NULL));
114         for (c_loc = start, off = 0; c_loc <= end; ++c_loc, ++c) {
115           switch (elements[off]) {
116           case CGNS_ENUMV(BAR_2):
117             numCorners = 2;
118             ctype      = DM_POLYTOPE_SEGMENT;
119             break;
120           case CGNS_ENUMV(TRI_3):
121             numCorners = 3;
122             ctype      = DM_POLYTOPE_TRIANGLE;
123             break;
124           case CGNS_ENUMV(QUAD_4):
125             numCorners = 4;
126             ctype      = DM_POLYTOPE_QUADRILATERAL;
127             break;
128           case CGNS_ENUMV(TETRA_4):
129             numCorners = 4;
130             ctype      = DM_POLYTOPE_TETRAHEDRON;
131             break;
132           case CGNS_ENUMV(PENTA_6):
133             numCorners = 6;
134             ctype      = DM_POLYTOPE_TRI_PRISM;
135             break;
136           case CGNS_ENUMV(HEXA_8):
137             numCorners = 8;
138             ctype      = DM_POLYTOPE_HEXAHEDRON;
139             break;
140           default:
141             SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Invalid cell type %d", (int)elements[off]);
142           }
143           PetscCall(DMPlexSetConeSize(*dm, c, numCorners));
144           PetscCall(DMPlexSetCellType(*dm, c, ctype));
145           off += numCorners + 1;
146         }
147         PetscCall(PetscFree(elements));
148       } else {
149         switch (cellType) {
150         case CGNS_ENUMV(BAR_2):
151           numCorners = 2;
152           ctype      = DM_POLYTOPE_SEGMENT;
153           break;
154         case CGNS_ENUMV(TRI_3):
155           numCorners = 3;
156           ctype      = DM_POLYTOPE_TRIANGLE;
157           break;
158         case CGNS_ENUMV(QUAD_4):
159           numCorners = 4;
160           ctype      = DM_POLYTOPE_QUADRILATERAL;
161           break;
162         case CGNS_ENUMV(TETRA_4):
163           numCorners = 4;
164           ctype      = DM_POLYTOPE_TETRAHEDRON;
165           break;
166         case CGNS_ENUMV(PENTA_6):
167           numCorners = 6;
168           ctype      = DM_POLYTOPE_TRI_PRISM;
169           break;
170         case CGNS_ENUMV(HEXA_8):
171           numCorners = 8;
172           ctype      = DM_POLYTOPE_HEXAHEDRON;
173           break;
174         default:
175           SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Invalid cell type %d", (int)cellType);
176         }
177         for (c_loc = start; c_loc <= end; ++c_loc, ++c) {
178           PetscCall(DMPlexSetConeSize(*dm, c, numCorners));
179           PetscCall(DMPlexSetCellType(*dm, c, ctype));
180         }
181       }
182     }
183     for (v = numCells; v < numCells + numVertices; ++v) PetscCall(DMPlexSetCellType(*dm, v, DM_POLYTOPE_POINT));
184   }
185 
186   PetscCall(DMSetUp(*dm));
187 
188   PetscCall(DMCreateLabel(*dm, "zone"));
189   if (rank == 0) {
190     int z, c, c_loc, v_loc;
191 
192     PetscCall(DMGetLabel(*dm, "zone", &label));
193     for (z = 1, c = 0; z <= nzones; ++z) {
194       CGNS_ENUMT(ElementType_t) cellType;
195       cgsize_t  elementDataSize, *elements, start, end;
196       int       nbndry, parentFlag;
197       PetscInt *cone, numc, numCorners, maxCorners = 27;
198 
199       PetscCallCGNS(cg_section_read(cgid, 1, z, 1, buffer, &cellType, &start, &end, &nbndry, &parentFlag));
200       numc = end - start;
201       /* This alone is reason enough to bludgeon every single CGNDS developer, this must be what they describe as the "idiocy of crowds" */
202       PetscCallCGNS(cg_ElementDataSize(cgid, 1, z, 1, &elementDataSize));
203       PetscCall(PetscMalloc2(elementDataSize, &elements, maxCorners, &cone));
204       PetscCallCGNS(cg_poly_elements_read(cgid, 1, z, 1, elements, NULL, NULL));
205       if (cellType == CGNS_ENUMV(MIXED)) {
206         /* CGNS uses Fortran-based indexing, DMPlex uses C-style and numbers cell first then vertices. */
207         for (c_loc = 0, v = 0; c_loc <= numc; ++c_loc, ++c) {
208           switch (elements[v]) {
209           case CGNS_ENUMV(BAR_2):
210             numCorners = 2;
211             break;
212           case CGNS_ENUMV(TRI_3):
213             numCorners = 3;
214             break;
215           case CGNS_ENUMV(QUAD_4):
216             numCorners = 4;
217             break;
218           case CGNS_ENUMV(TETRA_4):
219             numCorners = 4;
220             break;
221           case CGNS_ENUMV(PENTA_6):
222             numCorners = 6;
223             break;
224           case CGNS_ENUMV(HEXA_8):
225             numCorners = 8;
226             break;
227           default:
228             SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Invalid cell type %d", (int)elements[v]);
229           }
230           ++v;
231           for (v_loc = 0; v_loc < numCorners; ++v_loc, ++v) cone[v_loc] = elements[v] + numCells - 1;
232           PetscCall(DMPlexReorderCell(*dm, c, cone));
233           PetscCall(DMPlexSetCone(*dm, c, cone));
234           PetscCall(DMLabelSetValue(label, c, z));
235         }
236       } else {
237         switch (cellType) {
238         case CGNS_ENUMV(BAR_2):
239           numCorners = 2;
240           break;
241         case CGNS_ENUMV(TRI_3):
242           numCorners = 3;
243           break;
244         case CGNS_ENUMV(QUAD_4):
245           numCorners = 4;
246           break;
247         case CGNS_ENUMV(TETRA_4):
248           numCorners = 4;
249           break;
250         case CGNS_ENUMV(PENTA_6):
251           numCorners = 6;
252           break;
253         case CGNS_ENUMV(HEXA_8):
254           numCorners = 8;
255           break;
256         default:
257           SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Invalid cell type %d", (int)cellType);
258         }
259         /* CGNS uses Fortran-based indexing, DMPlex uses C-style and numbers cell first then vertices. */
260         for (c_loc = 0, v = 0; c_loc <= numc; ++c_loc, ++c) {
261           for (v_loc = 0; v_loc < numCorners; ++v_loc, ++v) cone[v_loc] = elements[v] + numCells - 1;
262           PetscCall(DMPlexReorderCell(*dm, c, cone));
263           PetscCall(DMPlexSetCone(*dm, c, cone));
264           PetscCall(DMLabelSetValue(label, c, z));
265         }
266       }
267       PetscCall(PetscFree2(elements, cone));
268     }
269   }
270 
271   PetscCall(DMPlexSymmetrize(*dm));
272   PetscCall(DMPlexStratify(*dm));
273   if (interpolate) {
274     DM idm;
275 
276     PetscCall(DMPlexInterpolate(*dm, &idm));
277     PetscCall(DMDestroy(dm));
278     *dm = idm;
279   }
280 
281   /* Read coordinates */
282   PetscCall(DMSetCoordinateDim(*dm, coordDim));
283   PetscCall(DMGetCoordinateDM(*dm, &cdm));
284   PetscCall(DMGetLocalSection(cdm, &coordSection));
285   PetscCall(PetscSectionSetNumFields(coordSection, 1));
286   PetscCall(PetscSectionSetFieldComponents(coordSection, 0, coordDim));
287   PetscCall(PetscSectionSetChart(coordSection, numCells, numCells + numVertices));
288   for (v = numCells; v < numCells + numVertices; ++v) {
289     PetscCall(PetscSectionSetDof(coordSection, v, dim));
290     PetscCall(PetscSectionSetFieldDof(coordSection, v, 0, coordDim));
291   }
292   PetscCall(PetscSectionSetUp(coordSection));
293 
294   PetscCall(DMCreateLocalVector(cdm, &coordinates));
295   PetscCall(VecGetArray(coordinates, &coords));
296   if (rank == 0) {
297     PetscInt off = 0;
298     float   *x[3];
299     int      z, d;
300 
301     PetscCall(PetscMalloc3(numVertices, &x[0], numVertices, &x[1], numVertices, &x[2]));
302     for (z = 1; z <= nzones; ++z) {
303       CGNS_ENUMT(DataType_t) datatype;
304       cgsize_t sizes[3]; /* Number of vertices, number of cells, number of boundary vertices */
305       cgsize_t range_min[3] = {1, 1, 1};
306       cgsize_t range_max[3] = {1, 1, 1};
307       int      ngrids, ncoords;
308 
309       PetscCallCGNS(cg_zone_read(cgid, 1, z, buffer, sizes));
310       range_max[0] = sizes[0];
311       PetscCallCGNS(cg_ngrids(cgid, 1, z, &ngrids));
312       PetscCheck(ngrids <= 1, PETSC_COMM_SELF, PETSC_ERR_LIB, "CGNS file must have a single grid, not %d", ngrids);
313       PetscCallCGNS(cg_ncoords(cgid, 1, z, &ncoords));
314       PetscCheck(ncoords == coordDim, PETSC_COMM_SELF, PETSC_ERR_LIB, "CGNS file must have a coordinate array for each dimension, not %d", ncoords);
315       for (d = 0; d < coordDim; ++d) {
316         PetscCallCGNS(cg_coord_info(cgid, 1, z, 1 + d, &datatype, buffer));
317         PetscCallCGNS(cg_coord_read(cgid, 1, z, buffer, CGNS_ENUMV(RealSingle), range_min, range_max, x[d]));
318       }
319       if (coordDim >= 1) {
320         for (v = 0; v < sizes[0]; ++v) coords[(v + off) * coordDim + 0] = x[0][v];
321       }
322       if (coordDim >= 2) {
323         for (v = 0; v < sizes[0]; ++v) coords[(v + off) * coordDim + 1] = x[1][v];
324       }
325       if (coordDim >= 3) {
326         for (v = 0; v < sizes[0]; ++v) coords[(v + off) * coordDim + 2] = x[2][v];
327       }
328       off += sizes[0];
329     }
330     PetscCall(PetscFree3(x[0], x[1], x[2]));
331   }
332   PetscCall(VecRestoreArray(coordinates, &coords));
333 
334   PetscCall(PetscObjectSetName((PetscObject)coordinates, "coordinates"));
335   PetscCall(VecSetBlockSize(coordinates, coordDim));
336   PetscCall(DMSetCoordinatesLocal(*dm, coordinates));
337   PetscCall(VecDestroy(&coordinates));
338 
339   /* Read boundary conditions */
340   PetscCall(DMGetNumLabels(*dm, &labelIdRange[0]));
341   if (rank == 0) {
342     CGNS_ENUMT(BCType_t) bctype;
343     CGNS_ENUMT(DataType_t) datatype;
344     CGNS_ENUMT(PointSetType_t) pointtype;
345     cgsize_t  *points;
346     PetscReal *normals;
347     int        normal[3];
348     char      *bcname = buffer;
349     cgsize_t   npoints, nnormals;
350     int        z, nbc, bc, c, ndatasets;
351 
352     for (z = 1; z <= nzones; ++z) {
353       PetscCallCGNS(cg_nbocos(cgid, 1, z, &nbc));
354       for (bc = 1; bc <= nbc; ++bc) {
355         PetscCallCGNS(cg_boco_info(cgid, 1, z, bc, bcname, &bctype, &pointtype, &npoints, normal, &nnormals, &datatype, &ndatasets));
356         PetscCall(DMCreateLabel(*dm, bcname));
357         PetscCall(DMGetLabel(*dm, bcname, &label));
358         PetscCall(PetscMalloc2(npoints, &points, nnormals, &normals));
359         PetscCallCGNS(cg_boco_read(cgid, 1, z, bc, points, (void *)normals));
360         if (pointtype == CGNS_ENUMV(ElementRange)) {
361           /* Range of cells: assuming half-open interval since the documentation sucks */
362           for (c = points[0]; c < points[1]; ++c) PetscCall(DMLabelSetValue(label, c - cellStart[z - 1], 1));
363         } else if (pointtype == CGNS_ENUMV(ElementList)) {
364           /* List of cells */
365           for (c = 0; c < npoints; ++c) PetscCall(DMLabelSetValue(label, points[c] - cellStart[z - 1], 1));
366         } else if (pointtype == CGNS_ENUMV(PointRange)) {
367           CGNS_ENUMT(GridLocation_t) gridloc;
368 
369           /* List of points: Oh please, someone get the CGNS developers away from a computer. This is unconscionable. */
370           PetscCallCGNS(cg_goto(cgid, 1, "Zone_t", z, "BC_t", bc, "end"));
371           PetscCallCGNS(cg_gridlocation_read(&gridloc));
372           /* Range of points: assuming half-open interval since the documentation sucks */
373           for (c = points[0]; c < points[1]; ++c) {
374             if (gridloc == CGNS_ENUMV(Vertex)) PetscCall(DMLabelSetValue(label, c - vertStart[z - 1], 1));
375             else PetscCall(DMLabelSetValue(label, c - cellStart[z - 1], 1));
376           }
377         } else if (pointtype == CGNS_ENUMV(PointList)) {
378           CGNS_ENUMT(GridLocation_t) gridloc;
379 
380           /* List of points: Oh please, someone get the CGNS developers away from a computer. This is unconscionable. */
381           PetscCallCGNS(cg_goto(cgid, 1, "Zone_t", z, "BC_t", bc, "end"));
382           PetscCallCGNS(cg_gridlocation_read(&gridloc));
383           for (c = 0; c < npoints; ++c) {
384             if (gridloc == CGNS_ENUMV(Vertex)) PetscCall(DMLabelSetValue(label, points[c] - vertStart[z - 1], 1));
385             else PetscCall(DMLabelSetValue(label, points[c] - cellStart[z - 1], 1));
386           }
387         } else SETERRQ(comm, PETSC_ERR_SUP, "Unsupported point set type %d", (int)pointtype);
388         PetscCall(PetscFree2(points, normals));
389       }
390     }
391     PetscCall(PetscFree2(cellStart, vertStart));
392   }
393   PetscCall(DMGetNumLabels(*dm, &labelIdRange[1]));
394   PetscCallMPI(MPI_Bcast(labelIdRange, 2, MPIU_INT, 0, comm));
395 
396   /* Create BC labels at all processes */
397   for (labelId = labelIdRange[0]; labelId < labelIdRange[1]; ++labelId) {
398     char       *labelName = buffer;
399     size_t      len       = sizeof(buffer);
400     const char *locName;
401 
402     if (rank == 0) {
403       PetscCall(DMGetLabelByNum(*dm, labelId, &label));
404       PetscCall(PetscObjectGetName((PetscObject)label, &locName));
405       PetscCall(PetscStrncpy(labelName, locName, len));
406     }
407     PetscCallMPI(MPI_Bcast(labelName, (PetscMPIInt)len, MPIU_INT, 0, comm));
408     PetscCallMPI(DMCreateLabel(*dm, labelName));
409   }
410   PetscFunctionReturn(0);
411 }
412 
413 // Permute plex closure ordering to CGNS
414 static PetscErrorCode DMPlexCGNSGetPermutation_Internal(DMPolytopeType cell_type, PetscInt closure_size, CGNS_ENUMT(ElementType_t) * element_type, const int **perm)
415 {
416   // https://cgns.github.io/CGNS_docs_current/sids/conv.html#unst_example
417   static const int bar_2[2]   = {0, 1};
418   static const int bar_3[3]   = {1, 2, 0};
419   static const int bar_4[4]   = {2, 3, 0, 1};
420   static const int bar_5[5]   = {3, 4, 0, 1, 2};
421   static const int tri_3[3]   = {0, 1, 2};
422   static const int tri_6[6]   = {3, 4, 5, 0, 1, 2};
423   static const int tri_10[10] = {7, 8, 9, 1, 2, 3, 4, 5, 6, 0};
424   static const int quad_4[4]  = {0, 1, 2, 3};
425   static const int quad_9[9]  = {
426     5, 6, 7, 8, // vertices
427     1, 2, 3, 4, // edges
428     0,          // center
429   };
430   static const int quad_16[] = {
431     12, 13, 14, 15,               // vertices
432     4,  5,  6,  7,  8, 9, 10, 11, // edges
433     0,  1,  3,  2,                // centers
434   };
435   static const int quad_25[] = {
436     21, 22, 23, 24,                                 // vertices
437     9,  10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, // edges
438     0,  1,  2,  5,  8,  7,  6,  3,  4,              // centers
439   };
440   static const int tetra_4[4]   = {0, 2, 1, 3};
441   static const int tetra_10[10] = {6, 8, 7, 9, 2, 1, 0, 3, 5, 4};
442   static const int tetra_20[20] = {
443     16, 18, 17, 19,         // vertices
444     9,  8,  7,  6,  5,  4,  // bottom edges
445     10, 11, 14, 15, 13, 12, // side edges
446     0,  2,  3,  1,          // faces
447   };
448   static const int hexa_8[8]   = {0, 3, 2, 1, 4, 5, 6, 7};
449   static const int hexa_27[27] = {
450     19, 22, 21, 20, 23, 24, 25, 26, // vertices
451     10, 9,  8,  7,                  // bottom edges
452     16, 15, 18, 17,                 // mid edges
453     11, 12, 13, 14,                 // top edges
454     1,  3,  5,  4,  6,  2,          // faces
455     0,                              // center
456   };
457   static const int hexa_64[64] = {
458     // debug with $PETSC_ARCH/tests/dm/impls/plex/tests/ex49 -dm_plex_simplex 0 -dm_plex_dim 3 -dm_plex_box_faces 1,1,1 -dm_coord_petscspace_degree 3
459     56, 59, 58, 57, 60, 61, 62, 63, // vertices
460     39, 38, 37, 36, 35, 34, 33, 32, // bottom edges
461     51, 50, 48, 49, 52, 53, 55, 54, // mid edges; Paraview needs edge 21-22 swapped with 23-24
462     40, 41, 42, 43, 44, 45, 46, 47, // top edges
463     8,  10, 11, 9,                  // z-minus face
464     16, 17, 19, 18,                 // y-minus face
465     24, 25, 27, 26,                 // x-plus face
466     20, 21, 23, 22,                 // y-plus face
467     30, 28, 29, 31,                 // x-minus face
468     12, 13, 15, 14,                 // z-plus face
469     0,  1,  3,  2,  4,  5,  7,  6,  // center
470   };
471 
472   PetscFunctionBegin;
473   *element_type = CGNS_ENUMV(ElementTypeNull);
474   *perm         = NULL;
475   switch (cell_type) {
476   case DM_POLYTOPE_SEGMENT:
477     switch (closure_size) {
478     case 2:
479       *element_type = CGNS_ENUMV(BAR_2);
480       *perm         = bar_2;
481     case 3:
482       *element_type = CGNS_ENUMV(BAR_3);
483       *perm         = bar_3;
484     case 4:
485       *element_type = CGNS_ENUMV(BAR_4);
486       *perm         = bar_4;
487       break;
488     case 5:
489       *element_type = CGNS_ENUMV(BAR_5);
490       *perm         = bar_5;
491       break;
492     default:
493       SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "Cell type %s with closure size %" PetscInt_FMT, DMPolytopeTypes[cell_type], closure_size);
494     }
495     break;
496   case DM_POLYTOPE_TRIANGLE:
497     switch (closure_size) {
498     case 3:
499       *element_type = CGNS_ENUMV(TRI_3);
500       *perm         = tri_3;
501       break;
502     case 6:
503       *element_type = CGNS_ENUMV(TRI_6);
504       *perm         = tri_6;
505       break;
506     case 10:
507       *element_type = CGNS_ENUMV(TRI_10);
508       *perm         = tri_10;
509       break;
510     default:
511       SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "Cell type %s with closure size %" PetscInt_FMT, DMPolytopeTypes[cell_type], closure_size);
512     }
513     break;
514   case DM_POLYTOPE_QUADRILATERAL:
515     switch (closure_size) {
516     case 4:
517       *element_type = CGNS_ENUMV(QUAD_4);
518       *perm         = quad_4;
519       break;
520     case 9:
521       *element_type = CGNS_ENUMV(QUAD_9);
522       *perm         = quad_9;
523       break;
524     case 16:
525       *element_type = CGNS_ENUMV(QUAD_16);
526       *perm         = quad_16;
527       break;
528     case 25:
529       *element_type = CGNS_ENUMV(QUAD_25);
530       *perm         = quad_25;
531       break;
532     default:
533       SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "Cell type %s with closure size %" PetscInt_FMT, DMPolytopeTypes[cell_type], closure_size);
534     }
535     break;
536   case DM_POLYTOPE_TETRAHEDRON:
537     switch (closure_size) {
538     case 4:
539       *element_type = CGNS_ENUMV(TETRA_4);
540       *perm         = tetra_4;
541       break;
542     case 10:
543       *element_type = CGNS_ENUMV(TETRA_10);
544       *perm         = tetra_10;
545       break;
546     case 20:
547       *element_type = CGNS_ENUMV(TETRA_20);
548       *perm         = tetra_20;
549       break;
550     default:
551       SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "Cell type %s with closure size %" PetscInt_FMT, DMPolytopeTypes[cell_type], closure_size);
552     }
553     break;
554   case DM_POLYTOPE_HEXAHEDRON:
555     switch (closure_size) {
556     case 8:
557       *element_type = CGNS_ENUMV(HEXA_8);
558       *perm         = hexa_8;
559       break;
560     case 27:
561       *element_type = CGNS_ENUMV(HEXA_27);
562       *perm         = hexa_27;
563       break;
564     case 64:
565       *element_type = CGNS_ENUMV(HEXA_64);
566       *perm         = hexa_64;
567       break;
568     default:
569       SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "Cell type %s with closure size %" PetscInt_FMT, DMPolytopeTypes[cell_type], closure_size);
570     }
571     break;
572   default:
573     SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "Cell type %s with closure size %" PetscInt_FMT, DMPolytopeTypes[cell_type], closure_size);
574   }
575   PetscFunctionReturn(0);
576 }
577 
578 // node_l2g must be freed
579 static PetscErrorCode DMPlexCreateNodeNumbering(DM dm, PetscInt *num_local_nodes, PetscInt *num_global_nodes, PetscInt *nStart, PetscInt *nEnd, const PetscInt **node_l2g)
580 {
581   PetscSection    local_section;
582   PetscSF         point_sf;
583   PetscInt        pStart, pEnd, spStart, spEnd, *points, nleaves, ncomp, *nodes;
584   PetscMPIInt     comm_size;
585   const PetscInt *ilocal, field = 0;
586 
587   PetscFunctionBegin;
588   *num_local_nodes  = -1;
589   *num_global_nodes = -1;
590   *nStart           = -1;
591   *nEnd             = -1;
592   *node_l2g         = NULL;
593 
594   PetscCallMPI(MPI_Comm_size(PetscObjectComm((PetscObject)dm), &comm_size));
595   PetscCall(DMGetLocalSection(dm, &local_section));
596   PetscCall(DMPlexGetChart(dm, &pStart, &pEnd));
597   PetscCall(PetscSectionGetChart(local_section, &spStart, &spEnd));
598   PetscCall(DMGetPointSF(dm, &point_sf));
599   if (comm_size == 1) nleaves = 0;
600   else PetscCall(PetscSFGetGraph(point_sf, NULL, &nleaves, &ilocal, NULL));
601   PetscCall(PetscSectionGetFieldComponents(local_section, field, &ncomp));
602 
603   PetscInt local_node = 0, owned_node = 0, owned_start = 0;
604   for (PetscInt p = spStart, leaf = 0; p < spEnd; p++) {
605     PetscInt dof;
606     PetscCall(PetscSectionGetFieldDof(local_section, p, field, &dof));
607     PetscAssert(dof % ncomp == 0, PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "Field dof %" PetscInt_FMT " must be divisible by components %" PetscInt_FMT, dof, ncomp);
608     local_node += dof / ncomp;
609     if (leaf < nleaves && p == ilocal[leaf]) { // skip points owned by a different process
610       leaf++;
611     } else {
612       owned_node += dof / ncomp;
613     }
614   }
615   PetscCallMPI(MPI_Exscan(&owned_node, &owned_start, 1, MPIU_INT, MPI_SUM, PetscObjectComm((PetscObject)dm)));
616   PetscCall(PetscMalloc1(pEnd - pStart, &points));
617   owned_node = 0;
618   for (PetscInt p = spStart, leaf = 0; p < spEnd; p++) {
619     if (leaf < nleaves && p == ilocal[leaf]) { // skip points owned by a different process
620       points[p - pStart] = -1;
621       leaf++;
622       continue;
623     }
624     PetscInt dof, offset;
625     PetscCall(PetscSectionGetFieldDof(local_section, p, field, &dof));
626     PetscCall(PetscSectionGetFieldOffset(local_section, p, field, &offset));
627     PetscAssert(offset % ncomp == 0, PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "Field offset %" PetscInt_FMT " must be divisible by components %" PetscInt_FMT, offset, ncomp);
628     points[p - pStart] = owned_start + owned_node;
629     owned_node += dof / ncomp;
630   }
631   if (comm_size > 1) {
632     PetscCall(PetscSFBcastBegin(point_sf, MPIU_INT, points, points, MPI_REPLACE));
633     PetscCall(PetscSFBcastEnd(point_sf, MPIU_INT, points, points, MPI_REPLACE));
634   }
635 
636   // Set up global indices for each local node
637   PetscCall(PetscMalloc1(local_node, &nodes));
638   for (PetscInt p = spStart; p < spEnd; p++) {
639     PetscInt dof, offset;
640     PetscCall(PetscSectionGetFieldDof(local_section, p, field, &dof));
641     PetscCall(PetscSectionGetFieldOffset(local_section, p, field, &offset));
642     for (PetscInt n = 0; n < dof / ncomp; n++) nodes[offset / ncomp + n] = points[p - pStart] + n;
643   }
644   PetscCall(PetscFree(points));
645   *num_local_nodes = local_node;
646   *nStart          = owned_start;
647   *nEnd            = owned_start + owned_node;
648   PetscCallMPI(MPI_Allreduce(&owned_node, num_global_nodes, 1, MPIU_INT, MPI_SUM, PetscObjectComm((PetscObject)dm)));
649   *node_l2g = nodes;
650   PetscFunctionReturn(0);
651 }
652 
653 PetscErrorCode DMView_PlexCGNS(DM dm, PetscViewer viewer)
654 {
655   PetscViewer_CGNS *cgv = (PetscViewer_CGNS *)viewer->data;
656   PetscInt          topo_dim, coord_dim, num_global_elems;
657   PetscInt          cStart, cEnd, num_local_nodes, num_global_nodes, nStart, nEnd;
658   const PetscInt   *node_l2g;
659   Vec               coord;
660   DM                colloc_dm, cdm;
661   PetscMPIInt       size;
662   const char       *dm_name;
663   int               base, zone;
664   cgsize_t          isize[3];
665 
666   PetscFunctionBegin;
667   PetscCallMPI(MPI_Comm_size(PetscObjectComm((PetscObject)dm), &size));
668   PetscCall(DMGetDimension(dm, &topo_dim));
669   PetscCall(DMGetCoordinateDim(dm, &coord_dim));
670   PetscCall(PetscObjectGetName((PetscObject)dm, &dm_name));
671   PetscCallCGNS(cg_base_write(cgv->file_num, dm_name, topo_dim, coord_dim, &base));
672   PetscCallCGNS(cg_goto(cgv->file_num, base, NULL));
673   PetscCallCGNS(cg_dataclass_write(CGNS_ENUMV(NormalizedByDimensional)));
674 
675   {
676     PetscFE        fe, fe_coord;
677     PetscDualSpace dual_space, dual_space_coord;
678     PetscInt       field_order, field_order_coord;
679     PetscBool      is_simplex;
680     PetscCall(DMGetField(dm, 0, NULL, (PetscObject *)&fe));
681     if (fe) {
682       PetscCall(PetscFEGetDualSpace(fe, &dual_space));
683       PetscCall(PetscDualSpaceGetOrder(dual_space, &field_order));
684     } else field_order = 1;
685     PetscCall(DMGetCoordinateDM(dm, &cdm));
686     PetscCall(DMGetField(cdm, 0, NULL, (PetscObject *)&fe_coord));
687     if (fe_coord) {
688       PetscCall(PetscFEGetDualSpace(fe_coord, &dual_space_coord));
689       PetscCall(PetscDualSpaceGetOrder(dual_space_coord, &field_order_coord));
690     } else field_order_coord = 1;
691     if (field_order != field_order_coord) {
692       PetscInt quadrature_order = field_order;
693       PetscCall(DMClone(dm, &colloc_dm));
694       PetscCall(DMPlexIsSimplex(dm, &is_simplex));
695       PetscCall(PetscFECreateLagrange(PetscObjectComm((PetscObject)dm), topo_dim, coord_dim, is_simplex, field_order, quadrature_order, &fe));
696       PetscCall(DMProjectCoordinates(colloc_dm, fe));
697       PetscCall(PetscFEDestroy(&fe));
698     } else {
699       PetscCall(PetscObjectReference((PetscObject)dm));
700       colloc_dm = dm;
701     }
702   }
703   PetscCall(DMGetCoordinateDM(colloc_dm, &cdm));
704   PetscCall(DMPlexCreateNodeNumbering(cdm, &num_local_nodes, &num_global_nodes, &nStart, &nEnd, &node_l2g));
705   PetscCall(DMGetCoordinatesLocal(colloc_dm, &coord));
706   PetscCall(DMPlexGetHeightStratum(dm, 0, &cStart, &cEnd));
707   num_global_elems = cEnd - cStart;
708   PetscCallMPI(MPI_Allreduce(MPI_IN_PLACE, &num_global_elems, 1, MPIU_INT, MPI_SUM, PetscObjectComm((PetscObject)dm)));
709   isize[0] = num_global_nodes;
710   isize[1] = num_global_elems;
711   isize[2] = 0;
712   PetscCallCGNS(cg_zone_write(cgv->file_num, base, "Zone", isize, CGNS_ENUMV(Unstructured), &zone));
713 
714   {
715     const PetscScalar *X;
716     PetscScalar       *x;
717     int                coord_ids[3];
718 
719     PetscCall(VecGetArrayRead(coord, &X));
720     for (PetscInt d = 0; d < coord_dim; d++) {
721       const double exponents[] = {0, 1, 0, 0, 0};
722       char         coord_name[64];
723       PetscCall(PetscSNPrintf(coord_name, sizeof coord_name, "Coordinate%c", 'X' + (int)d));
724       PetscCallCGNS(cgp_coord_write(cgv->file_num, base, zone, CGNS_ENUMV(RealDouble), coord_name, &coord_ids[d]));
725       PetscCallCGNS(cg_goto(cgv->file_num, base, "Zone_t", zone, "GridCoordinates", 0, coord_name, 0, NULL));
726       PetscCallCGNS(cg_exponents_write(CGNS_ENUMV(RealDouble), exponents));
727     }
728 
729     DMPolytopeType cell_type;
730     int            section;
731     cgsize_t       e_owned, e_global, e_start, *conn = NULL;
732     const int     *perm;
733     CGNS_ENUMT(ElementType_t) element_type = CGNS_ENUMV(ElementTypeNull);
734     {
735       PetscCall(PetscMalloc1(nEnd - nStart, &x));
736       for (PetscInt d = 0; d < coord_dim; d++) {
737         for (PetscInt n = 0; n < num_local_nodes; n++) {
738           PetscInt gn = node_l2g[n];
739           if (gn < nStart || nEnd <= gn) continue;
740           x[gn - nStart] = X[n * coord_dim + d];
741         }
742         // CGNS nodes use 1-based indexing
743         cgsize_t start = nStart + 1, end = nEnd;
744         PetscCallCGNS(cgp_coord_write_data(cgv->file_num, base, zone, coord_ids[d], &start, &end, x));
745       }
746       PetscCall(PetscFree(x));
747       PetscCall(VecRestoreArrayRead(coord, &X));
748     }
749 
750     PetscCall(DMPlexGetCellType(dm, cStart, &cell_type));
751     for (PetscInt i = cStart, c = 0; i < cEnd; i++) {
752       PetscInt closure_dof, *closure_indices, elem_size;
753       PetscCall(DMPlexGetClosureIndices(cdm, cdm->localSection, cdm->localSection, i, PETSC_FALSE, &closure_dof, &closure_indices, NULL, NULL));
754       elem_size = closure_dof / coord_dim;
755       if (!conn) PetscCall(PetscMalloc1((cEnd - cStart) * elem_size, &conn));
756       PetscCall(DMPlexCGNSGetPermutation_Internal(cell_type, closure_dof / coord_dim, &element_type, &perm));
757       for (PetscInt j = 0; j < elem_size; j++) conn[c++] = node_l2g[closure_indices[perm[j] * coord_dim] / coord_dim] + 1;
758       PetscCall(DMPlexRestoreClosureIndices(cdm, cdm->localSection, cdm->localSection, i, PETSC_FALSE, &closure_dof, &closure_indices, NULL, NULL));
759     }
760     e_owned = cEnd - cStart;
761     PetscCallMPI(MPI_Allreduce(&e_owned, &e_global, 1, MPIU_INT64, MPI_SUM, PetscObjectComm((PetscObject)dm)));
762     PetscCheck(e_global == num_global_elems, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Unexpected number of elements %" PetscInt64_FMT "vs %" PetscInt_FMT, e_global, num_global_elems);
763     e_start = 0;
764     PetscCallMPI(MPI_Exscan(&e_owned, &e_start, 1, MPIU_INT64, MPI_SUM, PetscObjectComm((PetscObject)dm)));
765     PetscCallCGNS(cgp_section_write(cgv->file_num, base, zone, "Elem", element_type, 1, e_global, 0, &section));
766     PetscCallCGNS(cgp_elements_write_data(cgv->file_num, base, zone, section, e_start + 1, e_start + e_owned, conn));
767     PetscCall(PetscFree(conn));
768 
769     cgv->base            = base;
770     cgv->zone            = zone;
771     cgv->node_l2g        = node_l2g;
772     cgv->num_local_nodes = num_local_nodes;
773     cgv->nStart          = nStart;
774     cgv->nEnd            = nEnd;
775     if (1) {
776       PetscMPIInt rank;
777       int        *efield;
778       int         sol, field;
779       DMLabel     label;
780       PetscCallMPI(MPI_Comm_rank(PetscObjectComm((PetscObject)dm), &rank));
781       PetscCall(PetscMalloc1(e_owned, &efield));
782       for (PetscInt i = 0; i < e_owned; i++) efield[i] = rank;
783       PetscCallCGNS(cg_sol_write(cgv->file_num, base, zone, "CellInfo", CGNS_ENUMV(CellCenter), &sol));
784       PetscCallCGNS(cgp_field_write(cgv->file_num, base, zone, sol, CGNS_ENUMV(Integer), "Rank", &field));
785       cgsize_t start = e_start + 1, end = e_start + e_owned;
786       PetscCallCGNS(cgp_field_write_data(cgv->file_num, base, zone, sol, field, &start, &end, efield));
787       PetscCall(DMGetLabel(dm, "Cell Sets", &label));
788       if (label) {
789         for (PetscInt c = cStart; c < cEnd; c++) {
790           PetscInt value;
791           PetscCall(DMLabelGetValue(label, c, &value));
792           efield[c - cStart] = value;
793         }
794         PetscCallCGNS(cgp_field_write(cgv->file_num, base, zone, sol, CGNS_ENUMV(Integer), "CellSet", &field));
795         PetscCallCGNS(cgp_field_write_data(cgv->file_num, base, zone, sol, field, &start, &end, efield));
796       }
797       PetscCall(PetscFree(efield));
798     }
799   }
800   PetscCall(DMDestroy(&colloc_dm));
801   PetscFunctionReturn(0);
802 }
803 
804 static PetscErrorCode PetscCGNSDataType(PetscDataType pd, CGNS_ENUMT(DataType_t) * cd)
805 {
806   PetscFunctionBegin;
807   switch (pd) {
808   case PETSC_FLOAT:
809     *cd = CGNS_ENUMV(RealSingle);
810     break;
811   case PETSC_DOUBLE:
812     *cd = CGNS_ENUMV(RealDouble);
813     break;
814   case PETSC_COMPLEX:
815     *cd = CGNS_ENUMV(ComplexDouble);
816     break;
817   default:
818     SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "Data type %s", PetscDataTypes[pd]);
819   }
820   PetscFunctionReturn(0);
821 }
822 
823 PetscErrorCode VecView_Plex_Local_CGNS(Vec V, PetscViewer viewer)
824 {
825   PetscViewer_CGNS  *cgv = (PetscViewer_CGNS *)viewer->data;
826   DM                 dm;
827   PetscSection       section;
828   PetscInt           ncomp, time_step;
829   PetscReal          time, *time_slot;
830   const PetscInt     field = 0;
831   const PetscScalar *v;
832   char               solution_name[PETSC_MAX_PATH_LEN];
833   int                sol;
834 
835   PetscFunctionBegin;
836   PetscCall(VecGetDM(V, &dm));
837   if (!cgv->node_l2g) PetscCall(DMView(dm, viewer));
838   if (!cgv->nodal_field) PetscCall(PetscMalloc1(cgv->nEnd - cgv->nStart, &cgv->nodal_field));
839   if (!cgv->output_times) PetscCall(PetscSegBufferCreate(sizeof(PetscReal), 20, &cgv->output_times));
840 
841   PetscCall(DMGetOutputSequenceNumber(dm, &time_step, &time));
842   if (time_step < 0) {
843     time_step = 0;
844     time      = 0.;
845   }
846   PetscCall(PetscSegBufferGet(cgv->output_times, 1, &time_slot));
847   *time_slot = time;
848   PetscCall(PetscSNPrintf(solution_name, sizeof solution_name, "FlowSolution%" PetscInt_FMT, time_step));
849   PetscCallCGNS(cg_sol_write(cgv->file_num, cgv->base, cgv->zone, solution_name, CGNS_ENUMV(Vertex), &sol));
850   PetscCall(DMGetLocalSection(dm, &section));
851   PetscCall(PetscSectionGetFieldComponents(section, field, &ncomp));
852   PetscCall(VecGetArrayRead(V, &v));
853   for (PetscInt comp = 0; comp < ncomp; comp++) {
854     int         cgfield;
855     const char *comp_name;
856     CGNS_ENUMT(DataType_t) datatype;
857     PetscCall(PetscSectionGetComponentName(section, field, comp, &comp_name));
858     PetscCall(PetscCGNSDataType(PETSC_SCALAR, &datatype));
859     PetscCallCGNS(cgp_field_write(cgv->file_num, cgv->base, cgv->zone, sol, datatype, comp_name, &cgfield));
860     for (PetscInt n = 0; n < cgv->num_local_nodes; n++) {
861       PetscInt gn = cgv->node_l2g[n];
862       if (gn < cgv->nStart || cgv->nEnd <= gn) continue;
863       cgv->nodal_field[gn - cgv->nStart] = v[n * ncomp + comp];
864     }
865     // CGNS nodes use 1-based indexing
866     cgsize_t start = cgv->nStart + 1, end = cgv->nEnd;
867     PetscCallCGNS(cgp_field_write_data(cgv->file_num, cgv->base, cgv->zone, sol, cgfield, &start, &end, cgv->nodal_field));
868   }
869   PetscCall(VecRestoreArrayRead(V, &v));
870   PetscFunctionReturn(0);
871 }
872