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
They have the same properties as
CenteredGrid but the
values field may reference a
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
(xˢ=(x=11, y=10) int64, yˢ=(x=10, y=11) int64, vectorᶜ=x,y) non-uniform
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).
StaggeredGrid constructor supports two modes:
StaggeredGrid(values: Tensor, extrapolation, bounds). All required fields are passed as arguments and stored as-is. The
valuestensor must have the correct shape considering the extrapolation.
StaggeredGrid(values: Any, extrapolation, bounds, resolution, **resolution). When specifying the resolution as a
Shapeor via keyword arguments, non-Tensor values can be passed for
values, such as geometries, other fields, constants or functions (see the documentation).
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
vx = tensor(np.zeros([33, 32]), spatial('x,y')) vy = tensor(np.zeros([32, 33]), spatial('x,y')) StaggeredGrid(math.stack([vx, vy], channel('vector')), 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], channel('vector')), 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], channel('vector')), 0)
StaggeredGrid[(xˢ=32, yˢ=32, vectorᶜ=2), size=(x=32, y=32) int64, extrapolation=0]
Staggered grids can also be created from other fields using
@ by passing an existing
Some field functions also return
For non-periodic staggered grids, the
values tensor is non-uniform
to reflect the different number of sample points for each component.
(xˢ=(x=9, y=10) int64, yˢ=(x=10, y=9) int64, vectorᶜ=x,y) non-uniform
Functions to get a uniform tensor:
uniform_values()interpolates the staggered values to the cell centers and returns a
at_centers()interpolates the staggered values to the cell centers and returns a
staggered_tensor()pads the internal tensor to an invariant shape with n+1 entries along all dimensions.
(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
y, the result is represented as a
CenteredGrid with shifted locations.
grid.vector['x'] # select component
CenteredGrid[(xˢ=9, yˢ=10), size=(x=0.900, y=1.000), extrapolation=0]
Grids do not support slicing along spatial dimensions because the result would be ambiguous with StaggeredGrids.
Instead, slice the
grid.values.x[3:4] # spatial slice
(xˢ=1, yˢ=(x=10, y=9) int64, vectorᶜ=x,y) non-uniform
grid.values.x # spatial slice
(yˢ=(x=10, y=9) int64, vectorᶜ=x,y) non-uniform
Slicing along batch dimensions has no special effect, this just slices the
grid.batch # batch slice
StaggeredGrid[(xˢ=10, yˢ=10, vectorᶜ=x,y), size=(x=1, y=1) int64, extrapolation=0]