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.0It 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.0Here 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_array Function
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))JustPIC.CA Function
CA(backend, dims; eltype = Float64)Allocate an uninitialized CellArray of size dims on backend. Extended for the GPU backends by the package extensions.
JustPIC.cellaxes Function
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.
sourceJustPIC.cellnum Function
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.
sourceJustPIC.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 moreCellArrays with the same logical grid layout.
Notes
Every provided
CellArrayis updated; this is convenient forparticles.coords,particles.index, and particle field arrays returned byinit_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.The whole overlap is exchanged, so cells held by two neighboring ranks end up with identical particles, taken from the rank that owns each cell.
With periodic boundary conditions,
update_cell_halo!exchanges the overlap across the periodic domain boundaries as configured ininit_global_grid.If particles are reinjected with
inject_particles!, refresh the halos again before reconstructing grid fields withparticle2grid!.
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)