.. _boundary-conds:

Boundary Conditions
===================

Built-in boundary conditions
----------------------------

Natively, Parthenon supports three kinds of boundary conditions:

- ``periodic``
- ``outflow``

which are all imposed on variables with the ``Metadata::FillGhost``
metadata flag. To set the boundaries in each direction, set the
``*x*_bc`` variables under ``parthenon/mesh`` in the input file. e.g.,

::

   <parthenon/mesh>
   ix1_bc = outflow
   ox1_bc = outflow

   ix2_bc = outflow
   ox2_bc = outflow

   ix3_bc = periodic
   ox3_bc = periodic

sets outflow boundary conditions in the ``X1`` direction, reflecting in
``X2``, and periodic in ``X3``.

User-defined boundary conditions.
---------------------------------

You can provide your own boundary conditions by setting the the
``boundary_conditions`` function pointers in the ``ApplicationInput``
for your ``parthenon_manager``. e.g.,

.. code:: c++

   pman.app_input->RegisterBoundaryCondition(
	  parthenon::BoundaryFace::inner_x1,
	  "my_bc_name", MyBoundaryInnerX1);

where ``BoundaryFace`` is an enum defined in ``defs.hpp`` as

.. code:: c++

   // identifiers for all 6 faces of a MeshBlock
   constexpr int BOUNDARY_NFACES = 6;
   enum BoundaryFace {
     undef = -1,
     inner_x1 = 0,
     outer_x1 = 1,
     inner_x2 = 2,
     outer_x2 = 3,
     inner_x3 = 4,
     outer_x3 = 5
   };

You can then set this boundary condition by using the name you
registered in the input file:

::

   <parthenon/mesh>
   ix1_bc = my_bc_name

Boundary conditions so defined should look roughly like

.. code:: c++

   // an implementation of outflow conditions
   void MyBoundaryInnerX1(std::shared_ptr<MeshBlockData<Real>> &rc, bool coarse) {
     std::shared_ptr<MeshBlock> pmb = rc->GetBlockPointer();
     auto bounds = coarse ? pmb->c_cellbounds : pmb->cellbounds;
     int ref = bounds.GetBoundsI(IndexDomain::interior).s;
     auto q = rc->PackVariables(std::vector<MetadataFlag>{Metadata::FillGhost}, coarse);
     auto nb = IndexRange{0, q.GetDim(4) - 1};
     pmb->par_for_bndry(
         "OutflowInnerX1", nb, IndexDomain::inner_x1, coarse,
         KOKKOS_LAMBDA(const int &l, const int &k, const int &j, const int &i) {
           q(l, k, j, i) = q(l, k, j, ref);
         });
   }

Important things to note:

- The signature is a ``std::shared_ptr<MeshBlockData<Real>>`` and a boolean.
- The boolean determines whether the boundary condition is applied over ghost cells on
  a coarse buffer (for mesh refinement) or for a fine buffer.
- You can use the ``MeshBlock::par_for_bndry`` abstraction to loop over the
  appropriate cells by specifying the ``IndexDomain`` for the ghost region
  and whether or not the loop is coarse. For more information on the
  ``IndexDomain`` object, see :ref:`here <domain>`.
- You can pack over all the coarse or fine buffers of a variable with the
  ``PackVariables`` optional ``coarse`` boolean as seen here.

Other than these requirements, the ``Boundary`` object can do whatever
you like. Reference implementations of the standard boundary conditions
are available `here <https://github.com/parthenon-hpc-lab/parthenon/blob/develop/src/bvals/boundary_conditions.cpp>`__.

.. note::

  A per-variable reflecting boundary condition is available, but you
  must register it manually. To do so, simply call
  ``app_in->RegisterDefaultReflectingBoundaryConditions()`` and it
  will be available as a mesh boundary with the name ``reflecting``.
  The reason manual registration is required is to support custom
  reflecting boundary conditions in the case where a single variable
  is used as the state vector.


Per package user-defined boundary conditions.
---------------------------------

In addition to user defined *global* boundary conditions, Parthenon also supports 
registration of boundary conditions at the *per package* level. These per package 
boundary conditions are *not* controlled by parameter input in `<parthenon/mesh>`,
and they are always applied after the chosen global boundary conditions have 
been applied during `ApplyBoundaryConditions*`.

A `StateDescriptor` defining a package contains a member `UserBoundaryFunctions`, 
which is an array with an element for each boundary direction consisting of a vector 
of boundary function pointers. When the packages are resolved and associated with 
a `Mesh` object, these boundary conditions are called in the order in which they 
are registered within a package. For example, to register a boundary condition for 
your package you could do something like (see `examples/poisson_gmg/poisson_package.cpp` 
for a more complete example):

.. code:: c++

  template <CoordinateDirection DIR, BCSide SIDE>
  auto GetMyBC() {
    return [](std::shared_ptr<MeshBlockData<Real>> &rc, bool coarse) -> void {
      // Implementation of BC here
    };
  }
  
  std::shared_ptr<StateDescriptor> Initialize(ParameterInput *pin) {
    ...
    using BF = parthenon::BoundaryFace;
    pkg->UserBoundaryFunctions[BF::inner_x1].push_back(GetMyBC<X1DIR, BCSide::Inner>());
    pkg->UserBoundaryFunctions[BF::inner_x2].push_back(GetMyBC<X2DIR, BCSide::Inner>());
    ...
  }