SpECTRE Documentation Coverage Report
Current view: top level - Time/StepChoosers - ErrorControl.hpp Hit Total Coverage
Commit: 3c072f0ce967e2e56649d3fa12aa2a0e4fe2a42e Lines: 3 46 6.5 %
Date: 2024-04-23 20:50:18
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 <algorithm>
       7             : #include <cmath>
       8             : #include <limits>
       9             : #include <memory>
      10             : #include <optional>
      11             : #include <pup.h>
      12             : #include <pup_stl.h>
      13             : #include <string>
      14             : #include <type_traits>
      15             : #include <utility>
      16             : #include <vector>
      17             : 
      18             : #include "DataStructures/DataBox/DataBoxTag.hpp"
      19             : #include "Options/String.hpp"
      20             : #include "ParallelAlgorithms/EventsAndTriggers/EventsAndTriggers.hpp"
      21             : #include "ParallelAlgorithms/EventsAndTriggers/Tags.hpp"
      22             : #include "Time/ChangeSlabSize/Event.hpp"
      23             : #include "Time/StepChoosers/StepChooser.hpp"  // IWYU pragma: keep
      24             : #include "Time/Tags/HistoryEvolvedVariables.hpp"
      25             : #include "Time/Tags/IsUsingTimeSteppingErrorControl.hpp"
      26             : #include "Time/Tags/PreviousStepperError.hpp"
      27             : #include "Time/Tags/StepperError.hpp"
      28             : #include "Time/Tags/StepperErrorUpdated.hpp"
      29             : #include "Time/TimeSteppers/TimeStepper.hpp"
      30             : #include "Utilities/Serialization/CharmPupable.hpp"
      31             : #include "Utilities/Serialization/PupStlCpp17.hpp"
      32             : #include "Utilities/TMPL.hpp"
      33             : #include "Utilities/TypeTraits/IsIterable.hpp"
      34             : 
      35             : /// \cond
      36             : namespace Tags {
      37             : template <bool LocalTimeStepping>
      38             : struct IsUsingTimeSteppingErrorControlCompute;
      39             : struct StepChoosers;
      40             : template <typename StepperInterface>
      41             : struct TimeStepper;
      42             : }  // namespace Tags
      43             : /// \endcond
      44             : 
      45             : namespace StepChoosers {
      46             : namespace ErrorControl_detail {
      47             : struct IsAnErrorControl {};
      48             : }  // namespace ErrorControl_detail
      49             : 
      50             : /*!
      51             :  * \brief Suggests a step size based on a target absolute and/or relative error
      52             :  * measure.
      53             :  *
      54             :  * \details The suggested step is calculated via a simple specialization of the
      55             :  * scheme suggested in \cite Hairer1993. We first compute the aggregated error
      56             :  * measure from the stepper error:
      57             :  *
      58             :  * \f[
      59             :  * E = \max_i(|E_i| / sc_i),
      60             :  * \f]
      61             :  *
      62             :  * where \f$E_i\f$ is the ODE error reported for each individual grid point,
      63             :  * reported by the time stepper, and \f$sc_i\f$ is the step control measure
      64             :  * determined by the tolerances:
      65             :  *
      66             :  * \f[
      67             :  * sc_i = Atol_i + \max(|y_i|,|y_i + E_i|) Rtol_i,
      68             :  * \f]
      69             :  *
      70             :  * and \f$y_i\f$ is the value of the function at the current step at grid point
      71             :  * \f$i\f$.
      72             :  *
      73             :  * When no previous record of previous error is available, the step has size:
      74             :  *
      75             :  * \f[
      76             :  * h_{\text{new}} = h \cdot \min\left(F_{\text{max}},
      77             :  * \max\left(F_{\text{min}},
      78             :  * \frac{F_{\text{safety}}}{E^{1/(q + 1)}}\right)\right),
      79             :  * \f]
      80             :  *
      81             :  * where \f$h_{\text{new}}\f$ is the new suggested step size \f$h\f$ is the
      82             :  * previous step size, \f$F_{\text{max}}\f$ is the maximum factor by which we
      83             :  * allow the step to increase, \f$F_{\text{min}}\f$ is the minimum factor by
      84             :  * which we allow the step to decrease. \f$F_{\text{safety}}\f$ is the safety
      85             :  * factor on the computed error -- this forces the step size slightly lower
      86             :  * than we would naively compute so that the result of the step will likely be
      87             :  * within the target error. \f$q\f$ is the order of the stepper error
      88             :  * calculation. Intuitively, we should change the step less drastically for a
      89             :  * higher order stepper.
      90             :  *
      91             :  * After the first error calculation, the error \f$E\f$ is recorded in the \ref
      92             :  * DataBoxGroup "DataBox" using tag
      93             :  * `StepChoosers::Tags::PreviousStepError<EvolvedVariablesTag>`, and subsequent
      94             :  * error calculations use a simple PI scheme suggested in \cite NumericalRecipes
      95             :  * section 17.2.1:
      96             :  *
      97             :  * \f[
      98             :  * h_{\text{new}} = h \cdot \min\left(F_{\text{max}},
      99             :  * \max\left(F_{\text{min}},
     100             :  * F_{\text{safety}} E^{-0.7 / (q + 1)}
     101             :  * E_{\text{prev}}^{0.4 / (q + 1)}\right)\right),
     102             :  * \f]
     103             :  *
     104             :  * where \f$E_{\text{prev}}\f$ is the error computed in the previous step.
     105             :  *
     106             :  * \note The template parameter `ErrorControlSelector` is used to disambiguate
     107             :  * in the input-file options between `ErrorControl` step choosers that are
     108             :  * based on different variables. This is needed if multiple systems are evolved
     109             :  * in the same executable. The name used for the input file includes
     110             :  * `ErrorControlSelector::name()` if it is provided.
     111             :  */
     112             : template <typename StepChooserUse, typename EvolvedVariableTag,
     113             :           typename ErrorControlSelector = NoSuchType>
     114           1 : class ErrorControl : public StepChooser<StepChooserUse>,
     115             :                      public ErrorControl_detail::IsAnErrorControl {
     116             :  public:
     117           0 :   using evolved_variable_type = typename EvolvedVariableTag::type;
     118           0 :   using error_variable_type =
     119             :       typename ::Tags::StepperError<EvolvedVariableTag>::type;
     120             : 
     121             :   /// \cond
     122             :   ErrorControl() = default;
     123             :   explicit ErrorControl(CkMigrateMessage* /*unused*/) {}
     124             :   using PUP::able::register_constructor;
     125             :   WRAPPED_PUPable_decl_template(ErrorControl);  // NOLINT
     126             :   /// \endcond
     127             : 
     128           0 :   static std::string name() {
     129             :     if constexpr (std::is_same_v<ErrorControlSelector, NoSuchType>) {
     130             :       return "ErrorControl";
     131             :     } else {
     132             :       return "ErrorControl(" + ErrorControlSelector::name() + ")";
     133             :     }
     134             :   }
     135             : 
     136           0 :   struct AbsoluteTolerance {
     137           0 :     using type = double;
     138           0 :     static constexpr Options::String help{"Target absolute tolerance"};
     139           0 :     static type lower_bound() { return 0.0; }
     140             :   };
     141             : 
     142           0 :   struct RelativeTolerance {
     143           0 :     using type = double;
     144           0 :     static constexpr Options::String help{"Target relative tolerance"};
     145           0 :     static type lower_bound() { return 0.0; }
     146             :   };
     147             : 
     148           0 :   struct MaxFactor {
     149           0 :     using type = double;
     150           0 :     static constexpr Options::String help{
     151             :         "Maximum factor to increase the step by"};
     152           0 :     static type lower_bound() { return 1.0; }
     153             :   };
     154             : 
     155           0 :   struct MinFactor {
     156           0 :     using type = double;
     157           0 :     static constexpr Options::String help{
     158             :         "Minimum factor to increase the step by"};
     159           0 :     static type lower_bound() { return 0.0; }
     160           0 :     static type upper_bound() { return 1.0; }
     161             :   };
     162             : 
     163           0 :   struct SafetyFactor {
     164           0 :     using type = double;
     165           0 :     static constexpr Options::String help{
     166             :         "Extra factor to apply to step estimate; can be used to decrease step "
     167             :         "size to improve step acceptance rate."};
     168           0 :     static type lower_bound() { return 0.0; }
     169             :   };
     170             : 
     171           0 :   static constexpr Options::String help{
     172             :       "Chooses a step based on a target relative and absolute error tolerance"};
     173           0 :   using options = tmpl::list<AbsoluteTolerance, RelativeTolerance, MaxFactor,
     174             :                              MinFactor, SafetyFactor>;
     175             : 
     176           0 :   ErrorControl(const double absolute_tolerance, const double relative_tolerance,
     177             :                const double max_factor, const double min_factor,
     178             :                const double safety_factor)
     179             :       : absolute_tolerance_{absolute_tolerance},
     180             :         relative_tolerance_{relative_tolerance},
     181             :         max_factor_{max_factor},
     182             :         min_factor_{min_factor},
     183             :         safety_factor_{safety_factor} {}
     184             : 
     185           0 :   using simple_tags =
     186             :       tmpl::list<::Tags::StepperError<EvolvedVariableTag>,
     187             :                  ::Tags::PreviousStepperError<EvolvedVariableTag>,
     188             :                  ::Tags::StepperErrorUpdated>;
     189             : 
     190           0 :   using compute_tags = tmpl::list<Tags::IsUsingTimeSteppingErrorControlCompute<
     191             :       std::is_same_v<StepChooserUse, ::StepChooserUse::LtsStep>>>;
     192             : 
     193           0 :   using argument_tags =
     194             :       tmpl::list<::Tags::HistoryEvolvedVariables<EvolvedVariableTag>,
     195             :                  ::Tags::StepperError<EvolvedVariableTag>,
     196             :                  ::Tags::PreviousStepperError<EvolvedVariableTag>,
     197             :                  ::Tags::StepperErrorUpdated, ::Tags::TimeStepper<TimeStepper>>;
     198             : 
     199           0 :   std::pair<double, bool> operator()(
     200             :       const TimeSteppers::History<evolved_variable_type>& history,
     201             :       const error_variable_type& error,
     202             :       const error_variable_type& previous_error,
     203             :       const bool& stepper_error_updated, const TimeStepper& stepper,
     204             :       const double previous_step) const {
     205             :     // request that the step size not be changed if there isn't a new error
     206             :     // estimate
     207             :     if (not stepper_error_updated) {
     208             :       return std::make_pair(std::numeric_limits<double>::infinity(), true);
     209             :     }
     210             :     const double l_inf_error = error_calc_impl(history.latest_value(), error);
     211             :     double new_step;
     212             :     if (previous_error.number_of_grid_points() !=
     213             :         history.latest_value().number_of_grid_points()) {
     214             :       new_step = previous_step *
     215             :                  std::clamp(safety_factor_ *
     216             :                                 pow(1.0 / std::max(l_inf_error, 1e-14),
     217             :                                     1.0 / (stepper.error_estimate_order() + 1)),
     218             :                             min_factor_, max_factor_);
     219             :     } else {
     220             :       const double previous_l_inf_error =
     221             :           error_calc_impl(history.latest_value(), previous_error);
     222             :       // From simple advice from Numerical Recipes 17.2.1 regarding a heuristic
     223             :       // for PI step control.
     224             :       const double alpha_factor = 0.7 / (stepper.error_estimate_order() + 1);
     225             :       const double beta_factor = 0.4 / (stepper.error_estimate_order() + 1);
     226             :       new_step =
     227             :           previous_step *
     228             :           std::clamp(
     229             :               safety_factor_ *
     230             :                   pow(1.0 / std::max(l_inf_error, 1e-14), alpha_factor) *
     231             :                   pow(std::max(previous_l_inf_error, 1e-14), beta_factor),
     232             :               min_factor_, max_factor_);
     233             :     }
     234             :     return std::make_pair(new_step, l_inf_error <= 1.0);
     235             :   }
     236             : 
     237           1 :   bool uses_local_data() const override { return true; }
     238             : 
     239           0 :   void pup(PUP::er& p) override {  // NOLINT
     240             :     p | absolute_tolerance_;
     241             :     p | relative_tolerance_;
     242             :     p | min_factor_;
     243             :     p | max_factor_;
     244             :     p | safety_factor_;
     245             :   }
     246             : 
     247             :  private:
     248             :   template <typename EvolvedType, typename ErrorType>
     249           0 :   double error_calc_impl(const EvolvedType& values,
     250             :                          const ErrorType& errors) const {
     251             :     if constexpr (std::is_fundamental_v<std::remove_cv_t<EvolvedType>> or
     252             :                   tt::is_complex_of_fundamental_v<
     253             :                       std::remove_cv_t<EvolvedType>>) {
     254             :       return std::abs(errors) /
     255             :              (absolute_tolerance_ +
     256             :               relative_tolerance_ *
     257             :                   std::max(abs(values), abs(values + errors)));
     258             :     } else if constexpr (tt::is_iterable_v<std::remove_cv_t<EvolvedType>>) {
     259             :       double result = 0.0;
     260             :       double recursive_call_result;
     261             :       for (auto val_it = values.begin(), err_it = errors.begin();
     262             :            val_it != values.end(); ++val_it, ++err_it) {
     263             :         recursive_call_result = error_calc_impl(*val_it, *err_it);
     264             :         if (recursive_call_result > result) {
     265             :           result = recursive_call_result;
     266             :         }
     267             :       }
     268             :       return result;
     269             :     } else if constexpr (tt::is_a_v<Variables, std::remove_cv_t<EvolvedType>>) {
     270             :       double result = 0.0;
     271             :       double recursive_call_result;
     272             :       tmpl::for_each<typename EvolvedType::tags_list>(
     273             :           [this, &errors, &values, &recursive_call_result,
     274             :            &result](auto tag_v) {
     275             :             // clang erroneously warns that `this` is not used despite requiring
     276             :             // it in the capture...
     277             :             (void)this;
     278             :             using tag = typename decltype(tag_v)::type;
     279             :             recursive_call_result =
     280             :                 error_calc_impl(get<tag>(values), get<tag>(errors));
     281             :             if (recursive_call_result > result) {
     282             :               result = recursive_call_result;
     283             :             }
     284             :           });
     285             :       return result;
     286             :     } else if constexpr (is_any_spin_weighted_v<
     287             :                              std::remove_cv_t<EvolvedType>>) {
     288             :       return error_calc_impl(values.data(), errors.data());
     289             :     }
     290             :   }
     291             : 
     292           0 :   double absolute_tolerance_ = std::numeric_limits<double>::signaling_NaN();
     293           0 :   double relative_tolerance_ = std::numeric_limits<double>::signaling_NaN();
     294           0 :   double max_factor_ = std::numeric_limits<double>::signaling_NaN();
     295           0 :   double min_factor_ = std::numeric_limits<double>::signaling_NaN();
     296           0 :   double safety_factor_ = std::numeric_limits<double>::signaling_NaN();
     297             : };
     298             : /// \cond
     299             : template <typename StepChooserUse, typename EvolvedVariableTag,
     300             :           typename ErrorControlSelector>
     301             : PUP::able::PUP_ID ErrorControl<StepChooserUse, EvolvedVariableTag,
     302             :                                ErrorControlSelector>::my_PUP_ID = 0;  // NOLINT
     303             : /// \endcond
     304             : }  // namespace StepChoosers
     305             : 
     306             : namespace Tags {
     307             : /// \ingroup TimeGroup
     308             : /// \brief A tag that is true if the `ErrorControl` step chooser is one of the
     309             : /// option-created `Event`s.
     310             : template <bool LocalTimeStepping>
     311           1 : struct IsUsingTimeSteppingErrorControlCompute
     312             :     : db::ComputeTag,
     313             :     IsUsingTimeSteppingErrorControl {
     314           0 :   using base = IsUsingTimeSteppingErrorControl;
     315           0 :   using argument_tags =
     316             :       tmpl::conditional_t<LocalTimeStepping, tmpl::list<::Tags::StepChoosers>,
     317             :                           tmpl::list<::Tags::EventsAndTriggers>>;
     318             : 
     319             :   // local time stepping
     320           0 :   static void function(
     321             :       const gsl::not_null<bool*> is_using_error_control,
     322             :       const std::vector<
     323             :           std::unique_ptr<::StepChooser<StepChooserUse::LtsStep>>>&
     324             :           step_choosers) {
     325             :     *is_using_error_control = false;
     326             :     for (const auto& step_chooser : step_choosers) {
     327             :       if (dynamic_cast<
     328             :               const ::StepChoosers::ErrorControl_detail::IsAnErrorControl*>(
     329             :               &*step_chooser) != nullptr) {
     330             :         *is_using_error_control = true;
     331             :         return;
     332             :       }
     333             :     }
     334             :   }
     335             : 
     336             :   // global time stepping
     337           0 :   static void function(const gsl::not_null<bool*> is_using_error_control,
     338             :                        const ::EventsAndTriggers& events_and_triggers) {
     339             :     // In principle the slab size could be changed based on a dense
     340             :     // trigger, but it's not clear that there is ever a good reason to
     341             :     // do so, and it wouldn't make sense to use error control in that
     342             :     // context in any case.
     343             :     *is_using_error_control = false;
     344             :     events_and_triggers.for_each_event([&](const auto& event) {
     345             :       if (*is_using_error_control) {
     346             :         return;
     347             :       }
     348             :       if (const auto* const change_slab_size =
     349             :               dynamic_cast<const ::Events::ChangeSlabSize*>(&event)) {
     350             :         change_slab_size->for_each_step_chooser(
     351             :             [&](const StepChooser<StepChooserUse::Slab>& step_chooser) {
     352             :               if (*is_using_error_control) {
     353             :                 return;
     354             :               }
     355             :               if (dynamic_cast<const ::StepChoosers::ErrorControl_detail::
     356             :                                    IsAnErrorControl*>(&step_chooser) !=
     357             :                   nullptr) {
     358             :                 *is_using_error_control = true;
     359             :               }
     360             :             });
     361             :       }
     362             :     });
     363             :   }
     364             : };
     365             : }  // namespace Tags

Generated by: LCOV version 1.14