Containers

MeshBlockData

A MeshBlockData container owns Variables. Each Variable is named, and the MeshBlockData container knows about various types of Variables, such as whether the Variable is for cell-centered data, face-centered data, sparse data, or dense data. (For more details on anonymous variables, see here.) Variables in a MeshBlockData container can be different shapes, e.g., scalar, tensor, etc. A Variable can be added to a MeshBlockData container as:

parthenon::MeshBlockData.Add(name, metadata, shape)

where the name is a string, the metadata is a std::vector of metadata flags, and the shape is a std::vector of integers specifying the dimensions.

Note that if a location, such as Metadata::Cell or Metadata::Face, then shape is the shape of the variable at a given point. If you have a scalar, you do not need to specify the shape in Add. If you want a vector field with 3 components, you would use

shape = std::vector<int>({3});

and if you wanted a 3x3 rank-2 tensor field, you would use

shape = std::vector<int>({3,3});

If you do not specify a location on the grid which the variable lives, then shape is the shape of the full array. I.e., a 11x12x13x14 array would be added with a shape of

shape = std::vector<int>({11,12,13,14});

It is often desirable to extract from a MeshBlockData container a specific set of Variables that have desired names, sparse ids, or conform to specific metadata flags. This set of Variables must be collected in such a way that it can be accessed easily and performantly on a GPU, and such that one can index into the collection in a known way. This capability is provided by ``VariablePack``s.

To extract a VariablePack for Variables with a set of names, call

meshblock_data.PackVariables(names, map)

where names is a std::vector of strings and map is an instance of a parthenon::PackIndexMap. This will return a VariablePack object, which is essentially a Kokkos::View of parthenon::ParArray3Ds. The map will be filled by reference as a map from Variable names to indices in the VariablePack.

Similar methods are available for metadata and sparse IDs:

meshblock_data.PackVariables(metadata, ids, map)
meshblock_data.PackVariables(metadata, map)

If you would like all Variables in a MeshBlockData container, you can ommit the metadata or name arguments:

meshblock_data.PackVariables(map)

If you do not care about indexing into Variables by name, you can ommit the map argument in any of the above calls.

For examples of use, see here.

Coordinates

Variable packs contain a coords object, which is the coordinates on the meshblock for which the variables were packed. It can be accessed via

pack.GetCoords()

or

pack.GetCoords(i);

for any i. This latter API is used for consistency with MeshBlockPacks.

MeshData and MeshBlockPacks

Kokkos kernel launches come with an overhead (e.g., about 6 microsecond on a V100). For small kernels that perform little work (e.g., because of the simplicity of the kernel itself or the small number of cells per MeshBlock, say 163 or smaller), this can be a performance bottleneck when each kernel is launched per MeshBlock. Parthenon therefore provides the capability to combine variables into a single data structure that spans some number of meshblocks, the MeshBlockPack.

MeshBlockPacks created automatically and accessed transparently through MeshData objects. These MeshData objects are stored as a DataCollection of shared pointers in the Mesh object.

IMPORTANT, MeshData and MeshBlockPack are considered to be higher level representations of lower level data, i.e., the data used in the simulation itself always needs to be registered as MeshBlockData first before it can be accessed through MeshData and MeshBlockPacks.

Registering MeshData

MeshData is a lightweight object that aggregates multiple MeshBlocks. Therefore, it needs to be setup/registered with some number of MeshBlocks (at least one and at most all), which is referred to as partitioning.

The Partition machinery is implemented in here.

Registration and partitioning can be controlled manually or automatically (recommended in multi-stage drivers).

Manual registration

The following steps (used in the calculate_pi example here ) are needed to manually register and fill a MeshData object.

// Number of MeshBlocks per Partition
const int pack_size = pmesh->DefaultPackSize();
// Partition all blocks of the Mesh into separate partitions containing pack_size MeshBlocks
auto partitions = partition::ToSizeN(pmesh->block_list, pack_size);
// Register a MeshData object for each partition (collection of blocks) using the partition
// number as label and containing references to the data stored in the "base" MeshBlockPack
for (int i = 0; i < partitions.size(); i++) {
  const std::string label = std::to_string(i);
  auto mesh_data = pmesh->mesh_data.Add(label);
  // assign MeshBlocks of partitions[i] and data stored in "base" MeshBlockPack to MeshData object
  mesh_data->Set(partitions[i], "base");
}

