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

Generated by: LCOV version 1.14