diff --git a/src/Evolution/Executables/ScalarTensor/EvolveScalarTensorSingleBlackHole.hpp b/src/Evolution/Executables/ScalarTensor/EvolveScalarTensorSingleBlackHole.hpp index 4a8e23022758..d108ff003d43 100644 --- a/src/Evolution/Executables/ScalarTensor/EvolveScalarTensorSingleBlackHole.hpp +++ b/src/Evolution/Executables/ScalarTensor/EvolveScalarTensorSingleBlackHole.hpp @@ -15,7 +15,7 @@ #include "Domain/Structure/ObjectLabel.hpp" #include "Evolution/Actions/RunEventsAndTriggers.hpp" #include "Evolution/Executables/ScalarTensor/ScalarTensorBase.hpp" -#include "Evolution/Systems/GeneralizedHarmonic/Actions/SetInitialData.hpp" +#include "Evolution/Systems/ScalarTensor/Actions/SetInitialData.hpp" #include "Options/FactoryHelpers.hpp" #include "Options/Protocols/FactoryCreation.hpp" #include "Options/String.hpp" @@ -202,6 +202,15 @@ struct EvolutionMetavars : public ScalarTensorTemplateBase { tmpl::flatten, + Parallel::PhaseActions< + Parallel::Phase::RegisterWithElementDataReader, + tmpl::list>, + Parallel::PhaseActions< + Parallel::Phase::ImportInitialData, + tmpl::list>, Parallel::PhaseActions< Parallel::Phase::InitializeInitialDataDependentQuantities, initialize_initial_data_dependent_quantities_actions>, @@ -233,7 +242,8 @@ struct EvolutionMetavars : public ScalarTensorTemplateBase { observers::Observer, observers::ObserverWriter, mem_monitor::MemoryMonitor, - st_dg_element_array, intrp::Interpolator, + importers::ElementDataReader, st_dg_element_array, + intrp::Interpolator, control_system::control_components, tmpl::transform { ScalarTensor::BoundaryConditions::BoundaryCondition, ScalarTensor::BoundaryConditions::standard_boundary_conditions>, tmpl::pair, - tmpl::pair, + tmpl::pair< + evolution::initial_data::InitialData, + tmpl::push_back>, tmpl::pair, tmpl::pair, tmpl::pair, diff --git a/src/Evolution/Systems/ScalarTensor/Actions/CMakeLists.txt b/src/Evolution/Systems/ScalarTensor/Actions/CMakeLists.txt new file mode 100644 index 000000000000..22b44633d251 --- /dev/null +++ b/src/Evolution/Systems/ScalarTensor/Actions/CMakeLists.txt @@ -0,0 +1,15 @@ +# Distributed under the MIT License. +# See LICENSE.txt for details. + +spectre_target_sources( + ${LIBRARY} + PRIVATE + SetInitialData.cpp + ) + +spectre_target_headers( + ${LIBRARY} + INCLUDE_DIRECTORY ${CMAKE_SOURCE_DIR}/src + HEADERS + SetInitialData.hpp + ) diff --git a/src/Evolution/Systems/ScalarTensor/Actions/SetInitialData.cpp b/src/Evolution/Systems/ScalarTensor/Actions/SetInitialData.cpp new file mode 100644 index 000000000000..55ab11d8ef82 --- /dev/null +++ b/src/Evolution/Systems/ScalarTensor/Actions/SetInitialData.cpp @@ -0,0 +1,64 @@ +// Distributed under the MIT License. +// See LICENSE.txt for details. + +#include "Evolution/Systems/ScalarTensor/Actions/SetInitialData.hpp" + +#include +#include +#include +#include +#include +#include + +#include "DataStructures/DataVector.hpp" +#include "DataStructures/Tensor/Tensor.hpp" +#include "NumericalAlgorithms/LinearOperators/PartialDerivatives.hpp" +#include "PointwiseFunctions/GeneralRelativity/GeneralizedHarmonic/Phi.hpp" +#include "PointwiseFunctions/GeneralRelativity/GeneralizedHarmonic/Pi.hpp" +#include "PointwiseFunctions/GeneralRelativity/SpacetimeMetric.hpp" +#include "PointwiseFunctions/GeneralRelativity/TimeDerivativeOfSpatialMetric.hpp" +#include "Utilities/GenerateInstantiations.hpp" +#include "Utilities/Gsl.hpp" +#include "Utilities/MakeWithValue.hpp" +#include "Utilities/PrettyType.hpp" + +namespace ScalarTensor { + +NumericInitialData::NumericInitialData( + std::string file_glob, std::string subfile_name, + std::variant observation_value, + std::optional observation_value_epsilon, bool enable_interpolation, + typename GhNumericId::Variables::type gh_selected_variables, + typename ScalarNumericId::Variables::type hydro_selected_variables) + : gh_numeric_id_(file_glob, subfile_name, observation_value, + observation_value_epsilon.value_or(1.0e-12), + enable_interpolation, std::move(gh_selected_variables)), + scalar_numeric_id_( + std::move(file_glob), std::move(subfile_name), observation_value, + observation_value_epsilon.value_or(1.0e-12), enable_interpolation, + std::move(hydro_selected_variables)) {} + +NumericInitialData::NumericInitialData(CkMigrateMessage* msg) + : InitialData(msg) {} + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +PUP::able::PUP_ID NumericInitialData::my_PUP_ID = 0; + +size_t NumericInitialData::volume_data_id() const { + size_t hash = 0; + boost::hash_combine(hash, gh_numeric_id_.volume_data_id()); + boost::hash_combine(hash, scalar_numeric_id_.volume_data_id()); + return hash; +} + +void NumericInitialData::pup(PUP::er& p) { + p | gh_numeric_id_; + p | scalar_numeric_id_; +} + +bool operator==(const NumericInitialData& lhs, const NumericInitialData& rhs) { + return lhs.gh_numeric_id_ == rhs.gh_numeric_id_ and + lhs.scalar_numeric_id_ == rhs.scalar_numeric_id_; +} + +} // namespace ScalarTensor diff --git a/src/Evolution/Systems/ScalarTensor/Actions/SetInitialData.hpp b/src/Evolution/Systems/ScalarTensor/Actions/SetInitialData.hpp new file mode 100644 index 000000000000..648614a497da --- /dev/null +++ b/src/Evolution/Systems/ScalarTensor/Actions/SetInitialData.hpp @@ -0,0 +1,340 @@ +// Distributed under the MIT License. +// See LICENSE.txt for details. + +#pragma once + +#include +#include +#include +#include +#include + +#include "DataStructures/DataBox/DataBox.hpp" +#include "DataStructures/DataVector.hpp" +#include "DataStructures/Tensor/EagerMath/DeterminantAndInverse.hpp" +#include "DataStructures/Tensor/Tensor.hpp" +#include "Domain/Structure/ElementId.hpp" +#include "Domain/Tags.hpp" +#include "Evolution/Initialization/InitialData.hpp" +#include "Evolution/NumericInitialData.hpp" +#include "Evolution/Systems/CurvedScalarWave/Actions/NumericInitialData.hpp" +#include "Evolution/Systems/CurvedScalarWave/Tags.hpp" +#include "Evolution/Systems/GeneralizedHarmonic/Actions/SetInitialData.hpp" +#include "Evolution/Systems/GeneralizedHarmonic/Tags.hpp" +#include "IO/Importers/Actions/ReadVolumeData.hpp" +#include "IO/Importers/ElementDataReader.hpp" +#include "IO/Importers/Tags.hpp" +#include "NumericalAlgorithms/LinearOperators/PartialDerivatives.hpp" +#include "NumericalAlgorithms/Spectral/Mesh.hpp" +#include "Options/String.hpp" +#include "Parallel/AlgorithmExecution.hpp" +#include "Parallel/GlobalCache.hpp" +#include "Parallel/Invoke.hpp" +#include "PointwiseFunctions/GeneralRelativity/SpatialMetric.hpp" +#include "PointwiseFunctions/GeneralRelativity/Tags.hpp" +#include "PointwiseFunctions/InitialDataUtilities/InitialData.hpp" +#include "PointwiseFunctions/InitialDataUtilities/Tags/InitialData.hpp" +#include "Utilities/CallWithDynamicType.hpp" +#include "Utilities/ErrorHandling/Error.hpp" +#include "Utilities/Gsl.hpp" +#include "Utilities/Serialization/CharmPupable.hpp" +#include "Utilities/TMPL.hpp" +#include "Utilities/TaggedTuple.hpp" + +namespace ScalarTensor { + +/*! + * \brief Numeric initial data loaded from volume data files + */ +class NumericInitialData : public evolution::initial_data::InitialData, + public evolution::NumericInitialData { + private: + using GhNumericId = gh::NumericInitialData; + using ScalarNumericId = CurvedScalarWave::NumericInitialData; + + public: + using all_vars = + tmpl::append; + + struct GhVariables : GhNumericId::Variables {}; + struct ScalarVariables : ScalarNumericId::Variables {}; + + using options = tmpl::push_back; + + static constexpr Options::String help = + "Numeric initial data for the Scalar Tensor system loaded from volume " + "data files"; + + NumericInitialData() = default; + NumericInitialData(const NumericInitialData& rhs) = default; + NumericInitialData& operator=(const NumericInitialData& rhs) = default; + NumericInitialData(NumericInitialData&& /*rhs*/) = default; + NumericInitialData& operator=(NumericInitialData&& /*rhs*/) = default; + ~NumericInitialData() override = default; + + /// \cond + explicit NumericInitialData(CkMigrateMessage* msg); + using PUP::able::register_constructor; + WRAPPED_PUPable_decl_template(NumericInitialData); + /// \endcond + + std::unique_ptr get_clone() + const override { + return std::make_unique(*this); + } + + NumericInitialData( + std::string file_glob, std::string subfile_name, + std::variant observation_value, + std::optional observation_value_epsilon, + bool enable_interpolation, + typename GhNumericId::Variables::type gh_selected_variables, + typename ScalarNumericId::Variables::type hydro_selected_variables); + + const importers::ImporterOptions& importer_options() const { + return gh_numeric_id_.importer_options(); + } + + const GhNumericId& gh_numeric_id() const { return gh_numeric_id_; } + + const ScalarNumericId& scalar_numeric_id() const { + return scalar_numeric_id_; + } + + size_t volume_data_id() const; + + template + void select_for_import( + const gsl::not_null*> fields) const { + gh_numeric_id_.select_for_import(fields); + scalar_numeric_id_.select_for_import(fields); + } + + template + void set_initial_data( + const gsl::not_null*> spacetime_metric, + const gsl::not_null*> pi, + const gsl::not_null*> phi, + const gsl::not_null*> psi_scalar, + const gsl::not_null*> pi_scalar, + const gsl::not_null*> phi_scalar, + const gsl::not_null*> numeric_data, + const Mesh<3>& mesh, + const InverseJacobian& inv_jacobian) const { + gh_numeric_id_.set_initial_data(spacetime_metric, pi, phi, numeric_data, + mesh, inv_jacobian); + scalar_numeric_id_.set_initial_data(psi_scalar, pi_scalar, phi_scalar, + numeric_data); + } + + void pup(PUP::er& p) override; + + friend bool operator==(const NumericInitialData& lhs, + const NumericInitialData& rhs); + + private: + GhNumericId gh_numeric_id_{}; + ScalarNumericId scalar_numeric_id_{}; +}; + +namespace Actions { + +/*! + * \brief Dispatch loading numeric initial data from files. + * + * Place this action before + * ScalarTensor::Actions::SetNumericInitialData in the action list. + * See importers::Actions::ReadAllVolumeDataAndDistribute for details, which is + * invoked by this action. + */ +struct SetInitialData { + using const_global_cache_tags = + tmpl::list; + + template + static Parallel::iterable_action_return_t apply( + db::DataBox& box, + const tuples::TaggedTuple& /*inboxes*/, + Parallel::GlobalCache& cache, + const ArrayIndex& array_index, const ActionList /*meta*/, + const ParallelComponent* const parallel_component) { + // Dispatch to the correct `apply` overload based on type of initial data + using initial_data_classes = + tmpl::at; + return call_with_dynamic_type( + &db::get(box), + [&box, &cache, &array_index, + ¶llel_component](const auto* const initial_data) { + return apply(make_not_null(&box), *initial_data, cache, array_index, + parallel_component); + }); + } + + private: + static constexpr size_t Dim = 3; + + // Numeric initial data + template + static Parallel::iterable_action_return_t apply( + const gsl::not_null*> /*box*/, + const NumericInitialData& initial_data, + Parallel::GlobalCache& cache, + const ArrayIndex& array_index, const ParallelComponent* const /*meta*/) { + // If we are using GH Numeric ID, then we don't have to set Pi and Phi since + // we are reading them in. Also we only need to mutate this tag once so do + // it on the first element. + if (is_zeroth_element(array_index) and + std::holds_alternative( + initial_data.gh_numeric_id().selected_variables())) { + Parallel::mutate( + cache, false); + } + // Select the subset of the available variables that we want to read from + // the volume data file + tuples::tagged_tuple_from_typelist> + selected_fields{}; + initial_data.select_for_import(make_not_null(&selected_fields)); + // Dispatch loading the variables from the volume data file + // - Not using `ckLocalBranch` here to make sure the simple action + // invocation is asynchronous. + auto& reader_component = Parallel::get_parallel_component< + importers::ElementDataReader>(cache); + Parallel::simple_action>( + reader_component, initial_data.importer_options(), + initial_data.volume_data_id(), std::move(selected_fields)); + return {Parallel::AlgorithmExecution::Continue, std::nullopt}; + } + + // "AnalyticData"-type initial data + template + static Parallel::iterable_action_return_t apply( + const gsl::not_null*> box, + const InitialData& initial_data, + Parallel::GlobalCache& /*cache*/, + const ArrayIndex& /*array_index*/, + const ParallelComponent* const /*meta*/) { + // Get ADM + scalar variables from analytic data / solution + const auto& [coords, mesh, inv_jacobian] = [&box]() { + return std::forward_as_tuple( + db::get>(*box), + db::get>(*box), + db::get>(*box)); + }(); + auto vars = evolution::Initialization::initial_data( + initial_data, coords, db::get<::Tags::Time>(*box), + tmpl::append, + gr::Tags::Lapse, + gr::Tags::Shift, + gr::Tags::ExtrinsicCurvature>, + // Don't use the scalar gradient + tmpl::list>{}); + const auto& spatial_metric = + get>(vars); + const auto& lapse = get>(vars); + + const auto& shift = get>(vars); + + const auto& extrinsic_curvature = + get>(vars); + + // Compute GH vars from ADM vars + db::mutate, + gh::Tags::Pi, gh::Tags::Phi>( + &gh::initial_gh_variables_from_adm<3>, box, spatial_metric, lapse, + shift, extrinsic_curvature, mesh, inv_jacobian); + + // Move scalar variables and compute gradient + db::mutate>( + [&vars](const gsl::not_null*> psi_scalar, + const gsl::not_null*> pi_scalar, + const gsl::not_null*> phi_scalar, + const Mesh<3>& local_mesh, + const InverseJacobian& local_inv_jacobian) { + *psi_scalar = std::move(get(vars)); + *pi_scalar = std::move(get(vars)); + // Set Phi to the numerical spatial derivative of the scalar + partial_derivative(phi_scalar, *psi_scalar, local_mesh, + local_inv_jacobian); + }, + box, mesh, inv_jacobian); + + // No need to import numeric initial data, so we terminate the phase by + // pausing the algorithm on this element + return {Parallel::AlgorithmExecution::Pause, std::nullopt}; + } +}; + +/*! + * \brief Receive numeric initial data loaded by + * ScalarTensor::Actions::SetInitialData. + */ +struct ReceiveNumericInitialData { + static constexpr size_t Dim = 3; + using inbox_tags = + tmpl::list>; + + template + static Parallel::iterable_action_return_t apply( + db::DataBox& box, tuples::TaggedTuple& inboxes, + const Parallel::GlobalCache& /*cache*/, + const ElementId& /*element_id*/, const ActionList /*meta*/, + const ParallelComponent* const /*meta*/) { + auto& inbox = + tuples::get>( + inboxes); + const auto& initial_data = dynamic_cast( + db::get(box)); + const size_t volume_data_id = initial_data.volume_data_id(); + if (inbox.find(volume_data_id) == inbox.end()) { + return {Parallel::AlgorithmExecution::Retry, std::nullopt}; + } + auto numeric_data = std::move(inbox.extract(volume_data_id).mapped()); + + const auto& mesh = db::get>(box); + const auto& inv_jacobian = + db::get>(box); + + db::mutate, + gh::Tags::Pi, gh::Tags::Phi, + CurvedScalarWave::Tags::Psi, CurvedScalarWave::Tags::Pi, + CurvedScalarWave::Tags::Phi<3>>( + [&initial_data, &numeric_data, &mesh, &inv_jacobian]( + const gsl::not_null*> spacetime_metric, + const gsl::not_null*> pi, + const gsl::not_null*> phi, + const gsl::not_null*> psi_scalar, + const gsl::not_null*> pi_scalar, + const gsl::not_null*> phi_scalar) { + initial_data.set_initial_data(spacetime_metric, pi, phi, + + psi_scalar, pi_scalar, phi_scalar, + + make_not_null(&numeric_data), mesh, + inv_jacobian); + }, + make_not_null(&box)); + + return {Parallel::AlgorithmExecution::Continue, std::nullopt}; + } +}; + +} // namespace Actions + +} // namespace ScalarTensor diff --git a/src/Evolution/Systems/ScalarTensor/CMakeLists.txt b/src/Evolution/Systems/ScalarTensor/CMakeLists.txt index 92a7651999d8..ce45aa657559 100644 --- a/src/Evolution/Systems/ScalarTensor/CMakeLists.txt +++ b/src/Evolution/Systems/ScalarTensor/CMakeLists.txt @@ -43,6 +43,7 @@ target_link_libraries( Parallel ) +add_subdirectory(Actions) add_subdirectory(BoundaryConditions) add_subdirectory(BoundaryCorrections) add_subdirectory(Sources) diff --git a/tests/Unit/Evolution/Systems/ScalarTensor/Actions/Test_SetInitialData.cpp b/tests/Unit/Evolution/Systems/ScalarTensor/Actions/Test_SetInitialData.cpp new file mode 100644 index 000000000000..49d083203b28 --- /dev/null +++ b/tests/Unit/Evolution/Systems/ScalarTensor/Actions/Test_SetInitialData.cpp @@ -0,0 +1,399 @@ +// Distributed under the MIT License. +// See LICENSE.txt for details. + +#include "Framework/TestingFramework.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "DataStructures/DataBox/DataBox.hpp" +#include "DataStructures/DataVector.hpp" +#include "DataStructures/Tensor/Tensor.hpp" +#include "Domain/CoordinateMaps/CoordinateMap.tpp" +#include "Domain/CoordinateMaps/Wedge.hpp" +#include "Domain/Structure/ElementId.hpp" +#include "Domain/Tags.hpp" +#include "Evolution/Systems/CurvedScalarWave/Actions/NumericInitialData.hpp" +#include "Evolution/Systems/GeneralizedHarmonic/Actions/SetInitialData.hpp" +#include "Evolution/Systems/GeneralizedHarmonic/GaugeSourceFunctions/SetPiAndPhiFromConstraints.hpp" +#include "Evolution/Systems/GeneralizedHarmonic/System.hpp" +#include "Evolution/Systems/ScalarTensor/Actions/SetInitialData.hpp" +#include "Evolution/Systems/ScalarTensor/System.hpp" +#include "Framework/ActionTesting.hpp" +#include "Framework/TestCreation.hpp" +#include "IO/Importers/Actions/ReadVolumeData.hpp" +#include "IO/Importers/ElementDataReader.hpp" +#include "IO/Importers/Tags.hpp" +#include "NumericalAlgorithms/Spectral/Basis.hpp" +#include "NumericalAlgorithms/Spectral/LogicalCoordinates.hpp" +#include "NumericalAlgorithms/Spectral/Mesh.hpp" +#include "NumericalAlgorithms/Spectral/Quadrature.hpp" +#include "Options/Protocols/FactoryCreation.hpp" +#include "Parallel/Phase.hpp" +#include "PointwiseFunctions/AnalyticData/ScalarTensor/KerrSphericalHarmonic.hpp" +#include "PointwiseFunctions/AnalyticSolutions/GeneralRelativity/KerrSchild.hpp" +#include "PointwiseFunctions/AnalyticSolutions/GeneralRelativity/WrappedGr.tpp" +#include "Time/Tags/Time.hpp" +#include "Utilities/GetOutput.hpp" +#include "Utilities/MakeString.hpp" +#include "Utilities/Serialization/RegisterDerivedClassesWithCharm.hpp" +#include "Utilities/TaggedTuple.hpp" + +namespace ScalarTensor { +namespace { + +using st_system_vars = ScalarTensor::System::variables_tag::tags_list; + +template +struct MockElementArray { + using component_being_mocked = void; // Not needed + using metavariables = Metavariables; + using chare_type = ActionTesting::MockArrayChare; + using array_index = ElementId<3>; + using mutable_global_cache_tags = + tmpl::list; + using phase_dependent_action_list = tmpl::list< + Parallel::PhaseActions< + Parallel::Phase::Initialization, + tmpl::list, + domain::Tags::Coordinates<3, Frame::Inertial>, + domain::Tags::InverseJacobian<3, Frame::ElementLogical, + Frame::Inertial>, + ::Tags::Time>>>>>, + Parallel::PhaseActions< + Parallel::Phase::Testing, + tmpl::list>>; +}; + +struct MockReadVolumeData { + template + static void apply( + DataBox& /*box*/, Parallel::GlobalCache& cache, + const ArrayIndex& /*array_index*/, + const importers::ImporterOptions& options, const size_t volume_data_id, + tuples::tagged_tuple_from_typelist> + selected_fields) { + const auto& initial_data = dynamic_cast( + get(cache)); + CHECK(options == initial_data.importer_options()); + CHECK(volume_data_id == initial_data.volume_data_id()); + const auto gh_selected_vars = + initial_data.gh_numeric_id().selected_variables(); + if (std::holds_alternative( + gh_selected_vars)) { + CHECK(get>>(selected_fields) == + "CustomSpacetimeMetric"); + CHECK(get>>( + selected_fields) == "CustomPi"); + CHECK(get>>( + selected_fields) == "CustomPhi"); + CHECK_FALSE( + get>>(selected_fields)); + CHECK_FALSE(get>>( + selected_fields)); + CHECK_FALSE( + get>>( + selected_fields)); + } else if (std::holds_alternative( + gh_selected_vars)) { + CHECK(get>>(selected_fields) == + "CustomSpatialMetric"); + CHECK(get>>( + selected_fields) == "CustomLapse"); + CHECK(get>>( + selected_fields) == "CustomShift"); + CHECK(get>>( + selected_fields) == "CustomExtrinsicCurvature"); + CHECK_FALSE( + get>>(selected_fields)); + CHECK_FALSE(get>>( + selected_fields)); + CHECK_FALSE(get>>( + selected_fields)); + } else { + REQUIRE(false); + } + CHECK(get>( + selected_fields) == "CustomPsi"); + CHECK(get>( + selected_fields) == "CustomScalarPi"); + CHECK(get>>( + selected_fields) == "CustomScalarPhi"); + } +}; + +template +struct MockVolumeDataReader { + using component_being_mocked = importers::ElementDataReader; + using metavariables = Metavariables; + using chare_type = ActionTesting::MockNodeGroupChare; + using array_index = size_t; + using phase_dependent_action_list = tmpl::list< + Parallel::PhaseActions>>; + using replace_these_simple_actions = + tmpl::list>>; + using with_these_simple_actions = tmpl::list; +}; + +struct Metavariables { + static constexpr size_t volume_dim = 3; + using component_list = tmpl::list, + MockVolumeDataReader>; + + struct factory_creation + : tt::ConformsTo { + using factory_classes = tmpl::map>>>; + }; +}; + +void test_set_initial_data( + const evolution::initial_data::InitialData& initial_data, + const std::string& option_string, const bool is_numeric) { + { + INFO("Factory creation"); + const auto created = TestHelpers::test_creation< + std::unique_ptr, Metavariables>( + option_string); + if (is_numeric) { + CHECK(dynamic_cast(*created) == + dynamic_cast(initial_data)); + } else { + CHECK(dynamic_cast&>( + *created) == + dynamic_cast&>( + initial_data)); + } + } + + using reader_component = MockVolumeDataReader; + using element_array = MockElementArray; + + ActionTesting::MockRuntimeSystem runner{ + {initial_data.get_clone()}, {true}}; + + // Setup mock data file reader + ActionTesting::emplace_nodegroup_component( + make_not_null(&runner)); + + // Setup element + const ElementId<3> element_id{0}; + const Mesh<3> mesh{8, Spectral::Basis::Legendre, + Spectral::Quadrature::GaussLobatto}; + const auto map = + domain::make_coordinate_map( + domain::CoordinateMaps::Wedge<3>{ + 2., 4., 1., 1., OrientationMap<3>::create_aligned(), true}); + const auto logical_coords = logical_coordinates(mesh); + const auto coords = map(logical_coords); + const auto inv_jacobian = map.inv_jacobian(logical_coords); + ActionTesting::emplace_component_and_initialize( + make_not_null(&runner), element_id, + {tnsr::aa{}, tnsr::aa{}, + tnsr::iaa{}, Scalar{}, Scalar{}, + tnsr::i{}, mesh, coords, inv_jacobian, 0.}); + + const auto get_element_tag = [&runner, + &element_id](auto tag_v) -> decltype(auto) { + using tag = std::decay_t; + return ActionTesting::get_databox_tag(runner, + element_id); + }; + + // We use a Kerr solution to generate data + const gh::Solutions::WrappedGr< + ::ScalarTensor::AnalyticData::KerrSphericalHarmonic> + kerr{1., {{0., 0., 0.}}, 2.0, 1.0, 1.0, std::pair{1, 0}}; + const auto kerr_gh_vars = kerr.variables(coords, st_system_vars{}); + + ActionTesting::set_phase(make_not_null(&runner), Parallel::Phase::Testing); + + const auto& cache = ActionTesting::cache(runner, element_id); + + // SetInitialData + ActionTesting::next_action(make_not_null(&runner), element_id); + + if (is_numeric) { + INFO("Numeric initial data"); + const auto& numeric_id = + dynamic_cast(initial_data); + + CHECK(Parallel::get(cache) == + std::holds_alternative( + numeric_id.gh_numeric_id().selected_variables())); + + REQUIRE_FALSE(ActionTesting::next_action_if_ready( + make_not_null(&runner), element_id)); + + // MockReadVolumeData + ActionTesting::invoke_queued_simple_action( + make_not_null(&runner), 0); + + // Insert KerrSchild data into the inbox + using inbox_tag = importers::Tags::VolumeData; + auto& inboxes = + ActionTesting::get_inbox_tag( + make_not_null(&runner), element_id); + auto& inbox = inboxes[numeric_id.volume_data_id()]; + const auto& gh_selected_vars = + numeric_id.gh_numeric_id().selected_variables(); + if (std::holds_alternative( + gh_selected_vars)) { + get>(inbox) = + get>(kerr_gh_vars); + get>(inbox) = + get>(kerr_gh_vars); + get>(inbox) = + get>(kerr_gh_vars); + } else if (std::holds_alternative( + gh_selected_vars)) { + const auto kerr_adm_vars = kerr.variables( + coords, tmpl::list, + gr::Tags::Lapse, + gr::Tags::Shift, + gr::Tags::ExtrinsicCurvature>{}); + get>(inbox) = + get>(kerr_adm_vars); + get>(inbox) = + get>(kerr_adm_vars); + get>(inbox) = + get>(kerr_adm_vars); + get>(inbox) = + get>(kerr_adm_vars); + } else { + REQUIRE(false); + } + get(inbox) = + get(kerr_gh_vars); + get(inbox) = + get(kerr_gh_vars); + get>(inbox) = + get>(kerr_gh_vars); + + const std::string inbox_output = inbox_tag::output_inbox(inboxes, 1_st); + const std::string expected_inbox_output = + MakeString{} << " VolumeDataInbox:\n" + << " Index: " << numeric_id.volume_data_id() << "\n"; + CHECK(inbox_output == expected_inbox_output); + + // ReceiveNumericInitialData + ActionTesting::next_action(make_not_null(&runner), + element_id); + } else { + CHECK(Parallel::get(cache)); + } + + // Check result. These variables are not particularly precise because we are + // taking numerical derivatives on a fairly coarse wedge-shaped grid. + const Approx custom_approx = Approx::custom().epsilon(1.e-3).scale(1.0); + CHECK_ITERABLE_CUSTOM_APPROX( + get_element_tag(gr::Tags::SpacetimeMetric{}), + (get>(kerr_gh_vars)), + custom_approx); + CHECK_ITERABLE_CUSTOM_APPROX(get_element_tag(gh::Tags::Pi{}), + (get>(kerr_gh_vars)), + custom_approx); + CHECK_ITERABLE_CUSTOM_APPROX( + get_element_tag(gh::Tags::Phi{}), + (get>(kerr_gh_vars)), custom_approx); + CHECK_ITERABLE_CUSTOM_APPROX(get_element_tag(CurvedScalarWave::Tags::Psi{}), + (get(kerr_gh_vars)), + custom_approx); + CHECK_ITERABLE_CUSTOM_APPROX(get_element_tag(CurvedScalarWave::Tags::Pi{}), + (get(kerr_gh_vars)), + custom_approx); + CHECK_ITERABLE_CUSTOM_APPROX( + get_element_tag(CurvedScalarWave::Tags::Phi<3>{}), + (get>(kerr_gh_vars)), custom_approx); +} +} // namespace + +SPECTRE_TEST_CASE("Unit.Evolution.Systems.ScalarTensor.NumericInitialData", + "[Unit][Evolution]") { + register_factory_classes_with_charm(); + test_set_initial_data( + NumericInitialData{"TestInitialData.h5", + "VolumeData", + 0., + {1.0e-9}, + false, + gh::NumericInitialData::GhVars{ + "CustomSpacetimeMetric", "CustomPi", "CustomPhi"}, + CurvedScalarWave::NumericInitialData::ScalarVars{ + "CustomPsi", "CustomScalarPi", "CustomScalarPhi"}}, + "NumericInitialData:\n" + " FileGlob: TestInitialData.h5\n" + " Subgroup: VolumeData\n" + " ObservationValue: 0.\n" + " ObservationValueEpsilon: 1e-9\n" + " ElementsAreIdentical: False\n" + " GhVariables:\n" + " SpacetimeMetric: CustomSpacetimeMetric\n" + " Pi: CustomPi\n" + " Phi: CustomPhi\n" + " ScalarVariables:\n" + " Psi: CustomPsi\n" + " Pi: CustomScalarPi\n" + " Phi: CustomScalarPhi\n", + true); + test_set_initial_data( + NumericInitialData{ + "TestInitialData.h5", "VolumeData", 0., std::nullopt, false, + gh::NumericInitialData::AdmVars{"CustomSpatialMetric", "CustomLapse", + "CustomShift", + "CustomExtrinsicCurvature"}, + CurvedScalarWave::NumericInitialData::ScalarVars{ + "CustomPsi", "CustomScalarPi", "CustomScalarPhi"}}, + "NumericInitialData:\n" + " FileGlob: TestInitialData.h5\n" + " Subgroup: VolumeData\n" + " ObservationValue: 0.\n" + " ObservationValueEpsilon: Auto\n" + " ElementsAreIdentical: False\n" + " GhVariables:\n" + " SpatialMetric: CustomSpatialMetric\n" + " Lapse: CustomLapse\n" + " Shift: CustomShift\n" + " ExtrinsicCurvature: CustomExtrinsicCurvature\n" + " ScalarVariables:\n" + " Psi: CustomPsi\n" + " Pi: CustomScalarPi\n" + " Phi: CustomScalarPhi\n", + true); + test_set_initial_data( + gh::Solutions::WrappedGr< + ::ScalarTensor::AnalyticData::KerrSphericalHarmonic>{ + 1., {{0., 0., 0.}}, 2.0, 1.0, 1.0, std::pair{1, 0}}, + "GeneralizedHarmonic(KerrSphericalHarmonic):\n" + " Mass: 1.0 \n" + " Spin: [0.0, 0.0, 0.0] \n" + " Amplitude: 2.0 \n" + " Radius: 1.0 \n" + " Width: 1.0 \n" + " Mode: [1, 0]", + false); +} + +} // namespace ScalarTensor diff --git a/tests/Unit/Evolution/Systems/ScalarTensor/CMakeLists.txt b/tests/Unit/Evolution/Systems/ScalarTensor/CMakeLists.txt index 52f29b07c29d..a3deb8dc0543 100644 --- a/tests/Unit/Evolution/Systems/ScalarTensor/CMakeLists.txt +++ b/tests/Unit/Evolution/Systems/ScalarTensor/CMakeLists.txt @@ -4,6 +4,7 @@ set(LIBRARY "Test_ScalarTensor") set(LIBRARY_SOURCES + Actions/Test_SetInitialData.cpp BoundaryConditions/Test_ConstraintPreserving.cpp BoundaryConditions/Test_DemandOutgoingCharSpeeds.cpp Test_Characteristics.cpp