/***************************************************************************
* Copyright (c) 2016, Johan Mabille and Sylvain Corlay *
* *
* Distributed under the terms of the BSD 3-Clause License. *
* *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/
#ifndef PY_TENSOR_HPP
#define PY_TENSOR_HPP
#include
#include
#include
#include "xtensor/xbuffer_adaptor.hpp"
#include "xtensor/xiterator.hpp"
#include "xtensor/xsemantic.hpp"
#include "xtensor/xutils.hpp"
#include "pycontainer.hpp"
#include "pystrides_adaptor.hpp"
#include "xtensor_type_caster_base.hpp"
namespace xt
{
template
class pytensor;
}
namespace pybind11
{
namespace detail
{
template
struct handle_type_name
{
static PYBIND11_DESCR name()
{
return _("numpy.ndarray[") + npy_format_descriptor::name() + _("]");
}
};
template
struct pyobject_caster
{
using type = xt::pytensor;
bool load(handle src, bool convert)
{
if (!convert)
{
if (!xt::detail::check_array(src))
{
return false;
}
}
value = type::ensure(src);
return static_cast(value);
}
static handle cast(const handle& src, return_value_policy, handle)
{
return src.inc_ref();
}
PYBIND11_TYPE_CASTER(type, handle_type_name::name());
};
// Type caster for casting ndarray to xexpression
template
struct type_caster : pyobject_caster
{
using Type = xt::xexpression;
operator Type&()
{
return this->value;
}
operator const Type&()
{
return this->value;
}
};
// Type caster for casting xt::xtensor to ndarray
template
struct type_caster : xtensor_type_caster_base
{
};
}
}
namespace xt
{
template
struct xiterable_inner_types
: xcontainer_iterable_types
{
};
template
struct xcontainer_inner_types
{
using storage_type = xbuffer_adaptor;
using shape_type = std::array;
using strides_type = shape_type;
using backstrides_type = shape_type;
using inner_shape_type = shape_type;
using inner_strides_type = strides_type;
using inner_backstrides_type = backstrides_type;
using temporary_type = pytensor;
static constexpr layout_type layout = L;
};
/**
* @class pytensor
* @brief Multidimensional container providing the xtensor container semantics wrapping a numpy array.
*
* pytensor is similar to the xtensor container in that it has a static dimensionality.
*
* Unlike with the pyarray container, pytensor cannot be reshaped with a different number of dimensions
* and reshapes are not reflected on the Python side. However, pytensor has benefits compared to pyarray
* in terms of performances. pytensor shapes are stack-allocated which makes iteration upon pytensor
* faster than with pyarray.
*
* @tparam T The type of the element stored in the pyarray.
* @sa pyarray
*/
template
class pytensor : public pycontainer,
public xcontainer_semantic
{
public:
using self_type = pytensor;
using semantic_base = xcontainer_semantic;
using base_type = pycontainer;
using storage_type = typename base_type::storage_type;
using value_type = typename base_type::value_type;
using reference = typename base_type::reference;
using const_reference = typename base_type::const_reference;
using pointer = typename base_type::pointer;
using size_type = typename base_type::size_type;
using shape_type = typename base_type::shape_type;
using strides_type = typename base_type::strides_type;
using backstrides_type = typename base_type::backstrides_type;
using inner_shape_type = typename base_type::inner_shape_type;
using inner_strides_type = typename base_type::inner_strides_type;
using inner_backstrides_type = typename base_type::inner_backstrides_type;
pytensor();
pytensor(nested_initializer_list_t t);
pytensor(pybind11::handle h, pybind11::object::borrowed_t);
pytensor(pybind11::handle h, pybind11::object::stolen_t);
pytensor(const pybind11::object& o);
explicit pytensor(const shape_type& shape, layout_type l = layout_type::row_major);
explicit pytensor(const shape_type& shape, const_reference value, layout_type l = layout_type::row_major);
explicit pytensor(const shape_type& shape, const strides_type& strides, const_reference value);
explicit pytensor(const shape_type& shape, const strides_type& strides);
template
static pytensor from_shape(S&& shape);
pytensor(const self_type& rhs);
self_type& operator=(const self_type& rhs);
pytensor(self_type&&) = default;
self_type& operator=(self_type&& e) = default;
template
pytensor(const xexpression& e);
template
self_type& operator=(const xexpression& e);
using base_type::begin;
using base_type::end;
static self_type ensure(pybind11::handle h);
static bool check_(pybind11::handle h);
private:
inner_shape_type m_shape;
inner_strides_type m_strides;
inner_backstrides_type m_backstrides;
storage_type m_storage;
void init_tensor(const shape_type& shape, const strides_type& strides);
void init_from_python();
inner_shape_type& shape_impl() noexcept;
const inner_shape_type& shape_impl() const noexcept;
inner_strides_type& strides_impl() noexcept;
const inner_strides_type& strides_impl() const noexcept;
inner_backstrides_type& backstrides_impl() noexcept;
const inner_backstrides_type& backstrides_impl() const noexcept;
storage_type& storage_impl() noexcept;
const storage_type& storage_impl() const noexcept;
friend class xcontainer;
friend class pycontainer;
};
/***************************
* pytensor implementation *
***************************/
/**
* @name Constructors
*/
//@{
/**
* Allocates an uninitialized pytensor that holds 1 element.
*/
template
inline pytensor::pytensor()
: base_type()
{
m_shape = xtl::make_sequence(N, size_type(1));
m_strides = xtl::make_sequence(N, size_type(0));
init_tensor(m_shape, m_strides);
detail::default_initialize(m_storage);
}
/**
* Allocates a pytensor with a nested initializer list.
*/
template
inline pytensor::pytensor(nested_initializer_list_t t)
: base_type()
{
base_type::resize(xt::shape(t), layout_type::row_major);
nested_copy(m_storage.begin(), t);
}
template
inline pytensor::pytensor(pybind11::handle h, pybind11::object::borrowed_t b)
: base_type(h, b)
{
init_from_python();
}
template
inline pytensor::pytensor(pybind11::handle h, pybind11::object::stolen_t s)
: base_type(h, s)
{
init_from_python();
}
template
inline pytensor::pytensor(const pybind11::object& o)
: base_type(o)
{
init_from_python();
}
/**
* Allocates an uninitialized pytensor with the specified shape and
* layout.
* @param shape the shape of the pytensor
* @param l the layout_type of the pytensor
*/
template
inline pytensor::pytensor(const shape_type& shape, layout_type l)
{
compute_strides(shape, l, m_strides);
init_tensor(shape, m_strides);
}
/**
* Allocates a pytensor with the specified shape and layout. Elements
* are initialized to the specified value.
* @param shape the shape of the pytensor
* @param value the value of the elements
* @param l the layout_type of the pytensor
*/
template
inline pytensor::pytensor(const shape_type& shape,
const_reference value,
layout_type l)
{
compute_strides(shape, l, m_strides);
init_tensor(shape, m_strides);
std::fill(m_storage.begin(), m_storage.end(), value);
}
/**
* Allocates an uninitialized pytensor with the specified shape and strides.
* Elements are initialized to the specified value.
* @param shape the shape of the pytensor
* @param strides the strides of the pytensor
* @param value the value of the elements
*/
template
inline pytensor::pytensor(const shape_type& shape,
const strides_type& strides,
const_reference value)
{
init_tensor(shape, strides);
std::fill(m_storage.begin(), m_storage.end(), value);
}
/**
* Allocates an uninitialized pytensor with the specified shape and strides.
* @param shape the shape of the pytensor
* @param strides the strides of the pytensor
*/
template
inline pytensor::pytensor(const shape_type& shape,
const strides_type& strides)
{
init_tensor(shape, strides);
}
/**
* Allocates and returns an pytensor with the specified shape.
* @param shape the shape of the pytensor
*/
template
template
inline pytensor pytensor::from_shape(S&& shape)
{
detail::check_dims::run(shape.size());
auto shp = xtl::forward_sequence(shape);
return self_type(shp);
}
//@}
/**
* @name Copy semantic
*/
//@{
/**
* The copy constructor.
*/
template
inline pytensor::pytensor(const self_type& rhs)
: base_type(), semantic_base(rhs)
{
init_tensor(rhs.shape(), rhs.strides());
std::copy(rhs.storage().cbegin(), rhs.storage().cend(), this->storage().begin());
}
/**
* The assignment operator.
*/
template
inline auto pytensor::operator=(const self_type& rhs) -> self_type&
{
self_type tmp(rhs);
*this = std::move(tmp);
return *this;
}
//@}
/**
* @name Extended copy semantic
*/
//@{
/**
* The extended copy constructor.
*/
template
template
inline pytensor::pytensor(const xexpression& e)
: base_type()
{
shape_type shape = xtl::forward_sequence(e.derived_cast().shape());
strides_type strides = xtl::make_sequence(N, size_type(0));
compute_strides(shape, layout_type::row_major, strides);
init_tensor(shape, strides);
semantic_base::assign(e);
}
/**
* The extended assignment operator.
*/
template
template
inline auto pytensor::operator=(const xexpression& e) -> self_type&
{
return semantic_base::operator=(e);
}
//@}
template
inline auto pytensor::ensure(pybind11::handle h) -> self_type
{
return base_type::ensure(h);
}
template
inline bool pytensor::check_(pybind11::handle h)
{
return base_type::check_(h);
}
template
inline void pytensor::init_tensor(const shape_type& shape, const strides_type& strides)
{
npy_intp python_strides[N];
std::transform(strides.begin(), strides.end(), python_strides,
[](auto v) { return sizeof(T) * v; });
int flags = NPY_ARRAY_ALIGNED;
if (!std::is_const::value)
{
flags |= NPY_ARRAY_WRITEABLE;
}
auto dtype = pybind11::detail::npy_format_descriptor::dtype();
auto tmp = pybind11::reinterpret_steal(
PyArray_NewFromDescr(&PyArray_Type, (PyArray_Descr*) dtype.release().ptr(), static_cast(shape.size()),
const_cast(shape.data()), python_strides,
nullptr, flags, nullptr));
if (!tmp)
{
throw std::runtime_error("NumPy: unable to create ndarray");
}
this->m_ptr = tmp.release().ptr();
m_shape = shape;
m_strides = strides;
adapt_strides(m_shape, m_strides, m_backstrides);
m_storage = storage_type(reinterpret_cast(PyArray_DATA(this->python_array())),
static_cast(PyArray_SIZE(this->python_array())));
}
template
inline void pytensor::init_from_python()
{
if (PyArray_NDIM(this->python_array()) != N)
{
throw std::runtime_error("NumPy: ndarray has incorrect number of dimensions");
}
std::copy(PyArray_DIMS(this->python_array()), PyArray_DIMS(this->python_array()) + N, m_shape.begin());
std::transform(PyArray_STRIDES(this->python_array()), PyArray_STRIDES(this->python_array()) + N, m_strides.begin(),
[](auto v) { return v / sizeof(T); });
adapt_strides(m_shape, m_strides, m_backstrides);
if (L != layout_type::dynamic && !do_strides_match(m_shape, m_strides, L))
{
throw std::runtime_error("NumPy: passing container with bad strides for layout (is it a view?).");
}
m_storage = storage_type(reinterpret_cast(PyArray_DATA(this->python_array())),
this->get_min_stride() * static_cast(PyArray_SIZE(this->python_array())));
}
template
inline auto pytensor::shape_impl() noexcept -> inner_shape_type&
{
return m_shape;
}
template
inline auto pytensor::shape_impl() const noexcept -> const inner_shape_type&
{
return m_shape;
}
template
inline auto pytensor::strides_impl() noexcept -> inner_strides_type&
{
return m_strides;
}
template
inline auto pytensor::strides_impl() const noexcept -> const inner_strides_type&
{
return m_strides;
}
template
inline auto pytensor::backstrides_impl() noexcept -> inner_backstrides_type&
{
return m_backstrides;
}
template
inline auto pytensor::backstrides_impl() const noexcept -> const inner_backstrides_type&
{
return m_backstrides;
}
template
inline auto pytensor::storage_impl() noexcept -> storage_type&
{
return m_storage;
}
template
inline auto pytensor::storage_impl() const noexcept -> const storage_type&
{
return m_storage;
}
}
#endif