DMStag
The DMStag (Staggered Grid DM) module provides data management for staggered grids, commonly used in finite difference/volume methods for fluid dynamics and similar applications.
Overview
DMStag is designed for problems where:
- Variables live at different grid locations (vertices, edges, faces, cell centers)
- Staggered grids provide better stability for incompressible flow
- Multiple degrees of freedom per grid location are needed
Staggered Grid Layout
In a staggered grid, different physical quantities are stored at different locations:
1D:
- Vertices: Scalar quantities (pressure, temperature)
- Elements: Flux quantities
2D:
- Vertices: Corner values
- Edges: Face-normal velocities (u on vertical edges, v on horizontal edges)
- Elements: Cell-centered values (pressure)
3D:
- Vertices: Corner values
- Edges: Edge-centered values
- Faces: Face-normal quantities
- Elements: Cell-centered values
Creating a DMStag
# 2D staggered grid
dm = DMStag(
petsclib,
MPI.COMM_WORLD,
(PETSc.DM_BOUNDARY_NONE, PETSc.DM_BOUNDARY_NONE), # boundary types
(nx, ny), # global dimensions
(dof_vertex, dof_edge, dof_element), # DOF at each location
1, # stencil width
PETSc.DMSTAG_STENCIL_BOX, # stencil type
)
dm isa PETSc.DMStag{typeof(petsclib), 2} # true: the flavour and the dimension
# 3D staggered grid
dm = DMStag(
petsclib,
MPI.COMM_WORLD,
(PETSc.DM_BOUNDARY_NONE, PETSc.DM_BOUNDARY_NONE, PETSc.DM_BOUNDARY_NONE),
(nx, ny, nz),
(dof_vertex, dof_edge, dof_face, dof_element),
1,
PETSc.DMSTAG_STENCIL_BOX,
)Accessing Data
Grid Corners and Sizes
# Get local grid extent (without ghost points)
c = PETSc.corners(dm)
# (; lower, upper, size, nextra). `lower` and `upper` are CartesianIndex{N},
# `size` and `nextra` are NTuple{N,Int}: the returns are dimension-correct, so a
# 2D DM answers with 2-tuples and `c.size[3]` is a BoundsError (naming.md §12)
# Get local grid extent (with ghost points): (; lower, upper, size), no nextra
gc = PETSc.ghost_corners(dm)Working with Vectors
# Create global and local vectors
gvec = PETSc.global_vec(dm)
lvec = PETSc.local_vec(dm)
# Transfer data between global and local
# The written vector comes first, the DM follows it (naming.md §8)
PETSc.global_to_local!(lvec, dm, gvec, PETSc.INSERT_VALUES)
PETSc.local_to_global!(gvec, dm, lvec, PETSc.ADD_VALUES)
# Refresh the ghost points of a local vector from its neighbours, in place
PETSc.local_to_local!(lvec, dm)Getting Location Indices
# Get indices (ghost-aware) for accessing specific DOF locations in a local array
indices = PETSc.local_indices(dm)
# `indices.center` and `indices.vertex` are NamedTuples keyed by axis, and a 2D
# DM yields (x = …, y = …) with no `z` (naming.md §12)
# Get indices (no ghosts) for accessing specific DOF locations in a global array
indices = PETSc.global_indices(dm)Views by field
with_field_views! checks out local vectors and hands the block one view per field. Views are indexed by the element index, ghosts included, like stencil, and their types are concrete, so a loop over them compiles to plain array code:
flow = (PETSc.face_location(dm, 1) => 0, PETSc.face_location(dm, 2) => 0,
PETSc.element_location(dm) => 0)
c = PETSc.corners(dm)
PETSc.with_field_views!(dm, x_local, r_local; fields = flow, write = (false, true)) do (Vx, Vy, P), (Rx, Ry, Rp)
for I in c.lower:c.upper
Rp[I] = Vx[I + CartesianIndex(1, 0)] - Vx[I] + Vy[I + CartesianIndex(0, 1)] - Vy[I]
end
endWithout fields, the block gets each whole array, indexed [I..., slot] with slot from dof_slot. write = false checks out read-only.
Locations and stencils
DMStag names a point by where it sits on an element: DMSTAG_LEFT, DMSTAG_DOWN, DMSTAG_BACK_DOWN_LEFT, and so on. Those names shift meaning between dimensions (DOWN is the second axis, whatever it's called in your model), so the location functions count axes instead:
PETSc.vertex_location(dm) # the element's lower corner
PETSc.face_location(dm, axis) # the face normal to `axis`, on the lower side
PETSc.edge_location(dm, a, b) # the edge touching the lower faces of `a` and `b` (3D; the vertex in 2D)
PETSc.element_location(dm) # the element interiorface_location(dm, ndims(dm)) is the last axis in any dimension. An axis outside 1:ndims(dm) throws an ArgumentError.
on_lower_side(loc, axis) says whether a location sits on the lower side of its element along axis (LEFT, DOWN, BACK). Such points have one more index along that axis than there are elements, which is what an "is this column inside the domain" check needs. It takes no DM, so it can't check axis against the dimension.
A stencil addresses one unknown: a location, the element index and a component. stencil takes the 1-based element index that corners and ghost_corners use, and PETSc's 0-based component, as dof_slot does. Indices are not bounds-checked, so ghost elements work, and the call allocates nothing:
I = corners(dm).lower
row = PETSc.stencil(dm, PETSc.face_location(dm, 1), I)
cols = [PETSc.stencil(dm, PETSc.element_location(dm), I),
PETSc.stencil(dm, PETSc.element_location(dm), I + CartesianIndex(1, 0))]Assembling with stencils
J = PETSc.PetscMat(dm)
PETSc.set_values!(J, dm, [row], cols, [-1.0, 1.0], PETSc.ADD_VALUES) # row-major block
PETSc.assemble!(J)
PETSc.zero_rows_local!(J, dm, wall_rows, 1.0) # Dirichlet rows, given as stencils
PETSc.set_values!(b, dm, wall_rows, wall_values)Under ADD_VALUES, repeated (row, column) entries in one call are summed. zero_rows_local! is collective: a rank that owns no wall passes an empty vector.
A Vector, or a prefix view view(buf, 1:n) of one, is passed to PETSc without a copy, so one scratch buffer per kind can serve blocks of every size. Any other vector is copied first.
An index set of whole fields, for a field split, takes (location, component) pairs:
flow = LibPETSc.IS(dm, PETSc.face_location(dm, 1) => 0,
PETSc.face_location(dm, 2) => 0,
PETSc.element_location(dm) => 0)
PETSc.set_fieldsplit_is!(PETSc.pc(ksp), "flow", flow)
length(flow) # the global number of indicesSetting Coordinates
# Set uniform coordinates
PETSc.set_uniform_coordinates!(dm, xmin, xmax) # 1D
PETSc.set_uniform_coordinates!(dm, xmin, xmax, ymin, ymax) # 2D
PETSc.set_uniform_coordinates!(dm, xmin, xmax, ymin, ymax, zmin, zmax) # 3D
# Get local coordinate array
coords = PETSc.local_coordinate_array(dm)
# Read the per-axis coordinates: x[i, 1] is the lower face of element i, x[i, 2] its centre
PETSc.with_product_coordinates(dm) do x, y
x[i, 2], y[j, 2]
endStencil Types
DMSTAG_STENCIL_BOX- Full box stencil (includes diagonals)DMSTAG_STENCIL_STAR- Star stencil (axis-aligned neighbors only)
Example: 2D Stokes Flow Setup
# Create staggered grid for Stokes: velocity on edges, pressure in cells
dm = DMStag(
petsclib,
MPI.COMM_WORLD,
(PETSc.DM_BOUNDARY_NONE, PETSc.DM_BOUNDARY_NONE),
(64, 64), # 64x64 grid
(0, 1, 1), # 0 DOF at vertices, 1 at edges (velocity), 1 in elements (pressure)
1,
PETSc.DMSTAG_STENCIL_BOX,
)
PETSc.set_uniform_coordinates!(dm, 0.0, 1.0, 0.0, 1.0)
# Create vectors and matrix
x = PETSc.global_vec(dm)
b = PETSc.global_vec(dm)
A = PETSc.PetscMat(dm)PetscMat(dm) stores every coupling the stencil allows, as explicit zeros. To let the first assembly define the pattern instead, call PETSc.set_matrix_preallocate_only!(dm, true) before creating the matrix.
Functions
PETSc.DMStag — Method
da = DMStag(
petsclib::PetscLib
comm::MPI.Comm,
boundary_type::NTuple{D, DMBoundaryType},
global_dim::NTuple{D, Integer},
dof_per_node::NTuple{1 + D, Integer},
stencil_width::Integer,
stencil_type;
points_per_proc::Tuple,
processors::Tuple,
setfromoptions = true,
dmsetup = true,
prefix = "",
options...
)Creates a D-dimensional distributed staggered array with the options specified using keyword arguments.
The Tuple dof_per_node specifies how many degrees of freedom are at all the staggerings in the order:
- 1D:
(vertex, element) - 2D:
(vertex, edge, element) - 3D:
(vertex, edge, face, element)
If keyword argument points_per_proc[k] isa Vector{petsclib.PetscInt} then this specifies the points per processor in dimension k.
If keyword argument processors[k] isa Integer then this specifies the number of processors used in dimension k; ignored when D == 1.
If keyword argument setfromoptions == true then set_from_options! called.
If keyword argument dmsetup == true then setup! is called.
When D == 1 the stencil_type argument is not required and ignored if specified.
External Links
- PETSc Manual:
DMStag/DMStagCreate1d
- PETSc Manual:
DMStag/DMStagCreate2d
- PETSc Manual:
DMStag/DMStagCreate3d
PETSc.DMStag — Method
DMStag(dm::DMStag, dof_per_node; setfromoptions = true, dmsetup = true, options...)A DMStag compatible with dm — same dimension, communicator and layout — but with the degrees of freedom per stratum given by dof_per_node.
The v0.4 spelling took dmsetfromoptions and slurped anything else into an options collection it never used. Both are honoured here, and options... is the same options database keyword set the other constructors take.
External Links
- PETSc Manual:
DMSTAG/DMStagCreateCompatibleDMStag
PETSc.LibPETSc.IS — Method
LibPETSc.IS(dm::DMStag, loc => dof, ...)
LibPETSc.IS(dm::DMStag, pairs::AbstractVector{<:Pair})The index set, in the global numbering, of every point of dm at the given locations and components: each pair is a LibPETSc.DMStagStencilLocation and a 0-based component, as stencil takes them. The caller owns the result. It suits set_fieldsplit_is!:
flow = LibPETSc.IS(dm, face_location(dm, 1) => 0, face_location(dm, 2) => 0,
element_location(dm) => 0)
set_fieldsplit_is!(pc(ksp), "flow", flow)External Links
- PETSc Manual:
DMStag/DMStagCreateISFromStencils
PETSc.corners — Method
corners(dm::DMStag{PetscLib, N})Returns a NamedTuple with the global indices (excluding ghost points) of the lower and upper corners as well as the size. Also included is nextra, the number of extra partial elements in each direction.
The result is dimension-correct (§12): lower and upper are CartesianIndex{N}, size and nextra are NTuple{N,Int}, with no padding to three entries. This is a break with no shim (§16).
External Links
- PETSc Manual:
DMSTAG/DMStagGetCorners
PETSc.dof_slot — Method
slot::Int = dof_slot(dm::DMStag, loc::LibPETSc.DMStagStencilLocation, dof::Int)Returns the location slot for a degree of freedom dof at a given stencil location loc in the DMStag dm. Note that the returned slot is 1-based for Julia compatibility. dof is PETSc's 0-based component number at that location, as in stencil.
PETSc.edge_location — Method
edge_location(dm::DMStag, a::Integer, b::Integer)The location of the edge touching the lower faces of axes a and b, in either order: the edge a shear component τab lives on. In 3D, (1, 2) is `DMSTAGDOWNLEFT,(1, 3)isDMSTAGBACKLEFTand(2, 3)isDMSTAGBACKDOWN. In 2D the vertex plays that role, so(1, 2)returnsDMSTAGDOWN_LEFT. A 1D DM has no edges, and an axis outside1:ndims(dm), ora == b, throws anArgumentError`.
See also vertex_location, face_location.
External Links
- PETSc Manual:
DMStag/DMStagStencilLocation
PETSc.element_location — Method
element_location(dm::DMStag)The location of the element interior, DMSTAG_ELEMENT in every dimension.
See also vertex_location, face_location, edge_location.
External Links
- PETSc Manual:
DMStag/DMStagStencilLocation
PETSc.face_location — Method
face_location(dm::DMStag, axis::Integer)The location of the face normal to axis on the lower side of each element: DMSTAG_LEFT for axis 1, DMSTAG_DOWN for axis 2 and DMSTAG_BACK for axis 3. Axes count the DM's own axes, so face_location(dm, ndims(dm)) is the last axis in any dimension. An axis outside 1:ndims(dm) throws an ArgumentError.
See also vertex_location, edge_location, element_location.
External Links
- PETSc Manual:
DMStag/DMStagStencilLocation
PETSc.ghost_corners — Method
ghost_corners(dm::DMStag{PetscLib, N})Returns a NamedTuple with the global indices (including ghost points) of the lower and upper corners as well as the size.
There is no nextra field: DMStagGetGhostCorners does not report the extra partial elements, and v0.4's docstring promised a field the function never returned. Ask corners for nextra.
Dimension-correct like corners: CartesianIndex{N} and NTuple{N,Int}.
External Links
- PETSc Manual:
DMSTAG/DMStagGetGhostCorners
PETSc.global_indices — Method
global_indices(dm::DMStag)Return indices for the central/vertex nodes of the global (non-ghosted) array built from the input dm, i.e. the process-local interior region only, excluding ghost points.
Returns
A NamedTuple with:
center:NamedTupleof ranges keyedx,y,zfor cell-centered indicesvertex:NamedTupleof ranges keyedx,y,zfor vertex indices
Both are dimension-correct (§12): a 2D DMStag yields (x = …, y = …) with no z. This is a break with no shim (§16).
Note
In Julia, array indices start at 1, whereas PETSc uses 0-based indexing. This function handles the conversion automatically.
See also
local_indices for the equivalent indices into a ghosted, local array.
PETSc.local_indices — Method
local_indices(dm::DMStag)Return indices for the central/vertex nodes of a local (ghosted) array built from the input dm. This takes ghost points into account and provides index ranges for accessing staggered data, so that e.g. array[local_indices(dm).center.x] correctly skips the ghost region on the low side.
Returns
A NamedTuple with:
center:NamedTupleof ranges keyedx,y,zfor cell-centered indicesvertex:NamedTupleof ranges keyedx,y,zfor vertex indices
Both are dimension-correct (§12): a 2D DMStag yields (x = …, y = …) with no z. This is a break with no shim (§16).
Note
In Julia, array indices start at 1, whereas PETSc uses 0-based indexing with possibly negative ghost indices. This function handles the conversion automatically.
See also
global_indices for the equivalent indices into a non-ghosted, global array.
PETSc.on_lower_side — Method
on_lower_side(loc::LibPETSc.DMStagStencilLocation, axis::Integer)Whether loc sits on the lower side of its element along axis: true for DMSTAG_LEFT, DMSTAG_DOWN_LEFT and every other location with LEFT along axis 1, DOWN along axis 2 or BACK along axis 3; false for DMSTAG_ELEMENT and for the upper and centre sides. Points on the lower side of axis have one more index along it than there are elements, since the upper boundary is stored as the lower side of element n + 1.
It takes no DM, so it cannot check axis against the dimension: any axis in 1:3 is accepted, and a 2D location such as DMSTAG_LEFT is not on the lower side along axis 3.
PETSc.on_lower_side(face_location(dm, 1), 1) # true
PETSc.on_lower_side(face_location(dm, 1), 2) # falseSee also face_location, stencil.
External Links
- PETSc Manual:
DMStag/DMStagStencilLocation
PETSc.set_uniform_coordinates! — Method
set_uniform_coordinates!(dm::DMStag, xyzmin, xyzmax)The DMStag method of set_uniform_coordinates!: sets uniform coordinates on dm in the range specified by xyzmin and xyzmax.
External Links
- PETSc Manual:
DMSTAG/DMStagSetUniformCoordinatesProduct
PETSc.set_values! — Method
set_values!(J::AbstractPetscMat, dm::DMStag, rows, cols, vals, mode = INSERT_VALUES)Write the dense block vals into J at the stencils rows × cols and return J. vals is row-major, with length(rows) * length(cols) entries. Under ADD_VALUES, entries whose row and column repeat are summed. rows, cols and vals can be any AbstractVector. A Vector or a prefix view of one, view(buf, 1:n), is passed without a copy, so a scratch buffer can be reused for blocks of any size; any other vector is copied first.
External Links
- PETSc Manual:
DMStag/DMStagMatSetValuesStencil
PETSc.set_values! — Method
set_values!(v::AbstractPetscVec, dm::DMStag, positions, vals, mode = INSERT_VALUES)Write vals into v at the stencils positions and return v. Both can be any AbstractVector of the same length. A Vector or a prefix view of one, view(buf, 1:n), is passed without a copy; any other vector is copied first.
External Links
- PETSc Manual:
DMStag/DMStagVecSetValuesStencil
PETSc.stencil — Method
stencil(dm::DMStag, loc::LibPETSc.DMStagStencilLocation, I; dof = 0)The LibPETSc.DMStagStencil addressing component dof at location loc of element I. I is a CartesianIndex{N} or an NTuple{N, Integer} with the 1-based element indices corners and ghost_corners use, and the stencil holds them 0-based, as PETSc reads them. Indices are not checked, so a ghost element (0 or N + 1 on a periodic axis) is valid. dof is PETSc's 0-based component number, as in dof_slot.
Allocation free, for use inside assembly loops.
lower = corners(dm).lower
row = stencil(dm, face_location(dm, 1), lower) # the first owned x face
col = stencil(dm, element_location(dm), lower; dof = 1) # second element componentSee also set_values!, zero_rows_local!.
External Links
- PETSc Manual:
DMStag/DMStagStencil
PETSc.vertex_location — Method
vertex_location(dm::DMStag)The location of the vertex DMStag stores with each element, its lower corner: DMSTAG_LEFT in 1D, DMSTAG_DOWN_LEFT in 2D and DMSTAG_BACK_DOWN_LEFT in 3D.
See also face_location, edge_location, element_location and stencil.
External Links
- PETSc Manual:
DMStag/DMStagStencilLocation
PETSc.with_field_views! — Method
with_field_views!(f, dm::DMStag, vecs::AbstractPetscVec...; fields = nothing, read = true, write = true)Check out the local vectors vecs of dm, call f with one argument per vector, hand the vectors back when f returns or throws, and return what f returns.
fields is a tuple of location => dof pairs, as IS takes them, with the 0-based component of stencil. For each vector f gets a tuple of views, one per field and in that order. Without fields, f gets the whole array per vector, indexed [I..., slot] with slot from dof_slot. Either way the element index I is 1-based and covers the ghost elements, as in ghost_corners, so a view and stencil address the same point.
read and write work as in with_local_array!: a Bool, or one per vector. write = false checks out read-only; the views have the same types either way. The vectors must be local vectors of dm (local_vec); anything else throws an ArgumentError before any vector is checked out.
flow = (face_location(dm, 1) => 0, face_location(dm, 2) => 0, element_location(dm) => 0)
c = corners(dm)
with_field_views!(dm, x, r; fields = flow, write = (false, true)) do (Vx, Vy, P), (Rx, Ry, Rp)
for I in c.lower:c.upper # the owned elements
Rp[I] = Vx[I + CartesianIndex(1, 0)] - Vx[I] + Vy[I + CartesianIndex(0, 1)] - Vy[I]
end
endVectors of two DMs are checked out by nesting, and a vector is handed back early by closing its block. Here the stress τ, on a second DM dm_τ with the same elements, is computed, handed back for local_to_local! to fill its ghosts, then read by the momentum equation, all inside one checkout of the flow:
xx = (element_location(dm_τ) => 0,)
with_field_views!(dm, x, r; fields = flow, write = (false, true)) do (Vx, Vy, P), (Rx, Ry, Rp)
with_field_views!(dm_τ, τ; fields = xx) do (Txx,)
for I in c.lower:c.upper
Txx[I] = 2η * (Vx[I + CartesianIndex(1, 0)] - Vx[I]) / h
end
end
local_to_local!(τ, dm_τ)
with_field_views!(dm_τ, τ; fields = xx, write = false) do (Txx,)
for I in c.lower:c.upper
Rx[I] = (Txx[I] - Txx[I - CartesianIndex(1, 0)] - P[I] + P[I - CartesianIndex(1, 0)]) / h
end
end
endExternal Links
- PETSc Manual:
DMStag/DMStagVecGetArray
- PETSc Manual:
DMStag/DMStagGetLocationSlot
PETSc.with_product_coordinates — Method
with_product_coordinates(f, dm::DMStag)Call f(x), f(x, y) or f(x, y, z) with the local coordinate arrays of dm, one per axis, and return what f returns. It needs product coordinates, which set_uniform_coordinates! sets. The arrays are PETSc's own and are read only: they are handed back when f returns or throws, and writing to them, or using them after f, is undefined.
Each array is indexed [i, slot]: i is the 1-based element index, ghost elements included, as ghost_corners gives it, and slot is 1 for the coordinate of the element's lower face and 2 for its centre.
with_product_coordinates(dm) do x, y
x[i, 1], x[i, 2] # x of the lower face and of the centre of element i
endExternal Links
- PETSc Manual:
DMStag/DMStagGetProductCoordinateArraysRead
- PETSc Manual:
DMStag/DMStagRestoreProductCoordinateArraysRead
PETSc.zero_rows_local! — Method
zero_rows_local!(J::AbstractPetscMat, dm::DMStag, rows, diag = 1; x = nothing, b = nothing)zero_rows_local! with the rows given as stencils of dm, which J was created from. Collective: a rank that owns none of the rows passes an empty vector.
External Links
- PETSc Manual:
DMStag/DMStagStencilToIndexLocal