/* pybind11/eigen/tensor.h: Transparent conversion for Eigen tensors All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. */ #pragma once #include "../numpy.h" #if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) static_assert(__GNUC__ > 5, "Eigen Tensor support in pybind11 requires GCC > 5.0"); #endif // Disable warnings for Eigen PYBIND11_WARNING_PUSH PYBIND11_WARNING_DISABLE_MSVC(4554) PYBIND11_WARNING_DISABLE_MSVC(4127) #if defined(__MINGW32__) PYBIND11_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") #endif #include PYBIND11_WARNING_POP static_assert(EIGEN_VERSION_AT_LEAST(3, 3, 0), "Eigen Tensor support in pybind11 requires Eigen >= 3.3.0"); PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_WARNING_DISABLE_MSVC(4127) PYBIND11_NAMESPACE_BEGIN(detail) inline bool is_tensor_aligned(const void *data) { return (reinterpret_cast(data) % EIGEN_DEFAULT_ALIGN_BYTES) == 0; } template constexpr int compute_array_flag_from_tensor() { static_assert((static_cast(T::Layout) == static_cast(Eigen::RowMajor)) || (static_cast(T::Layout) == static_cast(Eigen::ColMajor)), "Layout must be row or column major"); return (static_cast(T::Layout) == static_cast(Eigen::RowMajor)) ? array::c_style : array::f_style; } template struct eigen_tensor_helper {}; template struct eigen_tensor_helper> { using Type = Eigen::Tensor; using ValidType = void; static Eigen::DSizes get_shape(const Type &f) { return f.dimensions(); } static constexpr bool is_correct_shape(const Eigen::DSizes & /*shape*/) { return true; } template struct helper {}; template struct helper> { static constexpr auto value = concat(const_name(((void) Is, "?"))...); }; static constexpr auto dimensions_descriptor = helper())>::value; template static Type *alloc(Args &&...args) { return new Type(std::forward(args)...); } static void free(Type *tensor) { delete tensor; } }; template struct eigen_tensor_helper< Eigen::TensorFixedSize, Options_, IndexType>> { using Type = Eigen::TensorFixedSize, Options_, IndexType>; using ValidType = void; static constexpr Eigen::DSizes get_shape(const Type & /*f*/) { return get_shape(); } static constexpr Eigen::DSizes get_shape() { return Eigen::DSizes(Indices...); } static bool is_correct_shape(const Eigen::DSizes &shape) { return get_shape() == shape; } static constexpr auto dimensions_descriptor = concat(const_name()...); template static Type *alloc(Args &&...args) { Eigen::aligned_allocator allocator; return ::new (allocator.allocate(1)) Type(std::forward(args)...); } static void free(Type *tensor) { Eigen::aligned_allocator allocator; tensor->~Type(); allocator.deallocate(tensor, 1); } }; template struct get_tensor_descriptor { static constexpr auto details = const_name(", flags.writeable", "") + const_name(Type::Layout) == static_cast(Eigen::RowMajor)>( ", flags.c_contiguous", ", flags.f_contiguous"); static constexpr auto value = const_name("numpy.ndarray[") + npy_format_descriptor::name + const_name("[") + eigen_tensor_helper>::dimensions_descriptor + const_name("]") + const_name(details, const_name("")) + const_name("]"); }; // When EIGEN_AVOID_STL_ARRAY is defined, Eigen::DSizes does not have the begin() member // function. Falling back to a simple loop works around this issue. // // We need to disable the type-limits warning for the inner loop when size = 0. PYBIND11_WARNING_PUSH PYBIND11_WARNING_DISABLE_GCC("-Wtype-limits") template std::vector convert_dsizes_to_vector(const Eigen::DSizes &arr) { std::vector result(size); for (size_t i = 0; i < size; i++) { result[i] = arr[i]; } return result; } template Eigen::DSizes get_shape_for_array(const array &arr) { Eigen::DSizes result; const T *shape = arr.shape(); for (size_t i = 0; i < size; i++) { result[i] = shape[i]; } return result; } PYBIND11_WARNING_POP template struct type_caster::ValidType> { using Helper = eigen_tensor_helper; static constexpr auto temp_name = get_tensor_descriptor::value; PYBIND11_TYPE_CASTER(Type, temp_name); bool load(handle src, bool convert) { if (!convert) { if (!isinstance(src)) { return false; } array temp = array::ensure(src); if (!temp) { return false; } if (!temp.dtype().is(dtype::of())) { return false; } } array_t()> arr( reinterpret_borrow(src)); if (arr.ndim() != Type::NumIndices) { return false; } auto shape = get_shape_for_array(arr); if (!Helper::is_correct_shape(shape)) { return false; } #if EIGEN_VERSION_AT_LEAST(3, 4, 0) auto data_pointer = arr.data(); #else // Handle Eigen bug auto data_pointer = const_cast(arr.data()); #endif if (is_tensor_aligned(arr.data())) { value = Eigen::TensorMap(data_pointer, shape); } else { value = Eigen::TensorMap(data_pointer, shape); } return true; } static handle cast(Type &&src, return_value_policy policy, handle parent) { if (policy == return_value_policy::reference || policy == return_value_policy::reference_internal) { pybind11_fail("Cannot use a reference return value policy for an rvalue"); } return cast_impl(&src, return_value_policy::move, parent); } static handle cast(const Type &&src, return_value_policy policy, handle parent) { if (policy == return_value_policy::reference || policy == return_value_policy::reference_internal) { pybind11_fail("Cannot use a reference return value policy for an rvalue"); } return cast_impl(&src, return_value_policy::move, parent); } static handle cast(Type &src, return_value_policy policy, handle parent) { if (policy == return_value_policy::automatic || policy == return_value_policy::automatic_reference) { policy = return_value_policy::copy; } return cast_impl(&src, policy, parent); } static handle cast(const Type &src, return_value_policy policy, handle parent) { if (policy == return_value_policy::automatic || policy == return_value_policy::automatic_reference) { policy = return_value_policy::copy; } return cast(&src, policy, parent); } static handle cast(Type *src, return_value_policy policy, handle parent) { if (policy == return_value_policy::automatic) { policy = return_value_policy::take_ownership; } else if (policy == return_value_policy::automatic_reference) { policy = return_value_policy::reference; } return cast_impl(src, policy, parent); } static handle cast(const Type *src, return_value_policy policy, handle parent) { if (policy == return_value_policy::automatic) { policy = return_value_policy::take_ownership; } else if (policy == return_value_policy::automatic_reference) { policy = return_value_policy::reference; } return cast_impl(src, policy, parent); } template static handle cast_impl(C *src, return_value_policy policy, handle parent) { object parent_object; bool writeable = false; switch (policy) { case return_value_policy::move: if (std::is_const::value) { pybind11_fail("Cannot move from a constant reference"); } src = Helper::alloc(std::move(*src)); parent_object = capsule(src, [](void *ptr) { Helper::free(reinterpret_cast(ptr)); }); writeable = true; break; case return_value_policy::take_ownership: if (std::is_const::value) { // This cast is ugly, and might be UB in some cases, but we don't have an // alternative here as we must free that memory Helper::free(const_cast(src)); pybind11_fail("Cannot take ownership of a const reference"); } parent_object = capsule(src, [](void *ptr) { Helper::free(reinterpret_cast(ptr)); }); writeable = true; break; case return_value_policy::copy: writeable = true; break; case return_value_policy::reference: parent_object = none(); writeable = !std::is_const::value; break; case return_value_policy::reference_internal: // Default should do the right thing if (!parent) { pybind11_fail("Cannot use reference internal when there is no parent"); } parent_object = reinterpret_borrow(parent); writeable = !std::is_const::value; break; default: pybind11_fail("pybind11 bug in eigen.h, please file a bug report"); } auto result = array_t()>( convert_dsizes_to_vector(Helper::get_shape(*src)), src->data(), parent_object); if (!writeable) { array_proxy(result.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_; } return result.release(); } }; template = true> StoragePointerType get_array_data_for_type(array &arr) { #if EIGEN_VERSION_AT_LEAST(3, 4, 0) return reinterpret_cast(arr.data()); #else // Handle Eigen bug return reinterpret_cast(const_cast(arr.data())); #endif } template = true> StoragePointerType get_array_data_for_type(array &arr) { return reinterpret_cast(arr.mutable_data()); } template struct get_storage_pointer_type; template struct get_storage_pointer_type> { using SPT = typename MapType::StoragePointerType; }; template struct get_storage_pointer_type> { using SPT = typename MapType::PointerArgType; }; template struct type_caster, typename eigen_tensor_helper>::ValidType> { using MapType = Eigen::TensorMap; using Helper = eigen_tensor_helper>; bool load(handle src, bool /*convert*/) { // Note that we have a lot more checks here as we want to make sure to avoid copies if (!isinstance(src)) { return false; } auto arr = reinterpret_borrow(src); if ((arr.flags() & compute_array_flag_from_tensor()) == 0) { return false; } if (!arr.dtype().is(dtype::of())) { return false; } if (arr.ndim() != Type::NumIndices) { return false; } constexpr bool is_aligned = (Options & Eigen::Aligned) != 0; if (is_aligned && !is_tensor_aligned(arr.data())) { return false; } auto shape = get_shape_for_array(arr); if (!Helper::is_correct_shape(shape)) { return false; } if (needs_writeable && !arr.writeable()) { return false; } auto result = get_array_data_for_type::SPT, needs_writeable>(arr); value.reset(new MapType(std::move(result), std::move(shape))); return true; } static handle cast(MapType &&src, return_value_policy policy, handle parent) { return cast_impl(&src, policy, parent); } static handle cast(const MapType &&src, return_value_policy policy, handle parent) { return cast_impl(&src, policy, parent); } static handle cast(MapType &src, return_value_policy policy, handle parent) { if (policy == return_value_policy::automatic || policy == return_value_policy::automatic_reference) { policy = return_value_policy::copy; } return cast_impl(&src, policy, parent); } static handle cast(const MapType &src, return_value_policy policy, handle parent) { if (policy == return_value_policy::automatic || policy == return_value_policy::automatic_reference) { policy = return_value_policy::copy; } return cast(&src, policy, parent); } static handle cast(MapType *src, return_value_policy policy, handle parent) { if (policy == return_value_policy::automatic) { policy = return_value_policy::take_ownership; } else if (policy == return_value_policy::automatic_reference) { policy = return_value_policy::reference; } return cast_impl(src, policy, parent); } static handle cast(const MapType *src, return_value_policy policy, handle parent) { if (policy == return_value_policy::automatic) { policy = return_value_policy::take_ownership; } else if (policy == return_value_policy::automatic_reference) { policy = return_value_policy::reference; } return cast_impl(src, policy, parent); } template static handle cast_impl(C *src, return_value_policy policy, handle parent) { object parent_object; constexpr bool writeable = !std::is_const::value; switch (policy) { case return_value_policy::reference: parent_object = none(); break; case return_value_policy::reference_internal: // Default should do the right thing if (!parent) { pybind11_fail("Cannot use reference internal when there is no parent"); } parent_object = reinterpret_borrow(parent); break; case return_value_policy::take_ownership: delete src; // fallthrough default: // move, take_ownership don't make any sense for a ref/map: pybind11_fail("Invalid return_value_policy for Eigen Map type, must be either " "reference or reference_internal"); } auto result = array_t()>( convert_dsizes_to_vector(Helper::get_shape(*src)), src->data(), std::move(parent_object)); if (!writeable) { array_proxy(result.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_; } return result.release(); } #if EIGEN_VERSION_AT_LEAST(3, 4, 0) static constexpr bool needs_writeable = !std::is_const::SPT>::type>::value; #else // Handle Eigen bug static constexpr bool needs_writeable = !std::is_const::value; #endif protected: // TODO: Move to std::optional once std::optional has more support std::unique_ptr value; public: static constexpr auto name = get_tensor_descriptor::value; explicit operator MapType *() { return value.get(); } explicit operator MapType &() { return *value; } explicit operator MapType &&() && { return std::move(*value); } template using cast_op_type = ::pybind11::detail::movable_cast_op_type; }; PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)