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 all commits
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
30 changes: 20 additions & 10 deletions src/Time/Triggers/OnSubsteps.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,26 @@ class DataBox;
/// \endcond

namespace Triggers {
/// \ingroup EventsAndTriggersGroup
/// \ingroup TimeGroup
/// Check a trigger on substeps, as well as full steps. Primarily for
/// debugging.
///
/// In LTS mode, only substeps of the first step in each slab will be
/// checked. Such substeps may not be aligned across the domain.
///
/// The observation value on a substep is set to the start time of the
/// step plus $10^6$ times the substep number.
/*!
* \ingroup EventsAndTriggersGroup
* \ingroup TimeGroup
* Check a trigger on substeps, as well as full steps. Primarily for
* debugging.
*
* In LTS mode, only substeps of the first step in each slab will be
* checked. Such substeps may not be aligned across the domain.
*
* The observation value on a substep is set to the start time of the
* step plus $10^6$ times the substep number.
*
* This trigger can be used as:
*
* ```yaml
* - Trigger:
* OnSubsteps:
* Always:
* ```
*/
class OnSubsteps : public Trigger {
public:
/// \cond
Expand Down
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
80 changes: 69 additions & 11 deletions tests/Unit/Framework/PyppFundamentals.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <initializer_list>
#include <optional>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <vector>

Expand All @@ -27,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 @@ -35,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 @@ -259,7 +262,9 @@ struct FromPyObject<long, std::nullptr_t> {
} else {
static_assert(false, "Only works on Python 2.7 and 3.x")
#endif
throw std::runtime_error{"Cannot convert non-long/int type to long."};
const std::string python_type{Py_TYPE(t)->tp_name};
throw std::runtime_error{
"Cannot convert non-long/int type to long. Got " + python_type};
}
return PyLong_AsLong(t);
}
Expand All @@ -279,7 +284,9 @@ struct FromPyObject<unsigned long, std::nullptr_t> {
} else {
static_assert(false, "Only works on Python 2.7 and 3.x");
#endif
throw std::runtime_error{"Cannot convert non-long/int type to long."};
const std::string python_type{Py_TYPE(t)->tp_name};
throw std::runtime_error{
"Cannot convert non-long/int type to long. Got " + python_type};
}
return PyLong_AsUnsignedLong(t);
}
Expand All @@ -291,7 +298,9 @@ struct FromPyObject<double, std::nullptr_t> {
if (t == nullptr) {
throw std::runtime_error{"Received null PyObject."};
} else if (not PyFloat_Check(t)) {
throw std::runtime_error{"Cannot convert non-double type to double."};
const std::string python_type{Py_TYPE(t)->tp_name};
throw std::runtime_error{
"Cannot convert non-double type to double. Got " + python_type};
}
return PyFloat_AsDouble(t);
}
Expand All @@ -303,7 +312,9 @@ struct FromPyObject<bool, std::nullptr_t> {
if (t == nullptr) {
throw std::runtime_error{"Received null PyObject."};
} else if (not PyBool_Check(t)) {
throw std::runtime_error{"Cannot convert non-bool type to bool."};
const std::string python_type{Py_TYPE(t)->tp_name};
throw std::runtime_error{"Cannot convert non-bool type to bool. Got " +
python_type};
}
return static_cast<bool>(PyLong_AsLong(t));
}
Expand All @@ -322,7 +333,9 @@ struct FromPyObject<std::string, std::nullptr_t> {
} else {
static_assert(false, "Only works on Python 2.7 and 3.x")
#endif
throw std::runtime_error{"Cannot convert non-string type to string."};
const std::string python_type{Py_TYPE(t)->tp_name};
throw std::runtime_error{
"Cannot convert non-string type to string. Got " + python_type};
}
#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION == 7
return std::string(PyString_AsString(t));
Expand All @@ -348,7 +361,9 @@ struct FromPyObject<void*, std::nullptr_t> {
if (t == nullptr) {
throw std::runtime_error{"Received null PyObject."};
} else if (t != Py_None) {
throw std::runtime_error{"Cannot convert non-None type to void."};
const std::string python_type{Py_TYPE(t)->tp_name};
throw std::runtime_error{"Cannot convert non-None type to void. Got " +
python_type};
}
return nullptr;
}
Expand All @@ -360,7 +375,9 @@ struct FromPyObject<T, Requires<tt::is_a_v<std::vector, T>>> {
if (p == nullptr) {
throw std::runtime_error{"Received null PyObject."};
} else if (not PyList_CheckExact(p)) {
throw std::runtime_error{"Cannot convert non-list type to vector."};
const std::string python_type{Py_TYPE(p)->tp_name};
throw std::runtime_error{"Cannot convert non-list type to vector. Got " +
python_type};
}
T t(static_cast<size_t>(PyList_Size(p)));
for (size_t i = 0; i < t.size(); ++i) {
Expand All @@ -380,7 +397,9 @@ struct FromPyObject<T, Requires<tt::is_std_array_v<T>>> {
if (p == nullptr) {
throw std::runtime_error{"Received null PyObject."};
} else if (not PyList_CheckExact(p)) {
throw std::runtime_error{"Cannot convert non-list type to array."};
const std::string python_type{Py_TYPE(p)->tp_name};
throw std::runtime_error{"Cannot convert non-list type to array. Got " +
python_type};
}
T t{};
// clang-tidy: Do no implicitly decay an array into a pointer
Expand All @@ -403,7 +422,9 @@ struct FromPyObject<DataVector, std::nullptr_t> {
}
// clang-tidy: c-style casts. (Expanded from macro)
if (not PyArray_CheckExact(p)) { // NOLINT
throw std::runtime_error{"Cannot convert non-array type to DataVector."};
const std::string python_type{Py_TYPE(p)->tp_name};
throw std::runtime_error{
"Cannot convert non-array type to DataVector. Got " + python_type};
}
// clang-tidy: reinterpret_cast
const auto npy_array = reinterpret_cast<PyArrayObject*>(p); // NOLINT
Expand Down Expand Up @@ -438,8 +459,10 @@ struct FromPyObject<ComplexDataVector, std::nullptr_t> {
}
// clang-tidy: c-style casts. (Expanded from macro)
if (not PyArray_CheckExact(p)) { // NOLINT
const std::string python_type{Py_TYPE(p)->tp_name};
throw std::runtime_error{
"Cannot convert non-array type to ComplexDataVector."};
"Cannot convert non-array type to ComplexDataVector. Got " +
python_type};
}
// clang-tidy: reinterpret_cast
const auto npy_array = reinterpret_cast<PyArrayObject*>(p); // NOLINT
Expand Down Expand Up @@ -559,7 +582,7 @@ template <typename... Symms, typename... Indices>
struct FromPyObject<std::tuple<Tensor<double, Symms, Indices>...>,
std::nullptr_t> {
static std::tuple<Tensor<double, Symms, Indices>...> convert(PyObject* p) {
if (PyTuple_Check(p) == 0) {
if (PyTuple_CheckExact(p) == 0) {
const std::string python_type{Py_TYPE(p)->tp_name};
throw std::runtime_error{"Expected a Python tuple but got " +
python_type};
Expand Down Expand Up @@ -594,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
Loading
Loading