diff --git a/src/Domain/CoordinateMaps/CMakeLists.txt b/src/Domain/CoordinateMaps/CMakeLists.txt index 6366d317078b..17fa590c14d8 100644 --- a/src/Domain/CoordinateMaps/CMakeLists.txt +++ b/src/Domain/CoordinateMaps/CMakeLists.txt @@ -32,6 +32,7 @@ spectre_target_sources( KerrHorizonConforming.cpp Rotation.cpp SpecialMobius.cpp + SphericalToCartesian.cpp UniformCylindricalEndcap.cpp UniformCylindricalFlatEndcap.cpp UniformCylindricalSide.cpp @@ -72,6 +73,7 @@ spectre_target_headers( ProductMaps.tpp Rotation.hpp SpecialMobius.hpp + SphericalToCartesian.hpp Tags.hpp TimeDependentHelpers.hpp UniformCylindricalEndcap.hpp diff --git a/src/Domain/CoordinateMaps/SphericalToCartesian.cpp b/src/Domain/CoordinateMaps/SphericalToCartesian.cpp new file mode 100644 index 000000000000..5a4a85f828b1 --- /dev/null +++ b/src/Domain/CoordinateMaps/SphericalToCartesian.cpp @@ -0,0 +1,175 @@ +// Distributed under the MIT License. +// See LICENSE.txt for details. + +#include "Domain/CoordinateMaps/SphericalToCartesian.hpp" + +#include +#include +#include + +#include "DataStructures/Tensor/Tensor.hpp" +#include "Utilities/ConstantExpressions.hpp" +#include "Utilities/DereferenceWrapper.hpp" +#include "Utilities/GenerateInstantiations.hpp" +#include "Utilities/MakeWithValue.hpp" + +namespace domain::CoordinateMaps { + +template +template +std::array, Dim> +SphericalToCartesian::operator()( + const std::array& spherical_coords) const { + if constexpr (Dim == 2) { + const auto& r = spherical_coords[radial_coord]; + const auto& phi = spherical_coords[polar_coord]; + return {{r * cos(phi), r * sin(phi)}}; + } else { + const auto& r = spherical_coords[radial_coord]; + const auto& theta = spherical_coords[polar_coord]; + const auto& phi = spherical_coords[azimuth_coord]; + return { + {r * sin(theta) * cos(phi), r * sin(theta) * sin(phi), r * cos(theta)}}; + } +} + +template +std::optional> SphericalToCartesian::inverse( + const std::array& cartesian_coords) const { + std::array spherical_coords{}; + if constexpr (Dim == 2) { + spherical_coords[radial_coord] = + sqrt(square(cartesian_coords[0]) + square(cartesian_coords[1])); + spherical_coords[polar_coord] = + atan2(cartesian_coords[1], cartesian_coords[0]); + } else { + spherical_coords[radial_coord] = + sqrt(square(cartesian_coords[0]) + square(cartesian_coords[1]) + + square(cartesian_coords[2])); + spherical_coords[polar_coord] = + acos(cartesian_coords[2] / spherical_coords[radial_coord]); + spherical_coords[azimuth_coord] = + atan2(cartesian_coords[1], cartesian_coords[0]); + } + return {std::move(spherical_coords)}; +} + +template +template +tnsr::Ij, Dim, Frame::NoFrame> +SphericalToCartesian::jacobian( + const std::array& spherical_coords) const { + // TODO: include sin(theta) factors for compatibility with Spherepack + // derivatives + using ReturnType = tt::remove_cvref_wrap_t; + auto jacobian_matrix = + make_with_value>( + spherical_coords[0], 0.); + if constexpr (Dim == 2) { + const auto& r = spherical_coords[radial_coord]; + const auto& phi = spherical_coords[polar_coord]; + get<0, radial_coord>(jacobian_matrix) = cos(phi); + get<1, radial_coord>(jacobian_matrix) = sin(phi); + get<0, polar_coord>(jacobian_matrix) = -r * sin(phi); + get<1, polar_coord>(jacobian_matrix) = r * cos(phi); + } else { + const auto& r = spherical_coords[radial_coord]; + const auto& theta = spherical_coords[polar_coord]; + const auto& phi = spherical_coords[azimuth_coord]; + get<0, radial_coord>(jacobian_matrix) = sin(theta) * cos(phi); + get<1, radial_coord>(jacobian_matrix) = sin(theta) * sin(phi); + get<2, radial_coord>(jacobian_matrix) = cos(theta); + get<0, polar_coord>(jacobian_matrix) = r * cos(theta) * cos(phi); + get<1, polar_coord>(jacobian_matrix) = r * cos(theta) * sin(phi); + get<2, polar_coord>(jacobian_matrix) = -sin(theta); + get<0, azimuth_coord>(jacobian_matrix) = -r * sin(theta) * sin(phi); + get<1, azimuth_coord>(jacobian_matrix) = r * sin(theta) * cos(phi); + get<2, azimuth_coord>(jacobian_matrix) = 0.; + } + return jacobian_matrix; +} + +template +template +tnsr::Ij, Dim, Frame::NoFrame> +SphericalToCartesian::inv_jacobian( + const std::array& spherical_coords) const { + using ReturnType = tt::remove_cvref_wrap_t; + auto inv_jacobian_matrix = + make_with_value>( + spherical_coords[0], 0.); + if constexpr (Dim == 2) { + const auto& r = spherical_coords[radial_coord]; + const auto& phi = spherical_coords[polar_coord]; + get(inv_jacobian_matrix) = cos(phi); + get(inv_jacobian_matrix) = sin(phi); + get(inv_jacobian_matrix) = -r * sin(phi); + get(inv_jacobian_matrix) = r * cos(phi); + } else { + const auto& r = spherical_coords[radial_coord]; + const auto [x, y, z] = operator()(spherical_coords); + get(inv_jacobian_matrix) = x / r; + get(inv_jacobian_matrix) = y / r; + get(inv_jacobian_matrix) = z / r; + const auto rho_square = square(x) + square(y); + const auto denominator = square(r) * sqrt(rho_square); + get(inv_jacobian_matrix) = x * z / denominator; + get(inv_jacobian_matrix) = y * z / denominator; + get(inv_jacobian_matrix) = -rho_square / denominator; + get(inv_jacobian_matrix) = -y / rho_square; + get(inv_jacobian_matrix) = x / rho_square; + get(inv_jacobian_matrix) = 0.; + } + return inv_jacobian_matrix; +} + +template +void SphericalToCartesian::pup(PUP::er& p) { + size_t version = 0; + p | version; + // Remember to increment the version number when making changes to this + // function. Retain support for unpacking data written by previous versions + // whenever possible. See `Domain` docs for details. +} + +template +bool operator!=(const SphericalToCartesian& lhs, + const SphericalToCartesian& rhs) { + return not(lhs == rhs); +} + +// Explicit instantiations +#define DIM(data) BOOST_PP_TUPLE_ELEM(0, data) +#define DTYPE(data) BOOST_PP_TUPLE_ELEM(1, data) + +#define INSTANTIATE_DIM(_, data) \ + template class SphericalToCartesian; \ + template bool operator==(const SphericalToCartesian& lhs, \ + const SphericalToCartesian& rhs); \ + template bool operator!=(const SphericalToCartesian& lhs, \ + const SphericalToCartesian& rhs); + +#define INSTANTIATE_DTYPE(_, data) \ + template std::array, DIM(data)> \ + SphericalToCartesian::operator()( \ + const std::array& source_coords) const; \ + template tnsr::Ij, DIM(data), \ + Frame::NoFrame> \ + SphericalToCartesian::jacobian( \ + const std::array& source_coords) const; \ + template tnsr::Ij, DIM(data), \ + Frame::NoFrame> \ + SphericalToCartesian::inv_jacobian( \ + const std::array& source_coords) const; + +GENERATE_INSTANTIATIONS(INSTANTIATE_DIM, (2, 3)) +GENERATE_INSTANTIATIONS(INSTANTIATE_DTYPE, (2, 3), + (double, DataVector, + std::reference_wrapper, + std::reference_wrapper)) + +#undef DIM +#undef DTYPE +#undef INSTANTIATE_DIM +#undef INSTANTIATE_DTYPE +} // namespace domain::CoordinateMaps diff --git a/src/Domain/CoordinateMaps/SphericalToCartesian.hpp b/src/Domain/CoordinateMaps/SphericalToCartesian.hpp new file mode 100644 index 000000000000..440f15bcdbfc --- /dev/null +++ b/src/Domain/CoordinateMaps/SphericalToCartesian.hpp @@ -0,0 +1,81 @@ +// Distributed under the MIT License. +// See LICENSE.txt for details. + +#pragma once + +#include +#include +#include + +#include "DataStructures/Tensor/TypeAliases.hpp" +#include "Domain/CoordinateMaps/Wedge.hpp" +#include "Utilities/TypeTraits/RemoveReferenceWrapper.hpp" + +/// \cond +namespace PUP { +class er; +} // namespace PUP +/// \endcond + +namespace domain::CoordinateMaps { + +/*! + * \ingroup CoordinateMapsGroup + * \brief Map from spherical to Cartesian coordinates + * + * The expected order of the input spherical coordinates is the same as in + * `domain::CoordinateMaps::Wedge`: [theta, phi, r] in 3D and [r, phi] in 2D. + */ +template +class SphericalToCartesian { + public: + static constexpr size_t dim = Dim; + static_assert(Dim == 2 or Dim == 3, + "The spherical shell map is implemented in 2D and 3D"); + + SphericalToCartesian() = default; + ~SphericalToCartesian() = default; + SphericalToCartesian(SphericalToCartesian&&) = default; + SphericalToCartesian(const SphericalToCartesian&) = default; + SphericalToCartesian& operator=(const SphericalToCartesian&) = default; + SphericalToCartesian& operator=(SphericalToCartesian&&) = default; + + template + std::array, Dim> operator()( + const std::array& source_coords) const; + + std::optional> inverse( + const std::array& target_coords) const; + + template + tnsr::Ij, Dim, Frame::NoFrame> jacobian( + const std::array& source_coords) const; + + template + tnsr::Ij, Dim, Frame::NoFrame> inv_jacobian( + const std::array& source_coords) const; + + // NOLINTNEXTLINE(google-runtime-references) + void pup(PUP::er& p); + + static constexpr bool is_identity() { return false; } + + private: + // maps between 2D and 3D choices for coordinate axis orientations + static constexpr size_t radial_coord = + detail::WedgeCoordOrientation::radial_coord; + static constexpr size_t polar_coord = + detail::WedgeCoordOrientation::polar_coord; + static constexpr size_t azimuth_coord = + detail::WedgeCoordOrientation::azimuth_coord; +}; + +template +bool operator==(const SphericalToCartesian& /*lhs*/, + const SphericalToCartesian& /*rhs*/) { + return true; +} +template +bool operator!=(const SphericalToCartesian& lhs, + const SphericalToCartesian& rhs); +} // namespace domain::CoordinateMaps