SpECTRE  v2024.04.12
Discontinuous Galerkin

Functions and classes specific to the Discontinuous Galerkin algorithm. More...

Classes

class  dg::SimpleMortarData< TemporalId, LocalVars, RemoteVars >
 Storage of boundary data on two sides of a mortar. More...
 
struct  Tags::Mortars< Tag, VolumeDim >
 Data on mortars, indexed by (Direction, ElementId) pairs. More...
 
struct  Tags::MortarSize< Dim >
 Size of a mortar, relative to the element face. That is, the part of the face that it covers. More...
 
class  Filters::Exponential< FilterIndex >
 A cached exponential filter. More...
 
class  dg::Actions::Filter< FilterType, tmpl::list< TagsToFilter... > >
 Applies a filter to the specified tags. More...
 
struct  Limiters::Tags::LimiterCommunicationTag< Metavariables >
 The inbox tag for limiter communication. More...
 
struct  Limiters::Actions::Limit< Metavariables >
 Receive limiter data from neighbors, then apply limiter. More...
 
struct  Limiters::Actions::SendData< Metavariables >
 Send local data needed for limiting. More...
 
class  dg::Events::ObserveFields< VolumeDim, tmpl::list< Tensors... >, tmpl::list< NonTensorComputeTags... >, ArraySectionIdTag >
 Observe volume tensor fields. More...
 

Enumerations

enum class  dg::Formulation { StrongInertial , WeakInertial }
 The DG formulation to use. More...
 

Functions

template<typename InboxTag , size_t Dim, typename TemporalIdType , typename... InboxTags>
bool dg::has_received_from_all_mortars (const TemporalIdType &temporal_id, const Element< Dim > &element, const tuples::TaggedTuple< InboxTags... > &inboxes)
 Determines if data on all mortars has been received for the InboxTag at time temporal_id. More...
 
template<size_t Dim>
void dg::metric_identity_det_jac_times_inv_jac (gsl::not_null< InverseJacobian< DataVector, Dim, Frame::ElementLogical, Frame::Inertial > * > det_jac_times_inverse_jacobian, const Mesh< Dim > &mesh, const tnsr::I< DataVector, Dim, Frame::Inertial > &inertial_coords, const Jacobian< DataVector, Dim, Frame::ElementLogical, Frame::Inertial > &jacobian)
 Compute the Jacobian determinant times the inverse Jacobian so that the result is divergence-free. More...
 
template<size_t Dim>
void dg::metric_identity_jacobian_quantities (gsl::not_null< InverseJacobian< DataVector, Dim, Frame::ElementLogical, Frame::Inertial > * > det_jac_times_inverse_jacobian, gsl::not_null< InverseJacobian< DataVector, Dim, Frame::ElementLogical, Frame::Inertial > * > inverse_jacobian, gsl::not_null< Jacobian< DataVector, Dim, Frame::ElementLogical, Frame::Inertial > * > jacobian, gsl::not_null< Scalar< DataVector > * > det_jacobian, const Mesh< Dim > &mesh, const tnsr::I< DataVector, Dim, Frame::Inertial > &inertial_coords)
 Compute the Jacobian, inverse Jacobian, and determinant of the Jacobian so that they satisfy the metric identities. More...
 
template<size_t Dim>
Mesh< Dim > dg::mortar_mesh (const Mesh< Dim > &face_mesh1, const Mesh< Dim > &face_mesh2)
 Find a mesh for a mortar capable of representing data from either of two faces. More...
 
template<size_t Dim>
MortarSize< Dim - 1 > dg::mortar_size (const ElementId< Dim > &self, const ElementId< Dim > &neighbor, size_t dimension, const OrientationMap< Dim > &orientation)
 Determine the size of the mortar (i.e., the part of the face it covers) for communicating with a neighbor. This is the size relative to the size of self, and will not generally agree with that determined by neighbor.
 
template<typename... BoundaryCorrectionTags>
void dg::lift_flux (const gsl::not_null< Variables< tmpl::list< BoundaryCorrectionTags... > > * > boundary_correction_terms, const size_t extent_perpendicular_to_boundary, const Scalar< DataVector > &magnitude_of_face_normal)
 Lifts the flux contribution from an interface to the volume. More...
 