There are two partitioning functions:

// Splits container into N equally sized partitions
template <typename T, typename Container_t>
Partition_t<T> ToNPartitions(Container_t<T> &container, const int N);

// Splits container into partitions of size N
template <typename T, typename Container_t>
std::vector<std::vector<T>> ToSizeN(Container_t<T> &container, const int N);

Both functions live within the namespace parthenon::partition and Partition_t is defined as:

template<typename T>
using Partition_t = std::vector<std::vector<T>>

The pmesh->DefaultPackSize() is controlled via the pack_size variable in a parthenon input file under the parthenon/mesh input block. e.g.,

<parthenon/mesh>
pack_size = 6

A pack_size < 1 in the input file indicates the entire mesh (per MPI rank) should be contained within a single pack.

The registered MeshData can then later be accessed, for example, via the Get(label) function:

auto &md = pmesh->mesh_data.Get(std::to_string(i));

Automatic registration

For ease of use, the steps illustrated in the manual registration are automated in the mesh_data.GetOrAdd(string MeshBlockData_label, int partition_id) function (e.g., used in the advection example here ). Here, the partitioning in the background uses the default Mesh partition size (pack_size) and the the total number of partition is accesse through pmesh->DefaultNumPartitions();. Thus, a sample usage in a driver that executes Tasks on multiple partitions in parallel may look like

TaskID no_dependency(0); // no dependency
const int num_partitions = pmesh->DefaultNumPartitions();
TaskRegion &single_tasklist_per_partition = tc.AddRegion(num_partitions);
for (int i = 0; i < num_partitions; i++) {
  auto &tl = single_tasklist_per_partition[i];
  // "base" MeshBlockData of blocks in partition i
  auto &mbase = pmesh->mesh_data.GetOrAdd("base", i);
  // MeshBlockData of the previous stage of blocks in partition i
  auto &mc0 = pmesh->mesh_data.GetOrAdd(stage_name[stage - 1], i);
  // MeshBlockData of the current stage of blocks in partition i
  auto &mc1 = pmesh->mesh_data.GetOrAdd(stage_name[stage], i);

  auto my_task = tl.AddTask(no_dependency, MyTaskFunction, mbase, mc0, mc1);
}

MeshBlockPack Access and Data Layout

The MeshBlockPack is indexable as a five-dimensional Kokkos::View. The slowest moving index indexes into a 4-dimensional VariablePack. The next slowest indexes into a Variable. The fastest three index into the cells on a meshblock. They are accessed from existing MeshData objects.

For example:

// MeshData object must exists (see Registering above)
auto &meshdata_base = pmesh->mesh_data.Get("base");

// Pack all "independent" variables (of MeshBlockData)
std::vector<MetadataFlag> flags({Metadata::Independent});
auto meshblockpack = in_obj->PackVariables(flags);

// If access to the "fluxes" of the Variable is required use PackVariableAndFluxes
//auto meshblockpack = in_obj->PackVariablesAndFluxes(flags);

auto variablepack = meshblockpack(b); // Indexes into the b'th meshblock
auto var = meshblockpack(b,n); // Indexes into the n'th variable on the b'th MB
// The n'th variable in the i,j,k'th cell of the b'th meshblock
Real r = meshblockpack(b,n,k,j,i);

For convenience, MeshBlockPack also includes the following methods and fields:

// An accessor method for the coords object on each meshblock
auto &coords = meshblockpack.GetCoords(m); // gets the Coordinates_t object on the m'th MB

// The dimensionality of the simulation. Will be 1, 2, or 3.
// This is needed because components of the flux vector
// are only allocated for dimensions in use.
int ndim = meshblockpack.GetNdim();

// Get the sparse index of the n'th sparse variable in the pack.
int sparse = meshblockpack.GetSparse(n);

// The size of the n'th dimension of the pack
int dim = meshblockpack.GetDim(n);

For an example using all these methods see the FluxDivergence function in update.cpp.

Type

The types for packs are:

MeshBlockVarPack<T>

and

MeshBlockVarFluxPack<T>

which correspond to packs over MeshBlocks that contain just variables or contain variables and fluxes.