diff --git a/src/ControlSystem/Trigger.hpp b/src/ControlSystem/Trigger.hpp index 9c5bf76635f2e..22f1238d5c40e 100644 --- a/src/ControlSystem/Trigger.hpp +++ b/src/ControlSystem/Trigger.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "ControlSystem/CombinedName.hpp" @@ -136,7 +137,8 @@ class Trigger : public DenseTrigger { // At least one control system is active const bool is_ready = domain::functions_of_time_are_ready< control_system::Tags::MeasurementTimescales>( - cache, array_index, component, time, std::array{measurement_name_}); + cache, array_index, component, time, + std::unordered_set{measurement_name_}); if (not is_ready) { if (Parallel::get(cache) >= ::Verbosity::Debug) { Parallel::printf( diff --git a/src/Domain/FunctionsOfTime/FunctionsOfTimeAreReady.hpp b/src/Domain/FunctionsOfTime/FunctionsOfTimeAreReady.hpp index 3faa57218d7cc..77843b2636e82 100644 --- a/src/Domain/FunctionsOfTime/FunctionsOfTimeAreReady.hpp +++ b/src/Domain/FunctionsOfTime/FunctionsOfTimeAreReady.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "DataStructures/DataBox/DataBox.hpp" @@ -18,6 +19,7 @@ #include "Parallel/ArrayComponentId.hpp" #include "Parallel/Callback.hpp" #include "Parallel/GlobalCache.hpp" +#include "Parallel/ParallelComponentHelpers.hpp" #include "Utilities/Algorithm.hpp" #include "Utilities/ErrorHandling/Assert.hpp" #include "Utilities/StdHelpers.hpp" @@ -36,55 +38,102 @@ class TaggedTuple; /// \endcond namespace domain { +namespace detail { +template +bool functions_of_time_are_ready_impl( + Parallel::GlobalCache& cache, const ArrayIndex& array_index, + const Component* /*meta*/, const double time, + const std::optional>& functions_to_check, + Args... args) { + if constexpr (Parallel::is_in_global_cache) { + const auto& proxy = + ::Parallel::get_parallel_component(cache)[array_index]; + const Parallel::ArrayComponentId array_component_id = + Parallel::make_array_component_id(array_index); + + return Parallel::mutable_cache_item_is_ready( + cache, array_component_id, + [&functions_to_check, &proxy, &time, + &args...](const std::unordered_map< + std::string, + std::unique_ptr>& + functions_of_time) { + using ::operator<<; + ASSERT( + alg::all_of( + functions_to_check.value_or( + std::unordered_set{}), + [&functions_of_time](const std::string& function_to_check) { + return functions_of_time.count(function_to_check) == 1; + }), + "Not all functions to check (" + << functions_to_check.value() << ") are in the global cache (" + << keys_of(functions_of_time) << ")"); + for (const auto& [name, f_of_t] : functions_of_time) { + if (functions_to_check.has_value() and + functions_to_check->count(name) == 0) { + continue; + } + const double expiration_time = f_of_t->time_bounds()[1]; + if (time > expiration_time) { + return std::unique_ptr( + new Callback(proxy, std::move(args)...)); + } + } + return std::unique_ptr{}; + }); + } else { + return true; + } +} +} // namespace detail + /// \ingroup ComputationalDomainGroup /// Check that functions of time are up-to-date. /// /// Check that functions of time in \p CacheTag with names in \p -/// functions_to_check are ready at time \p time. If no names are -/// listed in \p functions_to_check, checks all functions in \p -/// CacheTag. If any function is not ready, schedules a -/// `Parallel::PerformAlgorithmCallback` with the GlobalCache. +/// functions_to_check are ready at time \p time. If \p functions_to_check is +/// a `std::nullopt`, checks all functions in \p CacheTag. If any function is +/// not ready, schedules a `Parallel::PerformAlgorithmCallback` with the +/// GlobalCache.. template + typename Component> bool functions_of_time_are_ready( Parallel::GlobalCache& cache, const ArrayIndex& array_index, - const Component* /*meta*/, const double time, - const std::array& functions_to_check = - std::array{}) { - const auto& proxy = - ::Parallel::get_parallel_component(cache)[array_index]; - const Parallel::ArrayComponentId array_component_id = - Parallel::make_array_component_id(array_index); + const Component* component_p, const double time, + const std::optional>& functions_to_check = + std::nullopt) { + using ProxyType = std::decay_t(cache)[array_index])>; + return detail::functions_of_time_are_ready_impl< + CacheTag, Parallel::PerformAlgorithmCallback>( + cache, array_index, component_p, time, functions_to_check); +} - return Parallel::mutable_cache_item_is_ready( - cache, array_component_id, - [&functions_to_check, &proxy, - &time](const std::unordered_map< - std::string, - std::unique_ptr>& - functions_of_time) { - using ::operator<<; - ASSERT(alg::all_of( - functions_to_check, - [&functions_of_time](const std::string& function_to_check) { - return functions_of_time.count(function_to_check) == 1; - }), - "Not all functions to check (" - << functions_to_check << ") are in the global cache (" - << keys_of(functions_of_time) << ")"); - for (const auto& [name, f_of_t] : functions_of_time) { - if ((not functions_to_check.empty()) and - alg::find(functions_to_check, name) == functions_to_check.end()) { - continue; - } - const double expiration_time = f_of_t->time_bounds()[1]; - if (time > expiration_time) { - return std::unique_ptr( - new Parallel::PerformAlgorithmCallback(proxy)); - } - } - return std::unique_ptr{}; - }); +/// \ingroup ComputationalDomainGroup +/// Check that functions of time are up-to-date. +/// +/// Check that functions of time in \p CacheTag with names in \p +/// functions_to_check are ready at time \p time. If \p functions_to_check is +/// a `std::nullopt`, checks all functions in \p CacheTag. If any function is +/// ready, schedules a `Parallel::SimpleActionCallback` with the GlobalCache +/// which calls the simple action passed in as a template parameter. The `Args` +/// are moved into the callback. +template +bool functions_of_time_are_ready( + Parallel::GlobalCache& cache, const ArrayIndex& array_index, + const Component* component_p, const double time, + const std::optional>& functions_to_check, + Args... args) { + using ProxyType = std::decay_t(cache)[array_index])>; + return detail::functions_of_time_are_ready_impl< + CacheTag, + Parallel::SimpleActionCallback>( + cache, array_index, component_p, time, functions_to_check, + std::move(args)...); } namespace Actions { diff --git a/tests/Unit/Domain/FunctionsOfTime/Test_FunctionsOfTimeAreReady.cpp b/tests/Unit/Domain/FunctionsOfTime/Test_FunctionsOfTimeAreReady.cpp index f9ecb2825d901..03175febedd9b 100644 --- a/tests/Unit/Domain/FunctionsOfTime/Test_FunctionsOfTimeAreReady.cpp +++ b/tests/Unit/Domain/FunctionsOfTime/Test_FunctionsOfTimeAreReady.cpp @@ -18,6 +18,7 @@ #include "Framework/MockDistributedObject.hpp" #include "Framework/MockRuntimeSystem.hpp" #include "Framework/MockRuntimeSystemFreeFunctions.hpp" +#include "Parallel/Callback.hpp" #include "Parallel/GlobalCache.hpp" #include "Parallel/Phase.hpp" #include "Parallel/PhaseDependentActionList.hpp" @@ -44,7 +45,33 @@ struct UpdateFoT { } }; -template +size_t simple_action_no_args_call_count = 0; + +struct SimpleActionNoArgs { + template + static void apply(db::DataBox& /*box*/, + Parallel::GlobalCache& /*cache*/, + const ArrayIndex& /*array_index*/) { + ++simple_action_no_args_call_count; + } +}; + +size_t simple_action_args_call_count = 0; + +struct SimpleActionArgs { + template + static void apply(db::DataBox& /*box*/, + Parallel::GlobalCache& /*cache*/, + const ArrayIndex& /*array_index*/, const size_t size, + const DataVector& some_data) { + ++simple_action_args_call_count; + CHECK(some_data.size() == size); + } +}; + +template struct Component { using metavariables = Metavariables; using chare_type = ActionTesting::MockArrayChare; @@ -59,8 +86,17 @@ struct Component { tmpl::list>>; }; +template +struct EmptyComponent : Component { + using mutable_global_cache_tags = tmpl::list<>; +}; +struct EmptyMetavars { + using component_list = tmpl::list>; +}; + struct Metavariables { - using component_list = tmpl::list>; + using component_list = tmpl::list, + Component>; }; } // namespace @@ -68,8 +104,10 @@ SPECTRE_TEST_CASE("Unit.Domain.FunctionsOfTimeAreReady", "[Domain][Unit]") { register_classes_with_charm< domain::FunctionsOfTime::PiecewisePolynomial<2>>(); using MockRuntimeSystem = ActionTesting::MockRuntimeSystem; - using component = Component; - const component* const component_p = nullptr; + using component0 = Component; + using component1 = Component; + const component0* const component_0_p = nullptr; + const component1* const component_1_p = nullptr; const std::array fot_init{{{0.0}, {0.0}, {0.0}}}; FunctionMap functions_of_time{}; @@ -90,59 +128,209 @@ SPECTRE_TEST_CASE("Unit.Domain.FunctionsOfTimeAreReady", "[Domain][Unit]") { MockRuntimeSystem runner{ {}, {std::move(functions_of_time), std::move(other_functions_of_time)}}; - ActionTesting::emplace_array_component( + ActionTesting::emplace_array_component( + make_not_null(&runner), ActionTesting::NodeId{0}, + ActionTesting::LocalCoreId{0}, 0, 2.0); + ActionTesting::emplace_array_component( make_not_null(&runner), ActionTesting::NodeId{0}, ActionTesting::LocalCoreId{0}, 0, 2.0); ActionTesting::set_phase(make_not_null(&runner), Parallel::Phase::Testing); - auto& cache = ActionTesting::cache(runner, 0); + auto& cache = ActionTesting::cache(runner, 0); - // Test the free function. For this section of the test, the - // "standard" tags domain::Tags::FunctionsOfTime is ready, but this - // code should never examine it. + // Test the algorithm callback free function. For this section of the test, + // the "standard" tags domain::Tags::FunctionsOfTime is ready, but this code + // should never examine it. { // Neither function ready CHECK(not domain::functions_of_time_are_ready( - cache, 0, component_p, 0.5)); + cache, 0, component_0_p, 0.5, std::nullopt)); CHECK(not domain::functions_of_time_are_ready( - cache, 0, component_p, 0.5, std::array{"OtherA"s})); + cache, 0, component_0_p, 0.5, std::unordered_set{"OtherA"s})); // Make OtherA ready Parallel::mutate(cache, "OtherA"s, 123.0); CHECK(domain::functions_of_time_are_ready( - cache, 0, component_p, 0.5, std::array{"OtherA"s})); + cache, 0, component_0_p, 0.5, std::unordered_set{"OtherA"s})); CHECK(not domain::functions_of_time_are_ready( - cache, 0, component_p, 0.5, std::array{"OtherA"s, "OtherB"s})); + cache, 0, component_0_p, 0.5, + std::unordered_set{"OtherA"s, "OtherB"s})); // Make OtherB ready Parallel::mutate(cache, "OtherB"s, 456.0); CHECK(domain::functions_of_time_are_ready( - cache, 0, component_p, 0.5, std::array{"OtherA"s, "OtherB"s})); + cache, 0, component_0_p, 0.5, + std::unordered_set{"OtherA"s, "OtherB"s})); CHECK(domain::functions_of_time_are_ready( - cache, 0, component_p, 0.5)); + cache, 0, component_0_p, 0.5, std::nullopt)); } // Test the action. This should automatically look at // domain::Tags::FunctionsOfTime. { // Neither function ready - CHECK(not ActionTesting::next_action_if_ready( + CHECK(not ActionTesting::next_action_if_ready( make_not_null(&runner), 0)); // Make OtherA ready Parallel::mutate( cache, "FunctionA"s, 5.0); - CHECK(not ActionTesting::next_action_if_ready( + CHECK(not ActionTesting::next_action_if_ready( make_not_null(&runner), 0)); // Make OtherB ready Parallel::mutate( cache, "FunctionB"s, 10.0); - CHECK(ActionTesting::next_action_if_ready(make_not_null(&runner), - 0)); + CHECK(ActionTesting::next_action_if_ready( + make_not_null(&runner), 0)); + } + + // Test simple action callback free function + { + DataVector data{6, 0.0}; + const size_t size = data.size(); + + // No callbacks should be registered + CHECK(domain::functions_of_time_are_ready( + cache, 0, component_0_p, 5.0, std::nullopt)); + CHECK(ActionTesting::number_of_queued_simple_actions(runner, + 0) == 0); + CHECK(simple_action_no_args_call_count == 0_st); + CHECK(domain::functions_of_time_are_ready( + cache, 0, component_0_p, 5.0, std::nullopt, size, data)); + CHECK(ActionTesting::number_of_queued_simple_actions(runner, + 0) == 0); + CHECK(simple_action_args_call_count == 0_st); + + // Again no callbacks should be registered + CHECK(domain::functions_of_time_are_ready( + cache, 0, component_0_p, 6.0, std::unordered_set{"FunctionB"s})); + CHECK(ActionTesting::number_of_queued_simple_actions(runner, + 0) == 0); + CHECK(simple_action_no_args_call_count == 0_st); + CHECK(domain::functions_of_time_are_ready( + cache, 0, component_0_p, 6.0, std::unordered_set{"FunctionB"s}, size, + data)); + CHECK(ActionTesting::number_of_queued_simple_actions(runner, + 0) == 0); + CHECK(simple_action_args_call_count == 0_st); + + // Evaluate at time when A isn't ready. Can't have two different + // callbacks on same component so we use different components + CHECK(not domain::functions_of_time_are_ready( + cache, 0, component_0_p, 6.0, std::unordered_set{"FunctionA"s})); + CHECK(ActionTesting::number_of_queued_simple_actions(runner, + 0) == 0); + CHECK(simple_action_no_args_call_count == 0_st); + CHECK(not domain::functions_of_time_are_ready( + cache, 0, component_1_p, 6.0, std::unordered_set{"FunctionA"s}, size, + data)); + CHECK(ActionTesting::number_of_queued_simple_actions(runner, + 0) == 0); + CHECK(simple_action_args_call_count == 0_st); + + // Make FunctionA valid again + Parallel::mutate( + cache, "FunctionA"s, 10.0); + // Both actions should have been queued + CHECK(ActionTesting::number_of_queued_simple_actions(runner, + 0) == 1); + CHECK(ActionTesting::number_of_queued_simple_actions(runner, + 0) == 1); + CHECK(simple_action_no_args_call_count == 0_st); + CHECK(simple_action_args_call_count == 0_st); + ActionTesting::invoke_queued_simple_action( + make_not_null(&runner), 0); + // Only one ran + CHECK(simple_action_no_args_call_count == 1_st); + CHECK(simple_action_args_call_count == 0_st); + ActionTesting::invoke_queued_simple_action( + make_not_null(&runner), 0); + // Both ran + CHECK(simple_action_no_args_call_count == 1_st); + CHECK(simple_action_args_call_count == 1_st); + CHECK(domain::functions_of_time_are_ready( + cache, 0, component_0_p, 6.0, std::unordered_set{"FunctionA"s})); + CHECK(ActionTesting::number_of_queued_simple_actions(runner, + 0) == 0); + + // Evaluate at time when neither are ready. + CHECK(not domain::functions_of_time_are_ready( + cache, 0, component_0_p, 11.0, std::nullopt)); + CHECK(ActionTesting::number_of_queued_simple_actions(runner, + 0) == 0); + CHECK(simple_action_no_args_call_count == 1_st); + CHECK(not domain::functions_of_time_are_ready( + cache, 0, component_1_p, 11.0, std::nullopt, size, data)); + CHECK(ActionTesting::number_of_queued_simple_actions(runner, + 0) == 0); + CHECK(simple_action_args_call_count == 1_st); + + // Make A valid + Parallel::mutate( + cache, "FunctionA"s, 15.0); + // Both actions should have been queued + CHECK(ActionTesting::number_of_queued_simple_actions(runner, + 0) == 1); + CHECK(ActionTesting::number_of_queued_simple_actions(runner, + 0) == 1); + CHECK(simple_action_no_args_call_count == 1_st); + CHECK(simple_action_args_call_count == 1_st); + ActionTesting::invoke_queued_simple_action( + make_not_null(&runner), 0); + CHECK(simple_action_no_args_call_count == 2_st); + CHECK(simple_action_args_call_count == 1_st); + ActionTesting::invoke_queued_simple_action( + make_not_null(&runner), 0); + CHECK(simple_action_no_args_call_count == 2_st); + CHECK(simple_action_args_call_count == 2_st); + + // Make B valid. Nothing should have happened + Parallel::mutate( + cache, "FunctionB"s, 15.0); + CHECK(ActionTesting::number_of_queued_simple_actions(runner, + 0) == 0); + CHECK(ActionTesting::number_of_queued_simple_actions(runner, + 0) == 0); + // No actions should have run + CHECK(simple_action_no_args_call_count == 2_st); + CHECK(simple_action_args_call_count == 2_st); + } + + // No FoTs in the cache + { + using EmptyMockRuntimeSystem = + ActionTesting::MockRuntimeSystem; + using empty_comp = EmptyComponent; + const empty_comp* const empty_component_p = nullptr; + + EmptyMockRuntimeSystem empty_runner{{}}; + ActionTesting::emplace_array_component( + make_not_null(&empty_runner), ActionTesting::NodeId{0}, + ActionTesting::LocalCoreId{0}, 0, 2.0); + + auto& empty_cache = ActionTesting::cache(empty_runner, 0); + CHECK(domain::functions_of_time_are_ready( + empty_cache, 0, empty_component_p, 6.0)); + CHECK(domain::functions_of_time_are_ready( + empty_cache, 0, empty_component_p, 6.0, std::nullopt)); + CHECK(domain::functions_of_time_are_ready( + empty_cache, 0, empty_component_p, 6.0, std::nullopt, 0_st, + DataVector{})); } }