Metadata

Variables can be tagged with a variety of MetadataFlag values. These flags primarily allow an application to tell Parthenon to apply certain behaviors to each field.

Dependency Management

Several packages may add variables of the same name. These metadata flags tell Parthenon how to resolve conflicts.

  • Private: private metadata means a variable is package specific. The package name is prepended to the variable name, like a namespace. Two different variables can have the same name if they live in different packages.

  • Provides: variables with this metadata are shared, and demand they be the only ones to provide a variable. The metadata of these variables takes priority. If two packages provide the same variable with the Provides metadata, an error is raised at runtime. Provides is on by default unless overriden by another flag.

  • Requires: variables with this metadata flag are not added by a package. The package simply declares it expects the variable to exist and if it doesn’t, an error is raised.

  • Overridable: Variables with this metadata flag are provided by the package that registers them unless another package provides said variable (with the Provides flag), in which case, that other package determines what happens to that variable. If two packages request an Overridable variable, but it is not provided, it’s undefined behaviour and Parthenon warns as such.

Variable Topology

Topology essentially specifies on which place on the finite volume grid the variable is defined. These fields specify what index space the variable is allocated over. E.g. Metadata::Cell specifies that the variable is allocated over the cell index space in each block.

The following fields specify the topology of the variable, and are mutually exclusive:

  • Metadata::None: no topology specified. The variable could be anywhere, or location is not a meaningful concept for this variable.

  • Metadata::Cell: The variable is defined on cell centers. The variable is likely volume-averaged.

  • Metadata::Face: The variable is defined on cell faces. The variable is likely area-averaged.

  • Metadata::Edge: The variable is defined on cell edges. The variable is likely length-averaged.

  • Metadata::Node: The variable is defined at nodes, i.e., cell-corners. The variable might be volume-averaged, or defined pointwise.

Variable Behaviors

These flags can be used to tell an application code how to treat a variable in relation to the problem.

  • Metadata::Advected implies a variable is advected with the flow of another variable, e.g., a velocity.

  • Metadata::Conserved implies a variable obeys a conservation law.

  • Metadata::Intensive implies that the value of a variable does not scale with volume.

  • Metadata::Sparse implies that the variable is sparse and hence it may not be allocated on all blocks.

  • Metadata::ForceAllocOnNewBlocks forces allocation of a sparse variable on any new MeshBlock that may be created through AMR or load balancing.

Output

These flags specify how a variable interacts with I/O. Enable them to enable output properties.

  • Metadata::Restart implies a variable is required in restart files

  • Metadata::CoordinatesVec implies a variable should be used to

    describe coordinate positions of nodes. Must be a node-centered variable with 3 components.

Tensor properties and boundaries

For multidimensional variables, these flags specify how to treat the individual components at boundaries. For concreteness, we will discuss reflecting boundaries. But this may apply more broadly. A variable with no flag set is assumed to be a Scalar. Scalars obey Dirichlet boundary conditions at reflecting boundaries and are set to a constant value. The following flags are mutually exclusive.

  • Metadata::Vector implies the variable transforms as a vector at reflecting boundaries. And so i-th component is flipped for a boundary in the i-th direction.

  • Metadata::Tensor is the generalization of the vector boundary condition, but for tensor quantities.

Independent/Derived

These flags specify to an application code, and the infrastructure, whether or not a variable is part of independent state. Derived quantities can be calculated from the set of independent quantities, while independent quantities cannot. The following flags are mutually exclusive and required. All variables should be either independent or derived.

  • Metadata::Independent implies the variable is part of independent state. In particular, implies data is in restart files and is prolongated/restricted during remeshing. Buffers for a coarse grid are allocated for independent variables.

  • Metadata::Derived implies the variable can be calculated, given the independent state. This is the default.

Communication

These flags specify both how ghost zones are treated, and whether variables are copied or not in multiple stages.

  • Metadata::OneCopy are shared between stages. They are only ever allocated once.

  • Metadata::FillGhost specifies that ghost zones for this variable must be filled via communication or boundary conditions. This is not always required. OneCopy variables, for example, may not need this.

  • Metadata::Flux specifies that elements shared with neighbor blocks at coarse-fine boundaries are communicated and corrected during flux correction.

Ghost Zones, Communication, and Fluxes

Depending on a combination of flags, extra communication buffers and classes may be allocated. The behaviours are the following:

  • If Metadata::FillGhosts is set, boundary conditions data is set, MPI communication buffers are allocated, and buffers for a coarse grid are allocated. These buffers are one-copy, meaning they are shared between all instances of a variable in all Containers in a DataCollection.

  • If Metadata::WithFluxes is set, a new one-copy variable with the

correct topological type for a flux in the generalized Stokes theorem sense (e.g. if the WithFluxes variable has Metadata::Cell set the new variable will have Metadata::Face) will be created in the package with the name bnd_flux::name_of_original_variable and Metadata::Flux and Metadata::OneCopy. When creating packs that include fluxes, the new flux field will be included in the flux portion of the pack if the parent field is in the pack.

  • If Metadata::Flux is set, this field is exchanged on shared elements across fine-coarse boundaries when the flux correction tasks are called. If ``Metadata::Flux`` and ``Metadata::Face`` are both set, the underlying array that stores the field will be one smaller than a regular face field in its x1, x2, and x3 dimensions so that it has the same shape in the final three dimensions as a cell centered field. This is to align the memory of cell variables and their fluxes, both for performance reasons and to ensure backward compatibilty for downstream codes.

  • If Metadata::ForceRemeshComm is set, the variable is communicated between ranks during remeshing. Variables with Metadata::Independent and/or Metadata::FillGhost are also automatically communicated when a block is communicated from one process to another. Other variables are not communicated across ranks, since the Parthenon model assumes that all fields are either Independent or Derived and that the Derived fields can be reconstructed from only the Independent fields by calling FillDerived. Nevertheless, it is sometimes useful to pass certain Derived fields between ranks during remeshing rather than rebuild them (e.g. the initial guesses for a root find that may converge slowly or not at all without a good initial guess). This flag should be used with caution, since it has the possibility the possibility to mask errors in the ``FillDerived`` implementation in downstream codes.

