Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TaggedTuple support to pypp #5748

Merged
merged 4 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 90 additions & 8 deletions tests/Unit/Framework/Pypp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
#include <type_traits>

#include "DataStructures/ComplexDataVector.hpp"
#include "DataStructures/DataBox/TagName.hpp"
#include "DataStructures/DataVector.hpp"
#include "DataStructures/Tensor/Metafunctions.hpp"
#include "DataStructures/Tensor/Tensor.hpp"
#include "Framework/PyppFundamentals.hpp"
#include "Utilities/ErrorHandling/FloatingPointExceptions.hpp"
Expand Down Expand Up @@ -209,6 +211,50 @@ struct ContainerPackAndUnpack<std::tuple<Tensor<T, Symm, Indices>...>,
}
};

template <typename... Tags, typename ConversionClassList>
struct ContainerPackAndUnpack<tuples::TaggedTuple<Tags...>, ConversionClassList,
std::nullptr_t> {
private:
using first_tag = tmpl::front<tmpl::list<Tags...>>;

public:
template <typename Tag>
struct UnpackedTag {
static std::string name() { return db::tag_name<Tag>(); }
using type = typename ContainerPackAndUnpack<
typename Tag::type, ConversionClassList>::unpacked_container;
};
using unpacked_container = tuples::TaggedTuple<UnpackedTag<Tags>...>;
using packed_container = tuples::TaggedTuple<Tags...>;
// This assumes the first member of the TaggedTuple sets the packed
// type. I'm (Nils Deppe) not sure how else to handle this right now.
using packed_type =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so does this mean the tagged tuple must have the same packed_type for each element? Is there a way to static_assert this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how to check it (easily?) since other types are okay, as long as they aren't packed. I.e. if you want to pass in or out an int or double that's fine, it just can't be the first entry in the tuple. I don't know exactly how to assert this check since it's not different packed types that are bad.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, then I am fine with the PR being merged

typename ContainerPackAndUnpack<typename first_tag::type,
ConversionClassList>::packed_type;

static inline unpacked_container unpack(const packed_container& packed,
const size_t grid_point_index) {
return {ContainerPackAndUnpack<typename Tags::type, ConversionClassList>::
unpack(tuples::get<Tags>(packed), grid_point_index)...};
}

static inline void pack(const gsl::not_null<packed_container*> packed,
const unpacked_container& unpacked,
const size_t grid_point_index) {
EXPAND_PACK_LEFT_TO_RIGHT([grid_point_index, &packed, &unpacked]() {
ContainerPackAndUnpack<typename Tags::type, ConversionClassList>::pack(
make_not_null(&tuples::get<Tags>(*packed)),
tuples::get<UnpackedTag<Tags>>(unpacked), grid_point_index);
}());
}

static inline size_t get_size(const packed_container& packed) {
return ContainerPackAndUnpack<
typename first_tag::type,
ConversionClassList>::get_size(tuples::get<first_tag>(packed));
}
};

template <typename T, size_t Size, typename ConversionClassList>
struct ContainerPackAndUnpack<std::array<T, Size>, ConversionClassList,
std::nullptr_t> {
Expand Down Expand Up @@ -398,27 +444,61 @@ struct CallImpl {
}
};

template <typename T>
template <typename T, typename ConversionClassList>
struct is_tuple_of_tensors_of_vectors : std::false_type {
template <typename ConversionClassList>
static T make_zero(const size_t npts) {
return make_with_value<typename ContainerPackAndUnpack<
T, ConversionClassList>::packed_container>(npts, 0.0);
}
};

template <typename T, typename... Symms, typename... Indices>
struct is_tuple_of_tensors_of_vectors<std::tuple<Tensor<T, Symms, Indices>...>>
template <typename T, typename... Symms, typename... Indices,
typename ConversionClassList>
struct is_tuple_of_tensors_of_vectors<std::tuple<Tensor<T, Symms, Indices>...>,
ConversionClassList>
: std::bool_constant<std::is_same_v<DataVector, T> or
std::is_same_v<ComplexDataVector, T>> {
template <typename ConversionClassList>
static std::tuple<Tensor<T, Symms, Indices>...> make_zero(const size_t npts) {
return {make_with_value<typename ContainerPackAndUnpack<
Tensor<T, Symms, Indices>, ConversionClassList>::packed_container>(
npts, 0.0)...};
}
};

template <typename... Tags, typename ConversionClassList>
struct is_tuple_of_tensors_of_vectors<tuples::TaggedTuple<Tags...>,
ConversionClassList>
: std::bool_constant<
(std::is_same_v<typename ContainerPackAndUnpack<
tuples::TaggedTuple<Tags...>,
ConversionClassList>::packed_type,
DataVector> or
std::is_same_v<typename ContainerPackAndUnpack<
tuples::TaggedTuple<Tags...>,
ConversionClassList>::packed_type,
ComplexDataVector>)and tt::
is_a_v<Tensor, typename tmpl::front<tmpl::list<Tags...>>::type>> {
static tuples::TaggedTuple<Tags...> make_zero(const size_t npts) {
return {[npts]() {
using tag_type = typename Tags::type;
if constexpr (std::is_same_v<
typename ContainerPackAndUnpack<
tag_type, ConversionClassList>::packed_type,
DataVector> or
std::is_same_v<
typename ContainerPackAndUnpack<
tag_type, ConversionClassList>::packed_type,
ComplexDataVector>) {
return make_with_value<typename ContainerPackAndUnpack<
tag_type, ConversionClassList>::packed_container>(npts, 0.0);
} else {
(void)npts;
return tag_type{};
}
}()...};
}
};

