SpECTRE
v2021.10.04

Each element in a domain has a set of internal directions which it uses for computations in its own local coordinate system. These are referred to as the logical directions \(\xi\), \(\eta\), and \(\zeta\), where \(\xi\) is the first dimension, \(\eta\) is the second dimension, and \(\zeta\) is the third dimension. In a domain with multiple elements, the logical directions are not necessarily aligned on the interfaces between two elements, as shown in the figure below. As certain operations (e.g. fluxes, limiting) communicate information across the boundaries of adjacent elements, there needs to be a class that takes into account the relative orientations of elements which neighbor each other. This class is OrientationMap.
Each Block in a Domain has a set of BlockNeighbors, which each hold an OrientationMap. In this scenario, the Block is referred to as the host, and the OrientationMap held by each BlockNeighbor is referred to as "the orientation the BlockNeighbor has with respect to the host Block." This is a convention, so we give an example of constructing and assigning the correct OrientationMaps:
In the image above, we see a domain decomposition into two Blocks, which have their logical axes rotated relative to one another. With the left block as the host Block, we see that it has a neighbor in the \(+\xi\) direction. The host Block holds a std::unordered_map
from Directions to BlockNeighbors; the BlockNeighbor itself holds an OrientationMap that determines the mapping from each logical direction in the host Block to that in the neighboring Block. That is, the OrientationMap takes as input local information (i.e. logical directions in the host's coordinate system) and returns neighbor information (i.e. logical directions in the neighbor's coordinate system). An OrientationMap is constructed by passing in the block neighbor directions that correspond to the \(+\xi\), \(+\eta\), \(+\zeta\) directions in the host. In this case, these directions in the host map to the \(+\zeta\), \(+\xi\), \(+\eta\) directions in the neighbor, respectively. This BlockNeighbor thus holds the OrientationMap constructed with the list ( \(+\zeta\), \(+\xi\), \(+\eta\)). With the right block as the host block, we see that it has a BlockNeighbor in the \(\zeta\) direction, and the OrientationMap held by this BlockNeighbor is the one constructed with the array ( \(+\eta\), \(+\zeta\), \(+\xi\)). For convenience, OrientationMap has a method inverse_map
which returns the OrientationMap that takes as input neighbor information and returns local information.
OrientationMaps need to be provided for each BlockNeighbor in each direction for each Block. This quickly becomes too large of a number to determine by hand as the number of Blocks and the number of dimensions increases. A remedy to this problem is the corner numbering scheme.
The orientation of the \({dim}\) logical directions within each element determines an ordering of the \(2^{dim}\) vertices of that element. This is called the local corner numbering scheme (Local CNS) with respect to that element. We give the ordering of the local corners below for the case of a threedimensional element:
What remains is to endow the domain decomposition with a global corner numbering (Global CNS). We give an example below:
In the image above, we see that each vertex of the twoblock domain has been assigned a number. Although each block has eight corners, four are shared among them, so there are only twelve unique corners in this domain. Any numbering may be used in the global corner numbering, so long as the each distinct corner is given a single distinct corner number.
With the Global CNS in hand, each Block inherits an ordered subset of Global CNS. The ordering in this set is determined by the ordering of the Local CNS, and the elements of the set determined by how one assigned the Global CNS to the Domain. For the image above, the Subset CNS corresponding to the left block is {0, 1, 3, 4, 6, 7, 9, 10}, while the Subset CNS corresponding to the right block is {1, 4, 7, 10, 2, 5, 8, 11}. This ordering of the Subset CNS encodes the relative orientations between each Block. Subset CNSs need to be provided for each Block in a Domain. It turns out that for very regular domains, (i.e. spherical or rectilinear) we can generate the appropriate Subset CNSs. As this is a conceptual tutorial, how to construct these domains in SpECTRE is described in the Domain Creation tutorial.
For illustrative purposes, we will use the following Domain composed of two Blocks as described above as an example. Because there are 12 corners in this Domain, we will arbitrarily assign a unique id to each corner. Knowing the orientation of the logical axes within a block, we construct a Subset CNS for each Block.
Here is one possible result, given some relative orientation between the blocks:
The values of the ids only serve to identify which corners are unique and which are shared. This is determined by the Global CNS. The order of the ids in the list is determined by the Local CNS. We take advantage of the fact that the array index of the global corner id is the number of the corner in the local CNS.
The algorithm begins by determining the shared corners between the Blocks:
The next step is to determine the local ids of these shared global ids:
The next step is to convert these ids into their binary representation. For reference, we give the binary representation for each number 07:
Here 0 and 1 indicate lower and upper in the corresponding axis (zeta, eta, xi), respectively, and the ordering has been reversed so that the rightmost column corresponds to the xi position and the leftmost column to the zeta position. Returning to the example at hand, we have:
Note that we can now read off the shared face relative to each Block easily:
Now we know that Direction<3>::upper_xi()
in Block1 corresponds to Direction<3>::lower_zeta().opposite()
in Block2.
The use of .opposite()
is a result of the Directions to a Block face being antiparallel because each Block lies on the opposite side of the shared face.
The remaining two correspondences are given by the alignment of the shared face. It is useful to know the following information:
In the Local CNS, if an edge lies along the xi direction, if one takes the two corners making up that edge and takes the difference of their ids, one always gets the result \( \pm 1\). Similarly, if the edge lies in the eta direction, the result will be \( \pm 2\). Finally, if the edge lies in the zeta direction, the result will be \( \pm 4\). We use this information to determine the alignment of the shared face:
The corresponding directions in each Block have now been deduced.
To confirm, we can use the other ids as well and arrive at the same result:
It is also possible to identify faces of a Block using the subset CNS. For example, to identify the lower zeta face with the upper zeta face of a Block where the corners are labeled {3,0,4,1,9,6,10,7}
, one may supply the lists {3,0,4,1}
and {9,6,10,7}
to the set_identified_boundaries
function.
set_identified_boundaries
function is sensitive to the order of the corners in the lists supplied as arguments. This is because the function identifies corners and edges with each other as opposed to simply faces. This allows the user to specify more peculiar boundary conditions. For example, using {3,0,4,1}
and {6,7,9,10}
to set the periodic boundaries will identify the lower zeta face with the upper zeta face, but after a rotation of a quarterturn.For reference, here are the corners to use for each face for a Block with corners labelled as {0,1,2,3,4,5,6,7}
to set up periodic boundary conditions in each dimension, i.e. a \(\mathrm{T}^3\) topology:
Face  Corners 

upper xi  {1,3,5,7} 
lower xi  {0,2,4,6} 
upper eta  {2,3,6,7} 
lower eta  {0,1,4,5} 
upper zeta  {4,5,6,7} 
lower zeta  {0,1,2,3} 