SpECTRE Documentation Coverage Report
Current view: top level - Elliptic/DiscontinuousGalerkin/SubdomainOperator - SubdomainOperator.hpp Hit Total Coverage
Commit: 2068747df712b64688243d3254666212942d85f2 Lines: 2 33 6.1 %
Date: 2026-05-22 23:35:16
Legend: Lines: hit not hit

          Line data    Source code
       1           0 : // Distributed under the MIT License.
       2             : // See LICENSE.txt for details.
       3             : 
       4             : #pragma once
       5             : 
       6             : #include <boost/functional/hash.hpp>
       7             : #include <cstddef>
       8             : #include <optional>
       9             : #include <ostream>
      10             : #include <pup.h>
      11             : #include <tuple>
      12             : #include <unordered_map>
      13             : #include <utility>
      14             : 
      15             : #include "DataStructures/DataBox/DataBox.hpp"
      16             : #include "DataStructures/Tensor/EagerMath/Magnitude.hpp"
      17             : #include "DataStructures/Variables.hpp"
      18             : #include "Domain/Creators/Tags/ExternalBoundaryConditions.hpp"
      19             : #include "Domain/Domain.hpp"
      20             : #include "Domain/FaceNormal.hpp"
      21             : #include "Domain/Structure/Direction.hpp"
      22             : #include "Domain/Structure/DirectionMap.hpp"
      23             : #include "Domain/Structure/ElementId.hpp"
      24             : #include "Domain/Tags.hpp"
      25             : #include "Domain/Tags/FaceNormal.hpp"
      26             : #include "Domain/Tags/Faces.hpp"
      27             : #include "Domain/Tags/SurfaceJacobian.hpp"
      28             : #include "Elliptic/BoundaryConditions/ApplyBoundaryCondition.hpp"
      29             : #include "Elliptic/DiscontinuousGalerkin/DgOperator.hpp"
      30             : #include "Elliptic/DiscontinuousGalerkin/SubdomainOperator/Tags.hpp"
      31             : #include "Elliptic/DiscontinuousGalerkin/Tags.hpp"
      32             : #include "Elliptic/Systems/GetFluxesComputer.hpp"
      33             : #include "Elliptic/Systems/GetModifyBoundaryData.hpp"
      34             : #include "Elliptic/Systems/GetSourcesComputer.hpp"
      35             : #include "Elliptic/Utilities/ApplyAt.hpp"
      36             : #include "NumericalAlgorithms/DiscontinuousGalerkin/MortarHelpers.hpp"
      37             : #include "NumericalAlgorithms/DiscontinuousGalerkin/Tags.hpp"
      38             : #include "NumericalAlgorithms/Spectral/Mesh.hpp"
      39             : #include "ParallelAlgorithms/LinearSolver/Schwarz/ElementCenteredSubdomainData.hpp"
      40             : #include "ParallelAlgorithms/LinearSolver/Schwarz/OverlapHelpers.hpp"
      41             : #include "ParallelAlgorithms/LinearSolver/Schwarz/SubdomainOperator.hpp"
      42             : #include "ParallelAlgorithms/LinearSolver/Schwarz/Tags.hpp"
      43             : #include "Utilities/EqualWithinRoundoff.hpp"
      44             : #include "Utilities/ErrorHandling/Assert.hpp"
      45             : #include "Utilities/Gsl.hpp"
      46             : #include "Utilities/Serialization/PupStlCpp17.hpp"
      47             : #include "Utilities/TMPL.hpp"
      48             : 
      49             : /// Items related to the restriction of the DG operator to an element-centered
      50             : /// subdomain
      51             : namespace elliptic::dg::subdomain_operator {
      52             : 
      53             : namespace detail {
      54             : // Wrap the `Tag` in `LinearSolver::Schwarz::Tags::Overlaps`, except if it is
      55             : // included in `TakeFromCenterTags`.
      56             : template <typename Tag, typename Dim, typename OptionsGroup,
      57             :           typename TakeFromCenterTags>
      58             : struct make_overlap_tag_impl {
      59             :   using type = tmpl::conditional_t<
      60             :       tmpl::list_contains_v<TakeFromCenterTags, Tag>, Tag,
      61             :       LinearSolver::Schwarz::Tags::Overlaps<Tag, Dim::value, OptionsGroup>>;
      62             : };
      63             : 
      64             : // Wrap the `Tag` in `Tags::NeighborMortars`
      65             : template <typename Tag, typename Dim>
      66             : struct make_neighbor_mortars_tag_impl {
      67             :   using type = Tags::NeighborMortars<Tag, Dim::value>;
      68             : };
      69             : }  // namespace detail
      70             : 
      71             : /*!
      72             :  * \brief The elliptic DG operator on an element-centered subdomain
      73             :  *
      74             :  * This operator is a restriction of the full (linearized) DG-operator to an
      75             :  * element-centered subdomain with a few points overlap into neighboring
      76             :  * elements. It is a `LinearSolver::Schwarz::SubdomainOperator` to be used with
      77             :  * the Schwarz linear solver when it solves the elliptic DG operator.
      78             :  *
      79             :  * This operator requires the following tags are available on overlap regions
      80             :  * with neighboring elements:
      81             :  *
      82             :  * - Geometric quantities provided by
      83             :  *   `elliptic::dg::subdomain_operator::InitializeSubdomain`.
      84             :  * - All `System::fluxes_computer::argument_tags` and
      85             :  *   `System::sources_computer::argument_tags` (or
      86             :  *   `System::fluxes_computer_linearized::argument_tags` and
      87             :  *   `System::sources_computer_linearized::argument_tags` for nonlinear
      88             :  *   systems), except those listed in `ArgsTagsFromCenter`. The latter will be
      89             :  *   taken from the central element's DataBox, so they don't need to be made
      90             :  *   available on overlaps.
      91             :  * - The `System::fluxes_computer::argument_tags` (or
      92             :  *   `System::fluxes_computer_linearized::argument_tags`) on internal and
      93             :  *   external interfaces, except those listed in
      94             :  *   `System::fluxes_computer::volume_tags` (or
      95             :  *   `System::fluxes_computer_linearized::volume_tags`).
      96             :  *
      97             :  * Some of these tags may require communication between elements. For example,
      98             :  * nonlinear system fields are constant background fields for the linearized DG
      99             :  * operator, but are updated in every nonlinear solver iteration. Therefore, the
     100             :  * updated nonlinear fields must be communicated across overlaps between
     101             :  * nonlinear solver iterations. To perform the communication you can use
     102             :  * `LinearSolver::Schwarz::Actions::SendOverlapFields` and
     103             :  * `LinearSolver::Schwarz::Actions::ReceiveOverlapFields`, setting
     104             :  * `RestrictToOverlap` to `false`. See
     105             :  * `LinearSolver::Schwarz::SubdomainOperator` for details.
     106             :  *
     107             :  * \par Overriding boundary conditions
     108             :  * Sometimes the subdomain operator should not use the boundary conditions that
     109             :  * have been selected when setting up the domain. For example, when the
     110             :  * subdomain operator is cached between non-linear solver iterations but the
     111             :  * boundary conditions depend on the non-linear fields, the preconditioning can
     112             :  * become ineffective (see
     113             :  * `LinearSolver::Schwarz::Actions::ResetSubdomainSolver`). Another example is
     114             :  * `elliptic::subdomain_preconditioners::MinusLaplacian`, where an auxiliary
     115             :  * Poisson system is used for preconditioning that doesn't have boundary
     116             :  * conditions set up in the domain. In these cases, the boundary conditions used
     117             :  * for the subdomain operator can be overridden with the optional
     118             :  * `override_boundary_conditions` argument to the `operator()`. If the
     119             :  * overriding boundary conditions are different from those listed in
     120             :  * `Metavariables::factory_creation`, you can supply the list of
     121             :  * boundary-condition classes to the `BoundaryConditionClasses` template
     122             :  * parameter. Note that the subdomain operator always applies the _linearized_
     123             :  * boundary conditions.
     124             :  *
     125             :  * \warning The subdomain operator hasn't been tested with periodic boundary
     126             :  * conditions so far.
     127             :  */
     128             : template <typename System, typename OptionsGroup,
     129             :           typename BoundaryConditionClasses = tmpl::list<>>
     130           1 : struct SubdomainOperator
     131             :     : LinearSolver::Schwarz::SubdomainOperator<System::volume_dim> {
     132             :  public:
     133           0 :   using system = System;
     134           0 :   using options_group = OptionsGroup;
     135             : 
     136             :  private:
     137           0 :   static constexpr size_t Dim = System::volume_dim;
     138             : 
     139             :   // Operator applications happen sequentially so we don't have to keep track of
     140             :   // the temporal id
     141           0 :   static constexpr size_t temporal_id = 0;
     142             : 
     143             :   // The subdomain operator always applies the linearized DG operator
     144           0 :   static constexpr bool linearized = true;
     145             : 
     146           0 :   using BoundaryConditionsBase = typename System::boundary_conditions_base;
     147             : 
     148             :   // These are the arguments that we need to retrieve from the DataBox and pass
     149             :   // to the functions in `elliptic::dg`, both on the central element and on
     150             :   // neighbors
     151           0 :   using prepare_args_tags =
     152             :       tmpl::list<domain::Tags::Element<Dim>, domain::Tags::Mesh<Dim>,
     153             :                  domain::Tags::InverseJacobian<Dim, Frame::ElementLogical,
     154             :                                                Frame::Inertial>,
     155             :                  domain::Tags::Faces<Dim, domain::Tags::FaceNormal<Dim>>,
     156             :                  ::Tags::Mortars<domain::Tags::Mesh<Dim - 1>, Dim>,
     157             :                  ::Tags::Mortars<::Tags::MortarSize<Dim - 1>, Dim>>;
     158           0 :   using apply_args_tags = tmpl::list<
     159             :       domain::Tags::Element<Dim>, domain::Tags::Mesh<Dim>,
     160             :       domain::Tags::InverseJacobian<Dim, Frame::ElementLogical,
     161             :                                     Frame::Inertial>,
     162             :       domain::Tags::DetInvJacobian<Frame::ElementLogical, Frame::Inertial>,
     163             :       domain::Tags::DetJacobian<Frame::ElementLogical, Frame::Inertial>,
     164             :       domain::Tags::DetTimesInvJacobian<Dim, Frame::ElementLogical,
     165             :                                         Frame::Inertial>,
     166             :       domain::Tags::Faces<Dim, domain::Tags::FaceNormal<Dim>>,
     167             :       domain::Tags::Faces<Dim, domain::Tags::FaceNormalVector<Dim>>,
     168             :       domain::Tags::Faces<Dim,
     169             :                           domain::Tags::UnnormalizedFaceNormalMagnitude<Dim>>,
     170             :       domain::Tags::Faces<Dim, domain::Tags::DetSurfaceJacobian<
     171             :                                    Frame::ElementLogical, Frame::Inertial>>,
     172             :       domain::Tags::Faces<
     173             :           Dim, domain::Tags::DetTimesInvJacobian<Dim, Frame::ElementLogical,
     174             :                                                  Frame::Inertial>>,
     175             :       ::Tags::Mortars<domain::Tags::Mesh<Dim - 1>, Dim>,
     176             :       ::Tags::Mortars<::Tags::MortarSize<Dim - 1>, Dim>,
     177             :       ::Tags::Mortars<domain::Tags::DetSurfaceJacobian<Frame::ElementLogical,
     178             :                                                        Frame::Inertial>,
     179             :                       Dim>,
     180             :       ::Tags::Mortars<elliptic::dg::Tags::PenaltyFactor, Dim>,
     181             :       elliptic::dg::Tags::Massive, elliptic::dg::Tags::Formulation>;
     182           0 :   using fluxes_args_tags =
     183             :       elliptic::get_fluxes_argument_tags<System, linearized>;
     184           0 :   using sources_args_tags =
     185             :       elliptic::get_sources_argument_tags<System, linearized>;
     186           0 :   using modify_boundary_data_args_tags =
     187             :       elliptic::get_modify_boundary_data_args_tags<System, linearized>;
     188             :   // We need the fluxes args also on interfaces (internal and external). The
     189             :   // volume tags are the subset that don't have to be taken from interfaces.
     190           0 :   using fluxes_args_volume_tags =
     191             :       elliptic::get_fluxes_volume_tags<System, linearized>;
     192             : 
     193             :   // These tags can be taken directly from the central element's DataBox, even
     194             :   // when evaluating neighbors
     195           0 :   using args_tags_from_center = tmpl::remove_duplicates<tmpl::append<
     196             :       elliptic::get_fluxes_const_global_cache_tags<System, linearized>,
     197             :       elliptic::get_sources_const_global_cache_tags<System, linearized>,
     198             :       elliptic::get_modify_boundary_data_const_global_cache_tags<System,
     199             :                                                                  linearized>,
     200             :       tmpl::list<elliptic::dg::Tags::Massive,
     201             :                  elliptic::dg::Tags::Formulation>>>;
     202             : 
     203             :   // Data on neighbors is stored in the central element's DataBox in
     204             :   // `LinearSolver::Schwarz::Tags::Overlaps` maps, so we wrap the argument tags
     205             :   // with this prefix
     206           0 :   using make_overlap_tag =
     207             :       detail::make_overlap_tag_impl<tmpl::_1, tmpl::pin<tmpl::size_t<Dim>>,
     208             :                                     tmpl::pin<OptionsGroup>,
     209             :                                     tmpl::pin<args_tags_from_center>>;
     210           0 :   using prepare_args_tags_overlap =
     211             :       tmpl::transform<prepare_args_tags, make_overlap_tag>;
     212           0 :   using apply_args_tags_overlap =
     213             :       tmpl::transform<apply_args_tags, make_overlap_tag>;
     214           0 :   using fluxes_args_tags_overlap =
     215             :       tmpl::transform<fluxes_args_tags, make_overlap_tag>;
     216           0 :   using sources_args_tags_overlap =
     217             :       tmpl::transform<sources_args_tags, make_overlap_tag>;
     218           0 :   using modify_boundary_data_args_tags_overlap =
     219             :       tmpl::transform<modify_boundary_data_args_tags, make_overlap_tag>;
     220           0 :   using fluxes_args_tags_overlap_faces = tmpl::transform<
     221             :       domain::make_faces_tags<Dim, fluxes_args_tags, fluxes_args_volume_tags>,
     222             :       make_overlap_tag>;
     223             : 
     224             :   // We also need some data on the remote side of all neighbors' mortars. Such
     225             :   // data is stored in the central element's DataBox in `Tags::NeighborMortars`
     226             :   // maps
     227           0 :   using make_neighbor_mortars_tag =
     228             :       detail::make_neighbor_mortars_tag_impl<tmpl::_1,
     229             :                                              tmpl::pin<tmpl::size_t<Dim>>>;
     230             : 
     231             :  public:
     232             :   /// \warning This function is not thread-safe because it accesses mutable
     233             :   /// memory buffers.
     234             :   template <typename ResultTags, typename OperandTags, typename DbTagsList>
     235           1 :   void operator()(
     236             :       const gsl::not_null<
     237             :           LinearSolver::Schwarz::ElementCenteredSubdomainData<Dim, ResultTags>*>
     238             :           result,
     239             :       const LinearSolver::Schwarz::ElementCenteredSubdomainData<
     240             :           Dim, OperandTags>& operand,
     241             :       const db::DataBox<DbTagsList>& box,
     242             :       const std::unordered_map<std::pair<size_t, Direction<Dim>>,
     243             :                                const BoundaryConditionsBase&,
     244             :                                boost::hash<std::pair<size_t, Direction<Dim>>>>&
     245             :           override_boundary_conditions = {}) const {
     246             :     result->destructive_resize(operand);
     247             :     central_mortar_data_.clear();
     248             :     neighbors_mortar_data_.clear();
     249             : 
     250             :     // Used to retrieve items out of the DataBox to forward to functions. This
     251             :     // replaces a long series of db::get calls.
     252             :     const auto get_items = [](const auto&... args) {
     253             :       return std::forward_as_tuple(args...);
     254             :     };
     255             : 
     256             :     // Retrieve data out of the DataBox
     257             :     using tags_to_retrieve = tmpl::flatten<tmpl::list<
     258             :         domain::Tags::ExternalBoundaryConditions<Dim>,
     259             :         domain::Tags::Element<Dim>,
     260             :         ::Tags::Mortars<domain::Tags::Mesh<Dim - 1>, Dim>,
     261             :         // Data on overlaps with neighbors
     262             :         tmpl::transform<
     263             :             tmpl::flatten<tmpl::list<
     264             :                 Tags::ExtrudingExtent, domain::Tags::Element<Dim>,
     265             :                 domain::Tags::Mesh<Dim>,
     266             :                 domain::Tags::Faces<
     267             :                     Dim, domain::Tags::UnnormalizedFaceNormalMagnitude<Dim>>,
     268             :                 ::Tags::Mortars<domain::Tags::Mesh<Dim - 1>, Dim>,
     269             :                 ::Tags::Mortars<::Tags::MortarSize<Dim - 1>, Dim>,
     270             :                 // Data on the remote side of the neighbor's mortars
     271             :                 tmpl::transform<
     272             :                     tmpl::list<
     273             :                         domain::Tags::UnnormalizedFaceNormalMagnitude<Dim>,
     274             :                         domain::Tags::Mesh<Dim - 1>,
     275             :                         ::Tags::MortarSize<Dim - 1>>,
     276             :                     make_neighbor_mortars_tag>>>,
     277             :             make_overlap_tag>>>;
     278             :     const auto& [external_boundary_conditions, central_element,
     279             :                  central_mortar_meshes, all_overlap_extents,
     280             :                  all_neighbor_elements, all_neighbor_meshes,
     281             :                  all_neighbor_face_normal_magnitudes,
     282             :                  all_neighbor_mortar_meshes, all_neighbor_mortar_sizes,
     283             :                  all_neighbors_neighbor_face_normal_magnitudes,
     284             :                  all_neighbors_neighbor_mortar_meshes,
     285             :                  all_neighbors_neighbor_mortar_sizes] =
     286             :         db::apply<tags_to_retrieve>(get_items, box);
     287             :     const auto fluxes_args = db::apply<fluxes_args_tags>(get_items, box);
     288             :     const auto sources_args = db::apply<sources_args_tags>(get_items, box);
     289             :     const auto modify_boundary_data_args =
     290             :         db::apply<modify_boundary_data_args_tags>(get_items, box);
     291             :     using FluxesArgs = std::decay_t<decltype(fluxes_args)>;
     292             :     DirectionMap<Dim, FluxesArgs> fluxes_args_on_faces{};
     293             :     for (const auto& direction : Direction<Dim>::all_directions()) {
     294             :       fluxes_args_on_faces.emplace(
     295             :           direction, elliptic::util::apply_at<
     296             :                          domain::make_faces_tags<Dim, fluxes_args_tags,
     297             :                                                  fluxes_args_volume_tags>,
     298             :                          fluxes_args_volume_tags>(get_items, box, direction));
     299             :     }
     300             : 
     301             :     // Setup boundary conditions
     302             :     const auto apply_boundary_condition =
     303             :         [&box, &all_boundary_conditions = external_boundary_conditions,
     304             :          &override_boundary_conditions](const ElementId<Dim>& local_element_id,
     305             :                                         const Direction<Dim>& local_direction,
     306             :                                         auto is_overlap, const auto& map_keys,
     307             :                                         auto&&... fields_and_fluxes) {
     308             :           constexpr bool is_overlap_v =
     309             :               std::decay_t<decltype(is_overlap)>::value;
     310             :           // Get boundary conditions from domain, or use overridden boundary
     311             :           // conditions
     312             :           const auto& boundary_condition = [&all_boundary_conditions,
     313             :                                             &local_element_id, &local_direction,
     314             :                                             &override_boundary_conditions]()
     315             :               -> const BoundaryConditionsBase& {
     316             :             if (not override_boundary_conditions.empty()) {
     317             :               const auto found_overridden_boundary_conditions =
     318             :                   override_boundary_conditions.find(
     319             :                       {local_element_id.block_id(), local_direction});
     320             :               ASSERT(found_overridden_boundary_conditions !=
     321             :                          override_boundary_conditions.end(),
     322             :                      "Overriding boundary conditions in subdomain operator, "
     323             :                      "but none is available for block "
     324             :                          << local_element_id.block_id() << " in direction "
     325             :                          << local_direction
     326             :                          << ". Make sure you have considered this external "
     327             :                             "boundary of the subdomain. If this is "
     328             :                             "intentional, add support to "
     329             :                             "elliptic::dg::SubdomainOperator.");
     330             :               return found_overridden_boundary_conditions->second;
     331             :             }
     332             :             const auto& boundary_conditions =
     333             :                 all_boundary_conditions.at(local_element_id.block_id());
     334             :             ASSERT(boundary_conditions.contains(local_direction),
     335             :                    "No boundary condition is available in block "
     336             :                        << local_element_id.block_id() << " in direction "
     337             :                        << local_direction
     338             :                        << ". Make sure you are setting up boundary conditions "
     339             :                           "when creating the domain.");
     340             :             ASSERT(
     341             :                 dynamic_cast<const BoundaryConditionsBase*>(
     342             :                     boundary_conditions.at(local_direction).get()) != nullptr,
     343             :                 "The boundary condition in block "
     344             :                     << local_element_id.block_id() << " in direction "
     345             :                     << local_direction
     346             :                     << " has an unexpected type. Make sure it derives off the "
     347             :                        "'boundary_conditions_base' class set in the system.");
     348             :             return dynamic_cast<const BoundaryConditionsBase&>(
     349             :                 *boundary_conditions.at(local_direction));
     350             :           }();
     351             :           elliptic::apply_boundary_condition<
     352             :               linearized,
     353             :               tmpl::conditional_t<is_overlap_v, make_overlap_tag, void>,
     354             :               BoundaryConditionClasses>(
     355             :               boundary_condition, box, map_keys,
     356             :               std::forward<decltype(fields_and_fluxes)>(fields_and_fluxes)...);
     357             :         };
     358             : 
     359             :     // Check if the subdomain data is sparse, i.e. if some elements have zero
     360             :     // data. If they are, the operator is a lot cheaper to apply due to its
     361             :     // linearity.
     362             :     std::unordered_set<ElementId<Dim>> elements_in_subdomain{
     363             :         central_element.id()};
     364             :     std::unordered_set<ElementId<Dim>> elements_with_zero_data{};
     365             :     if (equal_within_roundoff(operand.element_data, 0.)) {
     366             :       elements_with_zero_data.insert(central_element.id());
     367             :     }
     368             :     for (const auto& [overlap_id, overlap_data] : operand.overlap_data) {
     369             :       elements_in_subdomain.insert(overlap_id.id());
     370             :       if (equal_within_roundoff(overlap_data, 0.)) {
     371             :         elements_with_zero_data.insert(overlap_id.id());
     372             :       }
     373             :     }
     374             :     const auto is_in_subdomain =
     375             :         [&elements_in_subdomain](const ElementId<Dim>& element_id) {
     376             :           return elements_in_subdomain.find(element_id) !=
     377             :                  elements_in_subdomain.end();
     378             :         };
     379             :     const auto data_is_zero = [&elements_with_zero_data, &is_in_subdomain](
     380             :                                   const ElementId<Dim>& element_id) {
     381             :       return elements_with_zero_data.find(element_id) !=
     382             :                  elements_with_zero_data.end() or
     383             :              // Data outside the subdomain is zero by definition
     384             :              not is_in_subdomain(element_id);
     385             :     };
     386             :     const bool central_data_is_zero = data_is_zero(central_element.id());
     387             : 
     388             :     // The subdomain operator essentially does two sweeps over all elements in
     389             :     // the subdomain: In the first sweep it prepares the mortar data and stores
     390             :     // them on both sides of all mortars, and in the second sweep it consumes
     391             :     // the mortar data to apply the operator. This implementation is relatively
     392             :     // simple because it can re-use the implementation for the parallel DG
     393             :     // operator. However, it is also possible to apply the subdomain operator in
     394             :     // a single sweep over all elements, incrementally building up the mortar
     395             :     // data and applying boundary corrections immediately to both adjacent
     396             :     // elements once the data is available. That approach is possibly a
     397             :     // performance optimization but requires re-implementing a lot of logic for
     398             :     // the DG operator here. It should be considered once the subdomain operator
     399             :     // has been identified as the performance bottleneck. An alternative to
     400             :     // optimizing the subdomain operator performance is to precondition the
     401             :     // subdomain solve with a _much_ simpler subdomain operator, such as a
     402             :     // finite-difference Laplacian, so fewer applications of the more expensive
     403             :     // DG subdomain operator are necessary.
     404             : 
     405             :     // 1. Prepare mortar data on all elements in the subdomain and store them on
     406             :     //    mortars, reorienting if needed
     407             :     //
     408             :     // Prepare central element
     409             :     const auto apply_boundary_condition_center =
     410             :         [&apply_boundary_condition, &local_central_element = central_element](
     411             :             const Direction<Dim>& local_direction,
     412             :             auto&&... fields_and_fluxes) {
     413             :           apply_boundary_condition(
     414             :               local_central_element.id(), local_direction, std::false_type{},
     415             :               local_direction,
     416             :               std::forward<decltype(fields_and_fluxes)>(fields_and_fluxes)...);
     417             :         };
     418             :     db::apply<prepare_args_tags>(
     419             :         [this, &operand](const auto&... args) {
     420             :           elliptic::dg::prepare_mortar_data<System, linearized>(
     421             :               make_not_null(&central_deriv_vars_),
     422             :               make_not_null(&central_primal_fluxes_),
     423             :               make_not_null(&central_mortar_data_), operand.element_data,
     424             :               args...);
     425             :         },
     426             :         box, temporal_id, apply_boundary_condition_center, fluxes_args,
     427             :         data_is_zero);
     428             :     // Prepare neighbors
     429             :     for (const auto& [direction, neighbors] : central_element.neighbors()) {
     430             :       for (const auto& neighbor_id : neighbors) {
     431             :         const auto& orientation = neighbors.orientation(neighbor_id);
     432             :         const auto direction_from_neighbor = orientation(direction.opposite());
     433             :         const LinearSolver::Schwarz::OverlapId<Dim> overlap_id{direction,
     434             :                                                                neighbor_id};
     435             :         const auto& overlap_extent = all_overlap_extents.at(overlap_id);
     436             :         const auto& neighbor = all_neighbor_elements.at(overlap_id);
     437             :         const auto& neighbor_mesh = all_neighbor_meshes.at(overlap_id);
     438             :         const auto& mortar_id = overlap_id;
     439             :         const auto& mortar_mesh = central_mortar_meshes.at(mortar_id);
     440             :         const bool neighbor_data_is_zero = data_is_zero(neighbor_id);
     441             : 
     442             :         // Intercept empty overlaps. In the unlikely case that overlaps have
     443             :         // zero extent, meaning no point of the neighbor is part of the
     444             :         // subdomain (which is fairly useless, except for testing), the
     445             :         // subdomain is identical to the central element and no communication
     446             :         // with neighbors is necessary. We can just handle the mortar between
     447             :         // central element and neighbor and continue.
     448             :         if (UNLIKELY(overlap_extent == 0)) {
     449             :           elliptic::dg::BoundaryData<typename System::primal_fields,
     450             :                                      typename System::primal_fluxes>
     451             :               remote_boundary_data{};
     452             :           remote_boundary_data.field_data.initialize(
     453             :               mortar_mesh.number_of_grid_points(), 0.);
     454             :           central_mortar_data_.at(mortar_id).remote_insert(
     455             :               temporal_id, std::move(remote_boundary_data));
     456             :           continue;
     457             :         }
     458             : 
     459             :         // Copy the central element's mortar data to the neighbor
     460             :         if (not(central_data_is_zero and neighbor_data_is_zero)) {
     461             :           auto oriented_mortar_data =
     462             :               central_mortar_data_.at(mortar_id).local_data(temporal_id);
     463             :           if (not orientation.is_aligned()) {
     464             :             oriented_mortar_data.orient_on_slice(
     465             :                 mortar_mesh.extents(), direction.dimension(), orientation);
     466             :           }
     467             :           neighbors_mortar_data_[overlap_id][::dg::MortarId<Dim>{
     468             :                                                  direction_from_neighbor,
     469             :                                                  central_element.id()}]
     470             :               .remote_insert(temporal_id, std::move(oriented_mortar_data));
     471             :         }
     472             : 
     473             :         // Now we switch perspective to the neighbor. First, we extend the
     474             :         // overlap data to the full neighbor mesh by padding it with zeros. This
     475             :         // is necessary because spectral operators such as derivatives require
     476             :         // data on the full mesh.
     477             :         // Possible performance optimization: this copy can be avoided if the
     478             :         // overlap covers the full neighbor, so no padding with zeros is needed.
     479             :         if (not neighbor_data_is_zero) {
     480             :           LinearSolver::Schwarz::extended_overlap_data(
     481             :               make_not_null(&extended_operand_vars_[overlap_id]),
     482             :               operand.overlap_data.at(overlap_id), neighbor_mesh.extents(),
     483             :               overlap_extent, direction_from_neighbor);
     484             :         }
     485             : 
     486             :         const auto apply_boundary_condition_neighbor =
     487             :             [&apply_boundary_condition, &local_neighbor_id = neighbor_id,
     488             :              &overlap_id](const Direction<Dim>& local_direction,
     489             :                           auto&&... fields_and_fluxes) {
     490             :               apply_boundary_condition(
     491             :                   local_neighbor_id, local_direction, std::true_type{},
     492             :                   std::forward_as_tuple(overlap_id, local_direction),
     493             :                   std::forward<decltype(fields_and_fluxes)>(
     494             :                       fields_and_fluxes)...);
     495             :             };
     496             : 
     497             :         const auto fluxes_args_on_overlap =
     498             :             elliptic::util::apply_at<fluxes_args_tags_overlap,
     499             :                                      args_tags_from_center>(get_items, box,
     500             :                                                             overlap_id);
     501             : 
     502             :         elliptic::util::apply_at<prepare_args_tags_overlap,
     503             :                                  args_tags_from_center>(
     504             :             [this, &overlap_id](const auto&... args) {
     505             :               elliptic::dg::prepare_mortar_data<System, linearized>(
     506             :                   make_not_null(&neighbors_deriv_vars_[overlap_id]),
     507             :                   make_not_null(&neighbors_primal_fluxes_[overlap_id]),
     508             :                   make_not_null(&neighbors_mortar_data_[overlap_id]),
     509             :                   extended_operand_vars_[overlap_id], args...);
     510             :             },
     511             :             box, overlap_id, temporal_id, apply_boundary_condition_neighbor,
     512             :             fluxes_args_on_overlap, data_is_zero);
     513             : 
     514             :         // Copy this neighbor's mortar data to the other side of the mortars. On
     515             :         // the other side we either have the central element, or another element
     516             :         // that may or may not be part of the subdomain.
     517             :         const auto& neighbor_mortar_meshes =
     518             :             all_neighbor_mortar_meshes.at(overlap_id);
     519             :         for (const auto& neighbor_mortar_id_and_data :
     520             :              neighbors_mortar_data_.at(overlap_id)) {
     521             :           // No structured bindings because capturing these in lambdas doesn't
     522             :           // work until C++20
     523             :           const auto& neighbor_mortar_id = neighbor_mortar_id_and_data.first;
     524             :           const auto& neighbor_mortar_data = neighbor_mortar_id_and_data.second;
     525             :           const auto& neighbor_direction = neighbor_mortar_id.direction();
     526             :           const auto& neighbors_neighbor_id = neighbor_mortar_id.id();
     527             :           // No need to do anything on external boundaries
     528             :           if (neighbors_neighbor_id == ElementId<Dim>::external_boundary_id()) {
     529             :             continue;
     530             :           }
     531             :           const auto& neighbor_orientation =
     532             :               neighbor.neighbors()
     533             :                   .at(neighbor_direction)
     534             :                   .orientation(neighbors_neighbor_id);
     535             :           const auto neighbors_neighbor_direction =
     536             :               neighbor_orientation(neighbor_direction.opposite());
     537             :           const ::dg::MortarId<Dim> mortar_id_from_neighbors_neighbor{
     538             :               neighbors_neighbor_direction, neighbor_id};
     539             :           const auto send_mortar_data =
     540             :               [&neighbor_orientation, &neighbor_mortar_meshes,
     541             :                &neighbor_mortar_data, &neighbor_mortar_id, &neighbor_direction,
     542             :                &neighbor_data_is_zero,
     543             :                &data_is_zero](auto& remote_mortar_data,
     544             :                               const ElementId<Dim>& remote_element_id) {
     545             :                 if (neighbor_data_is_zero and data_is_zero(remote_element_id)) {
     546             :                   return;
     547             :                 }
     548             :                 const auto& neighbor_mortar_mesh =
     549             :                     neighbor_mortar_meshes.at(neighbor_mortar_id);
     550             :                 auto oriented_neighbor_mortar_data =
     551             :                     neighbor_mortar_data.local_data(temporal_id);
     552             :                 if (not neighbor_orientation.is_aligned()) {
     553             :                   oriented_neighbor_mortar_data.orient_on_slice(
     554             :                       neighbor_mortar_mesh.extents(),
     555             :                       neighbor_direction.dimension(), neighbor_orientation);
     556             :                 }
     557             :                 remote_mortar_data.remote_insert(
     558             :                     temporal_id, std::move(oriented_neighbor_mortar_data));
     559             :               };
     560             :           if (neighbors_neighbor_id == central_element.id() and
     561             :               mortar_id_from_neighbors_neighbor == mortar_id) {
     562             :             send_mortar_data(central_mortar_data_.at(mortar_id),
     563             :                              central_element.id());
     564             :             continue;
     565             :           }
     566             :           // Determine whether the neighbor's neighbor overlaps with the
     567             :           // subdomain and find its overlap ID if it does.
     568             :           const auto neighbors_neighbor_overlap_id =
     569             :               [&local_all_neighbor_mortar_meshes = all_neighbor_mortar_meshes,
     570             :                &neighbors_neighbor_id, &mortar_id_from_neighbors_neighbor,
     571             :                &is_in_subdomain]()
     572             :               -> std::optional<LinearSolver::Schwarz::OverlapId<Dim>> {
     573             :             if (not is_in_subdomain(neighbors_neighbor_id)) {
     574             :               return std::nullopt;
     575             :             }
     576             :             for (const auto& [local_overlap_id, local_mortar_meshes] :
     577             :                  local_all_neighbor_mortar_meshes) {
     578             :               if (local_overlap_id.id() != neighbors_neighbor_id) {
     579             :                 continue;
     580             :               }
     581             :               for (const auto& local_mortar_id_and_mesh : local_mortar_meshes) {
     582             :                 if (local_mortar_id_and_mesh.first ==
     583             :                     mortar_id_from_neighbors_neighbor) {
     584             :                   return local_overlap_id;
     585             :                 }
     586             :               }
     587             :             }
     588             :             ERROR("The neighbor's neighbor "
     589             :                   << neighbors_neighbor_id
     590             :                   << " is part of the subdomain, but we didn't find its "
     591             :                      "overlap ID. This is a bug, so please file an issue.");
     592             :           }();
     593             :           if (neighbors_neighbor_overlap_id.has_value()) {
     594             :             // The neighbor's neighbor is part of the subdomain so we copy the
     595             :             // mortar data over. Once the loop is complete we will also have
     596             :             // received mortar data back. At that point, both neighbors have a
     597             :             // copy of each other's mortar data, which is the subject of the
     598             :             // possible optimizations mentioned above. Note that the data may
     599             :             // differ by orientations.
     600             :             send_mortar_data(
     601             :                 neighbors_mortar_data_[*neighbors_neighbor_overlap_id]
     602             :                                       [mortar_id_from_neighbors_neighbor],
     603             :                 neighbors_neighbor_overlap_id->id());
     604             :           } else if (not neighbor_data_is_zero) {
     605             :             // The neighbor's neighbor does not overlap with the subdomain, so
     606             :             // we don't copy mortar data and also don't expect to receive any.
     607             :             // Instead, we assume the data on it is zero and manufacture
     608             :             // appropriate remote boundary data.
     609             :             const auto& neighbors_neighbor_mortar_mesh =
     610             :                 all_neighbors_neighbor_mortar_meshes.at(overlap_id)
     611             :                     .at(neighbor_mortar_id);
     612             :             elliptic::dg::BoundaryData<typename System::primal_fields,
     613             :                                        typename System::primal_fluxes>
     614             :                 zero_mortar_data{};
     615             :             zero_mortar_data.field_data.initialize(
     616             :                 neighbors_neighbor_mortar_mesh.number_of_grid_points(), 0.);
     617             :             neighbors_mortar_data_.at(overlap_id)
     618             :                 .at(neighbor_mortar_id)
     619             :                 .remote_insert(temporal_id, std::move(zero_mortar_data));
     620             :           }
     621             :         }  // loop over neighbor's mortars
     622             :       }  // loop over neighbors
     623             :     }  // loop over directions
     624             : 
     625             :     // 2. Apply the operator on all elements in the subdomain
     626             :     //
     627             :     // Apply on central element
     628             :     db::apply<apply_args_tags>(
     629             :         [this, &result, &operand](const auto&... args) {
     630             :           elliptic::dg::apply_operator<System, linearized>(
     631             :               make_not_null(&result->element_data),
     632             :               make_not_null(&central_mortar_data_), operand.element_data,
     633             :               central_deriv_vars_, central_primal_fluxes_, args...);
     634             :         },
     635             :         box, temporal_id, fluxes_args_on_faces, sources_args,
     636             :         modify_boundary_data_args, data_is_zero);
     637             :     // Apply on neighbors
     638             :     for (const auto& [direction, neighbors] : central_element.neighbors()) {
     639             :       for (const auto& neighbor_id : neighbors) {
     640             :         const auto& orientation = neighbors.orientation(neighbor_id);
     641             :         const auto direction_from_neighbor = orientation(direction.opposite());
     642             :         const LinearSolver::Schwarz::OverlapId<Dim> overlap_id{direction,
     643             :                                                                neighbor_id};
     644             :         const auto& overlap_extent = all_overlap_extents.at(overlap_id);
     645             :         const auto& neighbor_mesh = all_neighbor_meshes.at(overlap_id);
     646             : 
     647             :         if (UNLIKELY(overlap_extent == 0)) {
     648             :           continue;
     649             :         }
     650             : 
     651             :         DirectionMap<Dim, FluxesArgs> fluxes_args_on_overlap_faces{};
     652             :         for (const auto& neighbor_direction :
     653             :              Direction<Dim>::all_directions()) {
     654             :           fluxes_args_on_overlap_faces.emplace(
     655             :               neighbor_direction,
     656             :               elliptic::util::apply_at<fluxes_args_tags_overlap_faces,
     657             :                                        args_tags_from_center>(
     658             :                   get_items, box,
     659             :                   std::forward_as_tuple(overlap_id, neighbor_direction)));
     660             :         }
     661             : 
     662             :         elliptic::util::apply_at<apply_args_tags_overlap,
     663             :                                  args_tags_from_center>(
     664             :             [this, &overlap_id](const auto&... args) {
     665             :               elliptic::dg::apply_operator<System, linearized>(
     666             :                   make_not_null(&extended_results_[overlap_id]),
     667             :                   make_not_null(&neighbors_mortar_data_.at(overlap_id)),
     668             :                   extended_operand_vars_.at(overlap_id),
     669             :                   neighbors_deriv_vars_.at(overlap_id),
     670             :                   neighbors_primal_fluxes_.at(overlap_id), args...);
     671             :             },
     672             :             box, overlap_id, temporal_id, fluxes_args_on_overlap_faces,
     673             :             elliptic::util::apply_at<sources_args_tags_overlap,
     674             :                                      args_tags_from_center>(get_items, box,
     675             :                                                             overlap_id),
     676             :             elliptic::util::apply_at<modify_boundary_data_args_tags_overlap,
     677             :                                      args_tags_from_center>(get_items, box,
     678             :                                                             overlap_id),
     679             :             data_is_zero);
     680             : 
     681             :         // Restrict the extended operator data back to the subdomain, assuming
     682             :         // we can discard any data outside the overlaps. WARNING: This
     683             :         // assumption may break with changes to the DG operator that affect its
     684             :         // sparsity. For example, multiplying the DG operator with the _full_
     685             :         // inverse mass-matrix ("massless" scheme with no "mass-lumping"
     686             :         // approximation) means that lifted boundary corrections bleed into the
     687             :         // volume.
     688             :         // Possible performance optimization: this copy can be avoided if the
     689             :         // overlap covers the full neighbor, so no restriction is needed.
     690             :         if (UNLIKELY(
     691             :                 result->overlap_data[overlap_id].number_of_grid_points() !=
     692             :                 operand.overlap_data.at(overlap_id).number_of_grid_points())) {
     693             :           result->overlap_data[overlap_id].initialize(
     694             :               operand.overlap_data.at(overlap_id).number_of_grid_points());
     695             :         }
     696             :         LinearSolver::Schwarz::data_on_overlap(
     697             :             make_not_null(&result->overlap_data[overlap_id]),
     698             :             extended_results_.at(overlap_id), neighbor_mesh.extents(),
     699             :             overlap_extent, direction_from_neighbor);
     700             :       }  // loop over neighbors
     701             :     }  // loop over directions
     702             :   }
     703             : 
     704             :   // NOLINTNEXTLINE(google-runtime-references)
     705           0 :   void pup(PUP::er& /*p*/) {}
     706             : 
     707             :  private:
     708             :   // Memory buffers for repeated operator applications
     709             :   // NOLINTNEXTLINE(spectre-mutable)
     710             :   mutable Variables<
     711             :       db::wrap_tags_in<::Tags::deriv, typename System::primal_fields,
     712             :                        tmpl::size_t<Dim>, Frame::Inertial>>
     713           0 :       central_deriv_vars_{};
     714             :   // NOLINTNEXTLINE(spectre-mutable)
     715           0 :   mutable Variables<typename System::primal_fluxes> central_primal_fluxes_{};
     716             :   // NOLINTNEXTLINE(spectre-mutable)
     717             :   mutable LinearSolver::Schwarz::OverlapMap<
     718             :       Dim,
     719             :       Variables<db::wrap_tags_in<::Tags::deriv, typename System::primal_fields,
     720             :                                  tmpl::size_t<Dim>, Frame::Inertial>>>
     721           0 :       neighbors_deriv_vars_{};
     722             :   // NOLINTNEXTLINE(spectre-mutable)
     723             :   mutable LinearSolver::Schwarz::OverlapMap<
     724             :       Dim, Variables<typename System::primal_fluxes>>
     725           0 :       neighbors_primal_fluxes_{};
     726             :   // NOLINTNEXTLINE(spectre-mutable)
     727             :   mutable LinearSolver::Schwarz::OverlapMap<
     728             :       Dim, Variables<typename System::primal_fields>>
     729           0 :       extended_operand_vars_{};
     730             :   // NOLINTNEXTLINE(spectre-mutable)
     731             :   mutable LinearSolver::Schwarz::OverlapMap<
     732             :       Dim, Variables<typename System::primal_fields>>
     733           0 :       extended_results_{};
     734             :   // NOLINTNEXTLINE(spectre-mutable)
     735             :   mutable ::dg::MortarMap<
     736             :       Dim, elliptic::dg::MortarData<size_t, typename System::primal_fields,
     737             :                                     typename System::primal_fluxes>>
     738           0 :       central_mortar_data_{};
     739             :   // NOLINTNEXTLINE(spectre-mutable)
     740             :   mutable LinearSolver::Schwarz::OverlapMap<
     741             :       Dim, ::dg::MortarMap<Dim, elliptic::dg::MortarData<
     742             :                                     size_t, typename System::primal_fields,
     743             :                                     typename System::primal_fluxes>>>
     744           0 :       neighbors_mortar_data_{};
     745             : };
     746             : 
     747             : }  // namespace elliptic::dg::subdomain_operator

Generated by: LCOV version 1.14