template <typename ReturnType, typename ConversionClassList>
struct CallImpl<
ReturnType, ConversionClassList,
Expand All @@ -430,7 +510,8 @@ struct CallImpl<
std::is_same_v<typename ContainerPackAndUnpack<
ReturnType, ConversionClassList>::packed_type,
ComplexDataVector>)) or
is_tuple_of_tensors_of_vectors<ReturnType>::value>> {
is_tuple_of_tensors_of_vectors<ReturnType,
ConversionClassList>::value>> {
template <typename... Args>
static ReturnType call(const std::string& module_name,
const std::string& function_name, const Args&... t) {
Expand Down Expand Up @@ -466,8 +547,9 @@ struct CallImpl<
}
}

auto return_container = is_tuple_of_tensors_of_vectors<
ReturnType>::template make_zero<ConversionClassList>(npts);
auto return_container =
is_tuple_of_tensors_of_vectors<ReturnType,
ConversionClassList>::make_zero(npts);

for (size_t s = 0; s < npts; ++s) {
PyObject* args = pypp::make_py_tuple(
Expand Down
37 changes: 37 additions & 0 deletions tests/Unit/Framework/PyppFundamentals.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <numpy/arrayobject.h>

#include "DataStructures/DataBox/TagName.hpp"
#include "DataStructures/DataVector.hpp"
#include "DataStructures/IndexIterator.hpp"
#include "DataStructures/SpinWeighted.hpp"
Expand All @@ -36,6 +37,7 @@
#include "Utilities/MakeArray.hpp"
#include "Utilities/Requires.hpp"
#include "Utilities/TMPL.hpp"
#include "Utilities/TaggedTuple.hpp"
#include "Utilities/TypeTraits.hpp"
#include "Utilities/TypeTraits/IsA.hpp"
#include "Utilities/TypeTraits/IsStdArray.hpp"
Expand Down Expand Up @@ -615,6 +617,41 @@ struct FromPyObject<std::tuple<Tensor<double, Symms, Indices>...>,
}
};

template <typename... Tags>
struct FromPyObject<tuples::TaggedTuple<Tags...>, std::nullptr_t> {
static tuples::TaggedTuple<Tags...> convert(PyObject* p) {
if (PyDict_CheckExact(p) == 0) {
const std::string python_type{Py_TYPE(p)->tp_name};
throw std::runtime_error{"Expected a Python dictionary but got " +
python_type};
}
tuples::TaggedTuple<Tags...> result{};
EXPAND_PACK_LEFT_TO_RIGHT([&result, &p]() {
const std::string tag_name = db::tag_name<Tags>();
PyObject* python_tag_name = PyUnicode_FromString(tag_name.c_str());
PyObject* tag_value = PyDict_GetItemWithError(p, python_tag_name);
if (tag_value == nullptr) {
PyObject* python_keys = PyDict_Keys(p);
const auto keys = from_py_object<std::vector<std::string>>(python_keys);
Py_DECREF(python_keys);
throw std::runtime_error("Could not find tag " + tag_name +
" in dictionary. Known keys are " +
std::string{MakeString{} << keys});
}
try {
get<Tags>(result) = from_py_object<typename Tags::type>(tag_value);
} catch (const std::exception& e) {
throw std::runtime_error{
std::string{"TaggedTuple conversion failed for tag name "} +
tag_name + ". Conversion error: " + std::string{e.what()}};
}
Py_DECREF(python_tag_name);
// tag_value is a borrowed reference, so no need to DECREF.
}());
return result;
}
};

template <>
struct FromPyObject<Scalar<std::complex<double>>> {
static Scalar<std::complex<double>> convert(PyObject* p) {
Expand Down
50 changes: 50 additions & 0 deletions tests/Unit/Framework/Tests/PyppPyTests.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,5 +311,55 @@ def tuple_of_tensor_works(a):
return (np.asarray(2.0 * a), np.asarray([5.0 * a]))


def tagged_tuple_of_tensor_wrong_type(a):
return 0


def tagged_tuple_of_tensor_missing_tag_too_small(a):
return {"Var1": np.asarray(2.0 * a), "Var3": 10}


def tagged_tuple_of_tensor_missing(a):
return {
"Var1": np.asarray(2.0 * a),
"Var2b": np.asarray([5.0 * a]),
"Var3": 10,
}


def tagged_tuple_of_tensor_convert_var1(a):
return {"Var1": np.asarray([a]), "Var2": np.asarray([5.0 * a]), "Var3": 10}


def tagged_tuple_of_tensor_convert_var2(a):
return {"Var1": np.asarray(2.0 * a), "Var2": np.asarray(a), "Var3": 10}


def tagged_tuple_of_tensor_convert_var3(a):
return {
"Var1": np.asarray(2.0 * a),
"Var2": np.asarray([5.0 * a]),
"Var3": np.asarray([5.0 * a]),
}


def tagged_tuple_of_tensor_works(a):
return {
"Var1": np.asarray(2.0 * a),
"Var2": np.asarray([5.0 * a]),
"Var3": 10,
}


def tagged_tuple_of_tensor_works_extra_dict(a):
return {
"Var1": np.asarray(2.0 * a),
"Var2": np.asarray([5.0 * a]),
"Var3": 10,
"Var4": 7.3,
"Var5": np.asarray(9.0 * a),
}


def custom_conversion(t, a):
return t * a
71 changes: 71 additions & 0 deletions tests/Unit/Framework/Tests/Test_Pypp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,76 @@ void test_tuple_of_tensors_of_data_vector() {
(DataVector{5.0, 6.0, 7.5}));
}

namespace Tags {
struct Var1 {
using type = Scalar<DataVector>;
};

struct Var2 {
using type = tnsr::I<DataVector, 1, Frame::Inertial>;
};

struct Var3 {
using type = long;
};
} // namespace Tags

void test_tagged_tuple() {
using ResultType = tuples::TaggedTuple<Tags::Var1, Tags::Var2, Tags::Var3>;
CHECK_THROWS_WITH(
pypp::call<ResultType>("PyppPyTests", "tagged_tuple_of_tensor_wrong_type",
Scalar<DataVector>{3_st, 1.0}),
Catch::Matchers::ContainsSubstring(
"Expected a Python dictionary but got"));

CHECK_THROWS_WITH(
pypp::call<ResultType>("PyppPyTests",
"tagged_tuple_of_tensor_missing_tag_too_small",
Scalar<DataVector>{3_st, 1.0}),
Catch::Matchers::ContainsSubstring(
"Could not find tag Var2 in dictionary. Known keys are "
"(Var1,Var3)"));
CHECK_THROWS_WITH(
pypp::call<ResultType>("PyppPyTests", "tagged_tuple_of_tensor_missing",
Scalar<DataVector>{3_st, 1.0}),
Catch::Matchers::ContainsSubstring(
"Could not find tag Var2 in dictionary. Known keys are "
"(Var1,Var2b,Var3)"));
CHECK_THROWS_WITH(pypp::call<ResultType>(
"PyppPyTests", "tagged_tuple_of_tensor_convert_var1",
Scalar<DataVector>{3_st, 1.0}),
Catch::Matchers::ContainsSubstring(
"TaggedTuple conversion failed for tag name Var1."));
CHECK_THROWS_WITH(pypp::call<ResultType>(
"PyppPyTests", "tagged_tuple_of_tensor_convert_var2",
Scalar<DataVector>{3_st, 1.0}),
Catch::Matchers::ContainsSubstring(
"TaggedTuple conversion failed for tag name Var2."));
CHECK_THROWS_WITH(pypp::call<ResultType>(
"PyppPyTests", "tagged_tuple_of_tensor_convert_var3",
Scalar<DataVector>{3_st, 1.0}),
Catch::Matchers::ContainsSubstring(
"TaggedTuple conversion failed for tag name Var3."));

const auto result =
pypp::call<ResultType>("PyppPyTests", "tagged_tuple_of_tensor_works",
Scalar<DataVector>{DataVector{1.0, 1.2, 1.5}});
CHECK_ITERABLE_APPROX(get(tuples::get<Tags::Var1>(result)),
(DataVector{2.0, 2.4, 3.0}));
CHECK_ITERABLE_APPROX(get<0>(tuples::get<Tags::Var2>(result)),
(DataVector{5.0, 6.0, 7.5}));
CHECK(tuples::get<Tags::Var3>(result) == 10);

const auto result2 = pypp::call<ResultType>(
"PyppPyTests", "tagged_tuple_of_tensor_works_extra_dict",
Scalar<DataVector>{DataVector{1.0, 1.2, 1.5}});
CHECK_ITERABLE_APPROX(get(tuples::get<Tags::Var1>(result2)),
(DataVector{2.0, 2.4, 3.0}));
CHECK_ITERABLE_APPROX(get<0>(tuples::get<Tags::Var2>(result2)),
(DataVector{5.0, 6.0, 7.5}));
CHECK(tuples::get<Tags::Var3>(result2) == 10);
}

void test_custom_conversion() {
const Scalar<DataVector> t{DataVector{5, 2.5}};
{
Expand Down Expand Up @@ -604,5 +674,6 @@ SPECTRE_TEST_CASE("Unit.Pypp", "[Pypp][Unit]") {
test_function_of_time();
test_optional<std::optional>();
test_tuple_of_tensors_of_data_vector();
test_tagged_tuple();
test_custom_conversion();
}
Loading