/* Additional functions in the DMStag API, which are not part of the general DM API. */
#include <petsc/private/dmstagimpl.h> /*I  "petscdmstag.h"   I*/
#include <petscdmproduct.h>           /*I  "petscdmproduct.h"   I*/

PetscErrorCode DMRestrictHook_Coordinates(DM, DM, void *);

/*@C
  DMStagGetBoundaryTypes - get boundary types

  Not Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameters:
+ boundaryTypeX - boundary type for x direction
. boundaryTypeY - boundary type for y direction, not set for one dimensional problems
- boundaryTypeZ - boundary type for z direction, not set for one and two dimensional problems

  Level: intermediate

.seealso: [](ch_stag), `DMSTAG`, `DMBoundaryType`
@*/
PetscErrorCode DMStagGetBoundaryTypes(DM dm, DMBoundaryType *boundaryTypeX, DMBoundaryType *boundaryTypeY, DMBoundaryType *boundaryTypeZ)
{
  const DM_Stag *const stag = (DM_Stag *)dm->data;
  PetscInt             dim;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  PetscCall(DMGetDimension(dm, &dim));
  if (boundaryTypeX) *boundaryTypeX = stag->boundaryType[0];
  if (boundaryTypeY && dim > 1) *boundaryTypeY = stag->boundaryType[1];
  if (boundaryTypeZ && dim > 2) *boundaryTypeZ = stag->boundaryType[2];
  PetscFunctionReturn(PETSC_SUCCESS);
}

