SpECTRE Documentation Coverage Report
Current view: top level - ParallelAlgorithms/Interpolation/Events - InterpolateWithoutInterpComponent.hpp Hit Total Coverage
Commit: de0084593a37be7727c6c4da0be2184b0f8d9ed4 Lines: 2 14 14.3 %
Date: 2025-11-04 23:26:01
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 <array>
       7             : #include <cstddef>
       8             : #include <limits>
       9             : #include <optional>
      10             : #include <pup.h>
      11             : #include <set>
      12             : #include <sstream>
      13             : #include <string>
      14             : #include <utility>
      15             : #include <vector>
      16             : 
      17             : #include "DataStructures/DataBox/DataBox.hpp"
      18             : #include "DataStructures/DataVector.hpp"
      19             : #include "DataStructures/Index.hpp"
      20             : #include "Domain/BlockLogicalCoordinates.hpp"
      21             : #include "Domain/Creators/Tags/Domain.hpp"
      22             : #include "Domain/Domain.hpp"
      23             : #include "Domain/ElementLogicalCoordinates.hpp"
      24             : #include "Domain/FunctionsOfTime/FunctionOfTime.hpp"
      25             : #include "Domain/Structure/BlockId.hpp"
      26             : #include "Domain/Structure/ElementId.hpp"
      27             : #include "Domain/Tags.hpp"
      28             : #include "IO/Logging/Verbosity.hpp"
      29             : #include "NumericalAlgorithms/Interpolation/IrregularInterpolant.hpp"
      30             : #include "Options/String.hpp"
      31             : #include "Parallel/GlobalCache.hpp"
      32             : #include "Parallel/Invoke.hpp"
      33             : #include "Parallel/ParallelComponentHelpers.hpp"
      34             : #include "Parallel/Printf/Printf.hpp"
      35             : #include "ParallelAlgorithms/EventsAndTriggers/Event.hpp"
      36             : #include "ParallelAlgorithms/Interpolation/Actions/InterpolationTargetVarsFromElement.hpp"
      37             : #include "ParallelAlgorithms/Interpolation/Events/GetComputeItemsOnSource.hpp"
      38             : #include "ParallelAlgorithms/Interpolation/InterpolationTargetDetail.hpp"
      39             : #include "ParallelAlgorithms/Interpolation/PointInfoTag.hpp"
      40             : #include "ParallelAlgorithms/Interpolation/Tags.hpp"
      41             : #include "ParallelAlgorithms/Interpolation/Targets/Sphere.hpp"
      42             : #include "PointwiseFunctions/GeneralRelativity/Tags.hpp"
      43             : #include "Utilities/Algorithm.hpp"
      44             : #include "Utilities/CartesianProduct.hpp"
      45             : #include "Utilities/ConstantExpressions.hpp"
      46             : #include "Utilities/PrettyType.hpp"
      47             : #include "Utilities/Serialization/CharmPupable.hpp"
      48             : #include "Utilities/TMPL.hpp"
      49             : #include "Utilities/TypeTraits/CreateGetStaticMemberVariableOrDefault.hpp"
      50             : #include "Utilities/TypeTraits/IsA.hpp"
      51             : 
      52             : /// \cond
      53             : namespace Events::Tags {
      54             : template <size_t Dim>
      55             : struct ObserverMesh;
      56             : template <size_t Dim, typename Fr>
      57             : struct ObserverCoordinates;
      58             : }  // namespace Events::Tags
      59             : namespace Tags {
      60             : template <typename TagsList>
      61             : struct Variables;
      62             : }  // namespace Tags
      63             : template <size_t VolumeDim>
      64             : class ElementId;
      65             : namespace intrp {
      66             : template <typename Metavariables, typename Tag>
      67             : struct InterpolationTarget;
      68             : }  // namespace intrp
      69             : /// \endcond
      70             : 
      71             : namespace intrp::Events {
      72             : /// \cond
      73             : template <size_t VolumeDim, typename InterpolationTargetTag,
      74             :           typename SourceVarTags>
      75             : class InterpolateWithoutInterpComponent;
      76             : /// \endcond
      77             : 
      78             : /*!
      79             :  * \brief Does an interpolation onto an InterpolationTargetTag by calling
      80             :  * Actions on the InterpolationTarget component.
      81             :  *
      82             :  * \note The `intrp::TargetPoints::Sphere` target is handled specially because
      83             :  * it has the potential to be very slow due to it usually having the most points
      84             :  * out of all the stationary targets. An optimization for the future would be to
      85             :  * have each target be responsible for intelligently computing the
      86             :  * `block_logical_coordinates` for it's own points.
      87             :  */
      88             : template <size_t VolumeDim, typename InterpolationTargetTag,
      89             :           typename... SourceVarTags>
      90           1 : class InterpolateWithoutInterpComponent<VolumeDim, InterpolationTargetTag,
      91             :                                         tmpl::list<SourceVarTags...>>
      92             :     : public Event {
      93             :  private:
      94           0 :   using frame = typename InterpolationTargetTag::compute_target_points::frame;
      95             : 
      96             :  public:
      97             :   /// \cond
      98             :   explicit InterpolateWithoutInterpComponent(CkMigrateMessage* /*unused*/) {}
      99             :   using PUP::able::register_constructor;
     100             :   WRAPPED_PUPable_decl_template(InterpolateWithoutInterpComponent);  // NOLINT
     101             :   /// \endcond
     102             : 
     103           0 :   using options = tmpl::list<>;
     104           0 :   static constexpr Options::String help =
     105             :       "Does interpolation using the given InterpolationTargetTag, "
     106             :       "without an Interpolator ParallelComponent.";
     107             : 
     108           0 :   static std::string name() {
     109             :     return pretty_type::name<InterpolationTargetTag>();
     110             :   }
     111             : 
     112           0 :   InterpolateWithoutInterpComponent() = default;
     113             : 
     114           0 :   using compute_tags_for_observation_box =
     115             :       detail::get_compute_items_on_source_or_default_t<InterpolationTargetTag,
     116             :                                                        tmpl::list<>>;
     117             : 
     118           0 :   using return_tags = tmpl::list<>;
     119           0 :   using argument_tags = tmpl::list<
     120             :       typename InterpolationTargetTag::temporal_id,
     121             :       Tags::PointInfo<InterpolationTargetTag, tmpl::size_t<VolumeDim>>,
     122             :       ::Events::Tags::ObserverMesh<VolumeDim>,
     123             :       // We always grab the DG coords because we use them to create a
     124             :       // bounding box for the sphere target optimization. DG coords
     125             :       // have points on the boundary, while FD coords don't. If we
     126             :       // had used FD coords, it's possible a target point would fall
     127             :       // between the outermost gridpoints of two elements. This point
     128             :       // would be outside our bounding box, and thus wouldn't get
     129             :       // interpolated to. We avoid this by always using DG coords,
     130             :       // even if the mesh is FD.
     131             :       domain::Tags::Coordinates<VolumeDim, frame>, SourceVarTags...>;
     132             : 
     133             :   template <typename ParallelComponent, typename Metavariables>
     134           0 :   void operator()(
     135             :       const typename InterpolationTargetTag::temporal_id::type& temporal_id,
     136             :       const tnsr::I<DataVector, VolumeDim, frame>& all_target_points,
     137             :       const Mesh<VolumeDim>& mesh,
     138             :       const tnsr::I<DataVector, VolumeDim, frame> coordinates,
     139             :       const typename SourceVarTags::type&... source_vars_input,
     140             :       Parallel::GlobalCache<Metavariables>& cache,
     141             :       const ElementId<VolumeDim>& array_index,
     142             :       const ParallelComponent* const /*meta*/,
     143             :       const ObservationValue& /*observation_value*/) const {
     144             :     std::vector<BlockLogicalCoords<VolumeDim>> block_logical_coords{};
     145             : 
     146             :     std::stringstream ss{};
     147             :     ss << std::setprecision(std::numeric_limits<double>::digits10 + 4)
     148             :        << std::scientific;
     149             :     const ::Verbosity verbosity = Parallel::get<intrp::Tags::Verbosity>(cache);
     150             :     const bool debug_print = verbosity >= ::Verbosity::Debug;
     151             :     if (debug_print) {
     152             :       ss << InterpolationTarget_detail::target_output_prefix<
     153             :                 InterpolateWithoutInterpComponent, InterpolationTargetTag>(
     154             :                 temporal_id)
     155             :          << ", " << array_index << ", ";
     156             :     }
     157             : 
     158             :     // The sphere target is special because we have a better idea of where the
     159             :     // points will be.
     160             :     if constexpr (tt::is_a_v<
     161             :                       TargetPoints::Sphere,
     162             :                       typename InterpolationTargetTag::compute_target_points>) {
     163             :       static_assert(VolumeDim == 3,
     164             :                     "Sphere target can only be used for VolumeDim = 3.");
     165             :       // Extremum for x, y, z, r
     166             :       std::array<std::pair<double, double>, 4> min_max_coordinates{};
     167             : 
     168             :       const auto& sphere =
     169             :           Parallel::get<Tags::Sphere<InterpolationTargetTag>>(cache);
     170             :       const std::array<double, 3>& center = sphere.center;
     171             : 
     172             :       // Calculate r^2 from center of sphere because sqrt is expensive
     173             :       DataVector radii_squared{get<0>(coordinates).size(), 0.0};
     174             :       for (size_t i = 0; i < VolumeDim; i++) {
     175             :         radii_squared += square(coordinates.get(i) - gsl::at(center, i));
     176             :       }
     177             : 
     178             :       // Compute min and max
     179             :       {
     180             :         const auto [min, max] = alg::minmax_element(radii_squared);
     181             :         min_max_coordinates[3].first = *min;
     182             :         min_max_coordinates[3].second = *max;
     183             :       }
     184             : 
     185             :       const std::set<double>& radii_of_sphere_target = sphere.radii;
     186             :       const size_t l_max = sphere.l_max;
     187             : 
     188             :       const size_t number_of_angular_points = (l_max + 1) * (2 * l_max + 1);
     189             :       // first size_t = position of first radius in bounds
     190             :       // second size_t = total bounds to use/check
     191             :       std::optional<std::pair<size_t, size_t>> offset_and_num_points{};
     192             : 
     193             :       // Have a very small buffer just in case of roundoff
     194             :       double epsilon =
     195             :           (min_max_coordinates[3].second - min_max_coordinates[3].first) *
     196             :           std::numeric_limits<double>::epsilon() * 100.0;
     197             :       size_t offset_index = 0;
     198             :       // Check if any radii of the target are within the radii of our element
     199             :       for (double radius : radii_of_sphere_target) {
     200             :         const double square_radius = square(radius);
     201             :         if (square_radius >=
     202             :                 (gsl::at(min_max_coordinates, 3).first - epsilon) and
     203             :             square_radius <=
     204             :                 (gsl::at(min_max_coordinates, 3).second + epsilon)) {
     205             :           if (offset_and_num_points.has_value()) {
     206             :             offset_and_num_points->second += number_of_angular_points;
     207             :           } else {
     208             :             offset_and_num_points =
     209             :                 std::make_pair(offset_index * number_of_angular_points,
     210             :                                number_of_angular_points);
     211             :           }
     212             :         }
     213             :         offset_index++;
     214             :       }
     215             : 
     216             :       // If no radii pass through this element, there's nothing to do so return
     217             :       if (not offset_and_num_points.has_value()) {
     218             :         if (debug_print) {
     219             :           using ::operator<<;
     220             :           ss << "No radii in this element.";
     221             :           Parallel::printf("%s\n", ss.str());
     222             :         }
     223             :         return;
     224             :       }
     225             : 
     226             :       // Get the x,y,z bounds
     227             :       for (size_t i = 0; i < VolumeDim; i++) {
     228             :         const auto [min, max] = alg::minmax_element(coordinates.get(i));
     229             :         gsl::at(min_max_coordinates, i).first = *min;
     230             :         gsl::at(min_max_coordinates, i).second = *max;
     231             :       }
     232             : 
     233             :       const tnsr::I<DataVector, VolumeDim, frame> target_points_to_check{};
     234             :       // Use the offset and number of points to create a view. We assume that if
     235             :       // there are multiple radii in this element, that they are successive
     236             :       // radii. If this wasn't true, that'd be a really weird topology.
     237             :       for (size_t i = 0; i < VolumeDim; i++) {
     238             :         make_const_view(make_not_null(&target_points_to_check.get(i)),
     239             :                         all_target_points.get(i), offset_and_num_points->first,
     240             :                         offset_and_num_points->second);
     241             :       }
     242             : 
     243             :       // To break out of inner loop and skip the point
     244             :       bool skip_point = false;
     245             :       tnsr::I<double, VolumeDim, frame> sphere_coords_to_map{};
     246             :       // Other code below expects block_logical_coords to be sized to the total
     247             :       // number of points on the target (including all radii). By default these
     248             :       // will all be nullopt and we'll only fill the ones we need
     249             :       block_logical_coords.resize(get<0>(all_target_points).size());
     250             : 
     251             :       const Block<VolumeDim>& block =
     252             :           Parallel::get<domain::Tags::Domain<VolumeDim>>(cache)
     253             :               .blocks()[array_index.block_id()];
     254             : 
     255             :       // Now for every radius in this element, we check if their points are
     256             :       // within the x,y,z bounds of the element. If they are, map the point to
     257             :       // the block logical frame and add it to the vector f all block logical
     258             :       // coordinates.
     259             :       for (size_t index = 0; index < get<0>(target_points_to_check).size();
     260             :            index++) {
     261             :         skip_point = false;
     262             :         for (size_t i = 0; i < VolumeDim; i++) {
     263             :           const double coord = target_points_to_check.get(i)[index];
     264             :           epsilon = (gsl::at(min_max_coordinates, i).second -
     265             :                      gsl::at(min_max_coordinates, i).first) *
     266             :                     std::numeric_limits<double>::epsilon() * 100.0;
     267             :           // If a point is outside any of the bounding box, skip it
     268             :           if (coord < (gsl::at(min_max_coordinates, i).first - epsilon) or
     269             :               coord > (gsl::at(min_max_coordinates, i).second + epsilon)) {
     270             :             skip_point = true;
     271             :             break;
     272             :           }
     273             : 
     274             :           sphere_coords_to_map.get(i) = coord;
     275             :         }
     276             : 
     277             :         if (skip_point) {
     278             :           continue;
     279             :         }
     280             : 
     281             :         std::optional<tnsr::I<double, VolumeDim, ::Frame::BlockLogical>>
     282             :             block_coords_of_target_point{};
     283             : 
     284             :         if constexpr (Parallel::is_in_global_cache<
     285             :                           Metavariables, domain::Tags::FunctionsOfTime>) {
     286             :           const auto& functions_of_time =
     287             :               Parallel::get<domain::Tags::FunctionsOfTime>(cache);
     288             :           const double time =
     289             :               InterpolationTarget_detail::get_temporal_id_value(temporal_id);
     290             : 
     291             :           block_coords_of_target_point = block_logical_coordinates_single_point(
     292             :               sphere_coords_to_map, block, time, functions_of_time);
     293             :         } else {
     294             :           block_coords_of_target_point = block_logical_coordinates_single_point(
     295             :               sphere_coords_to_map, block);
     296             :         }
     297             : 
     298             :         if (block_coords_of_target_point.has_value()) {
     299             :           // Get index into vector of all grid points of the target. This is
     300             :           // just the offset + index
     301             :           block_logical_coords[offset_and_num_points->first + index] =
     302             :               make_id_pair(domain::BlockId(array_index.block_id()),
     303             :                            std::move(block_coords_of_target_point.value()));
     304             :         }
     305             :       }
     306             :     } else {
     307             :       (void)coordinates;
     308             :       block_logical_coords = InterpolationTarget_detail::block_logical_coords<
     309             :           InterpolationTargetTag>(cache, all_target_points, temporal_id);
     310             :     }
     311             : 
     312             :     const std::vector<ElementId<VolumeDim>> element_ids{{array_index}};
     313             :     const auto element_coord_holders =
     314             :         element_logical_coordinates(element_ids, block_logical_coords);
     315             : 
     316             :     if (element_coord_holders.count(array_index) == 0) {
     317             :       if (debug_print) {
     318             :         ss << "No target points in this element. Skipping.";
     319             :         Parallel::printf("%s\n", ss.str());
     320             :       }
     321             :       // There are no target points in this element, so we don't need
     322             :       // to do anything.
     323             :       return;
     324             :     }
     325             : 
     326             :     // There are points in this element, so interpolate to them and
     327             :     // send the interpolated data to the target.  This is done
     328             :     // in several steps:
     329             :     const auto& element_coord_holder = element_coord_holders.at(array_index);
     330             : 
     331             :     // 1. Get the list of variables
     332             :     Variables<typename InterpolationTargetTag::vars_to_interpolate_to_target>
     333             :         interp_vars(mesh.number_of_grid_points());
     334             : 
     335             :     if constexpr (InterpolationTarget_detail::has_compute_vars_to_interpolate_v<
     336             :                       InterpolationTargetTag>) {
     337             :       // 1a. Call compute_vars_to_interpolate.  Need the source in a
     338             :       // Variables, so copy the variables here.
     339             :       // This copy would be unnecessary if we passed a Variables into
     340             :       // InterpolateWithoutInterpComponent instead of passing
     341             :       // individual Tensors, which would require that this Variables is
     342             :       // something in the DataBox. (Note that
     343             :       // InterpolationTarget_detail::compute_dest_vars_from_source_vars
     344             :       // allows the source variables to be different from the
     345             :       // destination variables).
     346             :       Variables<tmpl::list<SourceVarTags...>> source_vars(
     347             :           mesh.number_of_grid_points());
     348             :       [[maybe_unused]] const auto copy_to_variables =
     349             :           [&source_vars](const auto source_var_tag_v, const auto& source_var) {
     350             :             using source_var_tag = tmpl::type_from<decltype(source_var_tag_v)>;
     351             :             get<source_var_tag>(source_vars) = source_var;
     352             :             return 0;
     353             :           };
     354             :       expand_pack(copy_to_variables(tmpl::type_<SourceVarTags>{},
     355             :                                     source_vars_input)...);
     356             : 
     357             :       InterpolationTarget_detail::compute_dest_vars_from_source_vars<
     358             :           InterpolationTargetTag>(make_not_null(&interp_vars), source_vars,
     359             :                                   get<domain::Tags::Domain<VolumeDim>>(cache),
     360             :                                   mesh, array_index, cache, temporal_id);
     361             :     } else {
     362             :       // 1b. There is no compute_vars_to_interpolate. So copy the
     363             :       // source vars directly into the variables.
     364             :       // This copy would be unnecessary if:
     365             :       //   - We passed a Variables into InterpolateWithoutInterpComponent
     366             :       //     instead of passing individual Tensors.
     367             :       //  and
     368             :       //   - This Variables was actually something in the DataBox.
     369             :       //  and
     370             :       //   - Either the passed-in Variables was exactly the same as
     371             :       //     InterpolationTargetTag::vars_to_interpolate_to_target,
     372             :       //     or IrregularInterpolant::interpolate had the ability to
     373             :       //     interpolate only a subset of the Variables passed into it,
     374             :       //     or IrregularInterpolant::interpolate can interpolate individual
     375             :       //     DataVectors.
     376             :       [[maybe_unused]] const auto copy_to_variables =
     377             :           [&interp_vars](const auto tensor_tag_v, const auto& tensor) {
     378             :             using tensor_tag = tmpl::type_from<decltype(tensor_tag_v)>;
     379             :             get<tensor_tag>(interp_vars) = tensor;
     380             :             return 0;
     381             :           };
     382             :       expand_pack(copy_to_variables(tmpl::type_<SourceVarTags>{},
     383             :                                     source_vars_input)...);
     384             :     }
     385             : 
     386             :     // 2. Set up interpolator
     387             :     intrp::Irregular<VolumeDim> interpolator(
     388             :         mesh, element_coord_holder.element_logical_coords);
     389             : 
     390             :     // 3. Interpolate and send interpolated data to target
     391             :     auto& receiver_proxy = Parallel::get_parallel_component<
     392             :         InterpolationTarget<Metavariables, InterpolationTargetTag>>(cache);
     393             :     Parallel::simple_action<
     394             :         Actions::InterpolationTargetVarsFromElement<InterpolationTargetTag>>(
     395             :         receiver_proxy,
     396             :         std::vector<Variables<
     397             :             typename InterpolationTargetTag::vars_to_interpolate_to_target>>(
     398             :             {interpolator.interpolate(interp_vars)}),
     399             :         block_logical_coords,
     400             :         std::vector<std::vector<size_t>>({element_coord_holder.offsets}),
     401             :         temporal_id);
     402             : 
     403             :     if (debug_print) {
     404             :       ss << "Sending points and vars to target.";
     405             :       Parallel::printf("%s\n", ss.str());
     406             :     }
     407             :   }
     408             : 
     409           0 :   using is_ready_argument_tags = tmpl::list<>;
     410             : 
     411             :   template <typename ArrayIndex, typename Component, typename Metavariables>
     412           0 :   bool is_ready(Parallel::GlobalCache<Metavariables>& /*cache*/,
     413             :                 const ArrayIndex& /*array_index*/,
     414             :                 const Component* const /*meta*/) const {
     415             :     return true;
     416             :   }
     417             : 
     418           1 :   bool needs_evolved_variables() const override { return true; }
     419             : };
     420             : 
     421             : /// \cond
     422             : template <size_t VolumeDim, typename InterpolationTargetTag,
     423             :           typename... SourceVarTags>
     424             : PUP::able::PUP_ID
     425             :     InterpolateWithoutInterpComponent<VolumeDim, InterpolationTargetTag,
     426             :                                       tmpl::list<SourceVarTags...>>::my_PUP_ID =
     427             :         0;  // NOLINT
     428             : /// \endcond
     429             : 
     430             : }  // namespace intrp::Events

Generated by: LCOV version 1.14