diff --git a/src/ParallelAlgorithms/Amr/Events/CMakeLists.txt b/src/ParallelAlgorithms/Amr/Events/CMakeLists.txt index 4eb0aa7a521e..c1303d01eb50 100644 --- a/src/ParallelAlgorithms/Amr/Events/CMakeLists.txt +++ b/src/ParallelAlgorithms/Amr/Events/CMakeLists.txt @@ -16,6 +16,7 @@ spectre_target_headers( INCLUDE_DIRECTORY ${CMAKE_SOURCE_DIR}/src HEADERS Events.hpp + ObserveAmrCriteria.hpp RefineMesh.hpp ) diff --git a/src/ParallelAlgorithms/Amr/Events/ObserveAmrCriteria.hpp b/src/ParallelAlgorithms/Amr/Events/ObserveAmrCriteria.hpp new file mode 100644 index 000000000000..f52d3c5fa78f --- /dev/null +++ b/src/ParallelAlgorithms/Amr/Events/ObserveAmrCriteria.hpp @@ -0,0 +1,149 @@ +// Distributed under the MIT License. +// See LICENSE.txt for details. + +#pragma once + +#include +#include +#include +#include + +#include "DataStructures/DataBox/ObservationBox.hpp" +#include "DataStructures/FloatingPointType.hpp" +#include "DataStructures/Tensor/IndexType.hpp" +#include "DataStructures/Tensor/Tensor.hpp" +#include "DataStructures/Tensor/TypeAliases.hpp" +#include "Domain/Amr/Flag.hpp" +#include "IO/H5/TensorData.hpp" +#include "Options/String.hpp" +#include "ParallelAlgorithms/Amr/Criteria/Criterion.hpp" +#include "ParallelAlgorithms/Amr/Criteria/Tags/Criteria.hpp" +#include "ParallelAlgorithms/Events/ObserveConstantsPerElement.hpp" +#include "ParallelAlgorithms/EventsAndTriggers/Event.hpp" +#include "Utilities/Gsl.hpp" +#include "Utilities/Serialization/CharmPupable.hpp" +#include "Utilities/TMPL.hpp" + +/// \cond +template +class Domain; +template +class ElementId; +namespace domain { +namespace FunctionsOfTime { +class FunctionOfTime; +} // namespace FunctionsOfTime +namespace Tags { +template +struct Domain; +struct FunctionsOfTime; +} // namespace Tags +} // namespace domain +namespace Parallel { +template +class GlobalCache; +} // namespace Parallel +namespace Tags { +struct Time; +struct TimeStep; +} // namespace Tags +/// \endcond + +namespace amr::Events { +namespace detail { +template +struct get_compute_tags { + using type = typename Criterion::compute_tags_for_observation_box; +}; +} // namespace detail + +/// \brief Observe the desired decisions of AMR criteria +/// +/// \details The event will return a vector of decisions (an independent choice +/// in each logical dimension) for each of the AMR criteria. These are the raw +/// choices made by each AMR critera, not taking into account any AMR policies. +/// Each element is output as a single cell with two points per dimension and +/// the observation constant on all those points. The decisions are converted +/// to values as follows (in each logical dimension): +/// - 1.0 is for join with sibling (if possible) +/// - 2.0 is for decrease number of grid points +/// - 3.0 is for no change +/// - 4.0 is for increase number of grid points +/// - 5.0 is for splitting the element +template +class ObserveAmrCriteria + : public dg::Events::ObserveConstantsPerElement { + public: + static constexpr size_t volume_dim = Metavariables::volume_dim; + /// \cond + explicit ObserveAmrCriteria(CkMigrateMessage* m) + : dg::Events::ObserveConstantsPerElement(m) {} + using PUP::able::register_constructor; + WRAPPED_PUPable_decl_template(ObserveAmrCriteria); // NOLINT + /// \endcond + + static constexpr Options::String help = + "Observe the desired decisions of AMR criteria in the volume."; + + ObserveAmrCriteria() = default; + + ObserveAmrCriteria(const std::string& subfile_name, + ::FloatingPointType coordinates_floating_point_type, + ::FloatingPointType floating_point_type) + : dg::Events::ObserveConstantsPerElement( + subfile_name, coordinates_floating_point_type, + floating_point_type) {} + + using compute_tags_for_observation_box = + tmpl::remove_duplicates, + detail::get_compute_tags>>>; + + using return_tags = tmpl::list<>; + using argument_tags = tmpl::list<::Tags::ObservationBox>; + + template + void operator()(const ObservationBox& box, + Parallel::GlobalCache& cache, + const ElementId& element_id, + const ParallelComponent* const component, + const Event::ObservationValue& observation_value) const { + const auto& refinement_criteria = get(box); + const double time = get<::Tags::Time>(box); + const auto& functions_of_time = get<::domain::Tags::FunctionsOfTime>(box); + const Domain& domain = + get<::domain::Tags::Domain>(box); + + std::vector components = this->allocate_and_insert_coords( + volume_dim * refinement_criteria.size(), time, functions_of_time, + domain, element_id); + + for (const auto& criterion : refinement_criteria) { + const auto decision = criterion->evaluate(box, cache, element_id); + for (size_t d = 0; d < volume_dim; ++d) { + this->add_constant( + make_not_null(&components), + criterion->observation_name() + + tnsr::i::component_suffix(d), + static_cast(gsl::at(decision, d))); + } + } + + this->observe(components, cache, element_id, component, observation_value); + } + + bool needs_evolved_variables() const override { return true; } + + void pup(PUP::er& p) override { + dg::Events::ObserveConstantsPerElement::pup(p); + } +}; + +/// \cond +template +PUP::able::PUP_ID ObserveAmrCriteria::my_PUP_ID = 0; // NOLINT +/// \endcond +} // namespace amr::Events diff --git a/tests/Unit/ParallelAlgorithms/Amr/CMakeLists.txt b/tests/Unit/ParallelAlgorithms/Amr/CMakeLists.txt index 1c77b3eab79e..49fc0be6eed9 100644 --- a/tests/Unit/ParallelAlgorithms/Amr/CMakeLists.txt +++ b/tests/Unit/ParallelAlgorithms/Amr/CMakeLists.txt @@ -22,6 +22,7 @@ set(LIBRARY_SOURCES Criteria/Test_Persson.cpp Criteria/Test_Random.cpp Criteria/Test_TruncationError.cpp + Events/Test_ObserveAmrCriteria.cpp Events/Test_RefineMesh.cpp Policies/Test_EnforcePolicies.cpp Policies/Test_Isotropy.cpp diff --git a/tests/Unit/ParallelAlgorithms/Amr/Events/Test_ObserveAmrCriteria.cpp b/tests/Unit/ParallelAlgorithms/Amr/Events/Test_ObserveAmrCriteria.cpp new file mode 100644 index 000000000000..f1d38555bfae --- /dev/null +++ b/tests/Unit/ParallelAlgorithms/Amr/Events/Test_ObserveAmrCriteria.cpp @@ -0,0 +1,182 @@ +// Distributed under the MIT License. +// See LICENSE.txt for details. + +#include "Framework/TestingFramework.hpp" + +#include + +#include "Domain/CoordinateMaps/CoordinateMap.hpp" +#include "Domain/CoordinateMaps/CoordinateMap.tpp" +#include "Domain/CoordinateMaps/Identity.hpp" +#include "Domain/CoordinateMaps/TimeDependent/Translation.hpp" +#include "Domain/Creators/Tags/Domain.hpp" +#include "Domain/Domain.hpp" +#include "Domain/FunctionsOfTime/FunctionOfTime.hpp" +#include "Domain/FunctionsOfTime/PiecewisePolynomial.hpp" +#include "Domain/FunctionsOfTime/Tags.hpp" +#include "Domain/Structure/ElementId.hpp" +#include "Domain/Structure/SegmentId.hpp" +#include "Domain/Tags.hpp" +#include "Framework/ActionTesting.hpp" +#include "Framework/TestCreation.hpp" +#include "Helpers/ParallelAlgorithms/Events/ObserveFields.hpp" +#include "Options/Protocols/FactoryCreation.hpp" +#include "ParallelAlgorithms/Amr/Criteria/DriveToTarget.hpp" +#include "ParallelAlgorithms/Amr/Criteria/Tags/Criteria.hpp" +#include "ParallelAlgorithms/Amr/Events/ObserveAmrCriteria.hpp" +#include "ParallelAlgorithms/Amr/Policies/Isotropy.hpp" +#include "ParallelAlgorithms/Amr/Policies/Limits.hpp" +#include "ParallelAlgorithms/Amr/Policies/Policies.hpp" +#include "ParallelAlgorithms/Amr/Policies/Tags.hpp" +#include "ParallelAlgorithms/EventsAndTriggers/Event.hpp" +#include "ParallelAlgorithms/EventsAndTriggers/EventsAndTriggers.hpp" +#include "ParallelAlgorithms/EventsAndTriggers/LogicalTriggers.hpp" +#include "ParallelAlgorithms/EventsAndTriggers/Trigger.hpp" +#include "Time/Tags/Time.hpp" +#include "Utilities/Literals.hpp" +#include "Utilities/MakeArray.hpp" +#include "Utilities/MakeVector.hpp" +#include "Utilities/ProtocolHelpers.hpp" +#include "Utilities/TMPL.hpp" + +namespace { +namespace LocalTags { +struct FunctionsOfTime : domain::Tags::FunctionsOfTime, db::SimpleTag { + using type = domain::FunctionsOfTimeMap; +}; +} // namespace LocalTags + +struct Metavariables { + static constexpr size_t volume_dim = 1; + using component_list = tmpl::list< + TestHelpers::dg::Events::ObserveFields::ElementComponent, + TestHelpers::dg::Events::ObserveFields::MockObserverComponent< + Metavariables>>; + + struct factory_creation + : tt::ConformsTo { + using factory_classes = tmpl::map< + tmpl::pair>>, + tmpl::pair>>>; + }; +}; + +void test() { + std::vector> criteria; + criteria.emplace_back(std::make_unique>( + std::array{4_st}, std::array{1_st}, std::array{amr::Flag::DoNothing})); + criteria.emplace_back(std::make_unique>( + std::array{3_st}, std::array{1_st}, std::array{amr::Flag::DoNothing})); + criteria.emplace_back(std::make_unique>( + std::array{5_st}, std::array{1_st}, std::array{amr::Flag::DoNothing})); + criteria.emplace_back(std::make_unique>( + std::array{4_st}, std::array{0_st}, std::array{amr::Flag::DoNothing})); + criteria.emplace_back(std::make_unique>( + std::array{4_st}, std::array{2_st}, std::array{amr::Flag::DoNothing})); + const size_t number_of_criteria = criteria.size(); + const std::vector expected_values{3.0, 2.0, 4.0, 1.0, 5.0}; + register_factory_classes_with_charm(); + using element_component = + TestHelpers::dg::Events::ObserveFields::ElementComponent; + using observer_component = + TestHelpers::dg::Events::ObserveFields::MockObserverComponent< + Metavariables>; + element_component* const element_component_p = nullptr; + + const ElementId<1> element_id(0, make_array<1>(SegmentId(1, 0))); + const Mesh<1> mesh{4_st, Spectral::Basis::Legendre, + Spectral::Quadrature::GaussLobatto}; + + using MockRuntimeSystem = ActionTesting::MockRuntimeSystem; + MockRuntimeSystem runner{{}}; + ActionTesting::emplace_component(make_not_null(&runner), + element_id); + ActionTesting::emplace_group_component(&runner); + auto& cache = ActionTesting::cache(runner, element_id); + + const auto event = + TestHelpers::test_creation, Metavariables>( + "ObserveAmrCriteria:\n" + " SubfileName: amr_criteria\n" + " CoordinatesFloatingPointType: Double\n" + " FloatingPointType: Float"); + const double time = 3.0; + Domain domain(make_vector( + domain::make_coordinate_map_base( + domain::CoordinateMaps::Identity<1>{}))); + domain.inject_time_dependent_map_for_block( + 0, domain::make_coordinate_map_base( + domain::CoordinateMaps::TimeDependent::Translation<1>( + "translation"))); + + domain::FunctionsOfTimeMap functions_of_time{}; + functions_of_time.emplace( + "translation", + std::make_unique>( + 1.0, std::array{DataVector(1, 2.0), DataVector(1, 5.0)}, 4.0)); + const double expected_offset = 2.0 + (time - 1.0) * 5.0; + + auto box = db::create< + db::AddSimpleTags, + Tags::Time, LocalTags::FunctionsOfTime, + domain::Tags::Domain<1>, domain::Tags::Mesh<1>, + amr::Criteria::Tags::Criteria, amr::Tags::Policies>>( + Metavariables{}, time, std::move(functions_of_time), std::move(domain), + mesh, std::move(criteria), + amr::Policies{amr::Isotropy::Anisotropic, amr::Limits{}, true}); + + const double observation_value = 1.23; + + auto observation_box = + make_observation_box::compute_tags_for_observation_box>( + make_not_null(&box)); + + event->run(make_not_null(&observation_box), cache, element_id, + element_component_p, {"value_name", observation_value}); + + runner.template invoke_queued_simple_action(0); + CHECK(runner.template is_simple_action_queue_empty(0)); + + const auto& results = + TestHelpers::dg::Events::ObserveFields::MockContributeVolumeData::results; + CHECK(results.observation_id.value() == observation_value); + CHECK(results.observation_id.observation_key().tag() == "/amr_criteria.vol"); + CHECK(results.subfile_name == "/amr_criteria"); + CHECK(results.array_component_id == + Parallel::make_array_component_id(element_id)); + CHECK(results.received_volume_data.element_name == get_output(element_id)); + CHECK(results.received_volume_data.extents == std::vector(1, 2)); + const auto& components = results.received_volume_data.tensor_components; + REQUIRE(components.size() == 1 + number_of_criteria); + for (const auto& component : components) { + std::visit([](const auto& data) { CHECK(data.size() == 2); }, + component.data); + } + CHECK(components[0].name == "InertialCoordinates_x"); + std::visit( + [&](const auto& data) { + for (size_t i = 0; i < data.size(); ++i) { + CHECK(data[i] == + (i % 2 < 1 ? -1.0 + expected_offset : expected_offset)); + } + }, + components[0].data); + for (size_t i = 0; i < number_of_criteria; ++i) { + CHECK(components[i + 1].name == "DriveToTarget_x"); + std::visit( + [&](const auto& data) { + for (size_t j = 0; j < data.size(); ++j) { + CHECK(data[j] == expected_values[i]); + } + }, + components[i + 1].data); + } +} + +SPECTRE_TEST_CASE("Unit.ParallelAlgorithms.Amr.Events.ObserveAmrCriteria", + "[Unit][ParallelAlgorithms]") { + test(); +} +} // namespace