diff --git a/src/Evolution/Executables/GeneralizedHarmonic/EvolveGhBinaryBlackHole.hpp b/src/Evolution/Executables/GeneralizedHarmonic/EvolveGhBinaryBlackHole.hpp index 6601554899d5c..f2417dfdbcd90 100644 --- a/src/Evolution/Executables/GeneralizedHarmonic/EvolveGhBinaryBlackHole.hpp +++ b/src/Evolution/Executables/GeneralizedHarmonic/EvolveGhBinaryBlackHole.hpp @@ -58,6 +58,7 @@ #include "Evolution/Systems/GeneralizedHarmonic/System.hpp" #include "Evolution/Systems/GeneralizedHarmonic/Tags.hpp" #include "Evolution/Tags/Filter.hpp" +#include "Evolution/Triggers/FractionOfOrbit.hpp" #include "Evolution/Triggers/NumberOfOrbits.hpp" #include "Evolution/Triggers/SeparationLessThan.hpp" #include "Evolution/TypeTraits.hpp" @@ -504,6 +505,7 @@ struct EvolutionMetavars { tmpl::pair< Trigger, tmpl::append, tmpl::list, tmpl::list>>>; }; diff --git a/src/Evolution/Triggers/CMakeLists.txt b/src/Evolution/Triggers/CMakeLists.txt index 5a314948095b2..3f007f718b501 100644 --- a/src/Evolution/Triggers/CMakeLists.txt +++ b/src/Evolution/Triggers/CMakeLists.txt @@ -8,6 +8,7 @@ add_spectre_library(${LIBRARY}) spectre_target_sources( ${LIBRARY} PRIVATE + FractionOfOrbit.cpp SeparationLessThan.cpp NumberOfOrbits.cpp ) @@ -16,6 +17,7 @@ spectre_target_headers( ${LIBRARY} INCLUDE_DIRECTORY ${CMAKE_SOURCE_DIR}/src HEADERS + FractionOfOrbit.hpp SeparationLessThan.hpp NumberOfOrbits.hpp ) diff --git a/src/Evolution/Triggers/FractionOfOrbit.cpp b/src/Evolution/Triggers/FractionOfOrbit.cpp new file mode 100644 index 0000000000000..7f372cbca574f --- /dev/null +++ b/src/Evolution/Triggers/FractionOfOrbit.cpp @@ -0,0 +1,49 @@ +// Distributed under the MIT License. +// See LICENSE.txt for details. + +#include "Evolution/Triggers/FractionOfOrbit.hpp" + +#include +#include +#include +#include +#include +#include + +#include "Domain/FunctionsOfTime/QuaternionFunctionOfTime.hpp" + +namespace Triggers { +FractionOfOrbit::FractionOfOrbit(const double fraction) + : fraction_of_orbit_(fraction) {} + +bool FractionOfOrbit::operator()( + const double time, + const std::unordered_map< + std::string, std::unique_ptr>& + functions_of_time) { + for (auto i = functions_of_time.begin(); i != functions_of_time.end(); i++) { + const auto* const rot_f_of_t = dynamic_cast< + const domain::FunctionsOfTime::QuaternionFunctionOfTime<3>*>( + (i->second.get())); + if (rot_f_of_t != nullptr) { + const double orbits_since_last_trigger = + (rot_f_of_t->full_angle(time) - + rot_f_of_t->full_angle(last_trigger_time_)) / + (2.0 * M_PI); + if (orbits_since_last_trigger >= fraction_of_orbit_) { + last_trigger_time_ = time; + return true; + } else { + return false; + } + } + } + ERROR( + "FractionOfOrbit trigger can only be used when the rotation map is " + "active"); +} + +void FractionOfOrbit::pup(PUP::er& p) { p | fraction_of_orbit_; } + +PUP::able::PUP_ID FractionOfOrbit::my_PUP_ID = 0; // NOLINT +} // namespace Triggers diff --git a/src/Evolution/Triggers/FractionOfOrbit.hpp b/src/Evolution/Triggers/FractionOfOrbit.hpp new file mode 100644 index 0000000000000..d0584719b96bc --- /dev/null +++ b/src/Evolution/Triggers/FractionOfOrbit.hpp @@ -0,0 +1,63 @@ +// Distributed under the MIT License. +// See LICENSE.txt for details. + +#pragma once + +#include +#include +#include + +#include "Domain/FunctionsOfTime/FunctionOfTime.hpp" +#include "Options/String.hpp" +#include "ParallelAlgorithms/EventsAndTriggers/Trigger.hpp" +#include "Utilities/Serialization/CharmPupable.hpp" +#include "Utilities/TMPL.hpp" + +namespace domain::Tags { +template +struct Domain; +struct FunctionsOfTime; +} // namespace domain::Tags +namespace Tags { +struct Time; +} // namespace Tags + +namespace Triggers { +class FractionOfOrbit : public Trigger { + public: + /// \cond + FractionOfOrbit() = default; + explicit FractionOfOrbit(CkMigrateMessage* /*unused*/) {} + using PUP::able::register_constructor; + WRAPPED_PUPable_decl_template(FractionOfOrbit); // NOLINT + /// \endcond + + struct Value { + using type = double; + static constexpr Options::String help = { + "Fraction of an orbit completed between triggers."}; + }; + + using options = tmpl::list; + static constexpr Options::String help{ + "Trigger when the evolution has reached a fraction of an orbit since the " + "trigger was last triggered."}; + + explicit FractionOfOrbit(double fraction_of_orbit); + + using argument_tags = tmpl::list; + + bool operator()(const double time, + const std::unordered_map< + std::string, + std::unique_ptr>& + functions_of_time); + + // NOLINENEXTLINE(google-runtime-references) + void pup(PUP::er& p) override; + + private: + double fraction_of_orbit_{}; + double last_trigger_time_ = 0.0; +}; +} // namespace Triggers diff --git a/tests/Unit/Evolution/Triggers/CMakeLists.txt b/tests/Unit/Evolution/Triggers/CMakeLists.txt index e8a4a45224d66..7dd685d0ce7b6 100644 --- a/tests/Unit/Evolution/Triggers/CMakeLists.txt +++ b/tests/Unit/Evolution/Triggers/CMakeLists.txt @@ -4,6 +4,7 @@ set(LIBRARY "Test_EvolutionTriggers") set(LIBRARY_SOURCES + Test_FractionOfOrbit.cpp Test_SeparationLessThan.cpp Test_NumberOfOrbits.cpp ) diff --git a/tests/Unit/Evolution/Triggers/Test_FractionOfOrbit.cpp b/tests/Unit/Evolution/Triggers/Test_FractionOfOrbit.cpp new file mode 100644 index 0000000000000..5da046a88647d --- /dev/null +++ b/tests/Unit/Evolution/Triggers/Test_FractionOfOrbit.cpp @@ -0,0 +1,88 @@ +// Distributed under the MIT License. +// See LICENSE.txt for details. + +#include "Framework/TestingFramework.hpp" + +#include + +#include "Domain/CoordinateMaps/TimeDependent/Rotation.hpp" +#include "Domain/Domain.hpp" +#include "Domain/FunctionsOfTime/FunctionOfTime.hpp" +#include "Domain/FunctionsOfTime/QuaternionFunctionOfTime.hpp" +#include "Evolution/Triggers/FractionOfOrbit.hpp" +#include "Framework/TestCreation.hpp" +#include "Framework/TestHelpers.hpp" +#include "Options/Protocols/FactoryCreation.hpp" +#include "ParallelAlgorithms/EventsAndTriggers/Trigger.hpp" + +#include + +namespace { +struct Metavariables { + using component_list = tmpl::list<>; + struct factory_creation + : tt::ConformsTo { + using factory_classes = + tmpl::map>>; + }; +}; + +using RotationMap = domain::CoordinateMaps::TimeDependent::Rotation<3>; + +void test() { + const std::string f_of_t_name = "Rotation"; + std::unordered_map> + functions_of_time{}; + DataVector axis{{0.0, 0.0, 1.0}}; + std::array init_func = {axis, axis * M_PI / 10, axis * 0.0, + axis * 0.0}; + const std::array init_quat{DataVector{ + {cos(0.5), axis[0] * sin(0.5), axis[1] * sin(0.5), axis[2] * sin(0.5)}}}; + domain::FunctionsOfTime::QuaternionFunctionOfTime<3> quat_f_of_t{ + 0.0, init_quat, init_func, 20.0}; + functions_of_time[f_of_t_name] = + std::make_unique>( + quat_f_of_t); + double fraction_of_orbit = 0.25; + Triggers::FractionOfOrbit trigger{fraction_of_orbit}; + int time = 0; + while (time <= 20) { + bool is_triggered = trigger(time, functions_of_time); + bool expected_is_triggered = time % 5 == 0; + CHECK(is_triggered == expected_is_triggered); + time += 1; + } + + TestHelpers::test_creation, Metavariables>( + "FractionOfOrbit:\n" + " Value: 0.25"); +} + +void test_errors() { + const std::string f_of_t_name = "NeonPegasus"; + std::unordered_map> + functions_of_time{}; + DataVector axis{{0.0, 0.0, 1.0}}; + std::array init_func = {axis, axis, axis}; + const std::array init_quat{ + DataVector{{0.0, axis[0], axis[1], axis[2]}}}; + functions_of_time[f_of_t_name] = + std::make_unique>( + 0.0, init_quat, init_func, 20.0); + + Triggers::FractionOfOrbit trigger{0.1}; + + CHECK_THROWS_WITH(trigger(0.0, functions_of_time), + Catch::Matchers::ContainsSubstring( + "FractionOfOrbit trigger can only be used when the " + "rotation map is active")); +} + +SPECTRE_TEST_CASE("Unit.Evolution.Triggers.FractionOfOrbit", + "[Unit][Evolution]") { + test(); + test_errors(); +} +} // namespace