Staggered grids are a key component of the marker-and-cell (MAC) method [Harlow and Welch 1965]. They sample the velocity components not at the cell centers but in staggered form at the corresponding face centers. Their main advantage is that the divergence of a cell can be computed exactly.
ΦFlow only stores valid velocity values in memory. This may require non-uniform tensors for the values since the numbers of horizontal and vertical faces are generally not equal. Depending on the boundary conditions, the outer-most values may also be redundant and, thus, not stored.
ΦFlow represents staggered grids as instances of StaggeredGrid
.
They have the same properties as CenteredGrid
but the values
field may reference a
non-uniform tensor
to reflect the varying number of x, y and z sample points.
# !pip install --quiet phiflow
from phi.flow import *
grid = StaggeredGrid(0, extrapolation.BOUNDARY, x=10, y=10)
grid.values
(~vectorᵈ=x,y, xˢ=~(x=11, y=10) int64, yˢ=~(x=10, y=11) int64) const 0.0
Here, each component of the values tensor has one more sample point in the direction it is facing.
If the extrapolation was extrapolation.ZERO
, it would be one less (see above image).
The StaggeredGrid
constructor supports two modes:
StaggeredGrid(values: Tensor, extrapolation, bounds)
.
All required fields are passed as arguments and stored as-is.
The values
tensor must have the correct shape considering the extrapolation.StaggeredGrid(values: Any, extrapolation, bounds, resolution, **resolution)
.
When specifying the resolution as a Shape
or via keyword arguments, non-Tensor values can be passed for values
,
such as geometries, other fields, constants or functions (see the documentation).Examples:
domain = dict(x=10, y=10, bounds=Box(x=1, y=1), extrapolation=extrapolation.ZERO)
grid = StaggeredGrid((1, -1), **domain) # from constant vector
grid = StaggeredGrid(Noise(), **domain) # sample analytic field
grid = StaggeredGrid(grid, **domain) # resample existing field
grid = StaggeredGrid(lambda x: math.exp(-x), **domain) # function value(location)
grid = resample(Sphere(x=0, y=0, radius=1), StaggeredGrid(0, **domain)) # no anti-aliasing
grid = resample(Sphere(x=0, y=0, radius=1), StaggeredGrid(0, **domain), soft=True) # with anti-aliasing
To construct a StaggeredGrid
from NumPy arrays (or TensorFlow/PyTorch/Jax tensors), the tensors first need to be converted to ΦFlow tensors using tensor()
or wrap()
.
vx = tensor(np.zeros([33, 32]), spatial('x,y'))
vy = tensor(np.zeros([32, 33]), spatial('x,y'))
StaggeredGrid(math.stack([vx, vy], dual(vector='x,y')), extrapolation.BOUNDARY)
vx = tensor(np.zeros([32, 32]), spatial('x,y'))
vy = tensor(np.zeros([32, 32]), spatial('x,y'))
StaggeredGrid(math.stack([vx, vy], dual(vector='x,y')), extrapolation.PERIODIC)
vx = tensor(np.zeros([31, 32]), spatial('x,y'))
vy = tensor(np.zeros([32, 31]), spatial('x,y'))
StaggeredGrid(math.stack([vx, vy], dual(vector='x,y')), 0)
Grid faces[(~vectorᵈ=x,y, xˢ=~(x=31, y=32) int64, yˢ=~(x=32, y=31) int64) float64 const 0.0, ext=0]
Staggered grids can also be created from other fields using field.at()
or @
by passing an existing StaggeredGrid
.
Some field functions also return StaggeredGrids
:
spatial_gradient()
with type=StaggeredGrid
stagger()
For non-periodic staggered grids, the values
tensor is non-uniform
to reflect the different number of sample points for each component.
grid.values
(~vectorᵈ=x,y, xˢ=~(x=9, y=10) int64, yˢ=~(x=10, y=9) int64) 0.808 ± 0.378 (0e+00...1e+00)
Functions to get a uniform tensor:
uniform_values()
interpolates the staggered values to the cell centers and returns a CenteredGrid
at_centers()
interpolates the staggered values to the cell centers and returns a CenteredGrid
staggered_tensor()
pads the internal tensor to an invariant shape with n+1 entries along all dimensions.grid.uniform_values()
(xˢ=11, yˢ=11, vectorᶜ=x,y) 0.601 ± 0.480 (0e+00...1e+00)
Like tensors, grids can be sliced using the standard syntax.
When selecting a vector component, such as x
or y
, the result is represented as a CenteredGrid
with shifted locations.
grid.vector['x'] # select component
Grid[(xˢ=9, yˢ=10) 0.808 ± 0.378 (0e+00...1e+00), ext=0]
Grids do not support slicing along spatial dimensions because the result would be ambiguous with StaggeredGrids.
Instead, slice the values
directly.
grid.values.x[3:4] # spatial slice
(~vectorᵈ=x,y, xˢ=1, yˢ=~(x=10, y=9) int64) 0.950 ± 0.209 (6e-02...1e+00)
grid.values.x[0] # spatial slice
(~vectorᵈ=x,y, yˢ=~(x=10, y=9) int64) const 1.0
Slicing along batch dimensions has no special effect, this just slices the values
.
grid.batch[0] # batch slice
Grid faces[(~vectorᵈ=x,y, xˢ=~(x=9, y=10) int64, yˢ=~(x=10, y=9) int64) 0.808 ± 0.378 (0e+00...1e+00), ext=0]
Fields can also be sliced using unstack()
.
This returns a tuple
of all slices along a dimension.