xref: /petsc/src/dm/impls/plex/plexsfc.c (revision 4ad8454beace47809662cdae21ee081016eaa39a)
1 #include <petsc/private/dmpleximpl.h> /*I   "petscdmplex.h"   I*/
2 #include <petscsf.h>
3 #include <petsc/private/hashset.h>
4 
5 typedef uint64_t ZCode;
6 
7 PETSC_HASH_SET(ZSet, ZCode, PetscHash_UInt64, PetscHashEqual)
8 
9 typedef struct {
10   PetscInt i, j, k;
11 } Ijk;
12 
13 typedef struct {
14   Ijk         eextent;
15   Ijk         vextent;
16   PetscMPIInt comm_size;
17   ZCode      *zstarts;
18 } ZLayout;
19 
20 static unsigned ZCodeSplit1(ZCode z)
21 {
22   z = ((z & 01001001001001001) | ((z >> 2) & 02002002002002002) | ((z >> 4) & 04004004004004004));
23   z = (z | (z >> 6) | (z >> 12)) & 0000000777000000777;
24   z = (z | (z >> 18)) & 0777777;
25   return (unsigned)z;
26 }
27 
28 static ZCode ZEncode1(unsigned t)
29 {
30   ZCode z = t;
31   z       = (z | (z << 18)) & 0777000000777;
32   z       = (z | (z << 6) | (z << 12)) & 07007007007007007;
33   z       = (z | (z << 2) | (z << 4)) & 0111111111111111111;
34   return z;
35 }
36 
37 static Ijk ZCodeSplit(ZCode z)
38 {
39   Ijk c;
40   c.i = ZCodeSplit1(z >> 2);
41   c.j = ZCodeSplit1(z >> 1);
42   c.k = ZCodeSplit1(z >> 0);
43   return c;
44 }
45 
46 static ZCode ZEncode(Ijk c)
47 {
48   ZCode z = (ZEncode1(c.i) << 2) | (ZEncode1(c.j) << 1) | ZEncode1(c.k);
49   return z;
50 }
51 
52 static PetscBool IjkActive(Ijk extent, Ijk l)
53 {
54   if (l.i < extent.i && l.j < extent.j && l.k < extent.k) return PETSC_TRUE;
55   return PETSC_FALSE;
56 }
57 
58 // If z is not the base of an octet (last 3 bits 0), return 0.
59 //
60 // If z is the base of an octet, we recursively grow to the biggest structured octet. This is typically useful when a z
61 // is outside the domain and we wish to skip a (possibly recursively large) octet to find our next interesting point.
62 static ZCode ZStepOct(ZCode z)
63 {
64   if (PetscUnlikely(z == 0)) return 0; // Infinite loop below if z == 0
65   ZCode step = 07;
66   for (; (z & step) == 0; step = (step << 3) | 07) { }
67   return step >> 3;
68 }
69 
70 // Since element/vertex box extents are typically not equal powers of 2, Z codes that lie within the domain are not contiguous.
71 static PetscErrorCode ZLayoutCreate(PetscMPIInt size, const PetscInt eextent[3], const PetscInt vextent[3], ZLayout *layout)
72 {
73   PetscFunctionBegin;
74   layout->eextent.i = eextent[0];
75   layout->eextent.j = eextent[1];
76   layout->eextent.k = eextent[2];
77   layout->vextent.i = vextent[0];
78   layout->vextent.j = vextent[1];
79   layout->vextent.k = vextent[2];
80   layout->comm_size = size;
81   layout->zstarts   = NULL;
82   PetscCall(PetscMalloc1(size + 1, &layout->zstarts));
83 
84   PetscInt total_elems = eextent[0] * eextent[1] * eextent[2];
85   ZCode    z           = 0;
86   layout->zstarts[0]   = 0;
87   // This loop traverses all vertices in the global domain, so is worth making fast. We use ZStepBound
88   for (PetscMPIInt r = 0; r < size; r++) {
89     PetscInt elems_needed = (total_elems / size) + (total_elems % size > r), count;
90     for (count = 0; count < elems_needed; z++) {
91       ZCode skip = ZStepOct(z); // optimistically attempt a longer step
92       for (ZCode s = skip;; s >>= 3) {
93         Ijk trial = ZCodeSplit(z + s);
94         if (IjkActive(layout->eextent, trial)) {
95           while (count + s + 1 > (ZCode)elems_needed) s >>= 3; // Shrink the octet
96           count += s + 1;
97           z += s;
98           break;
99         }
100         if (s == 0) { // the whole skip octet is inactive
101           z += skip;
102           break;
103         }
104       }
105     }
106     // Pick up any extra vertices in the Z ordering before the next rank's first owned element.
107     //
108     // This leads to poorly balanced vertices when eextent is a power of 2, since all the fringe vertices end up
109     // on the last rank. A possible solution is to balance the Z-order vertices independently from the cells, which will
110     // result in a lot of element closures being remote. We could finish marking boundary conditions, then do a round of
111     // vertex ownership smoothing (which would reorder and redistribute vertices without touching element distribution).
112     // Another would be to have an analytic ownership criteria for vertices in the fringe veextent - eextent. This would
113     // complicate the job of identifying an owner and its offset.
114     //
115     // The current recommended approach is to let `-dm_distribute 1` (default) resolve vertex ownership. This is
116     // *mandatory* with isoperiodicity (except in special cases) to remove standed vertices from local spaces. Here's
117     // the issue:
118     //
119     // Consider this partition on rank 0 (left) and rank 1.
120     //
121     //    4 --------  5 -- 14 --10 -- 21 --11
122     //                |          |          |
123     // 7 -- 16 --  8  |          |          |
124     // |           |  3 -------  7 -------  9
125     // |           |             |          |
126     // 4 --------  6 ------ 10   |          |
127     // |           |         |   6 -- 16 -- 8
128     // |           |         |
129     // 3 ---11---  5 --18--  9
130     //
131     // The periodic face SF looks like
132     // [0] Number of roots=21, leaves=1, remote ranks=1
133     // [0] 16 <- (0,11)
134     // [1] Number of roots=22, leaves=2, remote ranks=2
135     // [1] 14 <- (0,18)
136     // [1] 21 <- (1,16)
137     //
138     // In handling face (0,16), rank 0 learns that (0,7) and (0,8) map to (0,3) and (0,5) respectively, thus we won't use
139     // the point SF links to (1,4) and (1,5). Rank 1 learns about the periodic mapping of (1,5) while handling face
140     // (1,14), but never learns that vertex (1,4) has been mapped to (0,3) by face (0,16).
141     //
142     // We can relatively easily inform vertex (1,4) of this mapping, but it stays in rank 1's local space despite not
143     // being in the closure and thus not being contributed to. This would be mostly harmless except that some viewer
144     // routines expect all local points to be somehow significant. It is not easy to analytically remove the (1,4)
145     // vertex because the point SF and isoperiodic face SF would need to be updated to account for removal of the
146     // stranded vertices.
147     for (; z <= ZEncode(layout->vextent); z++) {
148       Ijk loc = ZCodeSplit(z);
149       if (IjkActive(layout->eextent, loc)) break;
150       z += ZStepOct(z);
151     }
152     layout->zstarts[r + 1] = z;
153   }
154   layout->zstarts[size] = ZEncode(layout->vextent);
155   PetscFunctionReturn(PETSC_SUCCESS);
156 }
157 
158 static PetscInt ZLayoutElementsOnRank(const ZLayout *layout, PetscMPIInt rank)
159 {
160   PetscInt remote_elem = 0;
161   for (ZCode rz = layout->zstarts[rank]; rz < layout->zstarts[rank + 1]; rz++) {
162     Ijk loc = ZCodeSplit(rz);
163     if (IjkActive(layout->eextent, loc)) remote_elem++;
164     else rz += ZStepOct(rz);
165   }
166   return remote_elem;
167 }
168 
169 static PetscInt ZCodeFind(ZCode key, PetscInt n, const ZCode X[])
170 {
171   PetscInt lo = 0, hi = n;
172 
173   if (n == 0) return -1;
174   while (hi - lo > 1) {
175     PetscInt mid = lo + (hi - lo) / 2;
176     if (key < X[mid]) hi = mid;
177     else lo = mid;
178   }
179   return key == X[lo] ? lo : -(lo + (key > X[lo]) + 1);
180 }
181 
182 static PetscErrorCode DMPlexCreateBoxMesh_Tensor_SFC_Periodicity_Private(DM dm, const ZLayout *layout, const ZCode *vert_z, PetscSegBuffer per_faces, const PetscReal *lower, const PetscReal *upper, const DMBoundaryType *periodicity, PetscSegBuffer donor_face_closure, PetscSegBuffer my_donor_faces)
183 {
184   MPI_Comm     comm;
185   size_t       num_faces;
186   PetscInt     dim, *faces, vStart, vEnd;
187   PetscMPIInt  size;
188   ZCode       *donor_verts, *donor_minz;
189   PetscSFNode *leaf;
190 
191   PetscFunctionBegin;
192   PetscCall(PetscObjectGetComm((PetscObject)dm, &comm));
193   PetscCallMPI(MPI_Comm_size(comm, &size));
194   PetscCall(DMGetDimension(dm, &dim));
195   const PetscInt csize = PetscPowInt(2, dim - 1);
196   PetscCall(DMPlexGetDepthStratum(dm, 0, &vStart, &vEnd));
197   PetscCall(PetscSegBufferGetSize(per_faces, &num_faces));
198   PetscCall(PetscSegBufferExtractInPlace(per_faces, &faces));
199   PetscCall(PetscSegBufferExtractInPlace(donor_face_closure, &donor_verts));
200   PetscCall(PetscMalloc1(num_faces, &donor_minz));
201   PetscCall(PetscMalloc1(num_faces, &leaf));
202   for (PetscInt i = 0; i < (PetscInt)num_faces; i++) {
203     ZCode minz = donor_verts[i * csize];
204     for (PetscInt j = 1; j < csize; j++) minz = PetscMin(minz, donor_verts[i * csize + j]);
205     donor_minz[i] = minz;
206   }
207   {
208     PetscBool sorted;
209     PetscCall(PetscSortedInt64(num_faces, (const PetscInt64 *)donor_minz, &sorted));
210     PetscCheck(sorted, PETSC_COMM_SELF, PETSC_ERR_PLIB, "minz not sorted; periodicity in multiple dimensions not yet supported");
211   }
212   for (PetscInt i = 0; i < (PetscInt)num_faces;) {
213     ZCode    z           = donor_minz[i];
214     PetscInt remote_rank = ZCodeFind(z, size + 1, layout->zstarts), remote_count = 0;
215     if (remote_rank < 0) remote_rank = -(remote_rank + 1) - 1;
216     // Process all the vertices on this rank
217     for (ZCode rz = layout->zstarts[remote_rank]; rz < layout->zstarts[remote_rank + 1]; rz++) {
218       Ijk loc = ZCodeSplit(rz);
219       if (rz == z) {
220         leaf[i].rank  = remote_rank;
221         leaf[i].index = remote_count;
222         i++;
223         if (i == (PetscInt)num_faces) break;
224         z = donor_minz[i];
225       }
226       if (IjkActive(layout->vextent, loc)) remote_count++;
227     }
228   }
229   PetscCall(PetscFree(donor_minz));
230   PetscSF sfper;
231   PetscCall(PetscSFCreate(PetscObjectComm((PetscObject)dm), &sfper));
232   PetscCall(PetscSFSetGraph(sfper, vEnd - vStart, num_faces, NULL, PETSC_USE_POINTER, leaf, PETSC_USE_POINTER));
233   const PetscInt *my_donor_degree;
234   PetscCall(PetscSFComputeDegreeBegin(sfper, &my_donor_degree));
235   PetscCall(PetscSFComputeDegreeEnd(sfper, &my_donor_degree));
236   PetscInt num_multiroots = 0;
237   for (PetscInt i = 0; i < vEnd - vStart; i++) {
238     num_multiroots += my_donor_degree[i];
239     if (my_donor_degree[i] == 0) continue;
240     PetscAssert(my_donor_degree[i] == 1, PETSC_COMM_SELF, PETSC_ERR_SUP, "Local vertex has multiple faces");
241   }
242   PetscInt *my_donors, *donor_indices, *my_donor_indices;
243   size_t    num_my_donors;
244   PetscCall(PetscSegBufferGetSize(my_donor_faces, &num_my_donors));
245   PetscCheck((PetscInt)num_my_donors == num_multiroots, PETSC_COMM_SELF, PETSC_ERR_SUP, "Donor request does not match expected donors");
246   PetscCall(PetscSegBufferExtractInPlace(my_donor_faces, &my_donors));
247   PetscCall(PetscMalloc1(vEnd - vStart, &my_donor_indices));
248   for (PetscInt i = 0; i < (PetscInt)num_my_donors; i++) {
249     PetscInt f = my_donors[i];
250     PetscInt num_points, *points = NULL, minv = PETSC_MAX_INT;
251     PetscCall(DMPlexGetTransitiveClosure(dm, f, PETSC_TRUE, &num_points, &points));
252     for (PetscInt j = 0; j < num_points; j++) {
253       PetscInt p = points[2 * j];
254       if (p < vStart || vEnd <= p) continue;
255       minv = PetscMin(minv, p);
256     }
257     PetscCall(DMPlexRestoreTransitiveClosure(dm, f, PETSC_TRUE, &num_points, &points));
258     PetscAssert(my_donor_degree[minv - vStart] == 1, PETSC_COMM_SELF, PETSC_ERR_SUP, "Local vertex not requested");
259     my_donor_indices[minv - vStart] = f;
260   }
261   PetscCall(PetscMalloc1(num_faces, &donor_indices));
262   PetscCall(PetscSFBcastBegin(sfper, MPIU_INT, my_donor_indices, donor_indices, MPI_REPLACE));
263   PetscCall(PetscSFBcastEnd(sfper, MPIU_INT, my_donor_indices, donor_indices, MPI_REPLACE));
264   PetscCall(PetscFree(my_donor_indices));
265   // Modify our leafs so they point to donor faces instead of donor minz. Additionally, give them indices as faces.
266   for (PetscInt i = 0; i < (PetscInt)num_faces; i++) leaf[i].index = donor_indices[i];
267   PetscCall(PetscFree(donor_indices));
268   PetscInt pStart, pEnd;
269   PetscCall(DMPlexGetChart(dm, &pStart, &pEnd));
270   PetscCall(PetscSFSetGraph(sfper, pEnd - pStart, num_faces, faces, PETSC_COPY_VALUES, leaf, PETSC_OWN_POINTER));
271   PetscCall(PetscObjectSetName((PetscObject)sfper, "Z-order Isoperiodic Faces"));
272 
273   PetscCall(DMPlexSetIsoperiodicFaceSF(dm, sfper));
274 
275   PetscScalar t[4][4] = {{0}};
276   t[0][0]             = 1;
277   t[1][1]             = 1;
278   t[2][2]             = 1;
279   t[3][3]             = 1;
280   for (PetscInt i = 0; i < dim; i++)
281     if (periodicity[i] == DM_BOUNDARY_PERIODIC) t[i][3] = upper[i] - lower[i];
282   PetscCall(DMPlexSetIsoperiodicFaceTransform(dm, &t[0][0]));
283   PetscCall(PetscSFDestroy(&sfper));
284   PetscFunctionReturn(PETSC_SUCCESS);
285 }
286 
287 // This is a DMGlobalToLocalHook that applies the affine offsets. When extended for rotated periodicity, it'll need to
288 // apply a rotatonal transform and similar operations will be needed for fields (e.g., to rotate a velocity vector).
289 // We use this crude approach here so we don't have to write new GPU kernels yet.
290 static PetscErrorCode DMCoordAddPeriodicOffsets_Private(DM dm, Vec g, InsertMode mode, Vec l, void *ctx)
291 {
292   PetscFunctionBegin;
293   PetscCall(VecScatterBegin(dm->periodic.affine_to_local, dm->periodic.affine, l, ADD_VALUES, SCATTER_FORWARD));
294   PetscCall(VecScatterEnd(dm->periodic.affine_to_local, dm->periodic.affine, l, ADD_VALUES, SCATTER_FORWARD));
295   PetscFunctionReturn(PETSC_SUCCESS);
296 }
297 
298 // Start with an SF for a positive depth (e.g., faces) and create a new SF for matched closure. The caller must ensure
299 // that both the donor (root) face and the periodic (leaf) face have consistent orientation, meaning that their closures
300 // are isomorphic. It may be useful/necessary for this restriction to be loosened.
301 //
302 // Output Arguments:
303 //
304 // + closure_sf - augmented point SF (see `DMGetPointSF()`) that includes the faces and all points in its closure. This
305 //   can be used to create a global section and section SF.
306 // - is_points - index set for just the points in the closure of `face_sf`. These may be used to apply an affine
307 //   transformation to periodic dofs; see DMPeriodicCoordinateSetUp_Internal().
308 //
309 static PetscErrorCode DMPlexCreateIsoperiodicPointSF_Private(DM dm, PetscSF face_sf, PetscSF *closure_sf, IS *is_points)
310 {
311   MPI_Comm           comm;
312   PetscInt           nroots, nleaves, npoints;
313   const PetscInt    *filocal, *pilocal;
314   const PetscSFNode *firemote, *piremote;
315   PetscMPIInt        rank;
316   PetscSF            point_sf;
317 
318   PetscFunctionBegin;
319   PetscCall(PetscObjectGetComm((PetscObject)dm, &comm));
320   PetscCallMPI(MPI_Comm_rank(comm, &rank));
321   PetscCall(PetscSFGetGraph(face_sf, &nroots, &nleaves, &filocal, &firemote));
322   PetscCall(DMGetPointSF(dm, &point_sf)); // Point SF has remote points
323   PetscCall(PetscSFGetGraph(point_sf, NULL, &npoints, &pilocal, &piremote));
324   PetscInt *rootdata, *leafdata;
325   PetscCall(PetscCalloc2(2 * nroots, &rootdata, 2 * nroots, &leafdata));
326   for (PetscInt i = 0; i < nleaves; i++) {
327     PetscInt point = filocal[i], cl_size, *closure = NULL;
328     PetscCall(DMPlexGetTransitiveClosure(dm, point, PETSC_TRUE, &cl_size, &closure));
329     leafdata[point] = cl_size - 1;
330     PetscCall(DMPlexRestoreTransitiveClosure(dm, point, PETSC_TRUE, &cl_size, &closure));
331   }
332   PetscCall(PetscSFReduceBegin(face_sf, MPIU_INT, leafdata, rootdata + nroots, MPIU_SUM));
333   PetscCall(PetscSFReduceEnd(face_sf, MPIU_INT, leafdata, rootdata + nroots, MPIU_SUM));
334 
335   PetscInt root_offset = 0;
336   for (PetscInt p = 0; p < nroots; p++) {
337     const PetscInt *donor_dof = rootdata + nroots;
338     if (donor_dof[p] == 0) {
339       rootdata[2 * p]     = -1;
340       rootdata[2 * p + 1] = -1;
341       continue;
342     }
343     PetscInt  cl_size;
344     PetscInt *closure = NULL;
345     PetscCall(DMPlexGetTransitiveClosure(dm, p, PETSC_TRUE, &cl_size, &closure));
346     // cl_size - 1 = points not including self
347     PetscAssert(donor_dof[p] == cl_size - 1, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Reduced leaf cone sizes do not match root cone sizes");
348     rootdata[2 * p]     = root_offset;
349     rootdata[2 * p + 1] = cl_size - 1;
350     root_offset += cl_size - 1;
351     PetscCall(DMPlexRestoreTransitiveClosure(dm, p, PETSC_TRUE, &cl_size, &closure));
352   }
353   PetscCall(PetscSFBcastBegin(face_sf, MPIU_2INT, rootdata, leafdata, MPI_REPLACE));
354   PetscCall(PetscSFBcastEnd(face_sf, MPIU_2INT, rootdata, leafdata, MPI_REPLACE));
355   // Count how many leaves we need to communicate the closures
356   PetscInt leaf_offset = 0;
357   for (PetscInt i = 0; i < nleaves; i++) {
358     PetscInt point = filocal[i];
359     if (leafdata[2 * point + 1] < 0) continue;
360     leaf_offset += leafdata[2 * point + 1];
361   }
362 
363   PetscSFNode *closure_leaf;
364   PetscCall(PetscMalloc1(leaf_offset, &closure_leaf));
365   leaf_offset = 0;
366   for (PetscInt i = 0; i < nleaves; i++) {
367     PetscInt point   = filocal[i];
368     PetscInt cl_size = leafdata[2 * point + 1];
369     if (cl_size < 0) continue;
370     for (PetscInt j = 0; j < cl_size; j++) {
371       closure_leaf[leaf_offset].rank  = firemote[i].rank;
372       closure_leaf[leaf_offset].index = leafdata[2 * point] + j;
373       leaf_offset++;
374     }
375   }
376 
377   PetscSF sf_closure;
378   PetscCall(PetscSFCreate(comm, &sf_closure));
379   PetscCall(PetscSFSetGraph(sf_closure, root_offset, leaf_offset, NULL, PETSC_USE_POINTER, closure_leaf, PETSC_OWN_POINTER));
380 
381   // Pack root buffer with owner for every point in the root cones
382   PetscSFNode *donor_closure;
383   PetscCall(PetscCalloc1(root_offset, &donor_closure));
384   root_offset = 0;
385   for (PetscInt p = 0; p < nroots; p++) {
386     if (rootdata[2 * p] < 0) continue;
387     PetscInt  cl_size;
388     PetscInt *closure = NULL;
389     PetscCall(DMPlexGetTransitiveClosure(dm, p, PETSC_TRUE, &cl_size, &closure));
390     for (PetscInt j = 1; j < cl_size; j++) {
391       PetscInt c = closure[2 * j];
392       if (pilocal) {
393         PetscInt found = -1;
394         if (npoints > 0) PetscCall(PetscFindInt(c, npoints, pilocal, &found));
395         if (found >= 0) {
396           donor_closure[root_offset++] = piremote[found];
397           continue;
398         }
399       }
400       // we own c
401       donor_closure[root_offset].rank  = rank;
402       donor_closure[root_offset].index = c;
403       root_offset++;
404     }
405     PetscCall(DMPlexRestoreTransitiveClosure(dm, p, PETSC_TRUE, &cl_size, &closure));
406   }
407 
408   PetscSFNode *leaf_donor_closure;
409   PetscCall(PetscMalloc1(leaf_offset, &leaf_donor_closure));
410   PetscCall(PetscSFBcastBegin(sf_closure, MPIU_2INT, donor_closure, leaf_donor_closure, MPI_REPLACE));
411   PetscCall(PetscSFBcastEnd(sf_closure, MPIU_2INT, donor_closure, leaf_donor_closure, MPI_REPLACE));
412   PetscCall(PetscSFDestroy(&sf_closure));
413   PetscCall(PetscFree(donor_closure));
414 
415   PetscSFNode *new_iremote;
416   PetscCall(PetscCalloc1(nroots, &new_iremote));
417   for (PetscInt i = 0; i < nroots; i++) new_iremote[i].rank = -1;
418   // Walk leaves and match vertices
419   leaf_offset = 0;
420   for (PetscInt i = 0; i < nleaves; i++) {
421     PetscInt  point   = filocal[i], cl_size;
422     PetscInt *closure = NULL;
423     PetscCall(DMPlexGetTransitiveClosure(dm, point, PETSC_TRUE, &cl_size, &closure));
424     for (PetscInt j = 1; j < cl_size; j++) { // TODO: should we send donor edge orientations so we can flip for consistency?
425       PetscInt    c  = closure[2 * j];
426       PetscSFNode lc = leaf_donor_closure[leaf_offset];
427       // printf("[%d] face %d.%d: %d ?-- (%d,%d)\n", rank, point, j, c, lc.rank, lc.index);
428       if (new_iremote[c].rank == -1) {
429         new_iremote[c] = lc;
430       } else PetscCheck(new_iremote[c].rank == lc.rank && new_iremote[c].index == lc.index, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Mismatched cone ordering between faces");
431       leaf_offset++;
432     }
433     PetscCall(DMPlexRestoreTransitiveClosure(dm, point, PETSC_TRUE, &cl_size, &closure));
434   }
435   PetscCall(PetscFree(leaf_donor_closure));
436 
437   // Include face points in closure SF
438   for (PetscInt i = 0; i < nleaves; i++) new_iremote[filocal[i]] = firemote[i];
439   // consolidate leaves
440   PetscInt num_new_leaves = 0;
441   for (PetscInt i = 0; i < nroots; i++) {
442     if (new_iremote[i].rank == -1) continue;
443     new_iremote[num_new_leaves] = new_iremote[i];
444     leafdata[num_new_leaves]    = i;
445     num_new_leaves++;
446   }
447   PetscCall(ISCreateGeneral(PETSC_COMM_SELF, num_new_leaves, leafdata, PETSC_COPY_VALUES, is_points));
448 
449   PetscSF csf;
450   PetscCall(PetscSFCreate(comm, &csf));
451   PetscCall(PetscSFSetGraph(csf, nroots, num_new_leaves, leafdata, PETSC_COPY_VALUES, new_iremote, PETSC_COPY_VALUES));
452   PetscCall(PetscFree(new_iremote)); // copy and delete because new_iremote is longer than it needs to be
453   PetscCall(PetscFree2(rootdata, leafdata));
454 
455   if (npoints < 0) { // empty point_sf
456     *closure_sf = csf;
457   } else {
458     PetscCall(PetscSFMerge(point_sf, csf, closure_sf));
459     PetscCall(PetscSFDestroy(&csf));
460   }
461   PetscCall(PetscObjectSetName((PetscObject)*closure_sf, "Composed Periodic Points"));
462   PetscFunctionReturn(PETSC_SUCCESS);
463 }
464 
465 static PetscErrorCode DMGetIsoperiodicPointSF_Plex(DM dm, PetscSF *sf)
466 {
467   DM_Plex *plex = (DM_Plex *)dm->data;
468 
469   PetscFunctionBegin;
470   if (!plex->periodic.composed_sf) {
471     PetscSF face_sf = plex->periodic.face_sf;
472 
473     PetscCall(DMPlexCreateIsoperiodicPointSF_Private(dm, face_sf, &plex->periodic.composed_sf, &plex->periodic.periodic_points));
474   }
475   if (sf) *sf = plex->periodic.composed_sf;
476   PetscFunctionReturn(PETSC_SUCCESS);
477 }
478 
479 PetscErrorCode DMPlexMigrateIsoperiodicFaceSF_Internal(DM old_dm, DM dm, PetscSF sf_migration)
480 {
481   DM_Plex    *plex = (DM_Plex *)old_dm->data;
482   PetscSF     sf_point;
483   PetscMPIInt rank;
484 
485   PetscFunctionBegin;
486   if (!plex->periodic.face_sf) PetscFunctionReturn(PETSC_SUCCESS);
487   PetscCallMPI(MPI_Comm_rank(PetscObjectComm((PetscObject)dm), &rank));
488   PetscCall(DMGetPointSF(dm, &sf_point));
489   PetscInt           old_npoints, new_npoints, old_nleaf, new_nleaf, point_nleaf;
490   PetscSFNode       *new_leafdata, *rootdata, *leafdata;
491   const PetscInt    *old_local, *point_local;
492   const PetscSFNode *old_remote, *point_remote;
493   PetscCall(PetscSFGetGraph(plex->periodic.face_sf, &old_npoints, &old_nleaf, &old_local, &old_remote));
494   PetscCall(PetscSFGetGraph(sf_migration, NULL, &new_nleaf, NULL, NULL));
495   PetscCall(PetscSFGetGraph(sf_point, &new_npoints, &point_nleaf, &point_local, &point_remote));
496   PetscAssert(new_nleaf == new_npoints, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Expected migration leaf space to match new point root space");
497   PetscCall(PetscMalloc3(old_npoints, &rootdata, old_npoints, &leafdata, new_npoints, &new_leafdata));
498 
499   // Fill new_leafdata with new owners of all points
500   for (PetscInt i = 0; i < new_npoints; i++) {
501     new_leafdata[i].rank  = rank;
502     new_leafdata[i].index = i;
503   }
504   for (PetscInt i = 0; i < point_nleaf; i++) {
505     PetscInt j      = point_local[i];
506     new_leafdata[j] = point_remote[i];
507   }
508   // REPLACE is okay because every leaf agrees about the new owners
509   PetscCall(PetscSFReduceBegin(sf_migration, MPIU_2INT, new_leafdata, rootdata, MPI_REPLACE));
510   PetscCall(PetscSFReduceEnd(sf_migration, MPIU_2INT, new_leafdata, rootdata, MPI_REPLACE));
511   // rootdata now contains the new owners
512 
513   // Send to leaves of old space
514   for (PetscInt i = 0; i < old_npoints; i++) {
515     leafdata[i].rank  = -1;
516     leafdata[i].index = -1;
517   }
518   PetscCall(PetscSFBcastBegin(plex->periodic.face_sf, MPIU_2INT, rootdata, leafdata, MPI_REPLACE));
519   PetscCall(PetscSFBcastEnd(plex->periodic.face_sf, MPIU_2INT, rootdata, leafdata, MPI_REPLACE));
520 
521   // Send to new leaf space
522   PetscCall(PetscSFBcastBegin(sf_migration, MPIU_2INT, leafdata, new_leafdata, MPI_REPLACE));
523   PetscCall(PetscSFBcastEnd(sf_migration, MPIU_2INT, leafdata, new_leafdata, MPI_REPLACE));
524 
525   PetscInt     nface = 0, *new_local;
526   PetscSFNode *new_remote;
527   for (PetscInt i = 0; i < new_npoints; i++) nface += (new_leafdata[i].rank >= 0);
528   PetscCall(PetscMalloc1(nface, &new_local));
529   PetscCall(PetscMalloc1(nface, &new_remote));
530   nface = 0;
531   for (PetscInt i = 0; i < new_npoints; i++) {
532     if (new_leafdata[i].rank == -1) continue;
533     new_local[nface]  = i;
534     new_remote[nface] = new_leafdata[i];
535     nface++;
536   }
537   PetscCall(PetscFree3(rootdata, leafdata, new_leafdata));
538   PetscSF sf_face;
539   PetscCall(PetscSFCreate(PetscObjectComm((PetscObject)dm), &sf_face));
540   PetscCall(PetscSFSetGraph(sf_face, new_npoints, nface, new_local, PETSC_OWN_POINTER, new_remote, PETSC_OWN_POINTER));
541   PetscCall(PetscObjectSetName((PetscObject)sf_face, "Migrated Isoperiodic Faces"));
542   PetscCall(DMPlexSetIsoperiodicFaceSF(dm, sf_face));
543   PetscCall(DMPlexSetIsoperiodicFaceTransform(dm, &plex->periodic.transform[0][0]));
544   PetscCall(PetscSFDestroy(&sf_face));
545   PetscFunctionReturn(PETSC_SUCCESS);
546 }
547 
548 PetscErrorCode DMPeriodicCoordinateSetUp_Internal(DM dm)
549 {
550   DM_Plex *plex = (DM_Plex *)dm->data;
551 
552   PetscFunctionBegin;
553   if (!plex->periodic.face_sf) PetscFunctionReturn(PETSC_SUCCESS);
554   PetscCall(DMGetIsoperiodicPointSF_Plex(dm, NULL));
555   PetscCall(PetscObjectComposeFunction((PetscObject)dm, "DMGetIsoperiodicPointSF_C", DMGetIsoperiodicPointSF_Plex));
556 
557   PetscInt dim;
558   PetscCall(DMGetDimension(dm, &dim));
559   size_t count;
560   IS     isdof;
561   {
562     PetscInt        npoints;
563     const PetscInt *points;
564     IS              is = plex->periodic.periodic_points;
565     PetscSegBuffer  seg;
566     PetscSection    section;
567     PetscCall(DMGetLocalSection(dm, &section));
568     PetscCall(PetscSegBufferCreate(sizeof(PetscInt), 32, &seg));
569     PetscCall(ISGetSize(is, &npoints));
570     PetscCall(ISGetIndices(is, &points));
571     for (PetscInt i = 0; i < npoints; i++) {
572       PetscInt point = points[i], off, dof;
573       PetscCall(PetscSectionGetOffset(section, point, &off));
574       PetscCall(PetscSectionGetDof(section, point, &dof));
575       PetscAssert(dof % dim == 0, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Unexpected dof %" PetscInt_FMT " not divisible by dimension %" PetscInt_FMT, dof, dim);
576       for (PetscInt j = 0; j < dof / dim; j++) {
577         PetscInt *slot;
578         PetscCall(PetscSegBufferGetInts(seg, 1, &slot));
579         *slot = off / dim + j;
580       }
581     }
582     PetscInt *ind;
583     PetscCall(PetscSegBufferGetSize(seg, &count));
584     PetscCall(PetscSegBufferExtractAlloc(seg, &ind));
585     PetscCall(PetscSegBufferDestroy(&seg));
586     PetscCall(ISCreateBlock(PETSC_COMM_SELF, dim, count, ind, PETSC_OWN_POINTER, &isdof));
587   }
588   Vec        L, P;
589   VecType    vec_type;
590   VecScatter scatter;
591   PetscCall(DMGetLocalVector(dm, &L));
592   PetscCall(VecCreate(PETSC_COMM_SELF, &P));
593   PetscCall(VecSetSizes(P, count * dim, count * dim));
594   PetscCall(VecGetType(L, &vec_type));
595   PetscCall(VecSetType(P, vec_type));
596   PetscCall(VecScatterCreate(P, NULL, L, isdof, &scatter));
597   PetscCall(DMRestoreLocalVector(dm, &L));
598   PetscCall(ISDestroy(&isdof));
599 
600   {
601     PetscScalar *x;
602     PetscCall(VecGetArrayWrite(P, &x));
603     for (PetscInt i = 0; i < (PetscInt)count; i++) {
604       for (PetscInt j = 0; j < dim; j++) x[i * dim + j] = plex->periodic.transform[j][3];
605     }
606     PetscCall(VecRestoreArrayWrite(P, &x));
607   }
608 
609   dm->periodic.affine_to_local = scatter;
610   dm->periodic.affine          = P;
611   PetscCall(DMGlobalToLocalHookAdd(dm, NULL, DMCoordAddPeriodicOffsets_Private, NULL));
612   PetscFunctionReturn(PETSC_SUCCESS);
613 }
614 
615 // We'll just orient all the edges, though only periodic boundary edges need orientation
616 static PetscErrorCode DMPlexOrientPositiveEdges_Private(DM dm)
617 {
618   PetscInt dim, eStart, eEnd;
619 
620   PetscFunctionBegin;
621   PetscCall(DMGetDimension(dm, &dim));
622   if (dim < 3) PetscFunctionReturn(PETSC_SUCCESS); // not necessary
623   PetscCall(DMPlexGetDepthStratum(dm, 1, &eStart, &eEnd));
624   for (PetscInt e = eStart; e < eEnd; e++) {
625     const PetscInt *cone;
626     PetscCall(DMPlexGetCone(dm, e, &cone));
627     if (cone[0] > cone[1]) PetscCall(DMPlexOrientPoint(dm, e, -1));
628   }
629   PetscFunctionReturn(PETSC_SUCCESS);
630 }
631 
632 PetscErrorCode DMPlexCreateBoxMesh_Tensor_SFC_Internal(DM dm, PetscInt dim, const PetscInt faces[], const PetscReal lower[], const PetscReal upper[], const DMBoundaryType periodicity[], PetscBool interpolate)
633 {
634   PetscInt  eextent[3] = {1, 1, 1}, vextent[3] = {1, 1, 1};
635   const Ijk closure_1[] = {
636     {0, 0, 0},
637     {1, 0, 0},
638   };
639   const Ijk closure_2[] = {
640     {0, 0, 0},
641     {1, 0, 0},
642     {1, 1, 0},
643     {0, 1, 0},
644   };
645   const Ijk closure_3[] = {
646     {0, 0, 0},
647     {0, 1, 0},
648     {1, 1, 0},
649     {1, 0, 0},
650     {0, 0, 1},
651     {1, 0, 1},
652     {1, 1, 1},
653     {0, 1, 1},
654   };
655   const Ijk *const closure_dim[] = {NULL, closure_1, closure_2, closure_3};
656   // This must be kept consistent with DMPlexCreateCubeMesh_Internal
657   const PetscInt        face_marker_1[]   = {1, 2};
658   const PetscInt        face_marker_2[]   = {4, 2, 1, 3};
659   const PetscInt        face_marker_3[]   = {6, 5, 3, 4, 1, 2};
660   const PetscInt *const face_marker_dim[] = {NULL, face_marker_1, face_marker_2, face_marker_3};
661   // Orient faces so the normal is in the positive axis and the first vertex is the one closest to zero.
662   // These orientations can be determined by examining cones of a reference quad and hex element.
663   const PetscInt        face_orient_1[]   = {0, 0};
664   const PetscInt        face_orient_2[]   = {-1, 0, 0, -1};
665   const PetscInt        face_orient_3[]   = {-2, 0, -2, 1, -2, 0};
666   const PetscInt *const face_orient_dim[] = {NULL, face_orient_1, face_orient_2, face_orient_3};
667 
668   PetscFunctionBegin;
669   PetscAssertPointer(dm, 1);
670   PetscValidLogicalCollectiveInt(dm, dim, 2);
671   PetscCall(DMSetDimension(dm, dim));
672   PetscMPIInt rank, size;
673   PetscCallMPI(MPI_Comm_size(PetscObjectComm((PetscObject)dm), &size));
674   PetscCallMPI(MPI_Comm_rank(PetscObjectComm((PetscObject)dm), &rank));
675   for (PetscInt i = 0; i < dim; i++) {
676     eextent[i] = faces[i];
677     vextent[i] = faces[i] + 1;
678   }
679   ZLayout layout;
680   PetscCall(ZLayoutCreate(size, eextent, vextent, &layout));
681   PetscZSet vset; // set of all vertices in the closure of the owned elements
682   PetscCall(PetscZSetCreate(&vset));
683   PetscInt local_elems = 0;
684   for (ZCode z = layout.zstarts[rank]; z < layout.zstarts[rank + 1]; z++) {
685     Ijk loc = ZCodeSplit(z);
686     if (IjkActive(layout.vextent, loc)) PetscCall(PetscZSetAdd(vset, z));
687     else {
688       z += ZStepOct(z);
689       continue;
690     }
691     if (IjkActive(layout.eextent, loc)) {
692       local_elems++;
693       // Add all neighboring vertices to set
694       for (PetscInt n = 0; n < PetscPowInt(2, dim); n++) {
695         Ijk   inc  = closure_dim[dim][n];
696         Ijk   nloc = {loc.i + inc.i, loc.j + inc.j, loc.k + inc.k};
697         ZCode v    = ZEncode(nloc);
698         PetscCall(PetscZSetAdd(vset, v));
699       }
700     }
701   }
702   PetscInt local_verts, off = 0;
703   ZCode   *vert_z;
704   PetscCall(PetscZSetGetSize(vset, &local_verts));
705   PetscCall(PetscMalloc1(local_verts, &vert_z));
706   PetscCall(PetscZSetGetElems(vset, &off, vert_z));
707   PetscCall(PetscZSetDestroy(&vset));
708   // ZCode is unsigned for bitwise convenience, but highest bit should never be set, so can interpret as signed
709   PetscCall(PetscSortInt64(local_verts, (PetscInt64 *)vert_z));
710 
711   PetscCall(DMPlexSetChart(dm, 0, local_elems + local_verts));
712   for (PetscInt e = 0; e < local_elems; e++) PetscCall(DMPlexSetConeSize(dm, e, PetscPowInt(2, dim)));
713   PetscCall(DMSetUp(dm));
714   {
715     PetscInt e = 0;
716     for (ZCode z = layout.zstarts[rank]; z < layout.zstarts[rank + 1]; z++) {
717       Ijk loc = ZCodeSplit(z);
718       if (!IjkActive(layout.eextent, loc)) {
719         z += ZStepOct(z);
720         continue;
721       }
722       PetscInt cone[8], orient[8] = {0};
723       for (PetscInt n = 0; n < PetscPowInt(2, dim); n++) {
724         Ijk      inc  = closure_dim[dim][n];
725         Ijk      nloc = {loc.i + inc.i, loc.j + inc.j, loc.k + inc.k};
726         ZCode    v    = ZEncode(nloc);
727         PetscInt ci   = ZCodeFind(v, local_verts, vert_z);
728         PetscAssert(ci >= 0, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Did not find neighbor vertex in set");
729         cone[n] = local_elems + ci;
730       }
731       PetscCall(DMPlexSetCone(dm, e, cone));
732       PetscCall(DMPlexSetConeOrientation(dm, e, orient));
733       e++;
734     }
735   }
736 
737   PetscCall(DMPlexSymmetrize(dm));
738   PetscCall(DMPlexStratify(dm));
739 
740   { // Create point SF
741     PetscSF sf;
742     PetscCall(PetscSFCreate(PetscObjectComm((PetscObject)dm), &sf));
743     PetscInt owned_verts = ZCodeFind(layout.zstarts[rank + 1], local_verts, vert_z);
744     if (owned_verts < 0) owned_verts = -(owned_verts + 1); // We don't care whether the key was found
745     PetscInt     num_ghosts = local_verts - owned_verts;   // Due to sorting, owned vertices always come first
746     PetscInt    *local_ghosts;
747     PetscSFNode *ghosts;
748     PetscCall(PetscMalloc1(num_ghosts, &local_ghosts));
749     PetscCall(PetscMalloc1(num_ghosts, &ghosts));
750     for (PetscInt i = 0; i < num_ghosts;) {
751       ZCode    z           = vert_z[owned_verts + i];
752       PetscInt remote_rank = ZCodeFind(z, size + 1, layout.zstarts), remote_count = 0;
753       if (remote_rank < 0) remote_rank = -(remote_rank + 1) - 1;
754       // We have a new remote rank; find all the ghost indices (which are contiguous in vert_z)
755 
756       // Count the elements on remote_rank
757       PetscInt remote_elem = ZLayoutElementsOnRank(&layout, remote_rank);
758 
759       // Traverse vertices and make ghost links
760       for (ZCode rz = layout.zstarts[remote_rank]; rz < layout.zstarts[remote_rank + 1]; rz++) {
761         Ijk loc = ZCodeSplit(rz);
762         if (rz == z) {
763           local_ghosts[i] = local_elems + owned_verts + i;
764           ghosts[i].rank  = remote_rank;
765           ghosts[i].index = remote_elem + remote_count;
766           i++;
767           if (i == num_ghosts) break;
768           z = vert_z[owned_verts + i];
769         }
770         if (IjkActive(layout.vextent, loc)) remote_count++;
771         else rz += ZStepOct(rz);
772       }
773     }
774     PetscCall(PetscSFSetGraph(sf, local_elems + local_verts, num_ghosts, local_ghosts, PETSC_OWN_POINTER, ghosts, PETSC_OWN_POINTER));
775     PetscCall(PetscObjectSetName((PetscObject)sf, "SFC Point SF"));
776     PetscCall(DMSetPointSF(dm, sf));
777     PetscCall(PetscSFDestroy(&sf));
778   }
779   {
780     Vec          coordinates;
781     PetscScalar *coords;
782     PetscSection coord_section;
783     PetscInt     coord_size;
784     PetscCall(DMGetCoordinateSection(dm, &coord_section));
785     PetscCall(PetscSectionSetNumFields(coord_section, 1));
786     PetscCall(PetscSectionSetFieldComponents(coord_section, 0, dim));
787     PetscCall(PetscSectionSetChart(coord_section, local_elems, local_elems + local_verts));
788     for (PetscInt v = 0; v < local_verts; v++) {
789       PetscInt point = local_elems + v;
790       PetscCall(PetscSectionSetDof(coord_section, point, dim));
791       PetscCall(PetscSectionSetFieldDof(coord_section, point, 0, dim));
792     }
793     PetscCall(PetscSectionSetUp(coord_section));
794     PetscCall(PetscSectionGetStorageSize(coord_section, &coord_size));
795     PetscCall(VecCreate(PETSC_COMM_SELF, &coordinates));
796     PetscCall(PetscObjectSetName((PetscObject)coordinates, "coordinates"));
797     PetscCall(VecSetSizes(coordinates, coord_size, PETSC_DETERMINE));
798     PetscCall(VecSetBlockSize(coordinates, dim));
799     PetscCall(VecSetType(coordinates, VECSTANDARD));
800     PetscCall(VecGetArray(coordinates, &coords));
801     for (PetscInt v = 0; v < local_verts; v++) {
802       Ijk loc             = ZCodeSplit(vert_z[v]);
803       coords[v * dim + 0] = lower[0] + loc.i * (upper[0] - lower[0]) / layout.eextent.i;
804       if (dim > 1) coords[v * dim + 1] = lower[1] + loc.j * (upper[1] - lower[1]) / layout.eextent.j;
805       if (dim > 2) coords[v * dim + 2] = lower[2] + loc.k * (upper[2] - lower[2]) / layout.eextent.k;
806     }
807     PetscCall(VecRestoreArray(coordinates, &coords));
808     PetscCall(DMSetCoordinatesLocal(dm, coordinates));
809     PetscCall(VecDestroy(&coordinates));
810   }
811   if (interpolate) {
812     PetscCall(DMPlexInterpolateInPlace_Internal(dm));
813     // It's currently necessary to orient the donor and periodic edges consistently. An easy way to ensure that is ot
814     // give all edges positive orientation. Since vertices are created in Z-order, all ranks will agree about the
815     // ordering cone[0] < cone[1]. This is overkill and it would be nice to remove this preparation and make
816     // DMPlexCreateIsoperiodicClosureSF_Private() more resilient, so it fixes any inconsistent orientations. That might
817     // be needed in a general CGNS reader, for example.
818     PetscCall(DMPlexOrientPositiveEdges_Private(dm));
819 
820     DMLabel label;
821     PetscCall(DMCreateLabel(dm, "Face Sets"));
822     PetscCall(DMGetLabel(dm, "Face Sets", &label));
823     PetscSegBuffer per_faces, donor_face_closure, my_donor_faces;
824     PetscCall(PetscSegBufferCreate(sizeof(PetscInt), 64, &per_faces));
825     PetscCall(PetscSegBufferCreate(sizeof(PetscInt), 64, &my_donor_faces));
826     PetscCall(PetscSegBufferCreate(sizeof(ZCode), 64 * PetscPowInt(2, dim), &donor_face_closure));
827     PetscInt fStart, fEnd, vStart, vEnd;
828     PetscCall(DMPlexGetHeightStratum(dm, 1, &fStart, &fEnd));
829     PetscCall(DMPlexGetDepthStratum(dm, 0, &vStart, &vEnd));
830     for (PetscInt f = fStart; f < fEnd; f++) {
831       PetscInt npoints, *points = NULL, num_fverts = 0, fverts[8];
832       PetscCall(DMPlexGetTransitiveClosure(dm, f, PETSC_TRUE, &npoints, &points));
833       PetscInt bc_count[6] = {0};
834       for (PetscInt i = 0; i < npoints; i++) {
835         PetscInt p = points[2 * i];
836         if (p < vStart || vEnd <= p) continue;
837         fverts[num_fverts++] = p;
838         Ijk loc              = ZCodeSplit(vert_z[p - vStart]);
839         // Convention here matches DMPlexCreateCubeMesh_Internal
840         bc_count[0] += loc.i == 0;
841         bc_count[1] += loc.i == layout.vextent.i - 1;
842         bc_count[2] += loc.j == 0;
843         bc_count[3] += loc.j == layout.vextent.j - 1;
844         bc_count[4] += loc.k == 0;
845         bc_count[5] += loc.k == layout.vextent.k - 1;
846       }
847       PetscCall(DMPlexRestoreTransitiveClosure(dm, f, PETSC_TRUE, &npoints, &points));
848       for (PetscInt bc = 0, bc_match = 0; bc < 2 * dim; bc++) {
849         if (bc_count[bc] == PetscPowInt(2, dim - 1)) {
850           PetscCall(DMPlexOrientPoint(dm, f, face_orient_dim[dim][bc]));
851           if (periodicity[bc / 2] == DM_BOUNDARY_PERIODIC) {
852             PetscInt *put;
853             if (bc % 2 == 0) { // donor face; no label
854               PetscCall(PetscSegBufferGet(my_donor_faces, 1, &put));
855               *put = f;
856             } else { // periodic face
857               PetscCall(PetscSegBufferGet(per_faces, 1, &put));
858               *put = f;
859               ZCode *zput;
860               PetscCall(PetscSegBufferGet(donor_face_closure, num_fverts, &zput));
861               for (PetscInt i = 0; i < num_fverts; i++) {
862                 Ijk loc = ZCodeSplit(vert_z[fverts[i] - vStart]);
863                 switch (bc / 2) {
864                 case 0:
865                   loc.i = 0;
866                   break;
867                 case 1:
868                   loc.j = 0;
869                   break;
870                 case 2:
871                   loc.k = 0;
872                   break;
873                 }
874                 *zput++ = ZEncode(loc);
875               }
876             }
877             continue;
878           }
879           PetscAssert(bc_match == 0, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Face matches multiple face sets");
880           PetscCall(DMLabelSetValue(label, f, face_marker_dim[dim][bc]));
881           bc_match++;
882         }
883       }
884     }
885     // Ensure that the Coordinate DM has our new boundary labels
886     DM cdm;
887     PetscCall(DMGetCoordinateDM(dm, &cdm));
888     PetscCall(DMCopyLabels(dm, cdm, PETSC_COPY_VALUES, PETSC_FALSE, DM_COPY_LABELS_FAIL));
889     if (periodicity[0] == DM_BOUNDARY_PERIODIC || (dim > 1 && periodicity[1] == DM_BOUNDARY_PERIODIC) || (dim > 2 && periodicity[2] == DM_BOUNDARY_PERIODIC)) {
890       PetscCall(DMPlexCreateBoxMesh_Tensor_SFC_Periodicity_Private(dm, &layout, vert_z, per_faces, lower, upper, periodicity, donor_face_closure, my_donor_faces));
891     }
892     PetscCall(PetscSegBufferDestroy(&per_faces));
893     PetscCall(PetscSegBufferDestroy(&donor_face_closure));
894     PetscCall(PetscSegBufferDestroy(&my_donor_faces));
895   }
896   PetscCall(PetscFree(layout.zstarts));
897   PetscCall(PetscFree(vert_z));
898   PetscFunctionReturn(PETSC_SUCCESS);
899 }
900 
901 /*@
902   DMPlexSetIsoperiodicFaceSF - Express periodicity from an existing mesh
903 
904   Logically Collective
905 
906   Input Parameters:
907 + dm      - The `DMPLEX` on which to set periodicity
908 - face_sf - `PetscSF` in which roots are (owned) donor faces and leaves are faces that must be matched to a (possibly remote) donor face.
909 
910   Level: advanced
911 
912   Note:
913   One can use `-dm_plex_shape zbox` to use this mode of periodicity, wherein the periodic points are distinct both globally
914   and locally, but are paired when creating a global dof space.
915 
916 .seealso: [](ch_unstructured), `DMPLEX`, `DMGetGlobalSection()`, `DMPlexGetIsoperiodicFaceSF()`
917 @*/
918 PetscErrorCode DMPlexSetIsoperiodicFaceSF(DM dm, PetscSF face_sf)
919 {
920   DM_Plex *plex = (DM_Plex *)dm->data;
921 
922   PetscFunctionBegin;
923   PetscValidHeaderSpecific(dm, DM_CLASSID, 1);
924   PetscCall(PetscObjectReference((PetscObject)face_sf));
925   PetscCall(PetscSFDestroy(&plex->periodic.face_sf));
926   plex->periodic.face_sf = face_sf;
927   if (face_sf) PetscCall(PetscObjectComposeFunction((PetscObject)dm, "DMGetIsoperiodicPointSF_C", DMGetIsoperiodicPointSF_Plex));
928 
929   DM cdm = dm->coordinates[0].dm; // Can't DMGetCoordinateDM because it automatically creates one
930   if (cdm) {
931     PetscCall(DMPlexSetIsoperiodicFaceSF(cdm, face_sf));
932     if (face_sf) cdm->periodic.setup = DMPeriodicCoordinateSetUp_Internal;
933   }
934   PetscFunctionReturn(PETSC_SUCCESS);
935 }
936 
937 /*@
938   DMPlexGetIsoperiodicFaceSF - Obtain periodicity for a mesh
939 
940   Logically Collective
941 
942   Input Parameter:
943 . dm - The `DMPLEX` for which to obtain periodic relation
944 
945   Output Parameter:
946 . face_sf - `PetscSF` in which roots are (owned) donor faces and leaves are faces that must be matched to a (possibly remote) donor face.
947 
948   Level: advanced
949 
950 .seealso: [](ch_unstructured), `DMPLEX`, `DMGetGlobalSection()`, `DMPlexSetIsoperiodicFaceSF()`
951 @*/
952 PetscErrorCode DMPlexGetIsoperiodicFaceSF(DM dm, PetscSF *face_sf)
953 {
954   DM_Plex *plex = (DM_Plex *)dm->data;
955 
956   PetscFunctionBegin;
957   PetscValidHeaderSpecific(dm, DM_CLASSID, 1);
958   *face_sf = plex->periodic.face_sf;
959   PetscFunctionReturn(PETSC_SUCCESS);
960 }
961 
962 /*@C
963   DMPlexSetIsoperiodicFaceTransform - set geometric transform from donor to periodic points
964 
965   Logically Collective
966 
967   Input Parameters:
968 + dm - `DMPLEX` that has been configured with `DMPlexSetIsoperiodicFaceSF()`
969 - t  - 4x4 affine transformation basis.
970 
971   Level: advanced
972 
973   Notes:
974   Affine transforms are 4x4 matrices in which the leading 3x3 block expresses a rotation (or identity for no rotation),
975   the last column contains a translation vector, and the bottom row is all zero except the last entry, which must always
976   be 1. This representation is common in geometric modeling and allows affine transformations to be composed using
977   simple matrix multiplication.
978 
979   Although the interface accepts a general affine transform, only affine translation is supported at present.
980 
981   Developer Notes:
982   This interface should be replaced by making BasisTransform public, expanding it to support affine representations, and
983   adding GPU implementations to apply the G2L/L2G transforms.
984 
985 .seealso: [](ch_unstructured), `DMPLEX`, `DMGetGlobalSection()`, `DMPlexSetIsoperiodicFaceSF()`
986 @*/
987 PetscErrorCode DMPlexSetIsoperiodicFaceTransform(DM dm, const PetscScalar t[])
988 {
989   DM_Plex *plex = (DM_Plex *)dm->data;
990 
991   PetscFunctionBegin;
992   PetscValidHeaderSpecific(dm, DM_CLASSID, 1);
993   for (PetscInt i = 0; i < 4; i++) {
994     for (PetscInt j = 0; j < 4; j++) {
995       PetscCheck(i != j || t[i * 4 + j] == 1., PetscObjectComm((PetscObject)dm), PETSC_ERR_SUP, "Rotated transforms not supported");
996       plex->periodic.transform[i][j] = t[i * 4 + j];
997     }
998   }
999   PetscFunctionReturn(PETSC_SUCCESS);
1000 }
1001