template<typename... FluxTags>
auto dg::lift_flux (Variables< tmpl::list< FluxTags... > > flux, const size_t extent_perpendicular_to_boundary, const Scalar< DataVector > &magnitude_of_face_normal) -> Variables< tmpl::list< db::remove_tag_prefix< FluxTags >... > >
 Lifts the flux contribution from an interface to the volume. More...
 
template<typename Tags , size_t Dim>
void dg::project_to_mortar (const gsl::not_null< Variables< Tags > * > result, const Variables< Tags > &vars, const Mesh< Dim > &face_mesh, const Mesh< Dim > &mortar_mesh, const MortarSize< Dim > &mortar_size)
 Project variables from a face to a mortar.
 
template<typename Tags , size_t Dim>
Variables< Tags > dg::project_to_mortar (const Variables< Tags > &vars, const Mesh< Dim > &face_mesh, const Mesh< Dim > &mortar_mesh, const MortarSize< Dim > &mortar_size)
 Project variables from a face to a mortar.
 
template<typename Tags , size_t Dim>
void dg::project_from_mortar (const gsl::not_null< Variables< Tags > * > result, const Variables< Tags > &vars, const Mesh< Dim > &face_mesh, const Mesh< Dim > &mortar_mesh, const MortarSize< Dim > &mortar_size)
 Project variables from a mortar to a face.
 
template<typename Tags , size_t Dim>
Variables< Tags > dg::project_from_mortar (const Variables< Tags > &vars, const Mesh< Dim > &face_mesh, const Mesh< Dim > &mortar_mesh, const MortarSize< Dim > &mortar_size)
 Project variables from a mortar to a face.
 

Detailed Description

Functions and classes specific to the Discontinuous Galerkin algorithm.

Enumeration Type Documentation

◆ Formulation

enum class dg::Formulation
strong

The DG formulation to use.

  • The StrongInertial formulation is also known as the integrate then transform formulation. The "Inertial" part of the name refers to the fact that the integration is done over the physical/inertial coordinates, while the "strong" part refers to the fact that the boundary correction terms are zero if the solution is continuous at the interfaces. See [178] for an overview.
  • The WeakInertial formulation is also known as the integrate then transform formulation. The "Inertial" part of the name refers to the fact that the integration is done over the physical/inertial coordinates, while the "weak" part refers to the fact that the boundary correction terms are non-zero even if the solution is continuous at the interfaces. See [178] for an overview.

Function Documentation

◆ has_received_from_all_mortars()

template<typename InboxTag , size_t Dim, typename TemporalIdType , typename... InboxTags>
bool dg::has_received_from_all_mortars ( const TemporalIdType &  temporal_id,
const Element< Dim > &  element,
const tuples::TaggedTuple< InboxTags... > &  inboxes 
)

Determines if data on all mortars has been received for the InboxTag at time temporal_id.

The InboxTag must hold a container indexed by the TemporalIdType, representing received neighbor data at a given time. Each element in the container, i.e. the received neighbor data at a given time, must be another container indexed by the dg::MortarId<Dim>. The value it holds is not relevant for this function. Here's an example for such a type:

template <size_t Dim>
struct InboxTag {
};

◆ lift_flux() [1/2]

template<typename... BoundaryCorrectionTags>
void dg::lift_flux ( const gsl::not_null< Variables< tmpl::list< BoundaryCorrectionTags... > > * >  boundary_correction_terms,
const size_t  extent_perpendicular_to_boundary,
const Scalar< DataVector > &  magnitude_of_face_normal 
)

Lifts the flux contribution from an interface to the volume.

The lifting operation takes the (d-1)-dimensional flux term at the interface and computes the corresponding d-dimensional term in the volume. SpECTRE implements an efficient DG method in which each interface grid point contributes only to that same grid point of the volume.

Details

SpECTRE implements a DG method with a diagonalized mass matrix (also known as a mass-lumping scheme). This choice gives a large reduction in the computational cost of the lifting operation, however, the scheme is slightly less accurate, especially when the grid is deformed by non-trivial Jacobians. For more details on the diagonalization of the mass matrix and its implications, [178], especially Section 3.

Note
The result is still provided only on the boundary grid. The values away from the boundary are zero and are not stored.

◆ lift_flux() [2/2]

template<typename... FluxTags>
auto dg::lift_flux ( Variables< tmpl::list< FluxTags... > >  flux,
const size_t  extent_perpendicular_to_boundary,
const Scalar< DataVector > &  magnitude_of_face_normal 
) -> Variables<tmpl::list<db::remove_tag_prefix<FluxTags>...>>

