SpECTRE Documentation Coverage Report
Current view: top level - ParallelAlgorithms/ApparentHorizonFinder/Callbacks - FindApparentHorizon.hpp Hit Total Coverage
Commit: 1f2210958b4f38fdc0400907ee7c6d5af5111418 Lines: 1 4 25.0 %
Date: 2025-12-05 05:03:31
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 <cmath>
       7             : #include <deque>
       8             : #include <utility>
       9             : 
      10             : #include "DataStructures/DataBox/DataBox.hpp"
      11             : #include "DataStructures/VariablesTag.hpp"
      12             : #include "Domain/Creators/Tags/Domain.hpp"
      13             : #include "Domain/FunctionsOfTime/Tags.hpp"
      14             : #include "Domain/StrahlkorperTransformations.hpp"
      15             : #include "IO/Logging/Tags.hpp"
      16             : #include "IO/Logging/Verbosity.hpp"
      17             : #include "NumericalAlgorithms/SphericalHarmonics/Spherepack.hpp"
      18             : #include "NumericalAlgorithms/SphericalHarmonics/Strahlkorper.hpp"
      19             : #include "NumericalAlgorithms/SphericalHarmonics/Tags.hpp"
      20             : #include "Parallel/GlobalCache.hpp"
      21             : #include "Parallel/Invoke.hpp"
      22             : #include "Parallel/ParallelComponentHelpers.hpp"
      23             : #include "Parallel/Printf/Printf.hpp"
      24             : #include "ParallelAlgorithms/ApparentHorizonFinder/FastFlow.hpp"
      25             : #include "ParallelAlgorithms/ApparentHorizonFinder/InterpolationTarget.hpp"
      26             : #include "ParallelAlgorithms/ApparentHorizonFinder/Tags.hpp"
      27             : #include "ParallelAlgorithms/Interpolation/Actions/SendPointsToInterpolator.hpp"
      28             : #include "ParallelAlgorithms/Interpolation/InterpolationTargetDetail.hpp"
      29             : #include "ParallelAlgorithms/Interpolation/Protocols/PostInterpolationCallback.hpp"
      30             : #include "PointwiseFunctions/GeneralRelativity/Surfaces/Tags.hpp"
      31             : #include "PointwiseFunctions/GeneralRelativity/Tags.hpp"
      32             : #include "Utilities/ErrorHandling/Error.hpp"
      33             : #include "Utilities/Gsl.hpp"
      34             : #include "Utilities/PrettyType.hpp"
      35             : #include "Utilities/ProtocolHelpers.hpp"
      36             : #include "Utilities/TMPL.hpp"
      37             : 
      38             : /// \cond
      39             : namespace ylm::Tags {
      40             : template <typename Frame>
      41             : struct Strahlkorper;
      42             : }  // namespace ylm::Tags
      43             : namespace db {
      44             : template <typename TagsList>
      45             : class DataBox;
      46             : }  // namespace db
      47             : namespace intrp {
      48             : template <class Metavariables, typename InterpolationTargetTag>
      49             : struct InterpolationTarget;
      50             : namespace Tags {
      51             : template <typename Metavariables>
      52             : struct TemporalIds;
      53             : }  // namespace Tags
      54             : }  // namespace intrp
      55             : /// \endcond
      56             : 
      57             : namespace intrp::callbacks {
      58             : 
      59             : /// \brief post interpolation callback (see
      60             : /// intrp::protocols::PostInterpolationCallback) that does a FastFlow iteration
      61             : /// and triggers another one until convergence.
      62             : ///
      63             : /// Assumes that InterpolationTargetTag contains an additional
      64             : /// type alias called `post_horizon_find_callbacks`, which is a list of
      65             : /// structs, each of which has a function
      66             : ///
      67             : /// \snippet ParallelAlgorithms/ApparentHorizonFinder/Test_ApparentHorizonFinder.cpp post_horizon_find_callback_example
      68             : ///
      69             : /// that is called if the FastFlow iteration has converged.
      70             : /// InterpolationTargetTag also is assumed to contain an additional
      71             : /// type alias called `horizon_find_failure_callbacks`, which is a list of
      72             : /// structs, each of which has a function
      73             : ///
      74             : /// \snippet ParallelAlgorithms/ApparentHorizonFinder/Test_ApparentHorizonFinder.cpp horizon_find_failure_callbacks_example
      75             : ///
      76             : /// that is called if the FastFlow iteration or the interpolation has
      77             : /// failed.
      78             : ///
      79             : /// Uses:
      80             : /// - Metavariables:
      81             : ///   - `temporal_id`
      82             : /// - DataBox:
      83             : ///   - `logging::Tags::Verbosity<InterpolationTargetTag>`
      84             : ///   - `::gr::Tags::InverseSpatialMetric<DataVector, 3, Frame>`
      85             : ///   - `::gr::Tags::ExtrinsicCurvature<DataVector, 3, Frame>`
      86             : ///   - `::gr::Tags::SpatialChristoffelSecondKind<DataVector, 3, Frame>`
      87             : ///   - `::ah::Tags::FastFlow`
      88             : ///   - `ylm::Tags::Strahlkorper<Frame>`
      89             : ///
      90             : /// Modifies:
      91             : /// - DataBox:
      92             : ///   - `::ah::Tags::FastFlow`
      93             : ///   - `ylm::Tags::Strahlkorper<Frame>`
      94             : ///
      95             : /// This can be used in InterpolationTargetTag::post_interpolation_callbacks;
      96             : /// see intrp::protocols::InterpolationTargetTag for details on
      97             : /// InterpolationTargetTag.
      98             : ///
      99             : /// ### Output
     100             : ///
     101             : /// Optionally, a single line of output is printed to stdout either on each
     102             : /// iteration (if verbosity > Verbosity::Quiet) or on convergence
     103             : /// (if verbosity > Verbosity::Silent).  The output consists of the
     104             : /// following labels, and values associated with each label:
     105             : ///  - t        = time given by value of `temporal_id` argument to `apply`
     106             : ///  - its      = current iteration number.
     107             : ///  - R        = min and max of residual over all prolonged grid points.
     108             : ///  - |R|      = L2 norm of residual, counting only L modes solved for.
     109             : ///  - |R_mesh| = L2 norm of residual over prolonged grid points.
     110             : ///  - r        = min and max radius of trial horizon surface.
     111             : ///
     112             : /// #### Difference between |R| and |R_mesh|:
     113             : ///  The horizon is represented in a \f$Y_{lm}\f$ expansion up
     114             : ///  to \f$l=l_{\mathrm{surface}}\f$;
     115             : ///  the residual |R| represents the failure of that surface to satisfy
     116             : ///  the apparent horizon equation.
     117             : ///
     118             : ///  However, at each iteration we also interpolate the horizon surface
     119             : ///  to a higher resolution ("prolongation").  The prolonged surface
     120             : ///  includes \f$Y_{lm}\f$ coefficients up to \f$l=l_{\mathrm{mesh}}\f$,
     121             : ///  where \f$l_{\mathrm{mesh}} > l_{\mathrm{surface}}\f$.
     122             : ///  The residual computed on this higher-resolution surface is |R_mesh|.
     123             : ///
     124             : ///  As iterations proceed, |R| should decrease until it reaches
     125             : ///  numerical roundoff error, because we are varying all the \f$Y_{lm}\f$
     126             : ///  coefficients up to  \f$l=l_{\mathrm{surface}}\f$
     127             : ///  to minimize the residual.  However,
     128             : ///  |R_mesh| should eventually stop decreasing as iterations proceed,
     129             : ///  hitting a floor that represents the numerical truncation error of
     130             : ///  the solution.
     131             : ///
     132             : ///  The convergence criterion looks at both |R| and |R_mesh|:  Once
     133             : ///  |R| is small enough and |R_mesh| has stabilized, it is pointless
     134             : ///  to keep iterating (even though one could iterate until |R| reaches
     135             : ///  roundoff).
     136             : ///
     137             : ///  Problems with convergence of the apparent horizon finder can often
     138             : ///  be diagnosed by looking at the behavior of |R| and |R_mesh| as a
     139             : ///  function of iteration.
     140             : ///
     141             : template <typename InterpolationTargetTag, typename Frame>
     142           1 : struct FindApparentHorizon
     143             :     : tt::ConformsTo<intrp::protocols::PostInterpolationCallback> {
     144           0 :   using const_global_cache_tags =
     145             :       Parallel::get_const_global_cache_tags_from_actions<
     146             :           typename InterpolationTargetTag::post_horizon_find_callbacks>;
     147             :   template <typename DbTags, typename Metavariables, typename TemporalId>
     148           0 :   static bool apply(
     149             :       const gsl::not_null<db::DataBox<DbTags>*> box,
     150             :       const gsl::not_null<Parallel::GlobalCache<Metavariables>*> cache,
     151             :       const TemporalId& temporal_id) {
     152             :     bool horizon_finder_failed = false;
     153             : 
     154             :     const size_t current_iteration =
     155             :         get<::ah::Tags::FastFlow>(*box).current_iteration();
     156             : 
     157             :     if (current_iteration == 0) {
     158             :       // If we get here, we are in a new apparent horizon search, as
     159             :       // opposed to a subsequent iteration of the same horizon search.
     160             :       db::mutate<ah::Tags::PreviousIterationStrahlkorper<Frame>,
     161             :                  ah::Tags::FailedInterpolationIterations>(
     162             :           [](const gsl::not_null<ylm::Strahlkorper<Frame>*>
     163             :                  previous_fast_flow_strahlkorper,
     164             :              const gsl::not_null<size_t*> failed_interpolation_iterations,
     165             :              const ylm::Strahlkorper<Frame>& strahlkorper) {
     166             :             // Reset to zero. Will only be used starting from the second
     167             :             // iteration
     168             :             *failed_interpolation_iterations = 0;
     169             : 
     170             :             // First iteration the previous is just the initial guess
     171             :             (*previous_fast_flow_strahlkorper) = strahlkorper;
     172             :           },
     173             :           box, db::get<ylm::Tags::Strahlkorper<Frame>>(*box));
     174             :     }
     175             : 
     176             :     // Deal with the possibility that some of the points might be
     177             :     // outside of the Domain.
     178             :     const auto& indices_of_invalid_pts =
     179             :         db::get<Tags::IndicesOfInvalidInterpPoints<TemporalId>>(*box);
     180             :     size_t& failed_interpolation_iterations =
     181             :         db::get_mutable_reference<ah::Tags::FailedInterpolationIterations>(box);
     182             :     if (indices_of_invalid_pts.count(temporal_id) > 0 and
     183             :         not indices_of_invalid_pts.at(temporal_id).empty()) {
     184             :       ++failed_interpolation_iterations;
     185             : 
     186             :       const auto& options = Parallel::get<
     187             :           ah::Tags::ApparentHorizon<InterpolationTargetTag, Frame>>(*cache);
     188             : 
     189             :       // Can't recover if we've exceeded our number of attempts
     190             :       if (failed_interpolation_iterations <=
     191             :           options.max_interpolation_retries) {
     192             :         db::mutate<ylm::Tags::Strahlkorper<Frame>>(
     193             :             [&](const gsl::not_null<ylm::Strahlkorper<Frame>*> strahlkorper,
     194             :                 const ylm::Strahlkorper<Frame>&
     195             :                     previous_strahlkorper_iteration) {
     196             :               if (current_iteration == 0) {
     197             :                 // If this is the zeroth iteration and we couldn't interpolate,
     198             :                 // then just try increasing the size of the horizon by 50%
     199             :                 strahlkorper->coefficients()[0] *= 1.5;
     200             :               } else {
     201             :                 // Otherwise move the new trial surface halfway between the
     202             :                 // current surface and the previous surface
     203             :                 strahlkorper->coefficients() +=
     204             :                     0.5 * (previous_strahlkorper_iteration.coefficients() -
     205             :                            strahlkorper->coefficients());
     206             :               }
     207             :             },
     208             :             box, db::get<ah::Tags::PreviousIterationStrahlkorper<Frame>>(*box));
     209             : 
     210             :         // Resend the new coords at the same time and iteration. Use 0 for the
     211             :         // array index because it's always 0 due to the targets being
     212             :         // singletons. No need to worry about the invalid points since those get
     213             :         // reset within SendPointsToInterpolator
     214             :         Actions::SendPointsToInterpolator<InterpolationTargetTag>::
     215             :             template apply<::intrp::InterpolationTarget<
     216             :                 Metavariables, InterpolationTargetTag>>(
     217             :                 *box, *cache, 0, temporal_id, current_iteration,
     218             :                 failed_interpolation_iterations);
     219             : 
     220             :         // We return false because we don't want this iteration to clean
     221             :         // up the volume data, since we still need it.
     222             :         return false;
     223             :       } else {
     224             :         tmpl::for_each<
     225             :             typename InterpolationTargetTag::horizon_find_failure_callbacks>(
     226             :             [&box, &cache, &temporal_id](auto callback_v) {
     227             :               using callback = tmpl::type_from<decltype(callback_v)>;
     228             :               callback::template apply<InterpolationTargetTag>(
     229             :                   *box, *cache, temporal_id,
     230             :                   FastFlow::Status::InterpolationFailure);
     231             :             });
     232             :         horizon_finder_failed = true;
     233             :       }
     234             :     }
     235             : 
     236             :     // Reset now that we were able to interpolate (or if we are abandoning this
     237             :     // horizon find)
     238             :     failed_interpolation_iterations = 0;
     239             : 
     240             :     if (not horizon_finder_failed) {
     241             :       const auto& verbosity =
     242             :           db::get<logging::Tags::Verbosity<InterpolationTargetTag>>(*box);
     243             :       const auto& inv_g =
     244             :           db::get<::gr::Tags::InverseSpatialMetric<DataVector, 3, Frame>>(*box);
     245             :       const auto& ex_curv =
     246             :           db::get<::gr::Tags::ExtrinsicCurvature<DataVector, 3, Frame>>(*box);
     247             :       const auto& christoffel = db::get<
     248             :           ::gr::Tags::SpatialChristoffelSecondKind<DataVector, 3, Frame>>(*box);
     249             : 
     250             :       std::pair<FastFlow::Status, FastFlow::IterInfo> status_and_info;
     251             : 
     252             :       // Do a FastFlow iteration.
     253             :       db::mutate<::ah::Tags::FastFlow, ylm::Tags::Strahlkorper<Frame>,
     254             :                  ah::Tags::PreviousIterationStrahlkorper<Frame>>(
     255             :           [&inv_g, &ex_curv, &christoffel, &status_and_info](
     256             :               const gsl::not_null<::FastFlow*> fast_flow,
     257             :               const gsl::not_null<ylm::Strahlkorper<Frame>*> strahlkorper,
     258             :               const gsl::not_null<ylm::Strahlkorper<Frame>*>
     259             :                   previous_fast_flow_strahlkorper) {
     260             :             // Set this before we iterate the fast flow. Note that this will
     261             :             // have to be updated once we have adaptive horizon finding
     262             :             (*previous_fast_flow_strahlkorper) = *strahlkorper;
     263             : 
     264             :             status_and_info = fast_flow->template iterate_horizon_finder<Frame>(
     265             :                 strahlkorper, inv_g, ex_curv, christoffel);
     266             :           },
     267             :           box);
     268             : 
     269             :       // Determine whether we have converged, whether we need another step,
     270             :       // or whether we have encountered an error.
     271             : 
     272             :       const auto& status = status_and_info.first;
     273             :       const auto& info = status_and_info.second;
     274             :       const auto has_converged = converged(status);
     275             : 
     276             :       if (verbosity >= ::Verbosity::Verbose or
     277             :           (verbosity >= ::Verbosity::Quiet and has_converged)) {
     278             :         Parallel::printf(
     279             :             "%s: t=%.6g: its=%d: %.1e<R<%.0e, |R|=%.1g, "
     280             :             "|R_grid|=%.1g, %.4g<r<%.4g\n",
     281             :             pretty_type::name<InterpolationTargetTag>(),
     282             :             InterpolationTarget_detail::get_temporal_id_value(temporal_id),
     283             :             info.iteration, info.min_residual, info.max_residual,
     284             :             info.residual_ylm, info.residual_mesh, info.r_min, info.r_max);
     285             :       }
     286             : 
     287             :       if (status == FastFlow::Status::SuccessfulIteration) {
     288             :         // Do another iteration of the same horizon search.
     289             :         const auto& current_id =
     290             :             db::get<intrp::Tags::CurrentTemporalId<TemporalId>>(*box);
     291             :         using ::operator<<;
     292             :         ASSERT(current_id.has_value(),
     293             :                "While horizon finding, the current temporal id doesn't have a "
     294             :                "value, but it should.");
     295             :         // The iteration of these new coords is the fast flow iteration + 1
     296             :         // because the zeroth iteration was the initial guess. Use 0 for the
     297             :         // array index because it's always 0 due to the targets being singletons
     298             :         Actions::SendPointsToInterpolator<InterpolationTargetTag>::
     299             :             template apply<::intrp::InterpolationTarget<
     300             :                 Metavariables, InterpolationTargetTag>>(
     301             :                 *box, *cache, 0, current_id.value(), info.iteration + 1);
     302             :         // We return false because we don't want this iteration to clean
     303             :         // up the volume data, since we are using it for the next iteration
     304             :         // (i.e. the simple_action that we just called).
     305             :         return false;
     306             :       } else if (not has_converged) {
     307             :         tmpl::for_each<
     308             :             typename InterpolationTargetTag::horizon_find_failure_callbacks>(
     309             :             [&box, &cache, &temporal_id, &status](auto callback_v) {
     310             :               using callback = tmpl::type_from<decltype(callback_v)>;
     311             :               callback::template apply<InterpolationTargetTag>(
     312             :                   *box, *cache, temporal_id, status);
     313             :             });
     314             :         horizon_finder_failed = true;
     315             :       }
     316             :     }
     317             : 
     318             :     // If it failed, don't update any variables, just reset the Strahlkorper to
     319             :     // it's previous value
     320             :     if (horizon_finder_failed) {
     321             :       db::mutate<ylm::Tags::Strahlkorper<Frame>>(
     322             :           [](const gsl::not_null<ylm::Strahlkorper<Frame>*> strahlkorper,
     323             :              const std::deque<std::pair<double, ylm::Strahlkorper<Frame>>>&
     324             :                  previous_strahlkorpers) {
     325             :             // Don't keep a partially-converged strahlkorper in the
     326             :             // DataBox.  Reset to either the original initial guess or
     327             :             // to the last-found Strahlkorper (whichever one happens
     328             :             // to be in previous_strahlkorpers).
     329             :             *strahlkorper = previous_strahlkorpers.front().second;
     330             :           },
     331             :           box, db::get<ylm::Tags::PreviousStrahlkorpers<Frame>>(*box));
     332             :     } else {
     333             :       // The interpolated variables
     334             :       // Tags::Variables<InterpolationTargetTag::vars_to_interpolate_to_target>
     335             :       // have been interpolated from the volume to the points on the
     336             :       // prolonged_strahlkorper, not to the points on the actual
     337             :       // strahlkorper.  So here we do a restriction of these
     338             :       // quantities onto the actual strahlkorper.
     339             : 
     340             :       // Type alias to make code more understandable.
     341             :       using vars_tags =
     342             :           typename InterpolationTargetTag::vars_to_interpolate_to_target;
     343             :       db::mutate_apply<
     344             :           tmpl::list<::Tags::Variables<vars_tags>>,
     345             :           tmpl::list<ylm::Tags::Strahlkorper<Frame>, ::ah::Tags::FastFlow>>(
     346             :           [](const gsl::not_null<Variables<vars_tags>*> vars,
     347             :              const ylm::Strahlkorper<Frame>& strahlkorper,
     348             :              const FastFlow& fast_flow) {
     349             :             const size_t L_mesh = fast_flow.current_l_mesh(strahlkorper);
     350             :             const auto prolonged_strahlkorper =
     351             :                 ylm::Strahlkorper<Frame>(L_mesh, L_mesh, strahlkorper);
     352             :             auto new_vars = ::Variables<vars_tags>(
     353             :                 strahlkorper.ylm_spherepack().physical_size());
     354             : 
     355             :             tmpl::for_each<vars_tags>([&strahlkorper, &prolonged_strahlkorper,
     356             :                                        &vars, &new_vars](auto tag_v) {
     357             :               using tag = typename decltype(tag_v)::type;
     358             :               const auto& old_var = get<tag>(*vars);
     359             :               auto& new_var = get<tag>(new_vars);
     360             :               auto old_iter = old_var.begin();
     361             :               auto new_iter = new_var.begin();
     362             :               for (; old_iter != old_var.end() and new_iter != new_var.end();
     363             :                    ++old_iter, ++new_iter) {
     364             :                 *new_iter = strahlkorper.ylm_spherepack().spec_to_phys(
     365             :                     prolonged_strahlkorper.ylm_spherepack().prolong_or_restrict(
     366             :                         prolonged_strahlkorper.ylm_spherepack().phys_to_spec(
     367             :                             *old_iter),
     368             :                         strahlkorper.ylm_spherepack()));
     369             :               }
     370             :             });
     371             :             *vars = std::move(new_vars);
     372             :           },
     373             :           box);
     374             : 
     375             :       // Compute Strahlkorper Cartesian coordinates in Inertial frame
     376             :       // if the current frame is not inertial.
     377             :       if constexpr (not std::is_same_v<Frame, ::Frame::Inertial>) {
     378             :         db::mutate_apply<
     379             :             tmpl::list<ylm::Tags::CartesianCoords<::Frame::Inertial>>,
     380             :             tmpl::list<ylm::Tags::Strahlkorper<Frame>,
     381             :                        domain::Tags::Domain<Metavariables::volume_dim>>>(
     382             :             [&cache, &temporal_id](
     383             :                 const gsl::not_null<tnsr::I<DataVector, 3, ::Frame::Inertial>*>
     384             :                     inertial_strahlkorper_coords,
     385             :                 const ylm::Strahlkorper<Frame>& strahlkorper,
     386             :                 const Domain<Metavariables::volume_dim>& domain) {
     387             :               // Note that functions_of_time must already be up to
     388             :               // date at temporal_id because they were used in the AH
     389             :               // search above.
     390             :               const auto& functions_of_time =
     391             :                   get<domain::Tags::FunctionsOfTime>(*cache);
     392             :               strahlkorper_coords_in_different_frame(
     393             :                   inertial_strahlkorper_coords, strahlkorper, domain,
     394             :                   functions_of_time,
     395             :                   InterpolationTarget_detail::get_temporal_id_value(
     396             :                       temporal_id));
     397             :             },
     398             :             box);
     399             :       }
     400             : 
     401             :       // Update the previous strahlkorpers. We do this before the callbacks
     402             :       // in case any of the callbacks need the previous strahlkorpers with the
     403             :       // current strahlkorper already in it.
     404             :       db::mutate<ylm::Tags::Strahlkorper<Frame>,
     405             :                  ylm::Tags::PreviousStrahlkorpers<Frame>>(
     406             :           [&temporal_id](
     407             :               const gsl::not_null<ylm::Strahlkorper<Frame>*> strahlkorper,
     408             :               const gsl::not_null<
     409             :                   std::deque<std::pair<double, ylm::Strahlkorper<Frame>>>*>
     410             :                   previous_strahlkorpers) {
     411             :             // This is the number of previous strahlkorpers that we
     412             :             // keep around.
     413             :             const size_t num_previous_strahlkorpers = 3;
     414             : 
     415             :             // Save a new previous_strahlkorper.
     416             :             previous_strahlkorpers->emplace_front(
     417             :                 InterpolationTarget_detail::get_temporal_id_value(temporal_id),
     418             :                 *strahlkorper);
     419             : 
     420             :             // Remove old previous_strahlkorpers that are no longer relevant.
     421             :             while (previous_strahlkorpers->size() >
     422             :                    num_previous_strahlkorpers) {
     423             :               previous_strahlkorpers->pop_back();
     424             :             }
     425             :           },
     426             :           box);
     427             : 
     428             :       // Finally call callbacks
     429             :       tmpl::for_each<
     430             :           typename InterpolationTargetTag::post_horizon_find_callbacks>(
     431             :           [&box, &cache, &temporal_id](auto callback_v) {
     432             :             using callback = tmpl::type_from<decltype(callback_v)>;
     433             :             callback::apply(*box, *cache, temporal_id);
     434             :           });
     435             :     }
     436             : 
     437             :     // Prepare for finding horizon at a new time. Regardless of if we failed or
     438             :     // not, we reset fast flow.
     439             :     db::mutate<::ah::Tags::FastFlow>(
     440             :         [](const gsl::not_null<::FastFlow*> fast_flow) {
     441             :           fast_flow->reset_for_next_find();
     442             :         },
     443             :         box);
     444             : 
     445             :     // We return true because we are now done with all the volume data
     446             :     // at this temporal_id, so we want it cleaned up.
     447             :     return true;
     448             :   }
     449             : };
     450             : }  // namespace intrp::callbacks

Generated by: LCOV version 1.14