The boundary cell is accessed directly from the fluid cell through the `neighbour` offset with the inverse direction `inverse_dir`. Both of those are symbolic and are replaced by array accesses during code generation.
The arrangment of the populations in memory is abstracted away through two proxy fields
$$
f_{out}, \quad f_{in}
$$
for the populations streaming *out* of the current cell and *into* the current cell. With those, boundaries can be defined in general notation, without knowledge about the streaming pattern. For example, the NoSlip boundary is defined as:
During Code Generation, all accesses to $f_{out}$ and $f_{in}$ are replaced by accesses to `pdf_field` according to the streaming pattern.
### Example: Boundary handling after an even time step with the AA-Pattern
For the current cell, the populations flowing out are the ones written in the previous step, and the populations flowing in are those begin read in the next step. The previous step was controlled by the `AAEvenTimeStepAccessor`, and the next step is controlled by `AAOddTimeStepAccessor`. The required memory locations are thus given by:
The generated boundary kernel is of course only fit for application after an even time step. For the AA-pattern's odd time steps, a second kernel needs to be generated. The definition of the NoSlip boundary itself is the same; only the index translation arrays are different.
## Integration with the pystencils Boundary Handling Infrastructure
The existing `pystencils.boundaries.BoundaryHandling` class is extended by a new subclass `FlexibleLBMBoundaryHandling` analogously to the implementation of the existing `LatticeBoltzmannBoundaryHandling`. Its behaviour depends on the streaming pattern which is passed to its constructor. It overrides `__call__` and `_create_boundary_kernel`:
```
class FlexibleLBMBoundaryHandling(BoundaryHandling):
def __init__(self, kernel_type): # kernel_type in ['pull', 'push', 'aa', 'esotwist']
(...)
def __call__(self, **kwargs):
# Handle boundaries like in base class, but call different kernels
# depending on the time step modulus
def _create_boundary_kernel(self, **kwargs):
if self.kernel_type in ['aa', 'esotwist']:
# in-place methods: create two boundary kernels, for even and odd timesteps
else:
# two-fields methods: create only one boundary kernel
```
## Integration with waLBerla
The waLBerla boundary code generation can also be extended to generate implementations which track the time step internally and selectively call one of two implementations: