Line data Source code
1 0 : // Distributed under the MIT License. 2 : // See LICENSE.txt for details. 3 : 4 : #pragma once 5 : 6 : #include <cstddef> 7 : #include <string> 8 : #include <utility> 9 : #include <vector> 10 : 11 : #include "DataStructures/DataBox/DataBox.hpp" 12 : #include "DataStructures/DataBox/TagName.hpp" 13 : #include "IO/H5/TensorData.hpp" 14 : #include "IO/Observer/ObserverComponent.hpp" 15 : #include "IO/Observer/ReductionActions.hpp" 16 : #include "IO/Observer/Tags.hpp" 17 : #include "IO/Observer/VolumeActions.hpp" 18 : #include "NumericalAlgorithms/Spectral/Basis.hpp" 19 : #include "NumericalAlgorithms/Spectral/Quadrature.hpp" 20 : #include "NumericalAlgorithms/SphericalHarmonics/IO/FillYlmLegendAndData.hpp" 21 : #include "NumericalAlgorithms/SphericalHarmonics/Spherepack.hpp" 22 : #include "NumericalAlgorithms/SphericalHarmonics/Strahlkorper.hpp" 23 : #include "NumericalAlgorithms/SphericalHarmonics/Tags.hpp" 24 : #include "Parallel/GlobalCache.hpp" 25 : #include "Parallel/Invoke.hpp" 26 : #include "Parallel/Local.hpp" 27 : #include "Parallel/Reduction.hpp" 28 : #include "ParallelAlgorithms/Interpolation/InterpolationTargetDetail.hpp" 29 : #include "ParallelAlgorithms/Interpolation/Protocols/PostInterpolationCallback.hpp" 30 : #include "Utilities/Functional.hpp" 31 : #include "Utilities/Gsl.hpp" 32 : #include "Utilities/PrettyType.hpp" 33 : #include "Utilities/ProtocolHelpers.hpp" 34 : #include "Utilities/TMPL.hpp" 35 : 36 : namespace intrp { 37 : namespace callbacks { 38 : /// \brief post_interpolation_callback that outputs 2D "volume" data on a 39 : /// surface and the surface's spherical harmonic data 40 : /// 41 : /// \details 42 : /// Uses: 43 : /// - Metavariables 44 : /// - `temporal_id` 45 : /// - DataBox: 46 : /// - `TagsToObserve` (each tag must be a Scalar<DataVector>) 47 : /// 48 : /// Conforms to the intrp::protocols::PostInterpolationCallback protocol 49 : /// 50 : /// For requirements on InterpolationTargetTag, see 51 : /// intrp::protocols::InterpolationTargetTag 52 : /// 53 : /// The columns of spherical harmonic data written take the form 54 : /// 55 : /// \code 56 : /// [Time, {Frame}ExpansionCenter_x, {Frame}ExpansionCenter_y, 57 : /// {Frame}ExpansionCenter_z, Lmax, coef(0,0), ... coef(Lmax,Lmax)] 58 : /// \endcode 59 : /// 60 : /// where `coef(l,m)` refers to the strahlkorper coefficients stored and defined 61 : /// by `ylm::Strahlkorper::coefficients() const`. It is assumed that 62 : /// \f$l_{max} = m_{max}\f$. 63 : /// 64 : /// \note Currently, \f$l_{max}\f$ for a given surface does not change over the 65 : /// course of the simulation, which means that the total number of columns of 66 : /// coefficients that we need to write is also constant. The current 67 : /// implementation of writing the coefficients at one time assumes \f$l_{max}\f$ 68 : /// of a surface remains constant. If and when in the future functionality for 69 : /// an adaptive \f$l_{max}\f$ is added, the implementation for writing the 70 : /// coefficients will need to be updated to account for this. One possible way 71 : /// to address this is to have a known maximum \f$l_{max}\f$ for a given surface 72 : /// and write all coefficients up to that maximum \f$l_{max}\f$. 73 : template <typename TagsToObserve, typename InterpolationTargetTag, 74 : typename HorizonFrame> 75 1 : struct ObserveSurfaceData 76 : : tt::ConformsTo<intrp::protocols::PostInterpolationCallback> { 77 0 : static constexpr double fill_invalid_points_with = 78 : std::numeric_limits<double>::quiet_NaN(); 79 : 80 0 : using const_global_cache_tags = tmpl::list<observers::Tags::SurfaceFileName>; 81 : 82 : template <typename DbTags, typename Metavariables, typename TemporalId> 83 0 : static void apply(const db::DataBox<DbTags>& box, 84 : Parallel::GlobalCache<Metavariables>& cache, 85 : const TemporalId& temporal_id) { 86 : const auto& strahlkorper = get<ylm::Tags::Strahlkorper<HorizonFrame>>(box); 87 : const ylm::Spherepack& ylm = strahlkorper.ylm_spherepack(); 88 : 89 : // Output the inertial-frame coordinates of the Stralhlkorper. 90 : // Note that these coordinates are not 91 : // Spherepack-evenly-distributed over the inertial-frame sphere 92 : // (they are Spherepack-evenly-distributed over the HorizonFrame 93 : // sphere). 94 : std::vector<TensorComponent> tensor_components; 95 : if constexpr (db::tag_is_retrievable_v< 96 : ylm::Tags::CartesianCoords<::Frame::Inertial>, 97 : db::DataBox<DbTags>>) { 98 : const auto& inertial_strahlkorper_coords = 99 : get<ylm::Tags::CartesianCoords<::Frame::Inertial>>(box); 100 : tensor_components.push_back( 101 : {"InertialCoordinates_x"s, get<0>(inertial_strahlkorper_coords)}); 102 : tensor_components.push_back( 103 : {"InertialCoordinates_y"s, get<1>(inertial_strahlkorper_coords)}); 104 : tensor_components.push_back( 105 : {"InertialCoordinates_z"s, get<2>(inertial_strahlkorper_coords)}); 106 : } 107 : 108 : // Output each tag if it is a scalar. Otherwise, throw a compile-time 109 : // error. This could be generalized to handle tensors of nonzero rank by 110 : // looping over the components, so each component could be visualized 111 : // separately as a scalar. But in practice, this generalization is 112 : // probably unnecessary, because Strahlkorpers are typically only 113 : // visualized with scalar quantities (used set the color at different 114 : // points on the surface). 115 : tmpl::for_each<TagsToObserve>([&box, &tensor_components](auto tag_v) { 116 : using Tag = tmpl::type_from<decltype(tag_v)>; 117 : const auto tag_name = db::tag_name<Tag>(); 118 : const auto& tensor = get<Tag>(box); 119 : for (size_t i = 0; i < tensor.size(); ++i) { 120 : tensor_components.emplace_back(tag_name + tensor.component_suffix(i), 121 : tensor[i]); 122 : } 123 : }); 124 : 125 : const std::string& surface_name = 126 : pretty_type::name<InterpolationTargetTag>(); 127 : const std::string subfile_path{std::string{"/"} + surface_name}; 128 : const std::vector<size_t> extents_vector{ 129 : {ylm.physical_extents()[0], ylm.physical_extents()[1]}}; 130 : const std::vector<Spectral::Basis> bases_vector{ 131 : 2, Spectral::Basis::SphericalHarmonic}; 132 : const std::vector<Spectral::Quadrature> quadratures_vector{ 133 : {Spectral::Quadrature::Gauss, Spectral::Quadrature::Equiangular}}; 134 : const double time = 135 : InterpolationTarget_detail::get_temporal_id_value(temporal_id); 136 : const observers::ObservationId observation_id{time, subfile_path + ".vol"}; 137 : 138 : auto& proxy = Parallel::get_parallel_component< 139 : observers::ObserverWriter<Metavariables>>(cache); 140 : 141 : // We call this on proxy[0] because the 0th element of a NodeGroup is 142 : // always guaranteed to be present. 143 : Parallel::threaded_action<observers::ThreadedActions::WriteVolumeData>( 144 : proxy[0], Parallel::get<observers::Tags::SurfaceFileName>(cache), 145 : subfile_path, observation_id, 146 : std::vector<ElementVolumeData>{{surface_name, tensor_components, 147 : extents_vector, bases_vector, 148 : quadratures_vector}}); 149 : 150 : std::vector<std::string> ylm_legend{}; 151 : std::vector<double> ylm_data{}; 152 : // The number of coefficients written will be (l_max + 1)^2 where l_max is 153 : // the current value of l_max for this surface's strahlkorper. Because l_max 154 : // remains constant, the number of coefficient columns written does, too. In 155 : // the future when l_max is adaptive, instead of passing in the current 156 : // l_max of the strahlkorper, we could pass in the maximum value that l_max 157 : // could be to ensure that we (a) have enough columns to write all the 158 : // coefficients regardless of the current value of l_max and (b) write a 159 : // constant number of columns for each row of data regardless of the current 160 : // l_max. 161 : ylm::fill_ylm_legend_and_data(make_not_null(&ylm_legend), 162 : make_not_null(&ylm_data), strahlkorper, time, 163 : strahlkorper.l_max()); 164 : 165 : const std::string ylm_subfile_name{std::string{"/"} + surface_name + 166 : "_Ylm"}; 167 : 168 : Parallel::threaded_action< 169 : observers::ThreadedActions::WriteReductionDataRow>( 170 : proxy[0], ylm_subfile_name, std::move(ylm_legend), 171 : std::make_tuple(std::move(ylm_data))); 172 : } 173 : }; 174 : } // namespace callbacks 175 : } // namespace intrp