SpECTRE Documentation Coverage Report
Current view: top level - DataStructures/Tensor - ContractFirstNIndices.hpp Hit Total Coverage
Commit: c4864ba59ab2d0d4227eb983d3e1eb61f059bc16 Lines: 2 4 50.0 %
Date: 2024-05-05 16:16:17
Legend: Lines: hit not hit

          Line data    Source code
       1           0 : // Distributed under the MIT License.
       2             : // See LICENSE.txt for details.
       3             : 
       4             : #pragma once
       5             : 
       6             : #include <array>
       7             : #include <cstddef>
       8             : #include <tuple>
       9             : #include <utility>
      10             : 
      11             : #include "DataStructures/Tensor/Tensor.hpp"
      12             : #include "Utilities/MakeWithValue.hpp"
      13             : #include "Utilities/TMPL.hpp"
      14             : 
      15             : template <typename X, typename Symm, typename IndexList>
      16           0 : class Tensor;
      17             : 
      18             : namespace detail {
      19             : // Get the values that encode the generic tensor indices for the first operand,
      20             : // second operand, and result tensor so that the first NumIndicesToContract
      21             : // indices will contract and the TensorExpression written with them will be
      22             : // valid
      23             : //
      24             : // Note: Implementation assumes that the indices in the index pairs to contract
      25             : // have the same type (spatial or spacetime)
      26             : template <size_t NumIndicesToContract, size_t NumIndices1, size_t NumIndices2>
      27             : constexpr auto
      28             : get_tensor_index_values_for_tensors_to_contract_and_result_tensor(
      29             :     const std::array<bool, NumIndices1>& index_type_is_spacetime1,
      30             :     const std::array<bool, NumIndices1>& valence_is_lower1,
      31             :     const std::array<bool, NumIndices2>& index_type_is_spacetime2,
      32             :     const std::array<bool, NumIndices2>& valence_is_lower2) {
      33             :   constexpr size_t num_result_indices =
      34             :       NumIndices1 + NumIndices2 - 2 * NumIndicesToContract;
      35             : 
      36             :   // (first op index values, second op index values, result tensor index values)
      37             :   std::tuple<std::array<size_t, NumIndices1>, std::array<size_t, NumIndices2>,
      38             :              std::array<size_t, num_result_indices>>
      39             :       tensor_index_values{};
      40             : 
      41             :   // the next lower spacetime index value that we have not yet used
      42             :   size_t next_lower_spacetime_value = 0;
      43             :   // the next lower spatial index value that we have not yet used
      44             :   size_t next_lower_spatial_value = tenex::TensorIndex_detail::spatial_sentinel;
      45             : 
      46             :   // assign first operand's tensor index values
      47             :   for (size_t i = 0; i < NumIndices1; i++) {
      48             :     const bool index1_is_spacetime = gsl::at(index_type_is_spacetime1, i);
      49             :     const bool index1_is_lower = gsl::at(valence_is_lower1, i);
      50             :     if (index1_is_spacetime) {
      51             :       gsl::at(std::get<0>(tensor_index_values), i) =
      52             :           index1_is_lower ? next_lower_spacetime_value
      53             :                           : tenex::get_tensorindex_value_with_opposite_valence(
      54             :                                 next_lower_spacetime_value);
      55             :       next_lower_spacetime_value++;
      56             :     } else {
      57             :       gsl::at(std::get<0>(tensor_index_values), i) =
      58             :           index1_is_lower ? next_lower_spatial_value
      59             :                           : tenex::get_tensorindex_value_with_opposite_valence(
      60             :                                 next_lower_spatial_value);
      61             :       next_lower_spatial_value++;
      62             :     }
      63             :   }
      64             : 
      65             :   // assign the first NumIndicesToContract index values of the second operand so
      66             :   // that they will contract with the first NumIndicesToContract indices of the
      67             :   // first operand
      68             :   for (size_t i = 0; i < NumIndicesToContract; i++) {
      69             :     gsl::at(std::get<1>(tensor_index_values), i) =
      70             :         tenex::get_tensorindex_value_with_opposite_valence(
      71             :             gsl::at(std::get<0>(tensor_index_values), i));
      72             :   }
      73             : 
      74             :   // assign the remaining index values of the second operand so that they are
      75             :   // not duplicates of any previously-used indices
      76             :   for (size_t i = NumIndicesToContract; i < NumIndices2; i++) {
      77             :     const bool index2_is_spacetime = gsl::at(index_type_is_spacetime2, i);
      78             :     const bool index2_is_lower = gsl::at(valence_is_lower2, i);
      79             :     if (index2_is_spacetime) {
      80             :       gsl::at(std::get<1>(tensor_index_values), i) =
      81             :           index2_is_lower ? next_lower_spacetime_value
      82             :                           : tenex::get_tensorindex_value_with_opposite_valence(
      83             :                                 next_lower_spacetime_value);
      84             :       next_lower_spacetime_value++;
      85             :     } else {
      86             :       gsl::at(std::get<1>(tensor_index_values), i) =
      87             :           index2_is_lower ? next_lower_spatial_value
      88             :                           : tenex::get_tensorindex_value_with_opposite_valence(
      89             :                                 next_lower_spatial_value);
      90             :       next_lower_spatial_value++;
      91             :     }
      92             :   }
      93             : 
      94             :   // assign the free indices of the first operand to the result tensor indices
      95             :   for (size_t i = 0; i < NumIndices1 - NumIndicesToContract; i++) {
      96             :     gsl::at(std::get<2>(tensor_index_values), i) =
      97             :         gsl::at(std::get<0>(tensor_index_values), i + NumIndicesToContract);
      98             :   }
      99             : 
     100             :   // assign the free indices of the second operand to the result tensor indices
     101             :   for (size_t i = 0; i < NumIndices2 - NumIndicesToContract; i++) {
     102             :     gsl::at(std::get<2>(tensor_index_values),
     103             :             i + NumIndices1 - NumIndicesToContract) =
     104             :         gsl::at(std::get<1>(tensor_index_values), i + NumIndicesToContract);
     105             :   }
     106             : 
     107             :   return tensor_index_values;
     108             : }
     109             : 
     110             : // \brief Helper struct for contracting the first `NumIndicesToContract`
     111             : // indices of two `Tensor`s
     112             : //
     113             : // \tparam NumIndicesToContract the number of indices to contract
     114             : // \param T1 the type of the first `Tensor` of the two to contract
     115             : // \param T2 the type of the second `Tensor` of the two to contract
     116             : template <size_t NumIndicesToContract, typename T1, typename T2,
     117             :           size_t NumIndices1 = tmpl::size<typename T1::symmetry>::value,
     118             :           size_t NumIndices2 = tmpl::size<typename T2::symmetry>::value,
     119             :           size_t NumResultIndices =
     120             :               NumIndices1 + NumIndices2 - 2 * NumIndicesToContract,
     121             :           typename ContractedIndicesSeq =
     122             :               std::make_index_sequence<NumIndicesToContract>,
     123             :           typename TensorIndices1Seq = std::make_index_sequence<NumIndices1>,
     124             :           typename TensorIndices2Seq = std::make_index_sequence<NumIndices2>,
     125             :           typename ResultIndicesSeq =
     126             :               std::make_index_sequence<NumResultIndices>>
     127             : struct contract_first_n_indices_impl;
     128             : 
     129             : template <size_t NumIndicesToContract, typename X1, typename Symm1,
     130             :           typename... Indices1, typename X2, typename Symm2,
     131             :           typename... Indices2, size_t NumIndices1, size_t NumIndices2,
     132             :           size_t NumResultIndices, size_t... ContractedIndicesInts,
     133             :           size_t... Ints1, size_t... Ints2, size_t... ResultInts>
     134             : struct contract_first_n_indices_impl<
     135             :     NumIndicesToContract, Tensor<X1, Symm1, tmpl::list<Indices1...>>,
     136             :     Tensor<X2, Symm2, tmpl::list<Indices2...>>, NumIndices1, NumIndices2,
     137             :     NumResultIndices, std::index_sequence<ContractedIndicesInts...>,
     138             :     std::index_sequence<Ints1...>, std::index_sequence<Ints2...>,
     139             :     std::index_sequence<ResultInts...>> {
     140             :   static_assert(NumIndicesToContract <= sizeof...(Indices1) and
     141             :                     NumIndicesToContract <= sizeof...(Indices2),
     142             :                 "Cannot request to contract more indices than indices in "
     143             :                 "either of the two Tensors to contract.");
     144             : 
     145             :   static constexpr std::array<bool, NumIndices1> valence_is_lower1 = {
     146             :       {(Indices1::ul == UpLo::Lo)...}};
     147             :   static constexpr std::array<bool, NumIndices2> valence_is_lower2 = {
     148             :       {(Indices2::ul == UpLo::Lo)...}};
     149             :   static constexpr std::array<bool, NumIndices1> index_type_is_spacetime1 = {
     150             :       {(Indices1::index_type == IndexType::Spacetime)...}};
     151             :   static constexpr std::array<bool, NumIndices2> index_type_is_spacetime2 = {
     152             :       {(Indices2::index_type == IndexType::Spacetime)...}};
     153             : 
     154             :   // if this is removed and support for automatic contraction of a spatial index
     155             :   // with a spacetime index is wanted, the implementation of
     156             :   // get_tensor_index_values_for_tensors_to_contract_and_result_tensor() also
     157             :   // needs to be updated to support this
     158             :   static_assert(
     159             :       (... and (index_type_is_spacetime1[ContractedIndicesInts] ==
     160             :                 index_type_is_spacetime2[ContractedIndicesInts])),
     161             :       "You are trying to automatically contract a spatial index with a "
     162             :       "spacetime index, but this is not supported for "
     163             :       "contract_first_n_indices().");
     164             : 
     165             :   // the values of the generic indices (i.e. `TensorIndex::value`s) that
     166             :   // uniquely identify different generic indices
     167             :   static constexpr auto tensor_index_values =
     168             :       get_tensor_index_values_for_tensors_to_contract_and_result_tensor<
     169             :           NumIndicesToContract>(index_type_is_spacetime1, valence_is_lower1,
     170             :                                 index_type_is_spacetime2, valence_is_lower2);
     171             :   static constexpr std::array<size_t, NumIndices1> tensor_index_values1 =
     172             :       std::get<0>(tensor_index_values);
     173             :   static constexpr std::array<size_t, NumIndices2> tensor_index_values2 =
     174             :       std::get<1>(tensor_index_values);
     175             :   static constexpr std::array<size_t, NumResultIndices>
     176             :       result_tensor_index_values = std::get<2>(tensor_index_values);
     177             : 
     178             :   using lhs_tensorindex_list =
     179             :       tmpl::list<TensorIndex<result_tensor_index_values[ResultInts]>...>;
     180             : 
     181             :   // \brief Contract first N indices by evaluating a `TensorExpression`
     182             :   //
     183             :   // \param lhs_tensor the result LHS `Tensor`
     184             :   // \param tensor1 the first `Tensor` of the two to contract
     185             :   // \param tensor2 the second `Tensor` of the two to contract
     186             :   template <typename LhsTensor>
     187             :   static void apply(const gsl::not_null<LhsTensor*> lhs_tensor,
     188             :                     const Tensor<X1, Symm1, tmpl::list<Indices1...>>& tensor1,
     189             :                     const Tensor<X2, Symm2, tmpl::list<Indices2...>>& tensor2) {
     190             :     constexpr bool evaluate_subtrees =
     191             :         decltype(tensor1(TensorIndex<tensor_index_values1[Ints1]>{}...) *
     192             :                  tensor2(TensorIndex<tensor_index_values2[Ints2]>{}...))::
     193             :             primary_subtree_contains_primary_start;
     194             :     // Calls `evaluate_impl()` instead of `evaluate()` because `evaluate()`
     195             :     // takes `TensorIndex` lvalue references as template parameters, but here we
     196             :     // have `TensorIndex` types, and `evaluate()` cannot be overloaded to accept
     197             :     // both the former and the latter, so we simply circumvent the "top level"
     198             :     // `evaluate()` call that is normally used when evaluating the result of a
     199             :     // `TensorExpression`
     200             :     tenex::detail::evaluate_impl<
     201             :         evaluate_subtrees,
     202             :         TensorIndex<result_tensor_index_values[ResultInts]>...>(
     203             :         lhs_tensor, tensor1(TensorIndex<tensor_index_values1[Ints1]>{}...) *
     204             :                         tensor2(TensorIndex<tensor_index_values2[Ints2]>{}...));
     205             :   }
     206             : 
     207             :   // \brief Contract first N indices by evaluating a `TensorExpression`
     208             :   //
     209             :   // \param tensor1 the first `Tensor` of the two to contract
     210             :   // \param tensor2 the second `Tensor` of the two to contract
     211             :   static auto apply(const Tensor<X1, Symm1, tmpl::list<Indices1...>>& tensor1,
     212             :                     const Tensor<X2, Symm2, tmpl::list<Indices2...>>& tensor2) {
     213             :     using rhs_expression =
     214             :         decltype(tensor1(TensorIndex<tensor_index_values1[Ints1]>{}...) *
     215             :                  tensor2(TensorIndex<tensor_index_values2[Ints2]>{}...));
     216             :     using rhs_tensorindex_list = typename rhs_expression::args_list;
     217             :     using rhs_symmetry = typename rhs_expression::symmetry;
     218             :     using rhs_tensorindextype_list = typename rhs_expression::index_list;
     219             : 
     220             :     using lhs_tensor_symm_and_indices =
     221             :         tenex::LhsTensorSymmAndIndices<rhs_tensorindex_list,
     222             :                                        lhs_tensorindex_list, rhs_symmetry,
     223             :                                        rhs_tensorindextype_list>;
     224             : 
     225             :     Tensor<typename rhs_expression::type,
     226             :            typename lhs_tensor_symm_and_indices::symmetry,
     227             :            typename lhs_tensor_symm_and_indices::tensorindextype_list>
     228             :         lhs_tensor{};
     229             : 
     230             :     apply(make_not_null(&lhs_tensor), tensor1, tensor2);
     231             : 
     232             :     return lhs_tensor;
     233             :   }
     234             : };
     235             : }  // namespace detail
     236             : 
     237             : /// \ingroup TensorGroup
     238             : /// \brief Contract the first N indices of two `Tensor`s
     239             : ///
     240             : /// \details
     241             : /// The indices of `lhs_tensor` should be the concatenation of the uncontracted
     242             : /// indices of `tensor1` and the uncontracted indices of `tensor2`, in this
     243             : /// order. For example, if `tensor1` is rank 3, `tensor2` is rank 4, and we want
     244             : /// to contract the first two indices, the indices of `lhs_tensor` need to be
     245             : /// the last index of `tensor1` followed by the 3rd index and then the 4th index
     246             : /// of `tensor2`.
     247             : ///
     248             : /// The index types (spatial or spacetime) must be the same for the two indices
     249             : /// in a pair of indices being contracted. Support can be added to this function
     250             : /// to automatically contract the spatial indices of a spacetime index with a
     251             : /// spatial index.
     252             : ///
     253             : /// \tparam NumIndicesToContract the number of indices to contract
     254             : /// \param lhs_tensor the result LHS `Tensor`
     255             : /// \param tensor1 the first `Tensor` of the two to contract
     256             : /// \param tensor2 the second `Tensor` of the two to contract
     257             : template <size_t NumIndicesToContract, typename LhsTensor, typename T1,
     258             :           typename T2>
     259           1 : void contract_first_n_indices(const gsl::not_null<LhsTensor*> lhs_tensor,
     260             :                               const T1& tensor1, const T2& tensor2) {
     261             :   detail::contract_first_n_indices_impl<NumIndicesToContract, T1, T2>::apply(
     262             :       lhs_tensor, tensor1, tensor2);
     263             : }
     264             : 
     265             : /// \ingroup TensorGroup
     266             : /// \brief Contract the first N indices of two `Tensor`s
     267             : ///
     268             : /// \details
     269             : /// The indices of the returned `Tensor` will be the concatenation of the
     270             : /// uncontracted indices of `tensor1` and the uncontracted indices of `tensor2`,
     271             : /// in this order. For example, if `tensor1` is rank 3, `tensor2` is rank 4, and
     272             : /// we want to contract the first two indices, the indices of the returned
     273             : /// `Tensor` will be the last index of `tensor1` followed by the 3rd index and
     274             : /// then the 4th index of `tensor2`.
     275             : ///
     276             : /// The index types (spatial or spacetime) must be the same for the two indices
     277             : /// in a pair of indices being contracted. Support can be added to this function
     278             : /// to automatically contract the spatial indices of a spacetime index with a
     279             : /// spatial index.
     280             : ///
     281             : /// \tparam NumIndicesToContract the number of indices to contract
     282             : /// \param tensor1 the first `Tensor` of the two to contract
     283             : /// \param tensor2 the second `Tensor` of the two to contract
     284             : template <size_t NumIndicesToContract, typename T1, typename T2>
     285           1 : auto contract_first_n_indices(const T1& tensor1, const T2& tensor2) {
     286             :   return detail::contract_first_n_indices_impl<NumIndicesToContract, T1,
     287             :                                                T2>::apply(tensor1, tensor2);
     288             : }

Generated by: LCOV version 1.14