Lifts the flux contribution from an interface to the volume.

The lifting operation takes the (d-1)-dimensional flux term at the interface and computes the corresponding d-dimensional term in the volume. SpECTRE implements an efficient DG method in which each interface grid point contributes only to that same grid point of the volume.

Details

SpECTRE implements a DG method with a diagonalized mass matrix (also known as a mass-lumping scheme). This choice gives a large reduction in the computational cost of the lifting operation, however, the scheme is slightly less accurate, especially when the grid is deformed by non-trivial Jacobians. For more details on the diagonalization of the mass matrix and its implications, [178], especially Section 3.

Note
The result is still provided only on the boundary grid. The values away from the boundary are zero and are not stored.

◆ metric_identity_det_jac_times_inv_jac()

template<size_t Dim>
void dg::metric_identity_det_jac_times_inv_jac ( gsl::not_null< InverseJacobian< DataVector, Dim, Frame::ElementLogical, Frame::Inertial > * >  det_jac_times_inverse_jacobian,
const Mesh< Dim > &  mesh,
const tnsr::I< DataVector, Dim, Frame::Inertial > &  inertial_coords,
const Jacobian< DataVector, Dim, Frame::ElementLogical, Frame::Inertial > &  jacobian 
)

Compute the Jacobian determinant times the inverse Jacobian so that the result is divergence-free.

The metric identities are given by

\begin{align*} \partial_{\hat{\imath}}\left(J\frac{\partial\xi^{\hat{\imath}}} {\partial x^i}\right)=0. \end{align*}

We want to compute \(J\partial\xi^{\hat{\imath}}/\partial x^i\) in such a way that the above metric identity is satisfied numerically/discretely. We refer to the inverse Jacobian computed this way as the "metric identity-satisfying inverse Jacobian".

The discretized form, with the Jacobian determinant \(J\) expanded, is given by

\begin{align*} 2\left(J\frac{\partial \xi^{\hat{\imath}}}{\partial x^i}\right)_{s} &=\epsilon_{ijk} \sum_{t_{\hat{1}}} \epsilon^{\hat{\imath}\hat{1}\hat{k}} D^{(\hat{1})}_{s t_1} \left(x^j\frac{\partial x^k}{\partial \xi^{\hat{k}}}\right)_{t} \notag \\ &+\epsilon_{ijk} \sum_{t_{\hat{2}}} \epsilon^{\hat{\imath}\hat{2}\hat{k}} D^{(\hat{2})}_{st_2} \left(x^j\frac{\partial x^k}{\partial \xi^{\hat{k}}}\right)_{t} \notag \\ &+\epsilon_{ijk} \sum_{t_{\hat{3}}}\epsilon^{\hat{\imath}\hat{3}\hat{k}} D^{(\hat{3})}_{st_3} \left(x^j\frac{\partial x^k}{\partial \xi^{\hat{k}}}\right)_{t}. \end{align*}

where indices \(s,t,t_1,t_2\) and \(t_3\) are over grid points. \(t_i\) are the grid points in the particular logical direction.

In 1d we have:

\begin{align*} J\frac{\partial \xi^{\hat{\imath}}}{\partial x^i}= = J\frac{\partial \xi^{\hat{i}}}{\partial x^i} = 1 \end{align*}

In 2d we have:

\begin{align*} J\frac{\partial \xi^{\hat{1}}}{\partial x^1}&=D^{\hat{(2)}}x^2 & J\frac{\partial \xi^{\hat{2}}}{\partial x^1}&=-D^{\hat{(1)}}x^2 \\ J\frac{\partial \xi^{\hat{1}}}{\partial x^2}&=-D^{\hat{(2)}}x^1 & J\frac{\partial \xi^{\hat{2}}}{\partial x^2}&=D^{\hat{(1)}}x^1 \\ \end{align*}

In 3d we have:

