#ifndef EINSUM_META_H #define EINSUM_META_H #include "Fastor/meta/meta.h" #include "Fastor/config/config.h" #include "Fastor/tensor/Tensor.h" #include namespace Fastor { //namespace detail { template struct Index; // Find return type of tensor contraction //------------------------------------------------------------------------------------------------------------// // is ind[i] unique in ind? template constexpr bool is_uniq(const int (&ind)[N], size_t i, size_t cur = 0){ return cur == N ? true : (cur == i || ind[cur] != ind[i]) ? is_uniq(ind, i, cur + 1) : false; } // For every i where ind[i] == index, is dim[i] == dimension? template constexpr bool check_all_eq(int index, int dimension, const int (&ind)[N], const int (&dim)[N], size_t cur = 0) { return cur == N ? true : (ind[cur] != index || dim[cur] == dimension) ? check_all_eq(index, dimension, ind, dim, cur + 1) : false; } // if position i should be contracted away, return 1001001, otherwise return dim[i]. // triggers a compile-time error on mismatch. template constexpr int calc(size_t i, const int (&ind)[N], const int (&dim)[N]){ return is_uniq(ind, i) ? dim[i] : check_all_eq(ind[i], dim[i], ind, dim) ? 1001001 : throw "dimension mismatch"; } // if position i should be contracted away, return 1001001, otherwise return ind[i]. // triggers a compile-time error on mismatch. template constexpr int calc_idx(size_t i, const int (&ind)[N], const int (&dim)[N]){ return is_uniq(ind, i) ? ind[i] : check_all_eq(ind[i], dim[i], ind, dim) ? 1001001 : throw "dimension mismatch"; } //Now we need a way to get rid of the 1001001s: template struct concat_ { using type = Ind; }; template struct concat_, Index, Inds...> : concat_, Inds...> {}; template struct filter_ : concat_, Index>::type...> {}; template using filter_t = typename filter_::type; //Use them: template struct contraction_impl; template class Derived, typename T, size_t... Ind, size_t... Dim, size_t... Seq> struct contraction_impl, Derived, std_ext::index_sequence>{ static constexpr int ind[sizeof...(Ind)] = { Ind... }; static constexpr int dim[sizeof...(Dim)] = { Dim... }; static constexpr int result[sizeof...(Seq)] = {calc(Seq, ind, dim)...}; template static auto unpack_helper(Index) -> Derived; using type = decltype(unpack_helper(typename filter_<1001001, result[Seq]...>::type{})); // Get the indices instead of values static constexpr int result2[sizeof...(Seq)] = {calc_idx(Seq, ind, dim)...}; template static auto unpack_helper2(Index) -> Index; using indices = decltype(unpack_helper2(typename filter_<1001001, result2[Seq]...>::type{})); }; //------------------------------------------------------------------------------------------------------------// // Generate product of dimensions e.g. products_a, products_b and products_out //------------------------------------------------------------------------------------------------------------// // products generator template constexpr int products(const size_t (&seq)[N], int i = N-1) { return i == (N-1) ? seq[N-1] : products(seq, i+1)*seq[i]; } template constexpr int shifter(const size_t (&seq)[N], int i) { return i < N-1 ? seq[i+1] : shifter(seq, i-1); } template constexpr int zeroer(const size_t (&seq)[N], int i) { return i == N-1 ? 0 : seq[i]; } template struct nprods; template struct nprods,std_ext::index_sequence> { constexpr static size_t vals[sizeof...(Rest)] = {Rest...}; static constexpr size_t pvals[sizeof...(Rest)] = {products(vals,ss)...}; static constexpr size_t svals[sizeof...(Rest)] = {shifter(pvals,ss)...}; static constexpr std::array values = {zeroer(svals,ss)...}; }; template constexpr std::array nprods,std_ext::index_sequence>::values; //------------------------------------------------------------------------------------------------------------// // For views only //------------------------------------------------------------------------------------------------------------// template constexpr int _oner(const size_t (&seq)[N], int i) { return i == N-1 ? 1 : seq[i]; } template struct nprods_views; template struct nprods_views,std_ext::index_sequence> { constexpr static size_t vals[sizeof...(Rest)] = {Rest...}; static constexpr size_t pvals[sizeof...(Rest)] = {products(vals,ss)...}; static constexpr size_t svals[sizeof...(Rest)] = {shifter(pvals,ss)...}; static constexpr std::array values = {_oner(svals,ss)...}; }; template constexpr std::array nprods_views,std_ext::index_sequence>::values; //------------------------------------------------------------------------------------------------------------// // Auxilary meta functions for tensor contraction //------------------------------------------------------------------------------------------------------------// // this is a meta-function equivalent to numpy's "where" template constexpr int find_index(const size_t (&ind)[N], int num, size_t i=0){ return (i==N) ? N : (static_cast(ind[i])==num ? i : find_index(ind,num,i+1)); } template constexpr int find_index(const std::array &ind, int num, size_t i=0){ return (i==N) ? N : (static_cast(ind[i])==num ? i : find_index(ind,num,i+1)); } // check if a given value is ind1 and not ind0 template constexpr bool check_diff(const size_t (&ind0)[M], const size_t (&ind1)[N], int num){ return (find_index(ind0,num) == static_cast(M)) & (find_index(ind1,num) < static_cast(N)); } // this is a meta-function somewhat equivalent to numpy's "setdiff1d" // if a given value is in ind1 and not in ind0, then it returns the index in to the array ind1 such that // ind1[index] = value (num) template constexpr int find_index_diff(const size_t (&ind0)[M], const size_t (&ind1)[N], int num){ return check_diff(ind0,ind1,num) ? find_index(ind1,num) : N; } // based on index from find_index_diff retrieve the actual value template constexpr int retrieve_value(const size_t (&ind0)[M], const size_t (&ind1)[N], const int (&nums1)[N], size_t i=0){ return find_index_diff(ind0,ind1,ind1[i]) == static_cast(N) ? 1 : nums1[find_index(ind1,ind1[i])]; } // does an array ind contain a number num (bool equivalent of find_index) template constexpr bool contains(const size_t (&ind)[N], int num){ return find_index(ind,num)!=N; } template struct put_dims_in_Index; template class Derived, size_t ... Rest, typename T> struct put_dims_in_Index> { using type = Index; }; //------------------------------------------------------------------------------------------------------------// //! Checks vectorisability and returns a stride and a type //------------------------------------------------------------------------------------------------------------// template struct is_vectorisable; template struct is_vectorisable,Index,Tensor> { template using _vec_size = internal::get_simd_vector_size>; static constexpr size_t fastest_changing_index = get_value::value; static constexpr size_t idx[sizeof...(Idx0)] = {Idx0...}; static constexpr bool does_2nd_tensor_disappear = ((int)no_of_unique::value == (int)sizeof...(Idx0) - (int)sizeof...(Idx1)); static constexpr bool last_index_contracted = contains(idx,get_value::value); static constexpr bool is_reducible = does_2nd_tensor_disappear && last_index_contracted; static constexpr bool value = (!last_index_contracted) && (fastest_changing_index % _vec_size::value==0); static constexpr bool sse_vectorisability = (!last_index_contracted) && (fastest_changing_index % _vec_size::value==0 && fastest_changing_index % _vec_size::value!=0); static constexpr bool avx_vectorisability = (!last_index_contracted) && (fastest_changing_index % _vec_size::value==0 && fastest_changing_index % _vec_size::value==0); static constexpr int stride = (avx_vectorisability ? _vec_size::value : (sse_vectorisability ? _vec_size::value : 1)); using type = typename std::conditional, typename std::conditional,SIMDVector>::type>::type; }; template struct is_vectorisable,Index,Tensor> { static constexpr size_t fastest_changing_index = get_value::value; static constexpr size_t idx[sizeof...(Idx0)] = {Idx0...}; static constexpr bool does_2nd_tensor_disappear = ((int)no_of_unique::value == (int)sizeof...(Idx0) - (int)sizeof...(Idx1)); static constexpr bool last_index_contracted = contains(idx,get_value::value); static constexpr bool is_reducible = does_2nd_tensor_disappear && last_index_contracted; static constexpr bool value = (!last_index_contracted) && (fastest_changing_index % 4==0); static constexpr bool sse_vectorisability = (!last_index_contracted) && (fastest_changing_index % 4==0 && fastest_changing_index % 8!=0); static constexpr bool avx_vectorisability = (!last_index_contracted) && (fastest_changing_index % 4==0 && fastest_changing_index % 8==0); static constexpr int stride = (avx_vectorisability ? 8 : (sse_vectorisability ? 4 : 1)); using type = typename std::conditional, typename std::conditional,SIMDVector>::type>::type; }; template struct is_vectorisable,Index,Tensor> { static constexpr size_t fastest_changing_index = get_value::value; static constexpr size_t idx[sizeof...(Idx0)] = {Idx0...}; static constexpr bool does_2nd_tensor_disappear = ((int)no_of_unique::value == (int)sizeof...(Idx0) - (int)sizeof...(Idx1)); static constexpr bool last_index_contracted = contains(idx,get_value::value); static constexpr bool is_reducible = does_2nd_tensor_disappear && last_index_contracted; static constexpr bool value = (!last_index_contracted) && (fastest_changing_index % 2==0); static constexpr bool sse_vectorisability = (!last_index_contracted) && (fastest_changing_index % 2==0 && fastest_changing_index % 4!=0); static constexpr bool avx_vectorisability = (!last_index_contracted) && (fastest_changing_index % 2==0 && fastest_changing_index % 4==0); static constexpr int stride = (avx_vectorisability ? 4 : (sse_vectorisability ? 2 : 1)); using type = typename std::conditional, typename std::conditional,SIMDVector>::type>::type; }; //------------------------------------------------------------------------------------------------------------// //! Checks reducible vectorisability and returns a stride and a type (use this for working on general strides) //------------------------------------------------------------------------------------------------------------// template struct is_reducibly_vectorisable; template struct is_reducibly_vectorisable,Tensor> { template using _vec_size = internal::get_simd_vector_size>; static constexpr size_t fastest_changing_index = get_value::value; static constexpr bool value = (fastest_changing_index % _vec_size::value==0); static constexpr bool sse_vectorisability = (fastest_changing_index % _vec_size::value==0 && fastest_changing_index % _vec_size::value!=0); static constexpr bool avx_vectorisability = (fastest_changing_index % _vec_size::value==0 && fastest_changing_index % _vec_size::value==0); static constexpr int stride = (avx_vectorisability ? _vec_size::value : (sse_vectorisability ? _vec_size::value : 1)); using type = typename std::conditional, typename std::conditional,SIMDVector>::type>::type; }; //------------------------------------------------------------------------------------------------------------// //! Check if indices in Einstein summation appear more than twice //------------------------------------------------------------------------------------------------------------// template struct einsum_index_checker; template struct einsum_index_checker> { static constexpr bool value = no_more_than_two::value; }; //template //struct einsum_index_checker,Index> { // static constexpr bool value = no_more_than_two::value; //}; //------------------------------------------------------------------------------------------------------------// //------------------------------------------------------------------------------------------------------------// template struct get_resuling_tensor; template struct get_resuling_tensor,Index,Tensor,Tensor> { using type = typename contraction_impl, Tensor, typename std_ext::make_index_sequence::type>::type; }; template struct get_resuling_index; template struct get_resuling_index,Index,Tensor,Tensor> { using type = typename contraction_impl, Tensor, typename std_ext::make_index_sequence::type>::indices; }; //------------------------------------------------------------------------------------------------------------// // How many nested loops should be set up //------------------------------------------------------------------------------------------------------------// template struct no_of_loops_to_set; template struct no_of_loops_to_set,Index,Tensor,Tensor,std_ext::index_sequence> { using index_temp = apply_typelist_t, uniq_t>>; static constexpr size_t concat_idx[sizeof...(Idx0)+sizeof...(Idx1)] = {Idx0...,Idx1...}; static constexpr size_t concat_nums[sizeof...(Idx0)+sizeof...(Idx1)] = {Rest0...,Rest1...}; static constexpr std::array idx_in_concat = {find_index(concat_idx,index_temp::values[ss])...}; static constexpr std::array dims = {concat_nums[idx_in_concat[ss]]...}; static constexpr int value = pack_prod::value; using type = Tensor; using indices = Index; using dims_type = Index; }; template constexpr std::array no_of_loops_to_set,Index,Tensor,Tensor,std_ext::index_sequence>::dims; //------------------------------------------------------------------------------------------------------------// // Find indices from the number of loops to set up e.g. idx_a, idx_b and idx_out //------------------------------------------------------------------------------------------------------------// template struct IndexFirstTensor; template struct IndexFirstTensor,Index,Tensor,Tensor,std_ext::index_sequence> { using index_temp = typename no_of_loops_to_set,Index,Tensor,Tensor, typename std_ext::make_index_sequence::value>::type>::indices; static constexpr size_t idx[sizeof...(Idx0)] = {Idx0...}; static constexpr std::array indices = {find_index(index_temp::values, idx[ss])...}; using type = Tensor; }; template constexpr std::array IndexFirstTensor,Index,Tensor,Tensor,std_ext::index_sequence>::indices; template struct IndexSecondTensor; template struct IndexSecondTensor,Index,Tensor,Tensor,std_ext::index_sequence> { using index_temp = typename no_of_loops_to_set,Index,Tensor,Tensor, typename std_ext::make_index_sequence::value>::type>::indices; static constexpr size_t idx[sizeof...(Idx1)] = {Idx1...}; static constexpr std::array indices = {find_index(index_temp::values, idx[ss])...}; using type = Tensor; }; template constexpr std::array IndexSecondTensor,Index,Tensor,Tensor,std_ext::index_sequence>::indices; template struct IndexResultingTensor; template struct IndexResultingTensor,Index,Tensor,Tensor,std_ext::index_sequence> { using index_temp = typename no_of_loops_to_set,Index,Tensor,Tensor, typename std_ext::make_index_sequence::value>::type>::indices; using resulting_index_0 = typename get_resuling_index,Index, Tensor,Tensor>::type; static constexpr std::array indices = {find_index(index_temp::values, resulting_index_0::values[ss])...}; using type = Tensor; }; template constexpr std::array IndexResultingTensor,Index,Tensor,Tensor,std_ext::index_sequence>::indices; //------------------------------------------------------------------------------------------------------------// //------------------------------------------------------------------------------------------------------------// // This is for pure inner reduction and not permuted reduction template struct is_pair_reduction; template struct is_pair_reduction,Index> { static constexpr bool value = is_same_v_,Index>; }; // helper template constexpr bool is_pair_reduction_v = is_pair_reduction::value; // Reduction for a single tensor template struct is_single_reduction; template struct is_single_reduction, Tensor> { using resulting_tensor = typename contraction_impl, Tensor, typename std_ext::make_index_sequence::type>::type; static constexpr bool value = resulting_tensor::dimension_t::value == 0; }; // helper template constexpr bool is_single_reduction_v = is_single_reduction::value; //------------------------------------------------------------------------------------------------------------// //--------------------------------------------------------------------------------------------------------------// template constexpr int contain_prod(const size_t (&ind)[N], const size_t (&sseq)[N], size_t num){ return (ind[num]==0) ? static_cast(sseq[num]) : -1; } template constexpr int last_indices_prod(const int (&sseq)[N], int num){ return num > 0 ? ( (sseq[num-1]!=-1) ? sseq[num-1]*last_indices_prod(sseq,num-1) : 1) : 1; } template struct general_stride_finder; template struct general_stride_finder,Index,Tensor,Tensor, std_ext::index_sequence> { using OutIndices = typename contraction_impl, Tensor, typename std_ext::make_index_sequence::type>::indices; static constexpr size_t b_idx[sizeof...(Idx1)] = {Idx1...}; static constexpr size_t b_dim[sizeof...(Rest1)] = {Rest1...}; // static constexpr std::array container_idx = {contains(OutIndices::values,b_idx[ss])...}; static constexpr size_t container_idxx[sizeof...(Rest1)] = {contains(OutIndices::values,b_idx[ss])...}; // static constexpr std::array container_dim = {contain_prod(container_idxx,b_dim,ss)...}; static constexpr int container_dim[sizeof...(Rest1)] = {contain_prod(container_idxx,b_dim,ss)...}; static constexpr int value = last_indices_prod(container_dim,sizeof...(Rest1)); }; //template //constexpr std::array //general_stride_finder,Index,Tensor,Tensor,std_ext::index_sequence>::container_idx; //template //constexpr std::array //general_stride_finder,Index,Tensor,Tensor,std_ext::index_sequence>::container_dim; //--------------------------------------------------------------------------------------------------------------------// namespace internal { //--------------------------------------------------------------------------------------------------------------------// //! Given the einsum indices of two tensors, matches their indices to see //! if it is a genearalised matrix-vector multiplication. If either the indices //! of the first tensor or the second tensor disappear while matching indices //! from the end, then it is a genearalised matrix-vector multiplication. //! Note that this function matches indices from the end in that it can detect //! generalised matrix-vector product of the forms or (it can detect swapping) //! but it cannot detect or which is vector-matrix product template constexpr inline bool match_indices_from_end(const T (&ind0)[N0], const T (&ind1)[N1], T num0=N0-1, T num1=N1-1) { return ind1[num1] == ind0[num0] ? ( num1 == 0 ? ind1[num1] == ind0[num0] : (num0 == 0 ? ind1[num1] == ind0[num0] : match_indices_from_end(ind0, ind1, num0 - 1, num1 - 1))) : false; } //! Same as above but gives the index up to which the higher order tensor (generalised matrix) //! matches the lower order tensor (generalised vector) from the end. The index is for higher //! higher order tensor counting from the start including the index itself for instance //! will return 0 (i.e. index 0 does not match) and will return 1 (i.e. indices 0 and 1 do no match) //! The function in essence gives the remainder indices that don't match the vector //! //! This function only works (gives the correct index) if the accompanying boolean //! function (match_indices_from_end) is true i.e. it only works if we have a true //! generalised matrix-vector product otherwise gives an incorrect index template constexpr inline T match_indices_from_end_index(const T (&ind0)[N0], const T (&ind1)[N1], T num0=N0-1, T num1=N1-1) { return ind1[num1] == ind0[num0] ? ( num1 == 0 ? (ind1[num1] == ind0[num0] ? num0-1 : num0) : num0 == 0 ? (ind1[num1] == ind0[num0] ? num1-1 : num1) : match_indices_from_end_index(ind0, ind1, num0 - 1, num1 - 1) ) : num0-1; } //! Given the einsum indices of two tensors, matches their indices to see //! if it is a genearalised vector-matrix multiplication. If either the indices //! of the first tensor or the second tensor disappear while matching indices //! from the beggining, then it is a genearalised vector-matrix multiplication. //! Note that this function matches indices from the beggingin in that it can detect //! generalised vector-matrix product of the forms or (it can detect swapping) //! but it cannot detect or which is matrix-vector product template constexpr inline bool match_indices_from_start(const T (&ind0)[N0], const T (&ind1)[N1], T num0=0, T num1=0) { return ind1[num1] == ind0[num0] ? ( num1 == N1-1 ? ind1[num1] == ind0[num0] : (num0 == N0-1 ? ind1[num1] == ind0[num0] : match_indices_from_start(ind0, ind1, num0 + 1, num1 + 1))) : false; } //! Same as above but gives the index up to which the higher order tensor (generalised matrix) //! matches the lower order tensor (generalised vector) from the start. The index is for higher //! higher order tensor counting from the end excluding the index itself for instance //! will return 1 (only 0 matches) and will return 2 (i.e. indices 0 and 1 match) //! The function in essence gives the remainder indices that don't match the vector //! //! function (match_indices_from_start) is true i.e. it only works if we have a true //! generalised vector-matrix product otherwise gives an incorrect index template constexpr inline T match_indices_from_start_index(const T (&ind0)[N0], const T (&ind1)[N1], T num0=0, T num1=0) { return ind1[num1] == ind0[num0] ? ( num1 == N1-1 ? (ind1[num1] == ind0[num0] ? num0+1 : num0) : num0 == N0-1 ? (ind1[num1] == ind0[num0] ? num1+1 : num1) : match_indices_from_start_index(ind0, ind1, num0 + 1, num1 + 1) ) : num0; } //! Given the einsum indices of two tensors, matches their indices to see //! if it is a standard genearalised matrix-matrix multiplication. //! By standard we mean . This function cannot detect transposed cases of //! gemm such as and so on. //! Note that this function matches indices from the two ends but it also detects //! as gemm. So it should be used in conjunction with is_vector_matrix/is_matrix_vec //! and is_inner. Look at the corresponding struct that uses it template constexpr inline bool match_indices_from_two_ends(const T (&ind0)[N0], const T (&ind1)[N1], int ncontracted, T num0=N0-1, T num1=0) { return ncontracted != 0 ? (ncontracted == 1 ? (ind1[num1] == ind0[num0 - ncontracted + 1] ? true : false) : (ind1[num1] == ind0[num0 - ncontracted + 1] ? match_indices_from_two_ends(ind0, ind1, ncontracted-1, num0, num1 + 1) : false)) : false; } template struct is_generalised_matrix_vector; template struct is_generalised_matrix_vector,Index > { static constexpr size_t which_one_is_vector = sizeof...(Idx0) > sizeof...(Idx1) ? 1 : 0; static constexpr size_t idx0[sizeof...(Idx0)] = {Idx0...}; static constexpr size_t idx1[sizeof...(Idx1)] = {Idx1...}; static constexpr bool value = match_indices_from_end(idx0, idx1) && sizeof...(Idx0) != sizeof...(Idx1); static constexpr size_t matches_up_to = match_indices_from_end_index(idx0, idx1); }; template struct is_generalised_vector_matrix; template struct is_generalised_vector_matrix,Index > { static constexpr size_t which_one_is_vector = sizeof...(Idx0) > sizeof...(Idx1) ? 1 : 0; static constexpr size_t idx0[sizeof...(Idx0)] = {Idx0...}; static constexpr size_t idx1[sizeof...(Idx1)] = {Idx1...}; static constexpr bool value = match_indices_from_start(idx0, idx1) && sizeof...(Idx0) != sizeof...(Idx1); static constexpr size_t matches_up_to = match_indices_from_start_index(idx0, idx1); }; template struct is_generalised_matrix_matrix; template struct is_generalised_matrix_matrix,Index > { static constexpr bool is_mat_vec = is_generalised_matrix_vector,Index>::value; static constexpr bool is_vec_mat = is_generalised_vector_matrix,Index>::value; static constexpr int ncontracted = sizeof...(Idx0) + sizeof...(Idx1) - no_of_unique::value; static constexpr bool is_inner = sizeof...(Idx0) == sizeof...(Idx1) && no_of_unique::value == sizeof...(Idx1); static constexpr size_t idx0[sizeof...(Idx0)] = {Idx0...}; static constexpr size_t idx1[sizeof...(Idx1)] = {Idx1...}; static constexpr bool value = !is_mat_vec && !is_vec_mat && !is_inner && match_indices_from_two_ends(idx0, idx1, ncontracted); }; //--------------------------------------------------------------------------------------------------------------------// } // namespace internal // A complete tensor contraction meta-engine //--------------------------------------------------------------------------------------------------------------// //--------------------------------------------------------------------------------------------------------------// #if FASTOR_CXX_VERSION >= 2017 template inline constexpr std::array find_remaining(const std::array &maxes_out, int total) { std::array remainings = {}; remainings[0] = total / maxes_out[0]; for (int i=1; i inline constexpr std::array,(size_t)total> cartesian_product_2(const std::array &maxes_out) { std::array,(size_t)total> as_all = {}; for (int i=0; i constexpr int find_remaining(const int (&maxes_out)[N], int remaining, int i) { return i==0 ? remaining/maxes_out[0] : find_remaining(maxes_out,remaining,i-1) / maxes_out[i]; } template constexpr int cartesian_product_single(const int (&maxes_out)[N], int remaining, int i, int n=0) { return (i/(find_remaining(maxes_out,remaining,n))) % maxes_out[n]; } template struct gen_single_cartesian_product; template struct gen_single_cartesian_product,std_ext::index_sequence> { static constexpr int vals[sizeof...(Rest)] = {Rest...}; static constexpr std::array values = {cartesian_product_single(vals,pack_prod::value,I,ss)...}; }; template constexpr std::array gen_single_cartesian_product,std_ext::index_sequence>::values; template constexpr std::array all_cartesian_product() { return gen_single_cartesian_product,typename std_ext::make_index_sequence::type>::values; } template struct cartesian_product; template struct cartesian_product,std_ext::index_sequence> { static constexpr std::array,sizeof...(ss)> values = {all_cartesian_product()...}; }; template constexpr std::array,sizeof...(ss)> cartesian_product,std_ext::index_sequence>::values; template constexpr int get_indices(const std::array &products, const std::array& idx, const std::array,All> &as_all, int i, int it) { return it==0 ? as_all[i][idx[static_cast(N)-1]] + products[0]*as_all[i][idx[0]] : products[it]*as_all[i][idx[it]]+get_indices(products,idx,as_all,i,it-1); } // Blowing compilation time and memory usage 101 //--------------------------------------------------------------------------------------------------------------// template struct contract_meta_engine; template struct contract_meta_engine,Index,Tensor,Tensor,std_ext::index_sequence> { using OutTensor = typename contraction_impl, Tensor, typename std_ext::make_index_sequence::type>::type; using OutIndices = typename contraction_impl, Tensor, typename std_ext::make_index_sequence::type>::indices; static constexpr int a_dim = sizeof...(Rest0); static constexpr int b_dim = sizeof...(Rest1); static constexpr int out_dim = OutTensor::Dimension; static constexpr int total = sizeof...(ss); static constexpr auto& idx_a = IndexFirstTensor,Index, Tensor,Tensor, typename std_ext::make_index_sequence::type>::indices; static constexpr auto& idx_b = IndexSecondTensor,Index, Tensor,Tensor, typename std_ext::make_index_sequence::type>::indices; static constexpr auto& idx_out = IndexResultingTensor,Index, Tensor,Tensor, typename std_ext::make_index_sequence::type>::indices; static constexpr int uniques = no_of_unique::value; using uniques_type = typename std_ext::make_index_sequence::type; static constexpr auto& maxes_out = no_of_loops_to_set,Index,Tensor,Tensor, uniques_type>::dims; using maxes_out_type = typename no_of_loops_to_set,Index,Tensor,Tensor, uniques_type>::type; static constexpr std::array products_a = nprods,typename std_ext::make_index_sequence::type>::values; static constexpr std::array products_b = nprods,typename std_ext::make_index_sequence::type>::values; using Index_with_dims = typename put_dims_in_Index::type; static constexpr std::array products_out = nprods::type>::values; // Generate the cartesian product static constexpr auto& as_all = cartesian_product::type>::values; // Alternatively you can pass the ss... directly into cartesian_product but that does not change anything in terms of // memory usage or compilation time //using maxes_out_indices = typename no_of_loops_to_set,Index,Tensor,Tensor, // uniques_type>::indices; //static constexpr std::array,total> as_all = {all_cartesian_product()...}; static constexpr std::array index_a = {get_indices(products_a,idx_a,as_all,ss,a_dim-1)...}; static constexpr std::array index_b = {get_indices(products_b,idx_b,as_all,ss,b_dim-1)...}; static constexpr std::array index_out = {get_indices(products_out,idx_out,as_all,ss,out_dim-1)...}; }; template constexpr std::array contract_meta_engine,Index, Tensor,Tensor, std_ext::index_sequence>::index_a; template constexpr std::array contract_meta_engine,Index, Tensor,Tensor, std_ext::index_sequence>::index_b; template constexpr std::array contract_meta_engine,Index, Tensor,Tensor, std_ext::index_sequence>::index_out; //--------------------------------------------------------------------------------------------------------------// //--------------------------------------------------------------------------------------------------------------// // The followings are generic implemenations that work for any type of complex tensor network //------------------------------------------------------------------------------------------------------------// //------------------------------------------------------------------------------------------------------------// //------------------------------------------------------------------------------------------------------------// // Find how many loops needs to be set. Works for any complex tensor network //------------------------------------------------------------------------------------------------------------// // This is equivalent to no_of_loops_to_set_up but more generic (works for arbitrary tensor networks) template struct loop_setter; template struct loop_setter,Tensor,std_ext::index_sequence> { using index_temp = apply_typelist_t, uniq_t>>; static constexpr size_t concat_idx[sizeof...(Idx)] = {Idx...}; static constexpr size_t concat_nums[sizeof...(Rest)] = {Rest...}; static constexpr std::array idx_in_concat = {find_index(concat_idx,index_temp::values[ss])...}; static constexpr std::array dims = {concat_nums[idx_in_concat[ss]]...}; static constexpr int value = pack_prod::value; using type = Tensor; using indices = Index; using dims_type = Index; }; template constexpr std::array loop_setter,Tensor,std_ext::index_sequence>::dims; //------------------------------------------------------------------------------------------------------------// // Get indices of every individual tensor in the network. Works for any complex tensor network //------------------------------------------------------------------------------------------------------------// template struct IndexTensors; template class Derived0, template class Derived1, typename T, size_t... Idx, size_t... Idx_t, size_t ...Rest, size_t ...Rest_t, size_t ... ss> struct IndexTensors,Derived0,Index,Derived1,std_ext::index_sequence> { using index_temp = typename loop_setter,Tensor, typename std_ext::make_index_sequence::value>::type>::indices; static constexpr size_t idx[sizeof...(Idx_t)] = {Idx_t...}; static constexpr std::array indices = {find_index(index_temp::values, idx[ss])...}; using type = Tensor; }; template class Derived0, template class Derived1, typename T, size_t... Idx, size_t... Idx_t, size_t ...Rest, size_t ...Rest_t, size_t ... ss> constexpr std::array IndexTensors,Derived0,Index,Derived1,std_ext::index_sequence>::indices; //------------------------------------------------------------------------------------------------------------// namespace internal { //------------------------------------------------------------------------------------------------------------// template struct meta_argmin_wrapper; template struct meta_argmin_wrapper> { static constexpr int value = meta_argmin::value; }; template struct meta_argmin_wrapper> { static constexpr int value = idx; }; // Complete compile-time sorting algorithm: Does not work if there a duplicate entries in a pack template struct meta_sort; template struct meta_sort> { static constexpr int least_value_idx = meta_argmin_wrapper>::value; static constexpr int least_value = get_value::value; using reduced_seq = typename filter_::type; using new_seq = typename concat_,typename meta_sort::new_seq>::type; }; template struct meta_sort> { using reduced_seq = Index; using new_seq = Index; }; // Complete compile-time arg-sorting algorithm: Does not work if there a duplicate entries in a pack template struct meta_argsort; template struct meta_argsort,Index> { static constexpr int least_value_idx = meta_argmin_wrapper>::value; static constexpr int least_value = get_value::value; using reduced_seq = typename filter_::type; using new_seq = typename concat_,typename meta_sort::new_seq>::type; static constexpr int least_index = get_value::value; using reduced_argseq = typename filter_::type; using new_argseq = typename concat_,typename meta_argsort::new_argseq>::type; }; template struct meta_argsort,Index> { using reduced_seq = Index; using new_seq = Index; using reduced_argseq = Index; using new_argseq = Index; }; //------------------------------------------------------------------------------------------------------------// // Permutation functions //------------------------------------------------------------------------------------------------------------// template constexpr size_t count_less(const size_t (&seq)[N], size_t i, size_t cur = 0) { return cur == N ? 0 : (count_less(seq, i, cur + 1) + (seq[cur] < i ? 1 : 0)); } /* Check if a compile time array is sequential */ template constexpr bool is_sequential(const size_t (&seq)[N], size_t i=0) { return i+1 == N ? true : ( seq[i] + 1 == seq[i+1] ? is_sequential(seq, i+1) : false ) ; } template constexpr bool is_sequential(const std::array &seq, size_t i=0) { return i+1 == N ? true : ( seq[i] + 1 == seq[i+1] ? is_sequential(seq, i+1) : false ) ; } // permutation helper class template struct permute_impl; template struct permute_impl, Tensor, std_ext::index_sequence> { constexpr static size_t lst[sizeof...(ls)] = { ls... }; constexpr static size_t fvals[sizeof...(ls)] = {fs...}; using resulting_tensor = Tensor; using resulting_index = typename meta_argsort,Index>::new_argseq; using maxes_out_type = Index,Index>::new_argseq::values[ss]]...>; static constexpr bool requires_permutation = !(is_same_v_> && is_sequential(resulting_index::values)); }; // permute helper class template struct new_permute_impl; template struct new_permute_impl, Tensor, std_ext::index_sequence> { constexpr static size_t lst[sizeof...(ls)] = { ls... }; constexpr static size_t fvals[sizeof...(ls)] = {fs...}; using resulting_tensor = Tensor; constexpr static size_t aranger[sizeof...(ss)] = { ss... }; using resulting_index = Index; static constexpr bool requires_permutation = !(is_same_v_> && is_sequential(resulting_index::values)); }; //------------------------------------------------------------------------------------------------------------// //------------------------------------------------------------------------------------------------------------// #if FASTOR_CXX_VERSION >= 2017 template constexpr std::array get_floor_map(const std::array &idx) { std::array out = {}; for (size_t i=0; i constexpr std::array find_permuation(const std::array &idx0, const std::array &idx1) { std::array out = {}; for (size_t i=0; i,OIndex>(a)]. In the above example the resulting index from einsum is already Index but the user decides to force this nevertheless using OIndex. Now if permuation is simply performed using OIndex the final output would be incorrect since permuation only works with what indices it is given and does not have the knowledge of the context. This meta function is responsible for creating a mapping between einsum and permutation. It takes the resulting index of einsum and the permutation index [the output index OIndex] and figures out how the tensor should be permuted. */ template struct permute_mapped_index_impl; // // specialisation for when the input and output indices are the same - the no permutation case // template // struct permute_mapped_index_impl,Index,std_ext::index_sequence> { // using resulting_index = Index; // }; template struct permute_mapped_index_impl,Index,std_ext::index_sequence> { constexpr static size_t einsum_idx[sizeof...(Idx0)] = { Idx0... }; constexpr static size_t to_be_permuted_idx[sizeof...(Idx1)] = { Idx1... }; /* Given that the resulting index from einsum can be discontinuous like Index<5,8,2> we need to first create a continuous Index starting from zero. This is done through [get_floor_map] function. We floor both resulting einsum and output indices and we then call the [find_permuation] function to know how many swaps/permutations are necessary going from the resulting einsum index to the output index */ static constexpr std::array argsort_idx0 = meta_argsort,Index>::new_argseq::values; static constexpr std::array argsort_idx1 = meta_argsort,Index>::new_argseq::values; static constexpr std::array mapped_idx0 = get_floor_map(argsort_idx0); static constexpr std::array mapped_idx1 = get_floor_map(argsort_idx1); static constexpr std::array mapped_idx = find_permuation(mapped_idx0,mapped_idx1); using resulting_index = Index; }; /* Provided for debugging */ template constexpr std::array permute_mapped_index_impl,Index,std_ext::index_sequence>::argsort_idx0; template constexpr std::array permute_mapped_index_impl,Index,std_ext::index_sequence>::argsort_idx1; template constexpr std::array permute_mapped_index_impl,Index,std_ext::index_sequence>::mapped_idx0; template constexpr std::array permute_mapped_index_impl,Index,std_ext::index_sequence>::mapped_idx1; template constexpr std::array permute_mapped_index_impl,Index,std_ext::index_sequence>::mapped_idx; template struct permute_mapped_index { using resulting_index = typename permute_mapped_index_impl::type>::resulting_index; }; template using permute_mapped_index_t = typename permute_mapped_index::resulting_index; #endif // CXX 2017 //------------------------------------------------------------------------------------------------------------// } // internal template struct requires_permutation; template struct requires_permutation, Tensor> { using _permute_impl = internal::permute_impl, Tensor, typename std_ext::make_index_sequence::type>; static constexpr bool value = _permute_impl::requires_permutation; }; // helper template constexpr bool requires_permutation_v = requires_permutation::value; template struct requires_permute; template struct requires_permute, Tensor> { using _permute_impl = internal::new_permute_impl, Tensor, typename std_ext::make_index_sequence::type>; static constexpr bool value = _permute_impl::requires_permutation; }; // helper template constexpr bool requires_permute_v = requires_permute::value; //------------------------------------------------------------------------------------------------------------// // einsum helper to extract the resulting index and the resulting tensor //------------------------------------------------------------------------------------------------------------// template struct permute_helper; template struct permute_helper> { using resulting_index = typename internal::new_permute_impl, typename std_ext::make_index_sequence::type>::resulting_index; using resulting_tensor = typename internal::new_permute_impl, typename std_ext::make_index_sequence::type>::resulting_tensor; }; //------------------------------------------------------------------------------------------------------------// } #endif // EINSUM_META_H