Requesting or excluding flux variables from searches

As discussed above, fluxes are themselves Metadata::OneCopy variables. A flux variable will have Metadata::Flux flag set. Several functions allow one to request variables by various properties such as name, unique ID, or metadata flag. These functions often take an optional enum argument FluxRequest. This variable can take on the values FluxRequest::Any, FluxRequest::NoFlux, and FluxRequest::OnlyFlux. The default is FluxRequest::NoFlux. Specifying FluxRequest::NoFlux enforces that no variables returned by the search will be flux variables. Specifying FluxRequest::OnlyFlux specifically pulls out the flux variable associated with the variable requested. FluxRequest::Any does not modify search parameters. You will get flux or non-flux variables, and variable associations will be ignored.

Application Metadata Flags

Applications can allocate their own flags by calling Metadata::AllocateNewFlag("FlagName"). For example:

using parthenon::Metadata;
using parthenon::MetadataFlag;

MetadataFlag const my_app_flag = Metadata::AllocateNewFlag("MyAppFlag");

These can be used in all the same contexts that the built-in metadata flags are used. Parthenon will not interpret them in any way - it’s up to the application to interpret them.

A user-registered metadata flag can be retrieved from the infrastructure by, for example:

MetadataFlag const my_app_flag = Metadata::GetUserFlag("MyAppFlag");

Note that this call will return an error if a flag is requested that hasn’t been registered.

Flag Collections

The Metadata::FlagCollection class provides a way to express a desire for a collection of Parthenon fields that satisfy some combinations of MetadataFlags. In particular, a FlagCollection specifies for a desire for fields with:

  • At least one of the flags in the Unions property of the FlagCollection

  • All of the flags in the Intersections property of the FlagCollection

  • None of the flags in the Exclusions property of the FlagCollection

Flag collections can be constructed from a C++ standard library container of MetadataFlag objects, or simply a comma separated list of them. For example:

using parthenon::Metadata;
using parthenon::MetadataFlag;
using FS_t = Metadata::FlagCollection
// Constructor from a container
FS_t set1(std::vector<MetadataFlag>{Metadata::Cell, Metadata::Face});
// Constructor from a comma separated list
FS_t set2(Metadata::Requires, Metadata::Overridable);

By default constructor arguments go into the Intersections property of the FlagCollection. However, if a container is passed into the constructor, you can also pass in an optional boolean flag to specify whether or not you want to match any flags instead of all flag. This adds the constructor arguments to the Unions property of the FlagCollection.

// Implicit construction from a container, which
// requests EITHER the following flags instead of BOTH
FS_t set2({Metadata::Independent, Metadata::FillGhost}, true);

The flags contained in the Unions, Intersections, and Exclusions properties of the FlagCollection can be extracted via equivalently named accessors, which return a std::set. For example:

const std::set<MetadataFlag> &u = set1.Unions();
const std::set<MetadataFlag> &i = set1.Intersections();
const std::set<MetadataFlag> &e = set1.Exclusions();

For the most part, you should not need these accessors. They are used by Parthenon internal functions, such as variable and meshblock packing, to compute the correct variables to pack.

You can add flags to these property fields with the TakeUnion, TakeIntersection, and Exclude methods. These methods take either a standard library container of metadata flags, or another FlagCollection instance. For example, you could write:

FS_t my_set;
my_set.TakeUnion(std::vector<MetadataFlag>{Flag1, Flag2});
my_set.TakeIntersection(Flag3, Flag4);
my_set.Exclude(Flag5, Flag6);

which expresses a desire for particles/fields with EITHER Flag1 or Flag2 AND Flag3 AND Flag4 and NOT Flag5 or Flag6. Note that these methods accept standard library containers as well as simple comma-separated lists.

The FlagCollection class supports algebraic operations, although they are not entirely consistent with standard arithmetic order of operations. In particular:

// this could also be auto s = set1 || set2;
auto s = set1 + set2;

produces a set s with the a unions field which is the set union of the union fields of set1 and set2. However,

// this could also be s = set1 && set2;
auto s = set1 * set2;

Produces a set s with a “unions” field of set1 and an intersections field containing the original intersections of set1, and the intersections of set2.

auto s = Set1 - Set2

Produces a set s with the “unions” and “intersections” fields of set1 and an exclusion field containing set1’s exlcusion field as well as ALL THREE fields (union, intersection, exclusion) contained by set2.

This feels unintuitive, but it makes expressions like

auto s = FS_t({Flag1, Flag2},true) * FS_t({Flag3, Flag4}) - FS_t({Flag5, Flag6})

behave in an intuitive way. This translates to a desire for particles/fields with EITHER Flag1 or Flag2 AND Flag3 AND Flag4 and NOT Flag5 or Flag6.

When in doubt about arithmetic with FlagCollections, aggressively use parenthesis to enforce the order of operations you expect.

Note that the unary inverse operator is not supported.