SpECTRE Documentation Coverage Report
Current view: top level - DataStructures/Tensor - ContractFirstNIndices.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 50.0 % 4 2
Test Date: 2026-10-22 17:29:42
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,
     204              :         tensor1(TensorIndex<tensor_index_values1[Ints1]>{}...) *
     205              :             tensor2(TensorIndex<tensor_index_values2[Ints2]>{}...),
     206              :         std::make_index_sequence<NumResultIndices>{});
     207              :   }
     208              : 
     209              :   // \brief Contract first N indices by evaluating a `TensorExpression`
     210              :   //
     211              :   // \param tensor1 the first `Tensor` of the two to contract
     212              :   // \param tensor2 the second `Tensor` of the two to contract
     213              :   static auto apply(const Tensor<X1, Symm1, tmpl::list<Indices1...>>& tensor1,
     214              :                     const Tensor<X2, Symm2, tmpl::list<Indices2...>>& tensor2) {
     215              :     using rhs_expression =
     216              :         decltype(tensor1(TensorIndex<tensor_index_values1[Ints1]>{}...) *
     217              :                  tensor2(TensorIndex<tensor_index_values2[Ints2]>{}...));
     218              :     using rhs_tensorindex_list = typename rhs_expression::args_list;
     219              :     using rhs_symmetry = typename rhs_expression::symmetry;
     220              :     using rhs_tensorindextype_list = typename rhs_expression::index_list;
     221              : 
     222              :     using lhs_tensor_symm_and_indices =
     223              :         tenex::LhsTensorSymmAndIndices<rhs_tensorindex_list,
     224              :                                        lhs_tensorindex_list, rhs_symmetry,
     225              :                                        rhs_tensorindextype_list>;
     226              : 
     227              :     Tensor<typename rhs_expression::type,
     228              :            typename lhs_tensor_symm_and_indices::symmetry,
     229              :            typename lhs_tensor_symm_and_indices::tensorindextype_list>
     230              :         lhs_tensor{};
     231              : 
     232              :     apply(make_not_null(&lhs_tensor), tensor1, tensor2);
     233              : 
     234              :     return lhs_tensor;
     235              :   }
     236              : };
     237              : }  // namespace detail
     238              : 
     239              : /// \ingroup TensorGroup
     240              : /// \brief Contract the first N indices of two `Tensor`s
     241              : ///
     242              : /// \details
     243              : /// The indices of `lhs_tensor` should be the concatenation of the uncontracted
     244              : /// indices of `tensor1` and the uncontracted indices of `tensor2`, in this
     245              : /// order. For example, if `tensor1` is rank 3, `tensor2` is rank 4, and we want
     246              : /// to contract the first two indices, the indices of `lhs_tensor` need to be
     247              : /// the last index of `tensor1` followed by the 3rd index and then the 4th index
     248              : /// of `tensor2`.
     249              : ///
     250              : /// The index types (spatial or spacetime) must be the same for the two indices
     251              : /// in a pair of indices being contracted. Support can be added to this function
     252              : /// to automatically contract the spatial indices of a spacetime index with a
     253              : /// spatial index.
     254              : ///
     255              : /// \tparam NumIndicesToContract the number of indices to contract
     256              : /// \param lhs_tensor the result LHS `Tensor`
     257              : /// \param tensor1 the first `Tensor` of the two to contract
     258              : /// \param tensor2 the second `Tensor` of the two to contract
     259              : template <size_t NumIndicesToContract, typename LhsTensor, typename T1,
     260              :           typename T2>
     261            1 : void contract_first_n_indices(const gsl::not_null<LhsTensor*> lhs_tensor,
     262              :                               const T1& tensor1, const T2& tensor2) {
     263              :   detail::contract_first_n_indices_impl<NumIndicesToContract, T1, T2>::apply(
     264              :       lhs_tensor, tensor1, tensor2);
     265              : }
     266              : 
     267              : /// \ingroup TensorGroup
     268              : /// \brief Contract the first N indices of two `Tensor`s
     269              : ///
     270              : /// \details
     271              : /// The indices of the returned `Tensor` will be the concatenation of the
     272              : /// uncontracted indices of `tensor1` and the uncontracted indices of `tensor2`,
     273              : /// in this order. For example, if `tensor1` is rank 3, `tensor2` is rank 4, and
     274              : /// we want to contract the first two indices, the indices of the returned
     275              : /// `Tensor` will be the last index of `tensor1` followed by the 3rd index and
     276              : /// then the 4th index of `tensor2`.
     277              : ///
     278              : /// The index types (spatial or spacetime) must be the same for the two indices
     279              : /// in a pair of indices being contracted. Support can be added to this function
     280              : /// to automatically contract the spatial indices of a spacetime index with a
     281              : /// spatial index.
     282              : ///
     283              : /// \tparam NumIndicesToContract the number of indices to contract
     284              : /// \param tensor1 the first `Tensor` of the two to contract
     285              : /// \param tensor2 the second `Tensor` of the two to contract
     286              : template <size_t NumIndicesToContract, typename T1, typename T2>
     287            1 : auto contract_first_n_indices(const T1& tensor1, const T2& tensor2) {
     288              :   return detail::contract_first_n_indices_impl<NumIndicesToContract, T1,
     289              :                                                T2>::apply(tensor1, tensor2);
     290              : }
        

Generated by: LCOV version 2.0-1