// Copyright (c) 2018-2019 // Cem Bassoy // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // // The authors gratefully acknowledge the support of // Fraunhofer and Google in producing this work // which started as a Google Summer of Code project. // /// \file tensor.hpp Definition for the tensor template class #ifndef BOOST_UBLAS_TENSOR_IMPL_HPP #define BOOST_UBLAS_TENSOR_IMPL_HPP #include #include #include "algorithms.hpp" #include "expression.hpp" #include "expression_evaluation.hpp" #include "extents.hpp" #include "strides.hpp" #include "index.hpp" namespace boost { namespace numeric { namespace ublas { template class tensor; template class matrix; template class vector; ///** \brief Base class for Tensor container models // * // * it does not model the Tensor concept but all derived types should. // * The class defines a common base type and some common interface for all // * statically derived Tensor classes // * We implement the casts to the statically derived type. // */ //template //class tensor_container: // public detail::tensor_expression //{ //public: // static const unsigned complexity = 0; // typedef C container_type; // typedef tensor_tag type_category; // BOOST_UBLAS_INLINE // const container_type &operator () () const { // return *static_cast (this); // } // BOOST_UBLAS_INLINE // container_type &operator () () { // return *static_cast (this); // } //}; /** @brief A dense tensor of values of type \c T. * * For a \f$n\f$-dimensional tensor \f$v\f$ and \f$0\leq i < n\f$ every element \f$v_i\f$ is mapped * to the \f$i\f$-th element of the container. A storage type \c A can be specified which defaults to \c unbounded_array. * Elements are constructed by \c A, which need not initialise their value. * * @tparam T type of the objects stored in the tensor (like int, double, complex,...) * @tparam A The type of the storage array of the tensor. Default is \c unbounded_array. \c and \c std::vector can also be used */ template> > class tensor: public detail::tensor_expression,tensor> { static_assert( std::is_same::value || std::is_same::value, "boost::numeric::tensor template class only supports first- or last-order storage formats."); using self_type = tensor; public: template using tensor_expression_type = detail::tensor_expression; template using matrix_expression_type = matrix_expression; template using vector_expression_type = vector_expression; using super_type = tensor_expression_type; // static_assert(std::is_same_v, detail::tensor_expression,tensor>>, "tensor_expression_type"); using array_type = A; using layout_type = F; using size_type = typename array_type::size_type; using difference_type = typename array_type::difference_type; using value_type = typename array_type::value_type; using reference = typename array_type::reference; using const_reference = typename array_type::const_reference; using pointer = typename array_type::pointer; using const_pointer = typename array_type::const_pointer; using iterator = typename array_type::iterator; using const_iterator = typename array_type::const_iterator; using reverse_iterator = typename array_type::reverse_iterator; using const_reverse_iterator = typename array_type::const_reverse_iterator; using tensor_temporary_type = self_type; using storage_category = dense_tag; using strides_type = basic_strides; using extents_type = shape; using matrix_type = matrix; using vector_type = vector; /** @brief Constructs a tensor. * * @note the tensor is empty. * @note the tensor needs to reshaped for further use. * */ BOOST_UBLAS_INLINE constexpr tensor () : tensor_expression_type() // container_type , extents_() , strides_() , data_() { } /** @brief Constructs a tensor with an initializer list * * By default, its elements are initialized to 0. * * @code tensor A{4,2,3}; @endcode * * @param l initializer list for setting the dimension extents of the tensor */ explicit BOOST_UBLAS_INLINE tensor (std::initializer_list l) : tensor_expression_type() , extents_ (std::move(l)) , strides_ (extents_) , data_ (extents_.product()) { } /** @brief Constructs a tensor with a \c shape * * By default, its elements are initialized to 0. * * @code tensor A{extents{4,2,3}}; @endcode * * @param s initial tensor dimension extents */ explicit BOOST_UBLAS_INLINE tensor (extents_type const& s) : tensor_expression_type() //tensor_container() , extents_ (s) , strides_ (extents_) , data_ (extents_.product()) {} /** @brief Constructs a tensor with a \c shape and initiates it with one-dimensional data * * @code tensor A{extents{4,2,3}, array }; @endcode * * * @param s initial tensor dimension extents * @param a container of \c array_type that is copied according to the storage layout */ BOOST_UBLAS_INLINE tensor (extents_type const& s, const array_type &a) : tensor_expression_type() //tensor_container() , extents_ (s) , strides_ (extents_) , data_ (a) { if(this->extents_.product() != this->data_.size()) throw std::runtime_error("Error in boost::numeric::ublas::tensor: size of provided data and specified extents do not match."); } /** @brief Constructs a tensor using a shape tuple and initiates it with a value. * * @code tensor A{extents{4,2,3}, 1 }; @endcode * * @param e initial tensor dimension extents * @param i initial value of all elements of type \c value_type */ BOOST_UBLAS_INLINE tensor (extents_type const& e, const value_type &i) : tensor_expression_type() //tensor_container () , extents_ (e) , strides_ (extents_) , data_ (extents_.product(), i) {} /** @brief Constructs a tensor from another tensor * * @param v tensor to be copied. */ BOOST_UBLAS_INLINE tensor (const tensor &v) : tensor_expression_type() , extents_ (v.extents_) , strides_ (v.strides_) , data_ (v.data_ ) {} /** @brief Constructs a tensor from another tensor * * @param v tensor to be moved. */ BOOST_UBLAS_INLINE tensor (tensor &&v) : tensor_expression_type() //tensor_container () , extents_ (std::move(v.extents_)) , strides_ (std::move(v.strides_)) , data_ (std::move(v.data_ )) {} /** @brief Constructs a tensor with a matrix * * \note Initially the tensor will be two-dimensional. * * @param v matrix to be copied. */ BOOST_UBLAS_INLINE tensor (const matrix_type &v) : tensor_expression_type() , extents_ () , strides_ () , data_ (v.data()) { if(!data_.empty()){ extents_ = extents_type{v.size1(),v.size2()}; strides_ = strides_type(extents_); } } /** @brief Constructs a tensor with a matrix * * \note Initially the tensor will be two-dimensional. * * @param v matrix to be moved. */ BOOST_UBLAS_INLINE tensor (matrix_type &&v) : tensor_expression_type() , extents_ {} , strides_ {} , data_ {} { if(v.size1()*v.size2() != 0){ extents_ = extents_type{v.size1(),v.size2()}; strides_ = strides_type(extents_); data_ = std::move(v.data()); } } /** @brief Constructs a tensor using a \c vector * * @note It is assumed that vector is column vector * @note Initially the tensor will be one-dimensional. * * @param v vector to be copied. */ BOOST_UBLAS_INLINE tensor (const vector_type &v) : tensor_expression_type() , extents_ () , strides_ () , data_ (v.data()) { if(!data_.empty()){ extents_ = extents_type{data_.size(),1}; strides_ = strides_type(extents_); } } /** @brief Constructs a tensor using a \c vector * * @param v vector to be moved. */ BOOST_UBLAS_INLINE tensor (vector_type &&v) : tensor_expression_type() , extents_ {} , strides_ {} , data_ {} { if(v.size() != 0){ extents_ = extents_type{v.size(),1}; strides_ = strides_type(extents_); data_ = std::move(v.data()); } } /** @brief Constructs a tensor with another tensor with a different layout * * @param other tensor with a different layout to be copied. */ BOOST_UBLAS_INLINE template tensor (const tensor &other) : tensor_expression_type () , extents_ (other.extents()) , strides_ (other.extents()) , data_ (other.extents().product()) { copy(this->rank(), this->extents().data(), this->data(), this->strides().data(), other.data(), other.strides().data()); } /** @brief Constructs a tensor with an tensor expression * * @code tensor A = B + 3 * C; @endcode * * @note type must be specified of tensor must be specified. * @note dimension extents are extracted from tensors within the expression. * * @param expr tensor expression */ BOOST_UBLAS_INLINE template tensor (const tensor_expression_type &expr) : tensor_expression_type () , extents_ ( detail::retrieve_extents(expr) ) , strides_ ( extents_ ) , data_ ( extents_.product() ) { static_assert( detail::has_tensor_types>::value, "Error in boost::numeric::ublas::tensor: expression does not contain a tensor. cannot retrieve shape."); detail::eval( *this, expr ); } /** @brief Constructs a tensor with a matrix expression * * @code tensor A = B + 3 * C; @endcode * * @note matrix expression is evaluated and pushed into a temporary matrix before assignment. * @note extents are automatically extracted from the temporary matrix * * @param expr matrix expression */ BOOST_UBLAS_INLINE template tensor (const matrix_expression_type &expr) : tensor( matrix_type ( expr ) ) { } /** @brief Constructs a tensor with a vector expression * * @code tensor A = b + 3 * b; @endcode * * @note matrix expression is evaluated and pushed into a temporary matrix before assignment. * @note extents are automatically extracted from the temporary matrix * * @param expr vector expression */ BOOST_UBLAS_INLINE template tensor (const vector_expression_type &expr) : tensor( vector_type ( expr ) ) { } /** @brief Evaluates the tensor_expression and assigns the results to the tensor * * @code A = B + C * 2; @endcode * * @note rank and dimension extents of the tensors in the expressions must conform with this tensor. * * @param expr expression that is evaluated. */ BOOST_UBLAS_INLINE template tensor &operator = (const tensor_expression_type &expr) { detail::eval(*this, expr); return *this; } tensor& operator=(tensor other) { swap (*this, other); return *this; } tensor& operator=(const_reference v) { std::fill(this->begin(), this->end(), v); return *this; } /** @brief Returns true if the tensor is empty (\c size==0) */ BOOST_UBLAS_INLINE bool empty () const { return this->data_.empty(); } /** @brief Returns the size of the tensor */ BOOST_UBLAS_INLINE size_type size () const { return this->data_.size (); } /** @brief Returns the size of the tensor */ BOOST_UBLAS_INLINE size_type size (size_type r) const { return this->extents_.at(r); } /** @brief Returns the number of dimensions/modes of the tensor */ BOOST_UBLAS_INLINE size_type rank () const { return this->extents_.size(); } /** @brief Returns the number of dimensions/modes of the tensor */ BOOST_UBLAS_INLINE size_type order () const { return this->extents_.size(); } /** @brief Returns the strides of the tensor */ BOOST_UBLAS_INLINE strides_type const& strides () const { return this->strides_; } /** @brief Returns the extents of the tensor */ BOOST_UBLAS_INLINE extents_type const& extents () const { return this->extents_; } /** @brief Returns a \c const reference to the container. */ BOOST_UBLAS_INLINE const_pointer data () const { return this->data_.data(); } /** @brief Returns a \c const reference to the container. */ BOOST_UBLAS_INLINE pointer data () { return this->data_.data(); } /** @brief Element access using a single index. * * @code auto a = A[i]; @endcode * * @param i zero-based index where 0 <= i < this->size() */ BOOST_UBLAS_INLINE const_reference operator [] (size_type i) const { return this->data_[i]; } /** @brief Element access using a single index. * * * @code A[i] = a; @endcode * * @param i zero-based index where 0 <= i < this->size() */ BOOST_UBLAS_INLINE reference operator [] (size_type i) { return this->data_[i]; } /** @brief Element access using a multi-index or single-index. * * * @code auto a = A.at(i,j,k); @endcode or * @code auto a = A.at(i); @endcode * * @param i zero-based index where 0 <= i < this->size() if sizeof...(is) == 0, else 0<= i < this->size(0) * @param is zero-based indices where 0 <= is[r] < this->size(r) where 0 < r < this->rank() */ template BOOST_UBLAS_INLINE const_reference at (size_type i, size_types ... is) const { if constexpr (sizeof...(is) == 0) return this->data_[i]; else return this->data_[detail::access<0ul>(size_type(0),this->strides_,i,std::forward(is)...)]; } /** @brief Element access using a multi-index or single-index. * * * @code A.at(i,j,k) = a; @endcode or * @code A.at(i) = a; @endcode * * @param i zero-based index where 0 <= i < this->size() if sizeof...(is) == 0, else 0<= i < this->size(0) * @param is zero-based indices where 0 <= is[r] < this->size(r) where 0 < r < this->rank() */ BOOST_UBLAS_INLINE template reference at (size_type i, size_types ... is) { if constexpr (sizeof...(is) == 0) return this->data_[i]; else return this->data_[detail::access<0ul>(size_type(0),this->strides_,i,std::forward(is)...)]; } /** @brief Element access using a single index. * * * @code A(i) = a; @endcode * * @param i zero-based index where 0 <= i < this->size() */ BOOST_UBLAS_INLINE const_reference operator()(size_type i) const { return this->data_[i]; } /** @brief Element access using a single index. * * @code A(i) = a; @endcode * * @param i zero-based index where 0 <= i < this->size() */ BOOST_UBLAS_INLINE reference operator()(size_type i){ return this->data_[i]; } /** @brief Generates a tensor index for tensor contraction * * * @code auto Ai = A(_i,_j,k); @endcode * * @param i placeholder * @param is zero-based indices where 0 <= is[r] < this->size(r) where 0 < r < this->rank() */ BOOST_UBLAS_INLINE template decltype(auto) operator() (index::index_type p, index_types ... ps) const { constexpr auto N = sizeof...(ps)+1; if( N != this->rank() ) throw std::runtime_error("Error in boost::numeric::ublas::operator(): size of provided index_types does not match with the rank."); return std::make_pair( std::cref(*this), std::make_tuple( p, std::forward(ps)... ) ); } /** @brief Reshapes the tensor * * * (1) @code A.reshape(extents{m,n,o}); @endcode or * (2) @code A.reshape(extents{m,n,o},4); @endcode * * If the size of this smaller than the specified extents than * default constructed (1) or specified (2) value is appended. * * @note rank of the tensor might also change. * * @param e extents with which the tensor is reshaped. * @param v value which is appended if the tensor is enlarged. */ BOOST_UBLAS_INLINE void reshape (extents_type const& e, value_type v = value_type{}) { this->extents_ = e; this->strides_ = strides_type(this->extents_); if(e.product() != this->size()) this->data_.resize (this->extents_.product(), v); } friend void swap(tensor& lhs, tensor& rhs) { std::swap(lhs.data_ , rhs.data_ ); std::swap(lhs.extents_, rhs.extents_); std::swap(lhs.strides_, rhs.strides_); } /// \brief return an iterator on the first element of the tensor BOOST_UBLAS_INLINE const_iterator begin () const { return data_.begin (); } /// \brief return an iterator on the first element of the tensor BOOST_UBLAS_INLINE const_iterator cbegin () const { return data_.cbegin (); } /// \brief return an iterator after the last element of the tensor BOOST_UBLAS_INLINE const_iterator end () const { return data_.end(); } /// \brief return an iterator after the last element of the tensor BOOST_UBLAS_INLINE const_iterator cend () const { return data_.cend (); } /// \brief Return an iterator on the first element of the tensor BOOST_UBLAS_INLINE iterator begin () { return data_.begin(); } /// \brief Return an iterator at the end of the tensor BOOST_UBLAS_INLINE iterator end () { return data_.end(); } /// \brief Return a const reverse iterator before the first element of the reversed tensor (i.e. end() of normal tensor) BOOST_UBLAS_INLINE const_reverse_iterator rbegin () const { return data_.rbegin(); } /// \brief Return a const reverse iterator before the first element of the reversed tensor (i.e. end() of normal tensor) BOOST_UBLAS_INLINE const_reverse_iterator crbegin () const { return data_.crbegin(); } /// \brief Return a const reverse iterator on the end of the reverse tensor (i.e. first element of the normal tensor) BOOST_UBLAS_INLINE const_reverse_iterator rend () const { return data_.rend(); } /// \brief Return a const reverse iterator on the end of the reverse tensor (i.e. first element of the normal tensor) BOOST_UBLAS_INLINE const_reverse_iterator crend () const { return data_.crend(); } /// \brief Return a const reverse iterator before the first element of the reversed tensor (i.e. end() of normal tensor) BOOST_UBLAS_INLINE reverse_iterator rbegin () { return data_.rbegin(); } /// \brief Return a const reverse iterator on the end of the reverse tensor (i.e. first element of the normal tensor) BOOST_UBLAS_INLINE reverse_iterator rend () { return data_.rend(); } #if 0 // ------------- // Serialization // ------------- /// Serialize a tensor into and archive as defined in Boost /// \param ar Archive object. Can be a flat file, an XML file or any other stream /// \param file_version Optional file version (not yet used) template void serialize(Archive & ar, const unsigned int /* file_version */){ ar & serialization::make_nvp("data",data_); } #endif private: extents_type extents_; strides_type strides_; array_type data_; }; }}} // namespaces #endif