559 lines
17 KiB
C++
559 lines
17 KiB
C++
//
|
|
// Copyright (c) 2018-2019, Cem Bassoy, cem.bassoy@gmail.com
|
|
//
|
|
// 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 IOSB, Ettlingen, Germany
|
|
//
|
|
|
|
|
|
#ifndef BOOST_UBLAS_TENSOR_FUNCTIONS_HPP
|
|
#define BOOST_UBLAS_TENSOR_FUNCTIONS_HPP
|
|
|
|
|
|
#include <stdexcept>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <numeric>
|
|
|
|
|
|
#include "multiplication.hpp"
|
|
#include "algorithms.hpp"
|
|
#include "expression.hpp"
|
|
#include "expression_evaluation.hpp"
|
|
#include "storage_traits.hpp"
|
|
|
|
namespace boost {
|
|
namespace numeric {
|
|
namespace ublas {
|
|
|
|
template<class Value, class Format, class Allocator>
|
|
class tensor;
|
|
|
|
template<class Value, class Format, class Allocator>
|
|
class matrix;
|
|
|
|
template<class Value, class Allocator>
|
|
class vector;
|
|
|
|
|
|
|
|
|
|
/** @brief Computes the m-mode tensor-times-vector product
|
|
*
|
|
* Implements C[i1,...,im-1,im+1,...,ip] = A[i1,i2,...,ip] * b[im]
|
|
*
|
|
* @note calls ublas::ttv
|
|
*
|
|
* @param[in] m contraction dimension with 1 <= m <= p
|
|
* @param[in] a tensor object A with order p
|
|
* @param[in] b vector object B
|
|
*
|
|
* @returns tensor object C with order p-1, the same storage format and allocator type as A
|
|
*/
|
|
template<class V, class F, class A1, class A2>
|
|
auto prod(tensor<V,F,A1> const& a, vector<V,A2> const& b, const std::size_t m)
|
|
{
|
|
|
|
using tensor_type = tensor<V,F,A1>;
|
|
using extents_type = typename tensor_type::extents_type;
|
|
using ebase_type = typename extents_type::base_type;
|
|
using value_type = typename tensor_type::value_type;
|
|
using size_type = typename extents_type::value_type;
|
|
|
|
auto const p = std::size_t(a.rank());
|
|
|
|
if( m == 0)
|
|
throw std::length_error("error in boost::numeric::ublas::prod(ttv): contraction mode must be greater than zero.");
|
|
|
|
if( p < m )
|
|
throw std::length_error("error in boost::numeric::ublas::prod(ttv): rank of tensor must be greater than or equal to the modus.");
|
|
|
|
if( p == 0)
|
|
throw std::length_error("error in boost::numeric::ublas::prod(ttv): rank of tensor must be greater than zero.");
|
|
|
|
if( a.empty() )
|
|
throw std::length_error("error in boost::numeric::ublas::prod(ttv): first argument tensor should not be empty.");
|
|
|
|
if( b.size() == 0)
|
|
throw std::length_error("error in boost::numeric::ublas::prod(ttv): second argument vector should not be empty.");
|
|
|
|
|
|
auto nc = ebase_type(std::max(p-1, size_type(2)) , size_type(1));
|
|
auto nb = ebase_type{b.size(),1};
|
|
|
|
|
|
for(auto i = 0u, j = 0u; i < p; ++i)
|
|
if(i != m-1)
|
|
nc[j++] = a.extents().at(i);
|
|
|
|
auto c = tensor_type(extents_type(nc),value_type{});
|
|
|
|
auto bb = &(b(0));
|
|
|
|
ttv(m, p,
|
|
c.data(), c.extents().data(), c.strides().data(),
|
|
a.data(), a.extents().data(), a.strides().data(),
|
|
bb, nb.data(), nb.data());
|
|
|
|
|
|
return c;
|
|
}
|
|
|
|
|
|
|
|
/** @brief Computes the m-mode tensor-times-matrix product
|
|
*
|
|
* Implements C[i1,...,im-1,j,im+1,...,ip] = A[i1,i2,...,ip] * B[j,im]
|
|
*
|
|
* @note calls ublas::ttm
|
|
*
|
|
* @param[in] a tensor object A with order p
|
|
* @param[in] b vector object B
|
|
* @param[in] m contraction dimension with 1 <= m <= p
|
|
*
|
|
* @returns tensor object C with order p, the same storage format and allocator type as A
|
|
*/
|
|
template<class V, class F, class A1, class A2>
|
|
auto prod(tensor<V,F,A1> const& a, matrix<V,F,A2> const& b, const std::size_t m)
|
|
{
|
|
|
|
using tensor_type = tensor<V,F,A1>;
|
|
using extents_type = typename tensor_type::extents_type;
|
|
using strides_type = typename tensor_type::strides_type;
|
|
using value_type = typename tensor_type::value_type;
|
|
|
|
|
|
auto const p = a.rank();
|
|
|
|
if( m == 0)
|
|
throw std::length_error("error in boost::numeric::ublas::prod(ttm): contraction mode must be greater than zero.");
|
|
|
|
if( p < m || m > a.extents().size())
|
|
throw std::length_error("error in boost::numeric::ublas::prod(ttm): rank of the tensor must be greater equal the modus.");
|
|
|
|
if( p == 0)
|
|
throw std::length_error("error in boost::numeric::ublas::prod(ttm): rank of the tensor must be greater than zero.");
|
|
|
|
if( a.empty() )
|
|
throw std::length_error("error in boost::numeric::ublas::prod(ttm): first argument tensor should not be empty.");
|
|
|
|
if( b.size1()*b.size2() == 0)
|
|
throw std::length_error("error in boost::numeric::ublas::prod(ttm): second argument matrix should not be empty.");
|
|
|
|
|
|
auto nc = a.extents().base();
|
|
auto nb = extents_type {b.size1(),b.size2()};
|
|
auto wb = strides_type (nb);
|
|
|
|
nc[m-1] = nb[0];
|
|
|
|
auto c = tensor_type(extents_type(nc),value_type{});
|
|
|
|
auto bb = &(b(0,0));
|
|
|
|
ttm(m, p,
|
|
c.data(), c.extents().data(), c.strides().data(),
|
|
a.data(), a.extents().data(), a.strides().data(),
|
|
bb, nb.data(), wb.data());
|
|
|
|
|
|
return c;
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @brief Computes the q-mode tensor-times-tensor product
|
|
*
|
|
* Implements C[i1,...,ir,j1,...,js] = sum( A[i1,...,ir+q] * B[j1,...,js+q] )
|
|
*
|
|
* @note calls ublas::ttt
|
|
*
|
|
* na[phia[x]] = nb[phib[x]] for 1 <= x <= q
|
|
*
|
|
* @param[in] phia one-based permutation tuple of length q for the first input tensor a
|
|
* @param[in] phib one-based permutation tuple of length q for the second input tensor b
|
|
* @param[in] a left-hand side tensor with order r+q
|
|
* @param[in] b right-hand side tensor with order s+q
|
|
* @result tensor with order r+s
|
|
*/
|
|
template<class V, class F, class A1, class A2>
|
|
auto prod(tensor<V,F,A1> const& a, tensor<V,F,A2> const& b,
|
|
std::vector<std::size_t> const& phia, std::vector<std::size_t> const& phib)
|
|
{
|
|
|
|
using tensor_type = tensor<V,F,A1>;
|
|
using extents_type = typename tensor_type::extents_type;
|
|
using value_type = typename tensor_type::value_type;
|
|
using size_type = typename extents_type::value_type;
|
|
|
|
auto const pa = a.rank();
|
|
auto const pb = b.rank();
|
|
|
|
auto const q = size_type(phia.size());
|
|
|
|
if(pa == 0ul)
|
|
throw std::runtime_error("error in ublas::prod: order of left-hand side tensor must be greater than 0.");
|
|
if(pb == 0ul)
|
|
throw std::runtime_error("error in ublas::prod: order of right-hand side tensor must be greater than 0.");
|
|
if(pa < q)
|
|
throw std::runtime_error("error in ublas::prod: number of contraction dimensions cannot be greater than the order of the left-hand side tensor.");
|
|
if(pb < q)
|
|
throw std::runtime_error("error in ublas::prod: number of contraction dimensions cannot be greater than the order of the right-hand side tensor.");
|
|
|
|
if(q != phib.size())
|
|
throw std::runtime_error("error in ublas::prod: permutation tuples must have the same length.");
|
|
|
|
if(pa < phia.size())
|
|
throw std::runtime_error("error in ublas::prod: permutation tuple for the left-hand side tensor cannot be greater than the corresponding order.");
|
|
if(pb < phib.size())
|
|
throw std::runtime_error("error in ublas::prod: permutation tuple for the right-hand side tensor cannot be greater than the corresponding order.");
|
|
|
|
|
|
auto const& na = a.extents();
|
|
auto const& nb = b.extents();
|
|
|
|
for(auto i = 0ul; i < q; ++i)
|
|
if( na.at(phia.at(i)-1) != nb.at(phib.at(i)-1))
|
|
throw std::runtime_error("error in ublas::prod: permutations of the extents are not correct.");
|
|
|
|
auto const r = pa - q;
|
|
auto const s = pb - q;
|
|
|
|
|
|
std::vector<std::size_t> phia1(pa), phib1(pb);
|
|
std::iota(phia1.begin(), phia1.end(), 1ul);
|
|
std::iota(phib1.begin(), phib1.end(), 1ul);
|
|
|
|
std::vector<std::size_t> nc( std::max ( r+s , size_type(2) ), size_type(1) );
|
|
|
|
for(auto i = 0ul; i < phia.size(); ++i)
|
|
* std::remove(phia1.begin(), phia1.end(), phia.at(i)) = phia.at(i);
|
|
|
|
//phia1.erase( std::remove(phia1.begin(), phia1.end(), phia.at(i)), phia1.end() ) ;
|
|
|
|
assert(phia1.size() == pa);
|
|
|
|
for(auto i = 0ul; i < r; ++i)
|
|
nc[ i ] = na[ phia1[ i ] - 1 ];
|
|
|
|
for(auto i = 0ul; i < phib.size(); ++i)
|
|
* std::remove(phib1.begin(), phib1.end(), phib.at(i)) = phib.at(i) ;
|
|
//phib1.erase( std::remove(phib1.begin(), phib1.end(), phia.at(i)), phib1.end() ) ;
|
|
|
|
assert(phib1.size() == pb);
|
|
|
|
for(auto i = 0ul; i < s; ++i)
|
|
nc[ r + i ] = nb[ phib1[ i ] - 1 ];
|
|
|
|
// std::copy( phib.begin(), phib.end(), phib1.end() );
|
|
|
|
assert( phia1.size() == pa );
|
|
assert( phib1.size() == pb );
|
|
|
|
auto c = tensor_type(extents_type(nc), value_type{});
|
|
|
|
ttt(pa, pb, q,
|
|
phia1.data(), phib1.data(),
|
|
c.data(), c.extents().data(), c.strides().data(),
|
|
a.data(), a.extents().data(), a.strides().data(),
|
|
b.data(), b.extents().data(), b.strides().data());
|
|
|
|
return c;
|
|
}
|
|
|
|
//template<class V, class F, class A1, class A2, std::size_t N, std::size_t M>
|
|
//auto operator*( tensor_index<V,F,A1,N> const& lhs, tensor_index<V,F,A2,M> const& rhs)
|
|
|
|
|
|
|
|
|
|
/** @brief Computes the q-mode tensor-times-tensor product
|
|
*
|
|
* Implements C[i1,...,ir,j1,...,js] = sum( A[i1,...,ir+q] * B[j1,...,js+q] )
|
|
*
|
|
* @note calls ublas::ttt
|
|
*
|
|
* na[phi[x]] = nb[phi[x]] for 1 <= x <= q
|
|
*
|
|
* @param[in] phi one-based permutation tuple of length q for bot input tensors
|
|
* @param[in] a left-hand side tensor with order r+q
|
|
* @param[in] b right-hand side tensor with order s+q
|
|
* @result tensor with order r+s
|
|
*/
|
|
template<class V, class F, class A1, class A2>
|
|
auto prod(tensor<V,F,A1> const& a, tensor<V,F,A2> const& b,
|
|
std::vector<std::size_t> const& phi)
|
|
{
|
|
return prod(a, b, phi, phi);
|
|
}
|
|
|
|
|
|
/** @brief Computes the inner product of two tensors
|
|
*
|
|
* Implements c = sum(A[i1,i2,...,ip] * B[i1,i2,...,jp])
|
|
*
|
|
* @note calls inner function
|
|
*
|
|
* @param[in] a tensor object A
|
|
* @param[in] b tensor object B
|
|
*
|
|
* @returns a value type.
|
|
*/
|
|
template<class V, class F, class A1, class A2>
|
|
auto inner_prod(tensor<V,F,A1> const& a, tensor<V,F,A2> const& b)
|
|
{
|
|
using value_type = typename tensor<V,F,A1>::value_type;
|
|
|
|
if( a.rank() != b.rank() )
|
|
throw std::length_error("error in boost::numeric::ublas::inner_prod: Rank of both tensors must be the same.");
|
|
|
|
if( a.empty() || b.empty())
|
|
throw std::length_error("error in boost::numeric::ublas::inner_prod: Tensors should not be empty.");
|
|
|
|
if( a.extents() != b.extents())
|
|
throw std::length_error("error in boost::numeric::ublas::inner_prod: Tensor extents should be the same.");
|
|
|
|
return inner(a.rank(), a.extents().data(),
|
|
a.data(), a.strides().data(),
|
|
b.data(), b.strides().data(), value_type{0});
|
|
}
|
|
|
|
/** @brief Computes the outer product of two tensors
|
|
*
|
|
* Implements C[i1,...,ip,j1,...,jq] = A[i1,i2,...,ip] * B[j1,j2,...,jq]
|
|
*
|
|
* @note calls outer function
|
|
*
|
|
* @param[in] a tensor object A
|
|
* @param[in] b tensor object B
|
|
*
|
|
* @returns tensor object C with the same storage format F and allocator type A1
|
|
*/
|
|
template<class V, class F, class A1, class A2>
|
|
auto outer_prod(tensor<V,F,A1> const& a, tensor<V,F,A2> const& b)
|
|
{
|
|
using tensor_type = tensor<V,F,A1>;
|
|
using extents_type = typename tensor_type::extents_type;
|
|
|
|
if( a.empty() || b.empty() )
|
|
throw std::runtime_error("error in boost::numeric::ublas::outer_prod: tensors should not be empty.");
|
|
|
|
auto nc = typename extents_type::base_type(a.rank() + b.rank());
|
|
for(auto i = 0u; i < a.rank(); ++i)
|
|
nc.at(i) = a.extents().at(i);
|
|
|
|
for(auto i = 0u; i < b.rank(); ++i)
|
|
nc.at(a.rank()+i) = b.extents().at(i);
|
|
|
|
auto c = tensor_type(extents_type(nc));
|
|
|
|
outer(c.data(), c.rank(), c.extents().data(), c.strides().data(),
|
|
a.data(), a.rank(), a.extents().data(), a.strides().data(),
|
|
b.data(), b.rank(), b.extents().data(), b.strides().data());
|
|
|
|
return c;
|
|
}
|
|
|
|
|
|
|
|
/** @brief Transposes a tensor according to a permutation tuple
|
|
*
|
|
* Implements C[tau[i1],tau[i2]...,tau[ip]] = A[i1,i2,...,ip]
|
|
*
|
|
* @note calls trans function
|
|
*
|
|
* @param[in] a tensor object of rank p
|
|
* @param[in] tau one-based permutation tuple of length p
|
|
* @returns a transposed tensor object with the same storage format F and allocator type A
|
|
*/
|
|
template<class V, class F, class A>
|
|
auto trans(tensor<V,F,A> const& a, std::vector<std::size_t> const& tau)
|
|
{
|
|
using tensor_type = tensor<V,F,A>;
|
|
using extents_type = typename tensor_type::extents_type;
|
|
// using strides_type = typename tensor_type::strides_type;
|
|
|
|
if( a.empty() )
|
|
return tensor<V,F,A>{};
|
|
|
|
auto const p = a.rank();
|
|
auto const& na = a.extents();
|
|
|
|
auto nc = typename extents_type::base_type (p);
|
|
for(auto i = 0u; i < p; ++i)
|
|
nc.at(tau.at(i)-1) = na.at(i);
|
|
|
|
// auto wc = strides_type(extents_type(nc));
|
|
|
|
auto c = tensor_type(extents_type(nc));
|
|
|
|
|
|
trans( a.rank(), a.extents().data(), tau.data(),
|
|
c.data(), c.strides().data(),
|
|
a.data(), a.strides().data());
|
|
|
|
// auto wc_pi = typename strides_type::base_type (p);
|
|
// for(auto i = 0u; i < p; ++i)
|
|
// wc_pi.at(tau.at(i)-1) = c.strides().at(i);
|
|
|
|
|
|
//copy(a.rank(),
|
|
// a.extents().data(),
|
|
// c.data(), wc_pi.data(),
|
|
// a.data(), a.strides().data() );
|
|
|
|
return c;
|
|
}
|
|
|
|
/** @brief Computes the frobenius norm of a tensor expression
|
|
*
|
|
* @note evaluates the tensor expression and calls the accumulate function
|
|
*
|
|
*
|
|
* Implements the two-norm with
|
|
* k = sqrt( sum_(i1,...,ip) A(i1,...,ip)^2 )
|
|
*
|
|
* @param[in] a tensor object of rank p
|
|
* @returns the frobenius norm of the tensor
|
|
*/
|
|
//template<class V, class F, class A>
|
|
//auto norm(tensor<V,F,A> const& a)
|
|
template<class T, class D>
|
|
auto norm(detail::tensor_expression<T,D> const& expr)
|
|
{
|
|
|
|
using tensor_type = typename detail::tensor_expression<T,D>::tensor_type;
|
|
using value_type = typename tensor_type::value_type;
|
|
|
|
auto a = tensor_type( expr );
|
|
|
|
if( a.empty() )
|
|
throw std::runtime_error("error in boost::numeric::ublas::norm: tensors should not be empty.");
|
|
|
|
return std::sqrt( accumulate( a.order(), a.extents().data(), a.data(), a.strides().data(), value_type{},
|
|
[](auto const& l, auto const& r){ return l + r*r; } ) ) ;
|
|
}
|
|
|
|
|
|
|
|
/** @brief Extract the real component of tensor elements within a tensor expression
|
|
*
|
|
* @param[in] lhs tensor expression
|
|
* @returns unary tensor expression
|
|
*/
|
|
template<class T, class D>
|
|
auto real(detail::tensor_expression<T,D> const& expr) {
|
|
return detail::make_unary_tensor_expression<T> (expr(), [] (auto const& l) { return std::real( l ); } );
|
|
}
|
|
|
|
/** @brief Extract the real component of tensor elements within a tensor expression
|
|
*
|
|
* @param[in] lhs tensor expression
|
|
* @returns unary tensor expression
|
|
*/
|
|
template<class V, class F, class A, class D>
|
|
auto real(detail::tensor_expression<tensor<std::complex<V>,F,A>,D> const& expr)
|
|
{
|
|
using tensor_complex_type = tensor<std::complex<V>,F,A>;
|
|
using tensor_type = tensor<V,F,typename storage_traits<A>::template rebind<V>>;
|
|
|
|
if( detail::retrieve_extents( expr ).empty() )
|
|
throw std::runtime_error("error in boost::numeric::ublas::real: tensors should not be empty.");
|
|
|
|
auto a = tensor_complex_type( expr );
|
|
auto c = tensor_type( a.extents() );
|
|
|
|
std::transform( a.begin(), a.end(), c.begin(), [](auto const& l){ return std::real(l) ; } );
|
|
|
|
return c;
|
|
}
|
|
|
|
|
|
/** @brief Extract the imaginary component of tensor elements within a tensor expression
|
|
*
|
|
* @param[in] lhs tensor expression
|
|
* @returns unary tensor expression
|
|
*/
|
|
template<class T, class D>
|
|
auto imag(detail::tensor_expression<T,D> const& lhs) {
|
|
return detail::make_unary_tensor_expression<T> (lhs(), [] (auto const& l) { return std::imag( l ); } );
|
|
}
|
|
|
|
|
|
/** @brief Extract the imag component of tensor elements within a tensor expression
|
|
*
|
|
* @param[in] lhs tensor expression
|
|
* @returns unary tensor expression
|
|
*/
|
|
template<class V, class A, class F, class D>
|
|
auto imag(detail::tensor_expression<tensor<std::complex<V>,F,A>,D> const& expr)
|
|
{
|
|
using tensor_complex_type = tensor<std::complex<V>,F,A>;
|
|
using tensor_type = tensor<V,F,typename storage_traits<A>::template rebind<V>>;
|
|
|
|
if( detail::retrieve_extents( expr ).empty() )
|
|
throw std::runtime_error("error in boost::numeric::ublas::real: tensors should not be empty.");
|
|
|
|
auto a = tensor_complex_type( expr );
|
|
auto c = tensor_type( a.extents() );
|
|
|
|
std::transform( a.begin(), a.end(), c.begin(), [](auto const& l){ return std::imag(l) ; } );
|
|
|
|
return c;
|
|
}
|
|
|
|
/** @brief Computes the complex conjugate component of tensor elements within a tensor expression
|
|
*
|
|
* @param[in] expr tensor expression
|
|
* @returns complex tensor
|
|
*/
|
|
template<class T, class D>
|
|
auto conj(detail::tensor_expression<T,D> const& expr)
|
|
{
|
|
using tensor_type = T;
|
|
using value_type = typename tensor_type::value_type;
|
|
using layout_type = typename tensor_type::layout_type;
|
|
using array_type = typename tensor_type::array_type;
|
|
|
|
using new_value_type = std::complex<value_type>;
|
|
using new_array_type = typename storage_traits<array_type>::template rebind<new_value_type>;
|
|
|
|
using tensor_complex_type = tensor<new_value_type,layout_type, new_array_type>;
|
|
|
|
if( detail::retrieve_extents( expr ).empty() )
|
|
throw std::runtime_error("error in boost::numeric::ublas::conj: tensors should not be empty.");
|
|
|
|
auto a = tensor_type( expr );
|
|
auto c = tensor_complex_type( a.extents() );
|
|
|
|
std::transform( a.begin(), a.end(), c.begin(), [](auto const& l){ return std::conj(l) ; } );
|
|
|
|
return c;
|
|
}
|
|
|
|
|
|
/** @brief Computes the complex conjugate component of tensor elements within a tensor expression
|
|
*
|
|
* @param[in] lhs tensor expression
|
|
* @returns unary tensor expression
|
|
*/
|
|
template<class V, class A, class F, class D>
|
|
auto conj(detail::tensor_expression<tensor<std::complex<V>,F,A>,D> const& expr)
|
|
{
|
|
return detail::make_unary_tensor_expression<tensor<std::complex<V>,F,A>> (expr(), [] (auto const& l) { return std::conj( l ); } );
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#endif
|