\begin{align*} 2J\frac{\partial \xi^{\hat{1}}}{\partial x^1}&= D^{(\hat{2})}\left(x^2\frac{\partial x^3}{\partial \xi^{\hat{3}}}\right) -D^{(\hat{2})}\left(x^3\frac{\partial x^2}{\partial \xi^{\hat{3}}}\right) +D^{(\hat{3})}\left(x^3\frac{\partial x^2}{\partial \xi^{\hat{2}}}\right) -D^{(\hat{3})}\left(x^2\frac{\partial x^3}{\partial \xi^{\hat{2}}}\right)\\ 2J\frac{\partial \xi^{\hat{2}}}{\partial x^1}&= D^{(\hat{1})}\left(x^3\frac{\partial x^2}{\partial\xi^{\hat{3}}}\right) -D^{(\hat{1})}\left(x^2\frac{\partial x^3}{\partial\xi^{\hat{3}}}\right) +D^{(\hat{3})}\left(x^2\frac{\partial x^3}{\partial\xi^{\hat{1}}}\right) -D^{(\hat{3})}\left(x^3\frac{\partial x^2}{\partial\xi^{\hat{1}}}\right) \\ 2J\frac{\partial \xi^{\hat{3}}}{\partial x^1}&= D^{(\hat{1})}\left(x^2\frac{\partial x^3}{\partial\xi^{\hat{2}}}\right) -D^{(\hat{1})}\left(x^3\frac{\partial x^2}{\partial\xi^{\hat{2}}}\right) +D^{(\hat{2})}\left(x^3\frac{\partial x^2}{\partial\xi^{\hat{1}}}\right) -D^{(\hat{2})}\left(x^2\frac{\partial x^3}{\partial\xi^{\hat{1}}}\right)\\ 2J\frac{\partial \xi^{\hat{1}}}{\partial x^2}&= D^{(\hat{2})}\left(x^3\frac{\partial x^1}{\partial \xi^{\hat{3}}}\right) -D^{(\hat{2})}\left(x^1\frac{\partial x^3}{\partial \xi^{\hat{3}}}\right) +D^{(\hat{3})}\left(x^1\frac{\partial x^3}{\partial \xi^{\hat{2}}}\right) -D^{(\hat{3})}\left(x^3\frac{\partial x^1}{\partial \xi^{\hat{2}}}\right) \\ 2J\frac{\partial \xi^{\hat{2}}}{\partial x^2}&= D^{(\hat{1})}\left(x^1\frac{\partial x^3}{\partial \xi^{\hat{3}}}\right) -D^{(\hat{1})}\left(x^3\frac{\partial x^1}{\partial \xi^{\hat{3}}}\right) +D^{(\hat{3})}\left(x^3\frac{\partial x^1}{\partial \xi^{\hat{1}}}\right) -D^{(\hat{3})}\left(x^1\frac{\partial x^3}{\partial \xi^{\hat{1}}}\right)\\ 2J\frac{\partial \xi^{\hat{3}}}{\partial x^2}&= D^{(\hat{1})}\left(x^3\frac{\partial x^1}{\partial \xi^{\hat{2}}}\right) -D^{(\hat{1})}\left(x^1\frac{\partial x^3}{\partial \xi^{\hat{2}}}\right) +D^{(\hat{2})}\left(x^1\frac{\partial x^3}{\partial \xi^{\hat{1}}}\right) -D^{(\hat{2})}\left(x^3\frac{\partial x^1}{\partial \xi^{\hat{1}}}\right) \\ 2J\frac{\partial \xi^{\hat{1}}}{\partial x^3}&= D^{(\hat{2})}\left(x^1\frac{\partial x^2}{\partial \xi^{\hat{3}}}\right) -D^{(\hat{2})}\left(x^2\frac{\partial x^1}{\partial \xi^{\hat{3}}}\right) +D^{(\hat{3})}\left(x^2\frac{\partial x^1}{\partial \xi^{\hat{2}}}\right) -D^{(\hat{3})}\left(x^1\frac{\partial x^2}{\partial \xi^{\hat{2}}}\right) \\ 2J\frac{\partial \xi^{\hat{2}}}{\partial x^3}&= D^{(\hat{1})}\left(x^2\frac{\partial x^1}{\partial \xi^{\hat{3}}}\right) -D^{(\hat{1})}\left(x^1\frac{\partial x^2}{\partial \xi^{\hat{3}}}\right) +D^{(\hat{3})}\left(x^1\frac{\partial x^2}{\partial \xi^{\hat{1}}}\right) -D^{(\hat{3})}\left(x^2\frac{\partial x^1}{\partial \xi^{\hat{1}}}\right) \\ 2J\frac{\partial \xi^{\hat{3}}}{\partial x^3}&= D^{(\hat{1})}\left(x^1\frac{\partial x^2}{\partial \xi^{\hat{2}}}\right) -D^{(\hat{1})}\left(x^2\frac{\partial x^1}{\partial \xi^{\hat{2}}}\right) +D^{(\hat{2})}\left(x^2\frac{\partial x^1}{\partial \xi^{\hat{1}}}\right) -D^{(\hat{2})}\left(x^1\frac{\partial x^2}{\partial \xi^{\hat{1}}}\right) \end{align*}

