Working with CellArrays

CellArrays are the storage primitive behind JustPIC particle containers. They represent a grid where every logical grid cell stores a small fixed-size payload, for example the particle slots belonging to that cell.

Instantiating a CellArray

cell_array(backend, value, ncells, ni) allocates a CellArray over an ni-shaped grid, giving every cell an ncells-shaped payload initialized to value. The payload is what holds per-particle data — coordinates, temperature, phase labels — for the particles belonging to that grid cell.

julia> using JustPIC

julia> import CellArraysIndexing as CAI

julia> ni = (2, 2)      # grid cells
(2, 2)

julia> ncells = (2,)    # payload slots per cell
(2,)

julia> CA = cell_array(JustPIC.CPU, 20.0, ncells, ni)
2×2 CellArrays.CPUCellArray{StaticArraysCore.SVector{2, Float64}, 2, 1, Float64}:
 [20.0, 20.0]  [20.0, 20.0]
 [20.0, 20.0]  [20.0, 20.0]

Indexing a CellArray

Indexing by grid cell returns the whole payload stored in that cell. This is convenient for inspection, but it materializes a StaticArray value:

julia> CA[1, 1]
2-element StaticArraysCore.SVector{2, Float64} with indices SOneTo(2):
 20.0
 20.0

It is however useful to read and mutate the data of the CellArray object directly, without instantiating a StaticArray. For this purpose, CellArraysIndexing provides @index to directly read and mutate the individual elements of the cell.

For example, to read a single element of CA:

julia> CAI.@index CA[2, 1, 1]
20.0

Here the first index selects the payload entry and the remaining indices select the grid cell. Mutation uses the same syntax:

julia> CAI.@index CA[2, 1, 1] = 0.0;

julia> CA
2×2 CellArrays.CPUCellArray{StaticArraysCore.SVector{2, Float64}, 2, 1, Float64}:
 [20.0, 0.0]   [20.0, 20.0]
 [20.0, 20.0]  [20.0, 20.0]

@cell is the companion macro for reading or writing an entire cell payload:

julia> @cell CA[1, 1]
2-element StaticArraysCore.SVector{2, Float64} with indices SOneTo(2):
 20.0
  0.0

julia> @cell CA[1, 1] = @cell(CA[1, 1]) .+ 1;

julia> CA
2×2 CellArrays.CPUCellArray{StaticArraysCore.SVector{2, Float64}, 2, 1, Float64}:
 [21.0, 1.0]   [20.0, 20.0]
 [20.0, 20.0]  [20.0, 20.0]

Backend and layout notes

cell_array dispatches on the KernelAbstractions backend, so the same call allocates a device-resident CellArray when a vendor extension is loaded. The backing layout differs between them: the CPU path uses a block length of 1, while GPU backends use a structure-of-arrays layout. Always go through CAI.@index, @cell, cellaxes and cellnum rather than indexing CA.data directly, so that code stays portable across backends.

API

JustPIC.cell_arrayFunction
cell_array(backend, x, ncells::NTuple, ni::NTuple)
cell_array(x, ncells::NTuple, ni::NTuple)

Allocate a CellArray on backend (a KernelAbstractions backend type such as CPU), with ncells entries per grid cell over a grid of size ni, and fill every entry with x.

The backend form is the preferred allocation path for particle storage and phase-ratio arrays. The backend-less form allocates on CPU.

Examples

index = cell_array(CPU, false, (24,), (64, 64))
field = cell_array(CPU, 0.0, (3,), (64, 64))
source
JustPIC.CAFunction
CA(backend, dims; eltype = Float64)

Allocate an uninitialized CellArray of size dims on backend. Extended for the GPU backends by the package extensions.

source
JustPIC.cellaxesFunction
cellaxes(A)

Return the one-based axes used to iterate over the entries inside each CellArray cell.

This is the preferred helper for loops over particle slots because it works for both scalar and multi-entry cell storage.

source
JustPIC.cellnumFunction
cellnum(A::CellArray)

Return the number of storage slots inside each logical cell of A.

For particle containers this is the number of particle slots reserved per grid cell, including inactive slots.

source
JustPIC.update_cell_halo!Function
update_cell_halo!(x::CellArray...)

Synchronize the overlapping MPI halo of one or more CellArrays in place.

This is the CellArray companion to ImplicitGlobalGrid.update_halo! and is typically used after particle coordinates or per-particle fields have changed on each rank.

Arguments

  • x: one or more CellArrays with the same logical grid layout.

Notes

  • Every provided CellArray is updated; this is convenient for particles.coords, particles.index, and particle field arrays returned by init_cell_arrays.
  • For MPI particle advection, halo exchange is usually required before move_particles! so that particles that crossed a rank boundary are visible to the neighboring rank.
  • With periodic boundary conditions, update_cell_halo! exchanges the overlap across the periodic domain boundaries as configured in init_global_grid.
  • If particles are reinjected with inject_particles!, refresh the halos again before reconstructing grid fields with particle2grid!.

Example

advection!(particles, RungeKutta2(), V, dt)
update_cell_halo!(particles.coords...)
update_cell_halo!(particle_args...)
update_cell_halo!(particles.index)
move_particles!(particles, particle_args)
inject_particles!(particles, particle_args)
particle2grid!(T, pT, particles)
source