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