static PetscErrorCode DMStagGetProductCoordinateArrays_Private(DM dm, void *arrX, void *arrY, void *arrZ, PetscBool read)
{
  PetscInt  dim, d, dofCheck[DMSTAG_MAX_STRATA], s;
  DM        dmCoord;
  void     *arr[DMSTAG_MAX_DIM];
  PetscBool checkDof;

  PetscFunctionBegin;
  PetscValidHeaderSpecific(dm, DM_CLASSID, 1);
  PetscCall(DMGetDimension(dm, &dim));
  PetscCheck(dim <= DMSTAG_MAX_DIM, PetscObjectComm((PetscObject)dm), PETSC_ERR_SUP, "Not implemented for %" PetscInt_FMT " dimensions", dim);
  arr[0] = arrX;
  arr[1] = arrY;
  arr[2] = arrZ;
  PetscCall(DMGetCoordinateDM(dm, &dmCoord));
  PetscCheck(dmCoord, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "DM does not have a coordinate DM");
  {
    PetscBool isProduct;
    DMType    dmType;
    PetscCall(DMGetType(dmCoord, &dmType));
    PetscCall(PetscStrcmp(DMPRODUCT, dmType, &isProduct));
    PetscCheck(isProduct, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "Coordinate DM is not of type DMPRODUCT");
  }
  for (s = 0; s < DMSTAG_MAX_STRATA; ++s) dofCheck[s] = 0;
  checkDof = PETSC_FALSE;
  for (d = 0; d < dim; ++d) {
    DM        subDM;
    DMType    dmType;
    PetscBool isStag;
    PetscInt  dof[DMSTAG_MAX_STRATA], subDim;
    Vec       coord1d_local;

    /* Ignore unrequested arrays */
    if (!arr[d]) continue;

    PetscCall(DMProductGetDM(dmCoord, d, &subDM));
    PetscCheck(subDM, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "Coordinate DM is missing sub DM %" PetscInt_FMT, d);
    PetscCall(DMGetDimension(subDM, &subDim));
    PetscCheck(subDim == 1, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "Coordinate sub-DM is not of dimension 1");
    PetscCall(DMGetType(subDM, &dmType));
    PetscCall(PetscStrcmp(DMSTAG, dmType, &isStag));
    PetscCheck(isStag, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "Coordinate sub-DM is not of type DMSTAG");
    PetscCall(DMStagGetDOF(subDM, &dof[0], &dof[1], &dof[2], &dof[3]));
    if (!checkDof) {
      for (s = 0; s < DMSTAG_MAX_STRATA; ++s) dofCheck[s] = dof[s];
      checkDof = PETSC_TRUE;
    } else {
      for (s = 0; s < DMSTAG_MAX_STRATA; ++s) PetscCheck(dofCheck[s] == dof[s], PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "Coordinate sub-DMs have different dofs");
    }
    PetscCall(DMGetCoordinatesLocal(subDM, &coord1d_local));
    if (read) {
      PetscCall(DMStagVecGetArrayRead(subDM, coord1d_local, arr[d]));
    } else {
      PetscCall(DMStagVecGetArray(subDM, coord1d_local, arr[d]));
    }
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@C
  DMStagGetProductCoordinateArrays - extract local product coordinate arrays, one per dimension

  Logically Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameters:
+ arrX - local 1D coordinate arrays for x direction
. arrY - local 1D coordinate arrays for y direction, not set for one dimensional problems
- arrZ - local 1D coordinate arrays for z direction, not set for one and two dimensional problems

  Level: intermediate

  Notes:
  A high-level helper function to quickly extract local coordinate arrays.

  Note that 2-dimensional arrays are returned. See
  `DMStagVecGetArray()`, which is called internally to produce these arrays
  representing coordinates on elements and vertices (element boundaries)
  for a 1-dimensional `DMSTAG` in each coordinate direction.

  One should use `DMStagGetProductCoordinateLocationSlot()` to determine appropriate
  indices for the second dimension in these returned arrays. This function
  checks that the coordinate array is a suitable product of 1-dimensional
  `DMSTAG` objects.

.seealso: [](ch_stag), `DMSTAG`, `DMPRODUCT`, `DMStagGetProductCoordinateArraysRead()`, `DMStagSetUniformCoordinates()`, `DMStagSetUniformCoordinatesProduct()`, `DMStagGetProductCoordinateLocationSlot()`
@*/
PetscErrorCode DMStagGetProductCoordinateArrays(DM dm, void *arrX, void *arrY, void *arrZ)
{
  PetscFunctionBegin;
  PetscCall(DMStagGetProductCoordinateArrays_Private(dm, arrX, arrY, arrZ, PETSC_FALSE));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@C
  DMStagGetProductCoordinateArraysRead - extract product coordinate arrays, read-only

  Logically Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameters:
+ arrX - local 1D coordinate arrays for `x` direction
. arrY - local 1D coordinate arrays for `y` direction, not set for one dimensional problems
- arrZ - local 1D coordinate arrays for `z` direction, not set for one and two dimensional problems

  Level: intermediate

  Note:
  See `DMStagGetProductCoordinateArrays()` for more information.

.seealso: [](ch_stag), `DMSTAG`, `DMPRODUCT`, `DMStagGetProductCoordinateArrays()`, `DMStagSetUniformCoordinates()`, `DMStagSetUniformCoordinatesProduct()`, `DMStagGetProductCoordinateLocationSlot()`
@*/
PetscErrorCode DMStagGetProductCoordinateArraysRead(DM dm, void *arrX, void *arrY, void *arrZ)
{
  PetscFunctionBegin;
  PetscCall(DMStagGetProductCoordinateArrays_Private(dm, arrX, arrY, arrZ, PETSC_TRUE));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@C
  DMStagGetProductCoordinateLocationSlot - get slot for use with local product coordinate arrays

  Not Collective

  Input Parameters:
+ dm  - the `DMSTAG` object
- loc - the grid location

  Output Parameter:
. slot - the index to use in local arrays

  Level: intermediate

  Notes:
  High-level helper function to get slot indices for 1D coordinate `DM`s,
  for use with `DMStagGetProductCoordinateArrays()` and related functions.

  For `loc`, one should use `DMSTAG_LEFT`, `DMSTAG_ELEMENT`, or `DMSTAG_RIGHT` for "previous", "center" and "next"
  locations, respectively, in each dimension.
  One can equivalently use `DMSTAG_DOWN` or `DMSTAG_BACK` in place of `DMSTAG_LEFT`,
  and `DMSTAG_UP` or `DMSTACK_FRONT` in place of `DMSTAG_RIGHT`;

  This function checks that the coordinates are actually set up so that using the
  slots from any of the 1D coordinate sub-`DM`s are valid for all the 1D coordinate sub-`DM`s.

.seealso: [](ch_stag), `DMSTAG`, `DMPRODUCT`, `DMStagGetProductCoordinateArrays()`, `DMStagGetProductCoordinateArraysRead()`, `DMStagSetUniformCoordinates()`
@*/
PETSC_EXTERN PetscErrorCode DMStagGetProductCoordinateLocationSlot(DM dm, DMStagStencilLocation loc, PetscInt *slot)
{
  DM       dmCoord;
  PetscInt dim, dofCheck[DMSTAG_MAX_STRATA], s, d;

  PetscFunctionBegin;
  PetscValidHeaderSpecific(dm, DM_CLASSID, 1);
  PetscCall(DMGetDimension(dm, &dim));
  PetscCall(DMGetCoordinateDM(dm, &dmCoord));
  PetscCheck(dmCoord, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "DM does not have a coordinate DM");
  {
    PetscBool isProduct;
    DMType    dmType;
    PetscCall(DMGetType(dmCoord, &dmType));
    PetscCall(PetscStrcmp(DMPRODUCT, dmType, &isProduct));
    PetscCheck(isProduct, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "Coordinate DM is not of type DMPRODUCT");
  }
  for (s = 0; s < DMSTAG_MAX_STRATA; ++s) dofCheck[s] = 0;
  for (d = 0; d < dim; ++d) {
    DM        subDM;
    DMType    dmType;
    PetscBool isStag;
    PetscInt  dof[DMSTAG_MAX_STRATA], subDim;
    PetscCall(DMProductGetDM(dmCoord, d, &subDM));
    PetscCheck(subDM, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "Coordinate DM is missing sub DM %" PetscInt_FMT, d);
    PetscCall(DMGetDimension(subDM, &subDim));
    PetscCheck(subDim == 1, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "Coordinate sub-DM is not of dimension 1");
    PetscCall(DMGetType(subDM, &dmType));
    PetscCall(PetscStrcmp(DMSTAG, dmType, &isStag));
    PetscCheck(isStag, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "Coordinate sub-DM is not of type DMSTAG");
    PetscCall(DMStagGetDOF(subDM, &dof[0], &dof[1], &dof[2], &dof[3]));
    if (d == 0) {
      const PetscInt component = 0;
      for (s = 0; s < DMSTAG_MAX_STRATA; ++s) dofCheck[s] = dof[s];
      PetscCall(DMStagGetLocationSlot(subDM, loc, component, slot));
    } else {
      for (s = 0; s < DMSTAG_MAX_STRATA; ++s) PetscCheck(dofCheck[s] == dof[s], PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "Coordinate sub-DMs have different dofs");
    }
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagGetCorners - return global element indices of the local region (excluding ghost points)

  Not Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameters:
+ x       - starting element index in first direction
. y       - starting element index in second direction
. z       - starting element index in third direction
. m       - element width in first direction
. n       - element width in second direction
. p       - element width in third direction
. nExtrax - number of extra partial elements in first direction
. nExtray - number of extra partial elements in second direction
- nExtraz - number of extra partial elements in third direction

  Level: beginner

  Notes:
  Arguments corresponding to higher dimensions are ignored for 1D and 2D grids. These arguments may be set to `NULL` in this case.

  The number of extra partial elements is either 1 or 0.
  The value is 1 on right, top, and front non-periodic domain ("physical") boundaries,
  in the x, y, and z directions respectively, and otherwise 0.

.seealso: [](ch_stag), `DMSTAG`, `DMStagGetGhostCorners()`, `DMDAGetCorners()`
@*/
PetscErrorCode DMStagGetCorners(DM dm, PetscInt *x, PetscInt *y, PetscInt *z, PetscInt *m, PetscInt *n, PetscInt *p, PetscInt *nExtrax, PetscInt *nExtray, PetscInt *nExtraz)
{
  const DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  if (x) *x = stag->start[0];
  if (y) *y = stag->start[1];
  if (z) *z = stag->start[2];
  if (m) *m = stag->n[0];
  if (n) *n = stag->n[1];
  if (p) *p = stag->n[2];
  if (nExtrax) *nExtrax = stag->boundaryType[0] != DM_BOUNDARY_PERIODIC && stag->lastRank[0] ? 1 : 0;
  if (nExtray) *nExtray = stag->boundaryType[1] != DM_BOUNDARY_PERIODIC && stag->lastRank[1] ? 1 : 0;
  if (nExtraz) *nExtraz = stag->boundaryType[2] != DM_BOUNDARY_PERIODIC && stag->lastRank[2] ? 1 : 0;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagGetDOF - get number of DOF associated with each stratum of the grid

  Not Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameters:
+ dof0 - the number of points per 0-cell (vertex/node)
. dof1 - the number of points per 1-cell (element in 1D, edge in 2D and 3D)
. dof2 - the number of points per 2-cell (element in 2D, face in 3D)
- dof3 - the number of points per 3-cell (element in 3D)

  Level: beginner

.seealso: [](ch_stag), `DMSTAG`, `DMStagGetCorners()`, `DMStagGetGhostCorners()`, `DMStagGetGlobalSizes()`, `DMStagGetStencilWidth()`, `DMStagGetBoundaryTypes()`, `DMStagGetLocationDOF()`, `DMDAGetDof()`
@*/
PetscErrorCode DMStagGetDOF(DM dm, PetscInt *dof0, PetscInt *dof1, PetscInt *dof2, PetscInt *dof3)
{
  const DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  if (dof0) *dof0 = stag->dof[0];
  if (dof1) *dof1 = stag->dof[1];
  if (dof2) *dof2 = stag->dof[2];
  if (dof3) *dof3 = stag->dof[3];
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagGetGhostCorners - return global element indices of the local region, including ghost points

  Not Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameters:
+ x - the starting element index in the first direction
. y - the starting element index in the second direction
. z - the starting element index in the third direction
. m - the element width in the first direction
. n - the element width in the second direction
- p - the element width in the third direction

  Level: beginner

  Note:
  Arguments corresponding to higher dimensions are ignored for 1D and 2D grids. These arguments may be set to `NULL` in this case.

.seealso: [](ch_stag), `DMSTAG`, `DMStagGetCorners()`, `DMDAGetGhostCorners()`
@*/
PetscErrorCode DMStagGetGhostCorners(DM dm, PetscInt *x, PetscInt *y, PetscInt *z, PetscInt *m, PetscInt *n, PetscInt *p)
{
  const DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  if (x) *x = stag->startGhost[0];
  if (y) *y = stag->startGhost[1];
  if (z) *z = stag->startGhost[2];
  if (m) *m = stag->nGhost[0];
  if (n) *n = stag->nGhost[1];
  if (p) *p = stag->nGhost[2];
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagGetGlobalSizes - get global element counts

  Not Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameters:
+ M - global element counts in the x direction
. N - global element counts in the y direction
- P - global element counts in the z direction

  Level: beginner

  Note:
  Arguments corresponding to higher dimensions are ignored for 1D and 2D grids. These arguments may be set to `NULL` in this case.

.seealso: [](ch_stag), `DMSTAG`, `DMStagGetLocalSizes()`, `DMDAGetInfo()`
@*/
PetscErrorCode DMStagGetGlobalSizes(DM dm, PetscInt *M, PetscInt *N, PetscInt *P)
{
  const DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  if (M) *M = stag->N[0];
  if (N) *N = stag->N[1];
  if (P) *P = stag->N[2];
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@C
  DMStagGetIsFirstRank - get boolean value for whether this rank is first in each direction in the rank grid

  Not Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameters:
+ isFirstRank0 - whether this rank is first in the x direction
. isFirstRank1 - whether this rank is first in the y direction
- isFirstRank2 - whether this rank is first in the z direction

  Level: intermediate

  Note:
  Arguments corresponding to higher dimensions are ignored for 1D and 2D grids. These arguments may be set to `NULL` in this case.

.seealso: [](ch_stag), `DMSTAG`, `DMStagGetIsLastRank()`
@*/
PetscErrorCode DMStagGetIsFirstRank(DM dm, PetscBool *isFirstRank0, PetscBool *isFirstRank1, PetscBool *isFirstRank2)
{
  const DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  if (isFirstRank0) *isFirstRank0 = stag->firstRank[0];
  if (isFirstRank1) *isFirstRank1 = stag->firstRank[1];
  if (isFirstRank2) *isFirstRank2 = stag->firstRank[2];
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@C
  DMStagGetIsLastRank - get boolean value for whether this rank is last in each direction in the rank grid

  Not Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameters:
+ isLastRank0 - whether this rank is last in the x direction
. isLastRank1 - whether this rank is last in the y direction
- isLastRank2 - whether this rank is last in the z direction

  Level: intermediate

  Note:
  Arguments corresponding to higher dimensions are ignored for 1D and 2D grids. These arguments may be set to `NULL` in this case.

.seealso: [](ch_stag), `DMSTAG`, `DMStagGetIsFirstRank()`
@*/
PetscErrorCode DMStagGetIsLastRank(DM dm, PetscBool *isLastRank0, PetscBool *isLastRank1, PetscBool *isLastRank2)
{
  const DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  if (isLastRank0) *isLastRank0 = stag->lastRank[0];
  if (isLastRank1) *isLastRank1 = stag->lastRank[1];
  if (isLastRank2) *isLastRank2 = stag->lastRank[2];
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagGetLocalSizes - get local elementwise sizes

  Not Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameters:
+ m - local element counts (excluding ghosts) in the x direction
. n - local element counts (excluding ghosts) in the y direction
- p - local element counts (excluding ghosts) in the z direction

  Level: beginner

  Note:
  Arguments corresponding to higher dimensions are ignored for 1D and 2D grids. These arguments may be set to `NULL` in this case.

.seealso: [](ch_stag), `DMSTAG`, `DMStagGetGlobalSizes()`, `DMStagGetDOF()`, `DMStagGetNumRanks()`, `DMDAGetLocalInfo()`
@*/
PetscErrorCode DMStagGetLocalSizes(DM dm, PetscInt *m, PetscInt *n, PetscInt *p)
{
  const DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  if (m) *m = stag->n[0];
  if (n) *n = stag->n[1];
  if (p) *p = stag->n[2];
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagGetNumRanks - get number of ranks in each direction in the global grid decomposition

  Not Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameters:
+ nRanks0 - number of ranks in the x direction in the grid decomposition
. nRanks1 - number of ranks in the y direction in the grid decomposition
- nRanks2 - number of ranks in the z direction in the grid decomposition

  Level: intermediate

.seealso: [](ch_stag), `DMSTAG`, `DMStagGetGlobalSizes()`, `DMStagGetLocalSize()`, `DMStagSetNumRanks()`, `DMDAGetInfo()`
@*/
PetscErrorCode DMStagGetNumRanks(DM dm, PetscInt *nRanks0, PetscInt *nRanks1, PetscInt *nRanks2)
{
  const DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  if (nRanks0) *nRanks0 = stag->nRanks[0];
  if (nRanks1) *nRanks1 = stag->nRanks[1];
  if (nRanks2) *nRanks2 = stag->nRanks[2];
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagGetEntries - get number of native entries in the global representation

  Not Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameter:
. entries - number of rank-native entries in the global representation

  Level: developer

  Note:
  This is the number of entries on this rank for a global vector associated with `dm`.
  That is, it is value of `size` returned by `VecGetLocalSize(vec,&size)` when
  `DMCreateGlobalVector(dm,&vec) is used to create a `Vec`. Users would typically
  use these functions.

.seealso: [](ch_stag), `DMSTAG`, `DMStagGetDOF()`, `DMStagGetEntriesLocal()`, `DMStagGetEntriesPerElement()`, `DMCreateLocalVector()`
@*/
PetscErrorCode DMStagGetEntries(DM dm, PetscInt *entries)
{
  const DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  if (entries) *entries = stag->entries;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagGetEntriesLocal - get number of entries in the local representation

  Not Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameter:
. entries - number of entries in the local representation

  Level: developer

  Note:
  This is the number of entries on this rank in the local representation.
  That is, it is value of `size` returned by `VecGetSize(vec,&size)` or
  `VecGetLocalSize(vec,&size)` when `DMCreateLocalVector(dm,&vec)` is used to
  create a `Vec`. Users would typically use these functions.

.seealso: [](ch_stag), `DMSTAG`, `DMStagGetDOF()`, `DMStagGetEntries()`, `DMStagGetEntriesPerElement()`, `DMCreateLocalVector()`
@*/
PetscErrorCode DMStagGetEntriesLocal(DM dm, PetscInt *entries)
{
  const DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  if (entries) *entries = stag->entriesGhost;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagGetEntriesPerElement - get number of entries per element in the local representation

  Not Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameter:
. entriesPerElement - number of entries associated with each element in the local representation

  Level: developer

  Notes:
  This is the natural block size for most local operations. In 1D it is equal to `dof0` $+$ `dof1`,
  in 2D it is equal to `dof0` $+ 2$`dof1` $+$ `dof2`, and in 3D it is equal to `dof0` $+ 3$`dof1` $+ 3$`dof2` $+$ `dof3`

.seealso: [](ch_stag), `DMSTAG`, `DMStagGetDOF()`
@*/
PetscErrorCode DMStagGetEntriesPerElement(DM dm, PetscInt *entriesPerElement)
{
  const DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  if (entriesPerElement) *entriesPerElement = stag->entriesPerElement;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagGetStencilType - get elementwise ghost/halo stencil type

  Not Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameter:
. stencilType - the elementwise ghost stencil type: `DMSTAG_STENCIL_BOX`, `DMSTAG_STENCIL_STAR`, or `DMSTAG_STENCIL_NONE`

  Level: beginner

.seealso: [](ch_stag), `DMSTAG`, `DMStagSetStencilType()`, `DMStagGetStencilWidth`, `DMStagStencilType`
@*/
PetscErrorCode DMStagGetStencilType(DM dm, DMStagStencilType *stencilType)
{
  DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  *stencilType = stag->stencilType;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagGetStencilWidth - get elementwise stencil width

  Not Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameter:
. stencilWidth - stencil/halo/ghost width in elements

  Level: beginner

.seealso: [](ch_stag), `DMSTAG`, `DMStagSetStencilWidth()`, `DMStagGetStencilType()`, `DMDAGetStencilType()`
@*/
PetscErrorCode DMStagGetStencilWidth(DM dm, PetscInt *stencilWidth)
{
  const DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  if (stencilWidth) *stencilWidth = stag->stencilWidth;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@C
  DMStagGetOwnershipRanges - get elements per rank in each direction

  Not Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameters:
+ lx - ownership along x direction (optional)
. ly - ownership along y direction (optional)
- lz - ownership along z direction (optional)

  Level: intermediate

  Notes:
  These correspond to the optional final arguments passed to `DMStagCreate1d()`, `DMStagCreate2d()`, and `DMStagCreate3d()`.

  Arguments corresponding to higher dimensions are ignored for 1D and 2D grids. These arguments may be set to `NULL` in this case.

  In C you should not free these arrays, nor change the values in them.
  They will only have valid values while the `DMSTAG` they came from still exists (has not been destroyed).

.seealso: [](ch_stag), `DMSTAG`, `DMStagSetGlobalSizes()`, `DMStagSetOwnershipRanges()`, `DMStagCreate1d()`, `DMStagCreate2d()`, `DMStagCreate3d()`, `DMDAGetOwnershipRanges()`
@*/
PetscErrorCode DMStagGetOwnershipRanges(DM dm, const PetscInt *lx[], const PetscInt *ly[], const PetscInt *lz[])
{
  const DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  if (lx) *lx = stag->l[0];
  if (ly) *ly = stag->l[1];
  if (lz) *lz = stag->l[2];
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagCreateCompatibleDMStag - create a compatible `DMSTAG` with different dof/stratum

  Collective

  Input Parameters:
+ dm   - the `DMSTAG` object
. dof0 - number of dof on the first stratum in the new `DMSTAG`
. dof1 - number of dof on the second stratum in the new `DMSTAG`
. dof2 - number of dof on the third stratum in the new `DMSTAG`
- dof3 - number of dof on the fourth stratum in the new `DMSTAG`

  Output Parameter:
. newdm - the new, compatible `DMSTAG`

  Level: intermediate

  Notes:
  DOF supplied for strata too big for the dimension are ignored; these may be set to `0`.
  For example, for a 2-dimensional `DMSTAG`, `dof2` sets the number of dof per element,
  and `dof3` is unused. For a 3-dimensional `DMSTAG`, `dof3` sets the number of DOF per element.

  In contrast to `DMDACreateCompatibleDMDA()`, coordinates are not reused.

.seealso: [](ch_stag), `DMSTAG`, `DMDACreateCompatibleDMDA()`, `DMGetCompatibility()`, `DMStagMigrateVec()`
@*/
PetscErrorCode DMStagCreateCompatibleDMStag(DM dm, PetscInt dof0, PetscInt dof1, PetscInt dof2, PetscInt dof3, DM *newdm)
{
  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  PetscCall(DMStagDuplicateWithoutSetup(dm, PetscObjectComm((PetscObject)dm), newdm));
  PetscCall(DMStagSetDOF(*newdm, dof0, dof1, dof2, dof3));
  PetscCall(DMSetUp(*newdm));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagGetLocationSlot - get index to use in accessing raw local arrays

  Not Collective

  Input Parameters:
+ dm  - the `DMSTAG` object
. loc - location relative to an element
- c   - component

  Output Parameter:
. slot - index to use

  Level: beginner

  Notes:
  Provides an appropriate index to use with `DMStagVecGetArray()` and friends.
  This is required so that the user doesn't need to know about the ordering of
  dof associated with each local element.

.seealso: [](ch_stag), `DMSTAG`, `DMStagVecGetArray()`, `DMStagVecGetArrayRead()`, `DMStagGetDOF()`, `DMStagGetEntriesPerElement()`
@*/
PetscErrorCode DMStagGetLocationSlot(DM dm, DMStagStencilLocation loc, PetscInt c, PetscInt *slot)
{
  DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  if (PetscDefined(USE_DEBUG)) {
    PetscInt dof;
    PetscCall(DMStagGetLocationDOF(dm, loc, &dof));
    PetscCheck(dof >= 1, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "Location %s has no dof attached", DMStagStencilLocations[loc]);
    PetscCheck(c <= dof - 1, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "Supplied component number (%" PetscInt_FMT ") for location %s is too big (maximum %" PetscInt_FMT ")", c, DMStagStencilLocations[loc], dof - 1);
  }
  *slot = stag->locationOffsets[loc] + c;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagGetRefinementFactor - get refinement ratios in each direction

  Not Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameters:
+ refine_x - ratio of fine grid to coarse in x-direction (2 by default)
. refine_y - ratio of fine grid to coarse in y-direction (2 by default)
- refine_z - ratio of fine grid to coarse in z-direction (2 by default)

  Level: intermediate

.seealso: [](ch_stag), `DMSTAG`, `DMRefine()`, `DMCoarsen()`, `DMStagSetRefinementFactor()`, `DMDASetRefinementFactor()`
@*/
PetscErrorCode DMStagGetRefinementFactor(DM dm, PetscInt *refine_x, PetscInt *refine_y, PetscInt *refine_z)
{
  DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  if (refine_x) *refine_x = stag->refineFactor[0];
  if (refine_y) *refine_y = stag->refineFactor[1];
  if (refine_z) *refine_z = stag->refineFactor[2];
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagMigrateVec - transfer a vector associated with a `DMSTAG` to a vector associated with a compatible `DMSTAG`

  Collective

  Input Parameters:
+ dm    - the source `DMSTAG` object
. vec   - the source vector, compatible with `dm`
. dmTo  - the compatible destination `DMSTAG` object
- vecTo - the destination vector, compatible with `dmTo`

  Level: advanced

  Notes:
  Extra dof are ignored, and unfilled dof are zeroed.
  Currently only implemented to migrate global vectors to global vectors.
  For the definition of compatibility of `DM`s, see `DMGetCompatibility()`.

.seealso: [](ch_stag), `DMSTAG`, `DMStagCreateCompatibleDMStag()`, `DMGetCompatibility()`, `DMStagVecSplitToDMDA()`
@*/
PetscErrorCode DMStagMigrateVec(DM dm, Vec vec, DM dmTo, Vec vecTo)
{
  DM_Stag *const     stag   = (DM_Stag *)dm->data;
  DM_Stag *const     stagTo = (DM_Stag *)dmTo->data;
  PetscInt           nLocalTo, nLocal, dim, i, j, k;
  PetscInt           start[DMSTAG_MAX_DIM], startGhost[DMSTAG_MAX_DIM], n[DMSTAG_MAX_DIM], nExtra[DMSTAG_MAX_DIM], offset[DMSTAG_MAX_DIM];
  Vec                vecToLocal, vecLocal;
  PetscBool          compatible, compatibleSet;
  const PetscScalar *arr;
  PetscScalar       *arrTo;
  const PetscInt     epe   = stag->entriesPerElement;
  const PetscInt     epeTo = stagTo->entriesPerElement;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  PetscValidHeaderSpecific(vec, VEC_CLASSID, 2);
  PetscValidHeaderSpecificType(dmTo, DM_CLASSID, 3, DMSTAG);
  PetscValidHeaderSpecific(vecTo, VEC_CLASSID, 4);
  PetscCall(DMGetCompatibility(dm, dmTo, &compatible, &compatibleSet));
  PetscCheck(compatibleSet && compatible, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_INCOMP, "DMStag objects must be shown to be compatible");
  PetscCall(DMGetDimension(dm, &dim));
  PetscCall(VecGetLocalSize(vec, &nLocal));
  PetscCall(VecGetLocalSize(vecTo, &nLocalTo));
  PetscCheck(nLocal == stag->entries && nLocalTo == stagTo->entries, PetscObjectComm((PetscObject)dm), PETSC_ERR_SUP, "Vector migration only implemented for global vector to global vector.");
  PetscCall(DMStagGetCorners(dm, &start[0], &start[1], &start[2], &n[0], &n[1], &n[2], &nExtra[0], &nExtra[1], &nExtra[2]));
  PetscCall(DMStagGetGhostCorners(dm, &startGhost[0], &startGhost[1], &startGhost[2], NULL, NULL, NULL));
  for (i = 0; i < DMSTAG_MAX_DIM; ++i) offset[i] = start[i] - startGhost[i];

  /* Proceed by transferring to a local vector, copying, and transferring back to a global vector */
  PetscCall(DMGetLocalVector(dm, &vecLocal));
  PetscCall(DMGetLocalVector(dmTo, &vecToLocal));
  PetscCall(DMGlobalToLocalBegin(dm, vec, INSERT_VALUES, vecLocal));
  PetscCall(DMGlobalToLocalEnd(dm, vec, INSERT_VALUES, vecLocal));
  PetscCall(VecGetArrayRead(vecLocal, &arr));
  PetscCall(VecGetArray(vecToLocal, &arrTo));
  /* Note that some superfluous copying of entries on partial dummy elements is done */
  if (dim == 1) {
    for (i = offset[0]; i < offset[0] + n[0] + nExtra[0]; ++i) {
      PetscInt d = 0, dTo = 0, b = 0, bTo = 0;
      PetscInt si;
      for (si = 0; si < 2; ++si) {
        b += stag->dof[si];
        bTo += stagTo->dof[si];
        for (; d < b && dTo < bTo; ++d, ++dTo) arrTo[i * epeTo + dTo] = arr[i * epe + d];
        for (; dTo < bTo; ++dTo) arrTo[i * epeTo + dTo] = 0.0;
        d = b;
      }
    }
  } else if (dim == 2) {
    const PetscInt epr   = stag->nGhost[0] * epe;
    const PetscInt eprTo = stagTo->nGhost[0] * epeTo;
    for (j = offset[1]; j < offset[1] + n[1] + nExtra[1]; ++j) {
      for (i = offset[0]; i < offset[0] + n[0] + nExtra[0]; ++i) {
        const PetscInt base   = j * epr + i * epe;
        const PetscInt baseTo = j * eprTo + i * epeTo;
        PetscInt       d = 0, dTo = 0, b = 0, bTo = 0;
        const PetscInt s[4] = {0, 1, 1, 2}; /* Dimensions of points, in order */
        PetscInt       si;
        for (si = 0; si < 4; ++si) {
          b += stag->dof[s[si]];
          bTo += stagTo->dof[s[si]];
          for (; d < b && dTo < bTo; ++d, ++dTo) arrTo[baseTo + dTo] = arr[base + d];
          for (; dTo < bTo; ++dTo) arrTo[baseTo + dTo] = 0.0;
          d = b;
        }
      }
    }
  } else if (dim == 3) {
    const PetscInt epr   = stag->nGhost[0] * epe;
    const PetscInt eprTo = stagTo->nGhost[0] * epeTo;
    const PetscInt epl   = stag->nGhost[1] * epr;
    const PetscInt eplTo = stagTo->nGhost[1] * eprTo;
    for (k = offset[2]; k < offset[2] + n[2] + nExtra[2]; ++k) {
      for (j = offset[1]; j < offset[1] + n[1] + nExtra[1]; ++j) {
        for (i = offset[0]; i < offset[0] + n[0] + nExtra[0]; ++i) {
          PetscInt       d = 0, dTo = 0, b = 0, bTo = 0;
          const PetscInt base   = k * epl + j * epr + i * epe;
          const PetscInt baseTo = k * eplTo + j * eprTo + i * epeTo;
          const PetscInt s[8]   = {0, 1, 1, 2, 1, 2, 2, 3}; /* dimensions of points, in order */
          PetscInt       is;
          for (is = 0; is < 8; ++is) {
            b += stag->dof[s[is]];
            bTo += stagTo->dof[s[is]];
            for (; d < b && dTo < bTo; ++d, ++dTo) arrTo[baseTo + dTo] = arr[base + d];
            for (; dTo < bTo; ++dTo) arrTo[baseTo + dTo] = 0.0;
            d = b;
          }
        }
      }
    }
  } else SETERRQ(PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "Unsupported dimension %" PetscInt_FMT, dim);
  PetscCall(VecRestoreArrayRead(vecLocal, &arr));
  PetscCall(VecRestoreArray(vecToLocal, &arrTo));
  PetscCall(DMRestoreLocalVector(dm, &vecLocal));
  PetscCall(DMLocalToGlobalBegin(dmTo, vecToLocal, INSERT_VALUES, vecTo));
  PetscCall(DMLocalToGlobalEnd(dmTo, vecToLocal, INSERT_VALUES, vecTo));
  PetscCall(DMRestoreLocalVector(dmTo, &vecToLocal));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagPopulateLocalToGlobalInjective - populate an internal 1-to-1 local-to-global map

  Collective

  Creates an internal object which explicitly maps a single local degree of
  freedom to each global degree of freedom. This is used, if populated,
  instead of SCATTER_REVERSE_LOCAL with the (1-to-many, in general)
  global-to-local map, when DMLocalToGlobal() is called with INSERT_VALUES.
  This allows usage, for example, even in the periodic, 1-rank case, where
  the inverse of the global-to-local map, even when restricted to on-rank
  communication, is non-injective. This is at the cost of storing an additional
  VecScatter object inside each `DMSTAG` object.

  Input Parameter:
. dm - the `DMSTAG` object

  Level: developer

  Notes:
  In normal usage, library users shouldn't be concerned with this function,
  as it is called during `DMSetUp()`, when required.

  Returns immediately if the internal map is already populated.

  Developer Notes:
  This could, if desired, be moved up to a general `DM` routine. It would allow,
  for example, `DMDA` to support `DMLocalToGlobal()` with `INSERT_VALUES`,
  even in the single-rank periodic case.

.seealso: [](ch_stag), `DMSTAG`, `DMLocalToGlobal()`, `VecScatter`
@*/
PetscErrorCode DMStagPopulateLocalToGlobalInjective(DM dm)
{
  PetscInt       dim;
  DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  if (stag->ltog_injective) PetscFunctionReturn(PETSC_SUCCESS); /* Don't re-populate */
  PetscCall(DMGetDimension(dm, &dim));
  switch (dim) {
  case 1:
    PetscCall(DMStagPopulateLocalToGlobalInjective_1d(dm));
    break;
  case 2:
    PetscCall(DMStagPopulateLocalToGlobalInjective_2d(dm));
    break;
  case 3:
    PetscCall(DMStagPopulateLocalToGlobalInjective_3d(dm));
    break;
  default:
    SETERRQ(PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "Unsupported dimension %" PetscInt_FMT, dim);
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

static PetscErrorCode DMStagRestoreProductCoordinateArrays_Private(DM dm, void *arrX, void *arrY, void *arrZ, PetscBool read)
{
  PetscInt dim, d;
  void    *arr[DMSTAG_MAX_DIM];
  DM       dmCoord;

  PetscFunctionBegin;
  PetscValidHeaderSpecific(dm, DM_CLASSID, 1);
  PetscCall(DMGetDimension(dm, &dim));
  PetscCheck(dim <= DMSTAG_MAX_DIM, PetscObjectComm((PetscObject)dm), PETSC_ERR_SUP, "Not implemented for %" PetscInt_FMT " dimensions", dim);
  arr[0] = arrX;
  arr[1] = arrY;
  arr[2] = arrZ;
  PetscCall(DMGetCoordinateDM(dm, &dmCoord));
  for (d = 0; d < dim; ++d) {
    DM  subDM;
    Vec coord1d_local;

    /* Ignore unrequested arrays */
    if (!arr[d]) continue;

    PetscCall(DMProductGetDM(dmCoord, d, &subDM));
    PetscCall(DMGetCoordinatesLocal(subDM, &coord1d_local));
    if (read) {
      PetscCall(DMStagVecRestoreArrayRead(subDM, coord1d_local, arr[d]));
    } else {
      PetscCall(DMStagVecRestoreArray(subDM, coord1d_local, arr[d]));
    }
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@C
  DMStagRestoreProductCoordinateArrays - restore local array access

  Logically Collective

  Input Parameter:
. dm - the `DMSTAG` object

  Output Parameters:
+ arrX - local 1D coordinate arrays for x direction
. arrY - local 1D coordinate arrays for y direction
- arrZ - local 1D coordinate arrays for z direction

  Level: intermediate

  Notes:
  This function does not automatically perform a local->global scatter to populate global coordinates from the local coordinates.
  Thus, it may be required to explicitly perform these operations in some situations, as in the following partial example\:
.vb
  PetscCall(DMGetCoordinateDM(dm, &cdm));
  for (PetscInt d = 0; d < 3; ++d) {
    DM  subdm;
    Vec coor, coor_local;

    PetscCall(DMProductGetDM(cdm, d, &subdm));
    PetscCall(DMGetCoordinates(subdm, &coor));
    PetscCall(DMGetCoordinatesLocal(subdm, &coor_local));
    PetscCall(DMLocalToGlobal(subdm, coor_local, INSERT_VALUES, coor));
    PetscCall(PetscPrintf(PETSC_COMM_WORLD, "Coordinates dim %" PetscInt_FMT ":\n", d));
    PetscCall(VecView(coor, PETSC_VIEWER_STDOUT_WORLD));
  }
.ve

.seealso: [](ch_stag), `DMSTAG`, `DMStagGetProductCoordinateArrays()`, `DMStagGetProductCoordinateArraysRead()`
@*/
PetscErrorCode DMStagRestoreProductCoordinateArrays(DM dm, void *arrX, void *arrY, void *arrZ)
{
  PetscFunctionBegin;
  PetscCall(DMStagRestoreProductCoordinateArrays_Private(dm, arrX, arrY, arrZ, PETSC_FALSE));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@C
  DMStagRestoreProductCoordinateArraysRead - restore local product array access, read-only

  Logically Collective

  Input Parameters:
+ dm   - the `DMSTAG` object
. arrX - local 1D coordinate arrays for x direction
. arrY - local 1D coordinate arrays for y direction
- arrZ - local 1D coordinate arrays for z direction

  Level: intermediate

.seealso: [](ch_stag), `DMSTAG`, `DMStagGetProductCoordinateArrays()`, `DMStagGetProductCoordinateArraysRead()`
@*/
PetscErrorCode DMStagRestoreProductCoordinateArraysRead(DM dm, void *arrX, void *arrY, void *arrZ)
{
  PetscFunctionBegin;
  PetscCall(DMStagRestoreProductCoordinateArrays_Private(dm, arrX, arrY, arrZ, PETSC_TRUE));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagSetBoundaryTypes - set `DMSTAG` boundary types

  Logically Collective; boundaryType0, boundaryType1, and boundaryType2 must contain common values

  Input Parameters:
+ dm            - the `DMSTAG` object
. boundaryType2 - boundary type for x direction
. boundaryType1 - boundary type for y direction, not set for one dimensional problems
- boundaryType0 - boundary type for z direction, not set for one and two dimensional problems

  Level: advanced

  Note:
  Arguments corresponding to higher dimensions are ignored for 1D and 2D grids.

.seealso: [](ch_stag), `DMSTAG`, `DMBoundaryType`, `DMStagCreate1d()`, `DMStagCreate2d()`, `DMStagCreate3d()`, `DMDASetBoundaryType()`
@*/
PetscErrorCode DMStagSetBoundaryTypes(DM dm, DMBoundaryType boundaryType0, DMBoundaryType boundaryType1, DMBoundaryType boundaryType2)
{
  DM_Stag *const stag = (DM_Stag *)dm->data;
  PetscInt       dim;

  PetscFunctionBegin;
  PetscCall(DMGetDimension(dm, &dim));
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  PetscValidLogicalCollectiveEnum(dm, boundaryType0, 2);
  if (dim > 1) PetscValidLogicalCollectiveEnum(dm, boundaryType1, 3);
  if (dim > 2) PetscValidLogicalCollectiveEnum(dm, boundaryType2, 4);
  PetscCheck(!dm->setupcalled, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "This function must be called before DMSetUp()");
  stag->boundaryType[0] = boundaryType0;
  if (dim > 1) stag->boundaryType[1] = boundaryType1;
  if (dim > 2) stag->boundaryType[2] = boundaryType2;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@C
  DMStagSetCoordinateDMType - set DM type to store coordinates

  Logically Collective; `dmtype` must contain common value

  Input Parameters:
+ dm     - the `DMSTAG` object
- dmtype - `DMtype` for coordinates, either `DMSTAG` or `DMPRODUCT`

  Level: advanced

.seealso: [](ch_stag), `DMSTAG`, `DMPRODUCT`, `DMGetCoordinateDM()`, `DMStagSetUniformCoordinates()`, `DMStagSetUniformCoordinatesExplicit()`, `DMStagSetUniformCoordinatesProduct()`, `DMType`
@*/
PetscErrorCode DMStagSetCoordinateDMType(DM dm, DMType dmtype)
{
  DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  PetscCall(PetscFree(stag->coordinateDMType));
  PetscCall(PetscStrallocpy(dmtype, (char **)&stag->coordinateDMType));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagSetDOF - set dof/stratum

  Logically Collective; `dof0`, `dof1`, `dof2`, and `dof3` must contain common values

  Input Parameters:
+ dm   - the `DMSTAG` object
. dof0 - the number of points per 0-cell (vertex/node)
. dof1 - the number of points per 1-cell (element in 1D, edge in 2D and 3D)
. dof2 - the number of points per 2-cell (element in 2D, face in 3D)
- dof3 - the number of points per 3-cell (element in 3D)

  Level: advanced

  Note:
  Arguments corresponding to higher dimensions are ignored for 1D and 2D grids.

.seealso: [](ch_stag), `DMSTAG`, `DMDASetDof()`
@*/
PetscErrorCode DMStagSetDOF(DM dm, PetscInt dof0, PetscInt dof1, PetscInt dof2, PetscInt dof3)
{
  DM_Stag *const stag = (DM_Stag *)dm->data;
  PetscInt       dim;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  PetscValidLogicalCollectiveInt(dm, dof0, 2);
  PetscValidLogicalCollectiveInt(dm, dof1, 3);
  PetscValidLogicalCollectiveInt(dm, dof2, 4);
  PetscValidLogicalCollectiveInt(dm, dof3, 5);
  PetscCheck(!dm->setupcalled, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "This function must be called before DMSetUp()");
  PetscCall(DMGetDimension(dm, &dim));
  PetscCheck(dof0 >= 0, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "dof0 cannot be negative");
  PetscCheck(dof1 >= 0, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "dof1 cannot be negative");
  PetscCheck(dim <= 1 || dof2 >= 0, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "dof2 cannot be negative");
  PetscCheck(dim <= 2 || dof3 >= 0, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "dof3 cannot be negative");
  stag->dof[0] = dof0;
  stag->dof[1] = dof1;
  if (dim > 1) stag->dof[2] = dof2;
  if (dim > 2) stag->dof[3] = dof3;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagSetNumRanks - set ranks in each direction in the global rank grid

  Logically Collective; `nRanks0`, `nRanks1`, and `nRanks2` must contain common values

  Input Parameters:
+ dm      - the `DMSTAG` object
. nRanks0 - number of ranks in the x direction
. nRanks1 - number of ranks in the y direction
- nRanks2 - number of ranks in the z direction

  Level: developer

  Note:
  Arguments corresponding to higher dimensions are ignored for 1D and 2D grids.

.seealso: [](ch_stag), `DMSTAG`, `DMDASetNumProcs()`
@*/
PetscErrorCode DMStagSetNumRanks(DM dm, PetscInt nRanks0, PetscInt nRanks1, PetscInt nRanks2)
{
  DM_Stag *const stag = (DM_Stag *)dm->data;
  PetscInt       dim;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  PetscValidLogicalCollectiveInt(dm, nRanks0, 2);
  PetscValidLogicalCollectiveInt(dm, nRanks1, 3);
  PetscValidLogicalCollectiveInt(dm, nRanks2, 4);
  PetscCheck(!dm->setupcalled, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "This function must be called before DMSetUp()");
  PetscCall(DMGetDimension(dm, &dim));
  PetscCheck(nRanks0 == PETSC_DECIDE || nRanks0 >= 1, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "number of ranks in X direction cannot be less than 1");
  PetscCheck(dim <= 1 || nRanks1 == PETSC_DECIDE || nRanks1 >= 1, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "number of ranks in Y direction cannot be less than 1");
  PetscCheck(dim <= 2 || nRanks2 == PETSC_DECIDE || nRanks2 >= 1, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "number of ranks in Z direction cannot be less than 1");
  if (nRanks0) PetscCall(PetscMPIIntCast(nRanks0, &stag->nRanks[0]));
  if (dim > 1 && nRanks1) PetscCall(PetscMPIIntCast(nRanks1, &stag->nRanks[1]));
  if (dim > 2 && nRanks2) PetscCall(PetscMPIIntCast(nRanks2, &stag->nRanks[2]));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagSetStencilType - set elementwise ghost/halo stencil type

  Logically Collective; `stencilType` must contain common value

  Input Parameters:
+ dm          - the `DMSTAG` object
- stencilType - the elementwise ghost stencil type: `DMSTAG_STENCIL_BOX`, `DMSTAG_STENCIL_STAR`, or `DMSTAG_STENCIL_NONE`

  Level: beginner

.seealso: [](ch_stag), `DMSTAG`, `DMStagGetStencilType()`, `DMStagSetStencilWidth()`, `DMStagStencilType`
@*/
PetscErrorCode DMStagSetStencilType(DM dm, DMStagStencilType stencilType)
{
  DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  PetscValidLogicalCollectiveEnum(dm, stencilType, 2);
  PetscCheck(!dm->setupcalled, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "This function must be called before DMSetUp()");
  stag->stencilType = stencilType;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagSetStencilWidth - set elementwise stencil width

  Logically Collective; `stencilWidth` must contain common value

  Input Parameters:
+ dm           - the `DMSTAG` object
- stencilWidth - stencil/halo/ghost width in elements

  Level: beginner

  Note:
  The width value is not used when `DMSTAG_STENCIL_NONE` is specified.

.seealso: [](ch_stag), `DMSTAG`, `DMStagGetStencilWidth()`, `DMStagGetStencilType()`, `DMStagStencilType`
@*/
PetscErrorCode DMStagSetStencilWidth(DM dm, PetscInt stencilWidth)
{
  DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  PetscValidLogicalCollectiveInt(dm, stencilWidth, 2);
  PetscCheck(!dm->setupcalled, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "This function must be called before DMSetUp()");
  PetscCheck(stencilWidth >= 0, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "Stencil width must be non-negative");
  stag->stencilWidth = stencilWidth;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagSetGlobalSizes - set global element counts in each direction

  Logically Collective; `N0`, `N1`, and `N2` must contain common values

  Input Parameters:
+ dm - the `DMSTAG` object
. N0 - global elementwise size in the x direction
. N1 - global elementwise size in the y direction
- N2 - global elementwise size in the z direction

  Level: advanced

  Note:
  Arguments corresponding to higher dimensions are ignored for 1D and 2D grids.

.seealso: [](ch_stag), `DMSTAG`, `DMStagGetGlobalSizes()`, `DMDASetSizes()`
@*/
PetscErrorCode DMStagSetGlobalSizes(DM dm, PetscInt N0, PetscInt N1, PetscInt N2)
{
  DM_Stag *const stag = (DM_Stag *)dm->data;
  PetscInt       dim;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  PetscCheck(!dm->setupcalled, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "This function must be called before DMSetUp()");
  PetscCall(DMGetDimension(dm, &dim));
  PetscCheck(N0 >= 1, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_SIZ, "Number of elements in X direction must be positive");
  PetscCheck(dim <= 1 || N1 >= 0, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_SIZ, "Number of elements in Y direction must be positive");
  PetscCheck(dim <= 2 || N2 >= 0, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_SIZ, "Number of elements in Z direction must be positive");
  if (N0) stag->N[0] = N0;
  if (N1) stag->N[1] = N1;
  if (N2) stag->N[2] = N2;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagSetOwnershipRanges - set elements per rank in each direction

  Logically Collective; `lx`, `ly`, and `lz` must contain common values

  Input Parameters:
+ dm - the `DMSTAG` object
. lx - element counts for each rank in the x direction, may be `NULL`
. ly - element counts for each rank in the y direction, may be `NULL`
- lz - element counts for each rank in the z direction, may be `NULL`

  Level: developer

  Note:
  Arguments corresponding to higher dimensions are ignored for 1D and 2D grids. These arguments may be set to `NULL` in this case.

.seealso: [](ch_stag), `DMSTAG`, `DMStagSetGlobalSizes()`, `DMStagGetOwnershipRanges()`, `DMDASetOwnershipRanges()`
@*/
PetscErrorCode DMStagSetOwnershipRanges(DM dm, const PetscInt lx[], const PetscInt ly[], const PetscInt lz[])
{
  DM_Stag *const  stag = (DM_Stag *)dm->data;
  const PetscInt *lin[3];
  PetscInt        d, dim;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  PetscCheck(!dm->setupcalled, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "This function must be called before DMSetUp()");
  lin[0] = lx;
  lin[1] = ly;
  lin[2] = lz;
  PetscCall(DMGetDimension(dm, &dim));
  for (d = 0; d < dim; ++d) {
    if (lin[d]) {
      PetscCheck(stag->nRanks[d] >= 0, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "Cannot set ownership ranges before setting number of ranks");
      if (!stag->l[d]) PetscCall(PetscMalloc1(stag->nRanks[d], &stag->l[d]));
      PetscCall(PetscArraycpy(stag->l[d], lin[d], stag->nRanks[d]));
    }
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagSetRefinementFactor - set refinement ratios in each direction

  Logically Collective

  Input Parameters:
+ dm       - the `DMSTAG` object
. refine_x - ratio of fine grid to coarse in x-direction (2 by default)
. refine_y - ratio of fine grid to coarse in y-direction (2 by default)
- refine_z - ratio of fine grid to coarse in z-direction (2 by default)

  Level: intermediate

  Note:
  Pass `PETSC_IGNORE` to leave a value unchanged

.seealso: [](ch_stag), `DMSTAG`, `DMRefine()`, `DMCoarsen()`, `DMStagGetRefinementFactor()`, `DMDAGetRefinementFactor()`
@*/
PetscErrorCode DMStagSetRefinementFactor(DM dm, PetscInt refine_x, PetscInt refine_y, PetscInt refine_z)
{
  DM_Stag *const stag = (DM_Stag *)dm->data;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  if (refine_x > 0) stag->refineFactor[0] = refine_x;
  if (refine_y > 0) stag->refineFactor[1] = refine_y;
  if (refine_z > 0) stag->refineFactor[2] = refine_z;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagSetUniformCoordinates - set `DMSTAG` coordinates to be a uniform grid

  Collective

  Input Parameters:
+ dm   - the `DMSTAG` object
. xmin - minimum global coordinate value in the `x` direction
. xmax - maximum global coordinate values in the `x` direction
. ymin - minimum global coordinate value in the `y` direction
. ymax - maximum global coordinate value in the `y` direction
. zmin - minimum global coordinate value in the `z` direction
- zmax - maximum global coordinate value in the `z` direction

  Level: advanced

  Notes:
  `DMSTAG` supports 2 different types of coordinate `DM`: `DMSTAG` and `DMPRODUCT`.
  Arguments corresponding to higher dimensions are ignored for 1D and 2D grids.

  Local coordinates are populated (using `DMSetCoordinatesLocal()`), linearly
  extrapolated to ghost cells, including those outside the physical domain.
  This is also done in case of periodic boundaries, meaning that the same
  global point may have different coordinates in different local representations,
  which are equivalent assuming a periodicity implied by the arguments to this function,
  i.e. two points are equivalent if their difference is a multiple of $($`xmax` $-$ `xmin` $)$
  in the x direction, $($ `ymax` $-$ `ymin` $)$ in the y direction, and $($ `zmax` $-$ `zmin` $)$ in the z direction.

.seealso: [](ch_stag), `DMSTAG`, `DMPRODUCT`, `DMStagSetUniformCoordinatesExplicit()`, `DMStagSetUniformCoordinatesProduct()`, `DMStagSetCoordinateDMType()`, `DMGetCoordinateDM()`, `DMGetCoordinates()`, `DMDASetUniformCoordinates()`, `DMBoundaryType`
@*/
PetscErrorCode DMStagSetUniformCoordinates(DM dm, PetscReal xmin, PetscReal xmax, PetscReal ymin, PetscReal ymax, PetscReal zmin, PetscReal zmax)
{
  DM_Stag *const stag = (DM_Stag *)dm->data;
  PetscBool      flg_stag, flg_product;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  PetscCheck(dm->setupcalled, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "This function must be called after DMSetUp()");
  PetscCheck(stag->coordinateDMType, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "You must first call DMStagSetCoordinateDMType()");
  PetscCall(PetscStrcmp(stag->coordinateDMType, DMSTAG, &flg_stag));
  PetscCall(PetscStrcmp(stag->coordinateDMType, DMPRODUCT, &flg_product));
  if (flg_stag) {
    PetscCall(DMStagSetUniformCoordinatesExplicit(dm, xmin, xmax, ymin, ymax, zmin, zmax));
  } else if (flg_product) {
    PetscCall(DMStagSetUniformCoordinatesProduct(dm, xmin, xmax, ymin, ymax, zmin, zmax));
  } else SETERRQ(PetscObjectComm((PetscObject)dm), PETSC_ERR_SUP, "Unsupported DM Type %s", stag->coordinateDMType);
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagSetUniformCoordinatesExplicit - set `DMSTAG` coordinates to be a uniform grid, storing all values

  Collective

  Input Parameters:
+ dm   - the `DMSTAG` object
. xmin - minimum global coordinate value in the `x` direction
. xmax - maximum global coordinate value in the `x` direction
. ymin - minimum global coordinate value in the `y` direction
. ymax - maximum global coordinate value in the `y` direction
. zmin - minimum global coordinate value in the `z` direction
- zmax - maximum global coordinate value in the `z` direction

  Level: beginner

  Notes:
  `DMSTAG` supports 2 different types of coordinate `DM`: either another `DMSTAG`, or a `DMPRODUCT`.
  If the grid is orthogonal, using `DMPRODUCT` should be more efficient.

  Arguments corresponding to higher dimensions are ignored for 1D and 2D grids.

  See the manual page for `DMStagSetUniformCoordinates()` for information on how
  coordinates for dummy cells outside the physical domain boundary are populated.

.seealso: [](ch_stag), `DMSTAG`, `DMStagSetUniformCoordinates()`, `DMStagSetUniformCoordinatesProduct()`, `DMStagSetCoordinateDMType()`
@*/
PetscErrorCode DMStagSetUniformCoordinatesExplicit(DM dm, PetscReal xmin, PetscReal xmax, PetscReal ymin, PetscReal ymax, PetscReal zmin, PetscReal zmax)
{
  DM_Stag *const stag = (DM_Stag *)dm->data;
  PetscInt       dim;
  PetscBool      flg;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  PetscCheck(dm->setupcalled, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "This function must be called after DMSetUp()");
  PetscCall(PetscStrcmp(stag->coordinateDMType, DMSTAG, &flg));
  PetscCheck(!stag->coordinateDMType || flg, PetscObjectComm((PetscObject)dm), PETSC_ERR_PLIB, "Refusing to change an already-set DM coordinate type");
  PetscCall(DMStagSetCoordinateDMType(dm, DMSTAG));
  PetscCall(DMGetDimension(dm, &dim));
  switch (dim) {
  case 1:
    PetscCall(DMStagSetUniformCoordinatesExplicit_1d(dm, xmin, xmax));
    break;
  case 2:
    PetscCall(DMStagSetUniformCoordinatesExplicit_2d(dm, xmin, xmax, ymin, ymax));
    break;
  case 3:
    PetscCall(DMStagSetUniformCoordinatesExplicit_3d(dm, xmin, xmax, ymin, ymax, zmin, zmax));
    break;
  default:
    SETERRQ(PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "Unsupported dimension %" PetscInt_FMT, dim);
  }
  PetscCall(DMCoarsenHookRemove(dm, DMRestrictHook_Coordinates, NULL, NULL));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@
  DMStagSetUniformCoordinatesProduct - create uniform coordinates, as a product of 1D arrays

  Set the coordinate `DM` to be a `DMPRODUCT` of 1D `DMSTAG` objects, each of which have a coordinate `DM` (also a 1d `DMSTAG`) holding uniform coordinates.

  Collective

  Input Parameters:
+ dm   - the `DMSTAG` object
. xmin - minimum global coordinate value in the `x` direction
. xmax - maximum global coordinate value in the `x` direction
. ymin - minimum global coordinate value in the `y` direction
. ymax - maximum global coordinate value in the `y` direction
. zmin - minimum global coordinate value in the `z` direction
- zmax - maximum global coordinate value in the `z` direction

  Level: intermediate

  Notes:
  Arguments corresponding to higher dimensions are ignored for 1D and 2D grids.

  The per-dimension 1-dimensional `DMSTAG` objects that comprise the product
  always have active 0-cells (vertices, element boundaries) and 1-cells
  (element centers).

  See the manual page for `DMStagSetUniformCoordinates()` for information on how
  coordinates for dummy cells outside the physical domain boundary are populated.

.seealso: [](ch_stag), `DMSTAG`, `DMPRODUCT`, `DMStagSetUniformCoordinates()`, `DMStagSetUniformCoordinatesExplicit()`, `DMStagSetCoordinateDMType()`
@*/
PetscErrorCode DMStagSetUniformCoordinatesProduct(DM dm, PetscReal xmin, PetscReal xmax, PetscReal ymin, PetscReal ymax, PetscReal zmin, PetscReal zmax)
{
  DM_Stag *const stag = (DM_Stag *)dm->data;
  DM             dmc;
  PetscInt       dim, d, dof0, dof1;
  PetscBool      flg;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  PetscCheck(dm->setupcalled, PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_WRONGSTATE, "This function must be called after DMSetUp()");
  PetscCall(PetscStrcmp(stag->coordinateDMType, DMPRODUCT, &flg));
  PetscCheck(!stag->coordinateDMType || flg, PetscObjectComm((PetscObject)dm), PETSC_ERR_PLIB, "Refusing to change an already-set DM coordinate type");
  PetscCall(DMStagSetCoordinateDMType(dm, DMPRODUCT));
  PetscCall(DMGetCoordinateDM(dm, &dmc));
  PetscCall(DMGetDimension(dm, &dim));

  /* Create 1D sub-DMs, living on subcommunicators.
     Always include both vertex and element dof, regardless of the active strata of the DMStag */
  dof0 = 1;
  dof1 = 1;

  for (d = 0; d < dim; ++d) {
    DM                subdm;
    MPI_Comm          subcomm;
    PetscMPIInt       color;
    const PetscMPIInt key = 0; /* let existing rank break ties */

    /* Choose colors based on position in the plane orthogonal to this dim, and split */
    switch (d) {
    case 0:
      color = (dim > 1 ? stag->rank[1] : 0) + (dim > 2 ? stag->nRanks[1] * stag->rank[2] : 0);
      break;
    case 1:
      color = stag->rank[0] + (dim > 2 ? stag->nRanks[0] * stag->rank[2] : 0);
      break;
    case 2:
      color = stag->rank[0] + stag->nRanks[0] * stag->rank[1];
      break;
    default:
      SETERRQ(PetscObjectComm((PetscObject)dm), PETSC_ERR_SUP, "Unsupported dimension index %" PetscInt_FMT, d);
    }
    PetscCallMPI(MPI_Comm_split(PetscObjectComm((PetscObject)dm), color, key, &subcomm));

    /* Create sub-DMs living on these new communicators (which are destroyed by DMProduct) */
    PetscCall(DMStagCreate1d(subcomm, stag->boundaryType[d], stag->N[d], dof0, dof1, stag->stencilType, stag->stencilWidth, stag->l[d], &subdm));
    /* Copy vector and matrix type information from parent DM */
    PetscCall(DMSetVecType(subdm, dm->vectype));
    PetscCall(DMSetMatType(subdm, dm->mattype));
    PetscCall(DMSetUp(subdm));
    switch (d) {
    case 0:
      PetscCall(DMStagSetUniformCoordinatesExplicit(subdm, xmin, xmax, 0, 0, 0, 0));
      break;
    case 1:
      PetscCall(DMStagSetUniformCoordinatesExplicit(subdm, ymin, ymax, 0, 0, 0, 0));
      break;
    case 2:
      PetscCall(DMStagSetUniformCoordinatesExplicit(subdm, zmin, zmax, 0, 0, 0, 0));
      break;
    default:
      SETERRQ(PetscObjectComm((PetscObject)dm), PETSC_ERR_SUP, "Unsupported dimension index %" PetscInt_FMT, d);
    }
    PetscCall(DMProductSetDM(dmc, d, subdm));
    PetscCall(DMProductSetDimensionIndex(dmc, d, 0));
    PetscCall(DMDestroy(&subdm));
    PetscCallMPI(MPI_Comm_free(&subcomm));
  }
  PetscCall(DMCoarsenHookRemove(dm, DMRestrictHook_Coordinates, NULL, NULL));
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@C
  DMStagVecGetArray - get access to local array

  Logically Collective

  Input Parameters:
+ dm  - the `DMSTAG` object
- vec - the `Vec` object

  Output Parameter:
. array - the array

  Level: beginner

  Note:
  This function returns a (dim+1)-dimensional array for a dim-dimensional
  `DMSTAG`.

  The first 1-3 dimensions indicate an element in the global
  numbering, using the standard C ordering.

  The final dimension in this array corresponds to a degree
  of freedom with respect to this element, for example corresponding to
  the element or one of its neighboring faces, edges, or vertices.

  For example, for a 3D `DMSTAG`, indexing is `array[k][j][i][slot]`, where `k` is the
  index in the z-direction, `j` is the index in the y-direction, and `i` is the
  index in the x-direction.

  `slot` is obtained with `DMStagGetLocationSlot()`, since the correct offset
  into the $(d+1)$-dimensional C array for a $d$-dimensional `DMSTAG` depends on the grid size and the number
  of DOF stored at each location.

  `DMStagVecRestoreArray()` must be called, once finished with the array

.seealso: [](ch_stag), `DMSTAG`, `DMStagVecGetArrayRead()`, `DMStagGetLocationSlot()`, `DMGetLocalVector()`, `DMCreateLocalVector()`, `DMGetGlobalVector()`, `DMCreateGlobalVector()`, `DMDAVecGetArray()`, `DMDAVecGetArrayDOF()`
@*/
PetscErrorCode DMStagVecGetArray(DM dm, Vec vec, void *array)
{
  DM_Stag *const stag = (DM_Stag *)dm->data;
  PetscInt       dim;
  PetscInt       nLocal;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  PetscValidHeaderSpecific(vec, VEC_CLASSID, 2);
  PetscCall(DMGetDimension(dm, &dim));
  PetscCall(VecGetLocalSize(vec, &nLocal));
  PetscCheck(nLocal == stag->entriesGhost, PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "Vector local size %" PetscInt_FMT " is not compatible with DMStag local size %" PetscInt_FMT, nLocal, stag->entriesGhost);
  switch (dim) {
  case 1:
    PetscCall(VecGetArray2d(vec, stag->nGhost[0], stag->entriesPerElement, stag->startGhost[0], 0, (PetscScalar ***)array));
    break;
  case 2:
    PetscCall(VecGetArray3d(vec, stag->nGhost[1], stag->nGhost[0], stag->entriesPerElement, stag->startGhost[1], stag->startGhost[0], 0, (PetscScalar ****)array));
    break;
  case 3:
    PetscCall(VecGetArray4d(vec, stag->nGhost[2], stag->nGhost[1], stag->nGhost[0], stag->entriesPerElement, stag->startGhost[2], stag->startGhost[1], stag->startGhost[0], 0, (PetscScalar *****)array));
    break;
  default:
    SETERRQ(PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "Unsupported dimension %" PetscInt_FMT, dim);
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@C
  DMStagVecGetArrayRead - get read-only access to a local array

  Logically Collective

  See the man page for `DMStagVecGetArray()` for more information.

  Input Parameters:
+ dm  - the `DMSTAG` object
- vec - the `Vec` object

  Output Parameter:
. array - the read-only array

  Level: beginner

  Note:
  `DMStagVecRestoreArrayRead()` must be called, once finished with the array

.seealso: [](ch_stag), `DMSTAG`, `DMStagGetLocationSlot()`, `DMGetLocalVector()`, `DMCreateLocalVector()`, `DMGetGlobalVector()`, `DMCreateGlobalVector()`, `DMDAVecGetArrayRead()`, `DMDAVecGetArrayDOFRead()`
@*/
PetscErrorCode DMStagVecGetArrayRead(DM dm, Vec vec, void *array)
{
  DM_Stag *const stag = (DM_Stag *)dm->data;
  PetscInt       dim;
  PetscInt       nLocal;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  PetscValidHeaderSpecific(vec, VEC_CLASSID, 2);
  PetscCall(DMGetDimension(dm, &dim));
  PetscCall(VecGetLocalSize(vec, &nLocal));
  PetscCheck(nLocal == stag->entriesGhost, PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "Vector local size %" PetscInt_FMT " is not compatible with DMSTAG local size %" PetscInt_FMT, nLocal, stag->entriesGhost);
  switch (dim) {
  case 1:
    PetscCall(VecGetArray2dRead(vec, stag->nGhost[0], stag->entriesPerElement, stag->startGhost[0], 0, (PetscScalar ***)array));
    break;
  case 2:
    PetscCall(VecGetArray3dRead(vec, stag->nGhost[1], stag->nGhost[0], stag->entriesPerElement, stag->startGhost[1], stag->startGhost[0], 0, (PetscScalar ****)array));
    break;
  case 3:
    PetscCall(VecGetArray4dRead(vec, stag->nGhost[2], stag->nGhost[1], stag->nGhost[0], stag->entriesPerElement, stag->startGhost[2], stag->startGhost[1], stag->startGhost[0], 0, (PetscScalar *****)array));
    break;
  default:
    SETERRQ(PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "Unsupported dimension %" PetscInt_FMT, dim);
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@C
  DMStagVecRestoreArray - restore access to a raw array

  Logically Collective

  Input Parameters:
+ dm  - the `DMSTAG` object
- vec - the `Vec` object

  Output Parameter:
. array - the array

  Level: beginner

.seealso: [](ch_stag), `DMSTAG`, `DMStagVecGetArray()`, `DMDAVecRestoreArray()`, `DMDAVecRestoreArrayDOF()`
@*/
PetscErrorCode DMStagVecRestoreArray(DM dm, Vec vec, void *array)
{
  DM_Stag *const stag = (DM_Stag *)dm->data;
  PetscInt       dim;
  PetscInt       nLocal;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  PetscValidHeaderSpecific(vec, VEC_CLASSID, 2);
  PetscCall(DMGetDimension(dm, &dim));
  PetscCall(VecGetLocalSize(vec, &nLocal));
  PetscCheck(nLocal == stag->entriesGhost, PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "Vector local size %" PetscInt_FMT " is not compatible with DMSTAG local size %" PetscInt_FMT, nLocal, stag->entriesGhost);
  switch (dim) {
  case 1:
    PetscCall(VecRestoreArray2d(vec, stag->nGhost[0], stag->entriesPerElement, stag->startGhost[0], 0, (PetscScalar ***)array));
    break;
  case 2:
    PetscCall(VecRestoreArray3d(vec, stag->nGhost[1], stag->nGhost[0], stag->entriesPerElement, stag->startGhost[1], stag->startGhost[0], 0, (PetscScalar ****)array));
    break;
  case 3:
    PetscCall(VecRestoreArray4d(vec, stag->nGhost[2], stag->nGhost[1], stag->nGhost[0], stag->entriesPerElement, stag->startGhost[2], stag->startGhost[1], stag->startGhost[0], 0, (PetscScalar *****)array));
    break;
  default:
    SETERRQ(PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "Unsupported dimension %" PetscInt_FMT, dim);
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

/*@C
  DMStagVecRestoreArrayRead - restore read-only access to a raw array

  Logically Collective

  Input Parameters:
+ dm  - the `DMSTAG` object
- vec - the Vec object

  Output Parameter:
. array - the read-only array

  Level: beginner

.seealso: [](ch_stag), `DMSTAG`, `DMStagVecGetArrayRead()`, `DMDAVecRestoreArrayRead()`, `DMDAVecRestoreArrayDOFRead()`
@*/
PetscErrorCode DMStagVecRestoreArrayRead(DM dm, Vec vec, void *array)
{
  DM_Stag *const stag = (DM_Stag *)dm->data;
  PetscInt       dim;
  PetscInt       nLocal;

  PetscFunctionBegin;
  PetscValidHeaderSpecificType(dm, DM_CLASSID, 1, DMSTAG);
  PetscValidHeaderSpecific(vec, VEC_CLASSID, 2);
  PetscCall(DMGetDimension(dm, &dim));
  PetscCall(VecGetLocalSize(vec, &nLocal));
  PetscCheck(nLocal == stag->entriesGhost, PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "Vector local size %" PetscInt_FMT " is not compatible with DMSTAG local size %" PetscInt_FMT, nLocal, stag->entriesGhost);
  switch (dim) {
  case 1:
    PetscCall(VecRestoreArray2dRead(vec, stag->nGhost[0], stag->entriesPerElement, stag->startGhost[0], 0, (PetscScalar ***)array));
    break;
  case 2:
    PetscCall(VecRestoreArray3dRead(vec, stag->nGhost[1], stag->nGhost[0], stag->entriesPerElement, stag->startGhost[1], stag->startGhost[0], 0, (PetscScalar ****)array));
    break;
  case 3:
    PetscCall(VecRestoreArray4dRead(vec, stag->nGhost[2], stag->nGhost[1], stag->nGhost[0], stag->entriesPerElement, stag->startGhost[2], stag->startGhost[1], stag->startGhost[0], 0, (PetscScalar *****)array));
    break;
  default:
    SETERRQ(PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "Unsupported dimension %" PetscInt_FMT, dim);
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}