Again, this ensures that the metric identities are satisfied discretely. That is,

\begin{align*} \partial_{\hat{\imath}}\left(J\frac{\partial\xi^{\hat{\imath}}} {\partial x^i}\right)=0 \end{align*}

numerically.

The reason for calculating \(J\partial\xi^{\hat{\imath}}/\partial x^i\) in this manner is because in the weak form of DG (and most spectral-type methods can be recast as DG) we effectively evaluate

\begin{align*} \partial_{\hat{\imath}}\left(J\frac{\partial\xi^{\hat{\imath}}} {\partial x^i} F^i\right), \end{align*}

which should be identically zero if \(F^i\) is constant. This feature of a scheme is referred to as free-stream preserving. Note that another way to achieve free-stream preservation is to subtract off the metric identity violations. That is,

\begin{align*} \partial_{\hat{\imath}}\left(J\frac{\partial\xi^{\hat{\imath}}} {\partial x^i} F^i\right) - F^i \partial_{\hat{\imath}}\left(J\frac{\partial\xi^{\hat{\imath}}} {\partial x^i}\right). \end{align*}

The subtraction technique is most commonly used in finite difference codes.

◆ metric_identity_jacobian_quantities()

template<size_t Dim>
void dg::metric_identity_jacobian_quantities ( gsl::not_null< InverseJacobian< DataVector, Dim, Frame::ElementLogical, Frame::Inertial > * >  det_jac_times_inverse_jacobian,
gsl::not_null< InverseJacobian< DataVector, Dim, Frame::ElementLogical, Frame::Inertial > * >  inverse_jacobian,
gsl::not_null< Jacobian< DataVector, Dim, Frame::ElementLogical, Frame::Inertial > * >  jacobian,
gsl::not_null< Scalar< DataVector > * >  det_jacobian,
const Mesh< Dim > &  mesh,
const tnsr::I< DataVector, Dim, Frame::Inertial > &  inertial_coords 
)

Compute the Jacobian, inverse Jacobian, and determinant of the Jacobian so that they satisfy the metric identities.

Uses dg::metric_identity_jacobian() to compute the determinant of the Jacobian times the inverse Jacobian. By taking the determinant of this product, we can isolate \(J\), the determinant of the Jacobian. In \(d\) dimensions, we have:

\begin{align} \mathrm{det}\left(J\frac{\partial \xi^{\hat{\imath}}}{\partial x^i}\right) = J^{d-1}. \end{align}

We assume the determinant of the Jacobian is positive, which means logical and inertial coordinates have the same handedness. With this assumption, we have

\begin{align} J = \sqrt[(d-1)]{\mathrm{det}\left(J\frac{\partial \xi^{\hat{\imath}}}{\partial x^i}\right)} \end{align}

We can now compute the inverse Jacobian using:

\begin{align} \frac{\partial \xi^{\hat{\imath}}}{\partial x^i}= \frac{1}{J}\left(J\frac{\partial \xi^{\hat{\imath}}}{\partial x^i}\right) \end{align}

This guarantees that multiplying the determinant of the Jacobian by the inverse Jacobian gives a result that satisfies the metric identities. We also compute the Jacobian by inverting the inverse Jacobian, which guarantees they are (numerical) inverses of each other.

Warning
on entry jacobian must be the Jacobian to use for computing the determinant of the Jacobian times the inverse Jacobian so that it satisfies the metric identities. The jacobian can be computed analytically or numerically, either is fine. On output the jacobian is the inverse of the inverse Jacobian that satisfies the metric identities.

◆ mortar_mesh()

template<size_t Dim>
Mesh< Dim > dg::mortar_mesh ( const Mesh< Dim > &  face_mesh1,
const Mesh< Dim > &  face_mesh2 
)

Find a mesh for a mortar capable of representing data from either of two faces.

Warning
Make sure the two face meshes are oriented the same, i.e. their dimensions align. This is facilitated by the orientation passed to domain::Initialization::create_initial_mesh, for example.