SpECTRE Documentation Coverage Report
Current view: top level - DataStructures/Tensor/Expressions - Product.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 92.7 % 41 38
Test Date: 2026-10-22 17:29:42
Legend: Lines:     hit not hit

            Line data    Source code
       1            1 : // Distributed under the MIT License.
       2              : // See LICENSE.txt for details.
       3              : 
       4              : /// \file
       5              : /// Defines ET for tensor inner and outer products
       6              : 
       7              : #pragma once
       8              : 
       9              : #include <array>
      10              : #include <complex>
      11              : #include <cstddef>
      12              : #include <limits>
      13              : #include <type_traits>
      14              : #include <utility>
      15              : 
      16              : #include "DataStructures/Tensor/Expressions/Contract.hpp"
      17              : #include "DataStructures/Tensor/Expressions/DataTypeSupport.hpp"
      18              : #include "DataStructures/Tensor/Expressions/NumberAsExpression.hpp"
      19              : #include "DataStructures/Tensor/Expressions/TensorExpression.hpp"
      20              : #include "DataStructures/Tensor/Symmetry.hpp"
      21              : #include "Utilities/ForceInline.hpp"
      22              : #include "Utilities/Gsl.hpp"
      23              : #include "Utilities/Requires.hpp"
      24              : #include "Utilities/TMPL.hpp"
      25              : 
      26              : namespace tenex {
      27              : namespace detail {
      28              : template <typename T1, typename T2, typename SymmList1 = typename T1::symmetry,
      29              :           typename SymmList2 = typename T2::symmetry>
      30              : struct OuterProductType;
      31              : 
      32              : template <typename T1, typename T2, template <typename...> class SymmList1,
      33              :           typename... Symm1, template <typename...> class SymmList2,
      34              :           typename... Symm2>
      35              : struct OuterProductType<T1, T2, SymmList1<Symm1...>, SymmList2<Symm2...>> {
      36              :   using type =
      37              :       typename get_binop_datatype<typename T1::type, typename T2::type>::type;
      38              :   using symmetry =
      39              :       Symmetry<(Symm1::value + sizeof...(Symm2))..., Symm2::value...>;
      40              :   using index_list =
      41              :       tmpl::append<typename T1::index_list, typename T2::index_list>;
      42              :   using tensorindex_list =
      43              :       tmpl::append<typename T1::args_list, typename T2::args_list>;
      44              : };
      45              : }  // namespace detail
      46              : 
      47              : /// \ingroup TensorExpressionsGroup
      48              : /// \brief Defines the tensor expression representing the outer product of two
      49              : /// tensor expressions
      50              : ///
      51              : /// \details
      52              : /// For details on aliases and members defined in this class, as well as general
      53              : /// `TensorExpression` terminology used in its members' documentation, see
      54              : /// documentation for `TensorExpression`.
      55              : ///
      56              : /// \tparam T1 the left operand expression of the outer product expression
      57              : /// \tparam T2 the right operand expression of the outer product expression
      58              : template <typename T1, typename T2,
      59              :           typename IndexList1 = typename T1::index_list,
      60              :           typename IndexList2 = typename T2::index_list,
      61              :           typename ArgsList1 = typename T1::args_list,
      62              :           typename ArgsList2 = typename T2::args_list>
      63            1 : struct OuterProduct;
      64              : 
      65              : template <typename T1, typename T2, template <typename...> class IndexList1,
      66              :           typename... Indices1, template <typename...> class IndexList2,
      67              :           typename... Indices2, template <typename...> class ArgsList1,
      68              :           typename... Args1, template <typename...> class ArgsList2,
      69              :           typename... Args2>
      70            0 : struct OuterProduct<T1, T2, IndexList1<Indices1...>, IndexList2<Indices2...>,
      71              :                     ArgsList1<Args1...>, ArgsList2<Args2...>>
      72              :     : public TensorExpression<
      73              :           OuterProduct<T1, T2>, typename detail::OuterProductType<T1, T2>::type,
      74              :           typename detail::OuterProductType<T1, T2>::symmetry,
      75              :           typename detail::OuterProductType<T1, T2>::index_list,
      76              :           typename detail::OuterProductType<T1, T2>::tensorindex_list> {
      77              :   static_assert(
      78              :       detail::tensorexpression_binop_datatypes_are_supported_v<T1, T2>,
      79              :       "Cannot multiply the given TensorExpressions with the given data types. "
      80              :       "This can occur from e.g. trying to multiply a Tensor with data type "
      81              :       "double and a Tensor with data type DataVector.");
      82              :   // === Index properties ===
      83              :   /// The type of the data being stored in the result of the expression
      84            1 :   using type = typename detail::OuterProductType<T1, T2>::type;
      85              :   /// The ::Symmetry of the result of the expression
      86            1 :   using symmetry = typename detail::OuterProductType<T1, T2>::symmetry;
      87              :   /// The list of \ref SpacetimeIndex "TensorIndexType"s of the result of the
      88              :   /// expression
      89            1 :   using index_list = typename detail::OuterProductType<T1, T2>::index_list;
      90              :   /// The list of generic `TensorIndex`s of the result of the
      91              :   /// expression
      92            1 :   using args_list = typename detail::OuterProductType<T1, T2>::tensorindex_list;
      93              :   /// The number of tensor indices in the result of the expression
      94            1 :   static constexpr auto num_tensor_indices = tmpl::size<index_list>::value;
      95              :   /// The number of tensor indices in the left operand expression
      96            1 :   static constexpr auto op1_num_tensor_indices =
      97              :       tmpl::size<typename T1::index_list>::value;
      98              :   /// The number of tensor indices in the right operand expression
      99            1 :   static constexpr auto op2_num_tensor_indices =
     100              :       num_tensor_indices - op1_num_tensor_indices;
     101              : 
     102              :   // === Expression subtree properties ===
     103              :   /// The number of arithmetic tensor operations done in the subtree for the
     104              :   /// left operand
     105            1 :   static constexpr size_t num_ops_left_child = T1::num_ops_subtree;
     106              :   /// The number of arithmetic tensor operations done in the subtree for the
     107              :   /// right operand
     108            1 :   static constexpr size_t num_ops_right_child = T2::num_ops_subtree;
     109              :   // This helps ensure we are minimizing breadth in the overall tree
     110              :   static_assert(num_ops_left_child >= num_ops_right_child,
     111              :                 "The left operand should be a subtree with equal or more "
     112              :                 "tensor operations than the right operand's subtree.");
     113              :   /// The total number of arithmetic tensor operations done in this expression's
     114              :   /// whole subtree
     115            1 :   static constexpr size_t num_ops_subtree =
     116              :       num_ops_left_child + num_ops_right_child + 1;
     117              :   /// The height of this expression's node in the expression tree relative to
     118              :   /// the closest `TensorAsExpression` leaf in its subtree
     119            1 :   static constexpr size_t height_relative_to_closest_tensor_leaf_in_subtree =
     120              :       T2::height_relative_to_closest_tensor_leaf_in_subtree <=
     121              :               T1::height_relative_to_closest_tensor_leaf_in_subtree
     122              :           ? (T2::height_relative_to_closest_tensor_leaf_in_subtree !=
     123              :                      std::numeric_limits<size_t>::max()
     124              :                  ? T2::height_relative_to_closest_tensor_leaf_in_subtree + 1
     125              :                  : T2::height_relative_to_closest_tensor_leaf_in_subtree)
     126              :           : T1::height_relative_to_closest_tensor_leaf_in_subtree + 1;
     127              : 
     128              :   // === Properties for splitting up subexpressions along the primary path ===
     129              :   // These definitions only have meaning if this expression actually ends up
     130              :   // being along the primary path that is taken when evaluating the whole tree.
     131              :   // See documentation for `TensorExpression` for more details.
     132              :   /// If on the primary path, whether or not the expression is an ending point
     133              :   /// of a leg
     134            1 :   static constexpr bool is_primary_end = T1::is_primary_start;
     135              :   /// If on the primary path, this is the remaining number of arithmetic tensor
     136              :   /// operations that need to be done in the subtree of the child along the
     137              :   /// primary path, given that we will have already computed the whole subtree
     138              :   /// at the next lowest leg's starting point.
     139            1 :   static constexpr size_t num_ops_to_evaluate_primary_left_child =
     140              :       is_primary_end ? 0 : T1::num_ops_to_evaluate_primary_subtree;
     141              :   /// If on the primary path, this is the remaining number of arithmetic tensor
     142              :   /// operations that need to be done in the right operand's subtree. No
     143              :   /// splitting is currently done, so this is just `num_ops_right_child`.
     144            1 :   static constexpr size_t num_ops_to_evaluate_primary_right_child =
     145              :       num_ops_right_child;
     146              :   /// If on the primary path, this is the remaining number of arithmetic tensor
     147              :   /// operations that need to be done for this expression's subtree, given that
     148              :   /// we will have already computed the subtree at the next lowest leg's
     149              :   /// starting point
     150            1 :   static constexpr size_t num_ops_to_evaluate_primary_subtree =
     151              :       num_ops_to_evaluate_primary_left_child +
     152              :       num_ops_to_evaluate_primary_right_child + 1;
     153              :   /// If on the primary path, whether or not the expression is a starting point
     154              :   /// of a leg
     155            1 :   static constexpr bool is_primary_start =
     156              :       num_ops_to_evaluate_primary_subtree >=
     157              :       detail::max_num_ops_in_sub_expression<type>;
     158              :   /// When evaluating along a primary path, whether each operand's subtrees
     159              :   /// should be evaluated separately. Since `DataVector` expression runtime
     160              :   /// scales poorly with increased number of operations, evaluating the two
     161              :   /// expression subtrees separately like this is beneficial when at least one
     162              :   /// of the subtrees contains a large number of operations.
     163            1 :   static constexpr bool evaluate_children_separately =
     164              :       is_primary_start and (num_ops_to_evaluate_primary_left_child >=
     165              :                                 detail::max_num_ops_in_sub_expression<type> or
     166              :                             num_ops_to_evaluate_primary_right_child >=
     167              :                                 detail::max_num_ops_in_sub_expression<type>);
     168              :   /// If on the primary path, whether or not the expression's child along the
     169              :   /// primary path is a subtree that contains a starting point of a leg along
     170              :   /// the primary path
     171            1 :   static constexpr bool primary_child_subtree_contains_primary_start =
     172              :       T1::primary_subtree_contains_primary_start;
     173              :   /// If on the primary path, whether or not this subtree contains a starting
     174              :   /// point of a leg along the primary path
     175            1 :   static constexpr bool primary_subtree_contains_primary_start =
     176              :       is_primary_start or primary_child_subtree_contains_primary_start;
     177              : 
     178            0 :   OuterProduct(T1 t1, T2 t2) : t1_(std::move(t1)), t2_(std::move(t2)) {}
     179            0 :   ~OuterProduct() override = default;
     180              : 
     181              :   /// \brief Assert that the LHS tensor of the equation does not also appear in
     182              :   /// this expression's subtree
     183              :   template <typename LhsTensor>
     184            1 :   SPECTRE_ALWAYS_INLINE void assert_lhs_tensor_not_in_rhs_expression(
     185              :       const gsl::not_null<LhsTensor*> lhs_tensor) const {
     186              :     if constexpr (not std::is_base_of_v<MarkAsNumberAsExpression, T1>) {
     187              :       t1_.assert_lhs_tensor_not_in_rhs_expression(lhs_tensor);
     188              :     }
     189              :     if constexpr (not std::is_base_of_v<MarkAsNumberAsExpression, T2>) {
     190              :       t2_.assert_lhs_tensor_not_in_rhs_expression(lhs_tensor);
     191              :     }
     192              :   }
     193              : 
     194              :   /// \brief Assert that each instance of the LHS tensor in the RHS tensor
     195              :   /// expression uses the same generic index order that the LHS uses
     196              :   ///
     197              :   /// \tparam LhsTensorIndices the list of generic `TensorIndex`s of the LHS
     198              :   /// result `Tensor` being computed
     199              :   /// \param lhs_tensor the LHS result `Tensor` being computed
     200              :   template <typename LhsTensorIndices, typename LhsTensor>
     201            1 :   SPECTRE_ALWAYS_INLINE void assert_lhs_tensorindices_same_in_rhs(
     202              :       const gsl::not_null<LhsTensor*> lhs_tensor) const {
     203              :     if constexpr (not std::is_base_of_v<MarkAsNumberAsExpression, T1>) {
     204              :       t1_.template assert_lhs_tensorindices_same_in_rhs<LhsTensorIndices>(
     205              :           lhs_tensor);
     206              :     }
     207              :     if constexpr (not std::is_base_of_v<MarkAsNumberAsExpression, T2>) {
     208              :       t2_.template assert_lhs_tensorindices_same_in_rhs<LhsTensorIndices>(
     209              :           lhs_tensor);
     210              :     }
     211              :   }
     212              : 
     213              :   /// \brief Get the size of a component from a `Tensor` in this expression's
     214              :   /// subtree of the RHS `TensorExpression`
     215              :   ///
     216              :   /// \return the size of a component from a `Tensor` in this expression's
     217              :   /// subtree of the RHS `TensorExpression`
     218            1 :   SPECTRE_ALWAYS_INLINE size_t get_rhs_tensor_component_size() const {
     219              :     if constexpr (T1::height_relative_to_closest_tensor_leaf_in_subtree <=
     220              :                   T2::height_relative_to_closest_tensor_leaf_in_subtree) {
     221              :       return t1_.get_rhs_tensor_component_size();
     222              :     } else {
     223              :       return t2_.get_rhs_tensor_component_size();
     224              :     }
     225              :   }
     226              : 
     227              :   /// \brief Return the first operand's multi-index given the outer product's
     228              :   /// multi-index
     229              :   ///
     230              :   /// \param result_multi_index the multi-index of the component of the outer
     231              :   /// product tensor
     232              :   /// \return the first operand's multi-index
     233              :   constexpr SPECTRE_ALWAYS_INLINE std::array<size_t, op1_num_tensor_indices>
     234            1 :   get_op1_multi_index(
     235              :       const std::array<size_t, num_tensor_indices>& result_multi_index) const {
     236              :     std::array<size_t, op1_num_tensor_indices> op1_multi_index{};
     237              :     for (size_t i = 0; i < op1_num_tensor_indices; i++) {
     238              :       gsl::at(op1_multi_index, i) = gsl::at(result_multi_index, i);
     239              :     }
     240              :     return op1_multi_index;
     241              :   }
     242              : 
     243              :   /// \brief Return the second operand's multi-index given the outer product's
     244              :   /// multi-index
     245              :   ///
     246              :   /// \param result_multi_index the multi-index of the component of the outer
     247              :   /// product tensor
     248              :   /// \return the second operand's multi-index
     249              :   constexpr SPECTRE_ALWAYS_INLINE std::array<size_t, op2_num_tensor_indices>
     250            1 :   get_op2_multi_index(
     251              :       const std::array<size_t, num_tensor_indices>& result_multi_index) const {
     252              :     std::array<size_t, op2_num_tensor_indices> op2_multi_index{};
     253              :     for (size_t i = 0; i < op2_num_tensor_indices; i++) {
     254              :       gsl::at(op2_multi_index, i) =
     255              :           gsl::at(result_multi_index, op1_num_tensor_indices + i);
     256              :     }
     257              :     return op2_multi_index;
     258              :   }
     259              : 
     260              :   /// \brief Return the value of the component of the outer product tensor at a
     261              :   /// given multi-index
     262              :   ///
     263              :   /// \details
     264              :   /// This function takes the multi-index of some component of the resultant
     265              :   /// outer product to compute. The function first computes the multi-indices of
     266              :   /// the pair of components in the two operand expressions, then multiplies the
     267              :   /// values at these multi-indices to obtain the value of the resultant outer
     268              :   /// product component. For example, say we are evaluating
     269              :   /// \f$L_abc = R_{b} * S_{ca}\f$. Let `result_multi_index == {0, 1, 2}`, which
     270              :   /// refers to the component \f$L_{012}\f$, the component we wish to compute.
     271              :   /// This function will compute the multi-indices of the operands that
     272              :   /// correspond to \f$R_{1}\f$ and \f$S_{20}\f$, retrieve their values, and
     273              :   /// return their product.
     274              :   ///
     275              :   /// \param result_multi_index the multi-index of the component of the outer
     276              :   /// product tensor to retrieve
     277              :   /// \return the value of the component at `result_multi_index` in the outer
     278              :   /// product tensor
     279            1 :   SPECTRE_ALWAYS_INLINE decltype(auto) get(
     280              :       const std::array<size_t, num_tensor_indices>& result_multi_index) const {
     281              :     return t1_.get(get_op1_multi_index(result_multi_index)) *
     282              :            t2_.get(get_op2_multi_index(result_multi_index));
     283              :   }
     284              : 
     285              :   /// \brief Return the product of the components at the given multi-indices of
     286              :   /// the left and right operands
     287              :   ///
     288              :   /// \details
     289              :   /// This function differs from `get` in that it takes into account whether we
     290              :   /// have already computed part of the result component at a lower subtree.
     291              :   /// In recursively computing this product, the current result component will
     292              :   /// be substituted in for the most recent (highest) subtree below it that has
     293              :   /// already been evaluated.
     294              :   ///
     295              :   /// \param result_component the LHS tensor component to evaluate
     296              :   /// \param op1_multi_index the multi-index of the component of the first
     297              :   /// operand of the product to retrieve
     298              :   /// \param op2_multi_index the multi-index of the component of the second
     299              :   /// operand of the product to retrieve
     300              :   template <typename ResultType>
     301            1 :   SPECTRE_ALWAYS_INLINE decltype(auto) get_primary(
     302              :       const ResultType& result_component,
     303              :       const std::array<size_t, op1_num_tensor_indices>& op1_multi_index,
     304              :       const std::array<size_t, op2_num_tensor_indices>& op2_multi_index) const {
     305              :     if constexpr (is_primary_end) {
     306              :       (void)op1_multi_index;
     307              :       // We've already computed the whole child subtree on the primary path, so
     308              :       // just return the product of the current result component and the result
     309              :       // of the other child's subtree
     310              :       return result_component * t2_.get(op2_multi_index);
     311              :     } else {
     312              :       // We haven't yet evaluated the whole subtree for this expression, so
     313              :       // return the product of the results of the two operands' subtrees
     314              :       return t1_.get_primary(result_component, op1_multi_index) *
     315              :              t2_.get(op2_multi_index);
     316              :     }
     317              :   }
     318              : 
     319              :   /// \brief Return the value of the component of the outer product tensor at a
     320              :   /// given multi-index
     321              :   ///
     322              :   /// \details
     323              :   /// This function differs from `get` in that it takes into account whether we
     324              :   /// have already computed part of the result component at a lower subtree.
     325              :   /// In recursively computing this product, the current result component will
     326              :   /// be substituted in for the most recent (highest) subtree below it that has
     327              :   /// already been evaluated.
     328              :   ///
     329              :   /// \param result_component the LHS tensor component to evaluate
     330              :   /// \param result_multi_index the multi-index of the component of the outer
     331              :   /// product tensor to retrieve
     332              :   /// \return the value of the component at `result_multi_index` in the outer
     333              :   /// product tensor
     334              :   template <typename ResultType>
     335            1 :   SPECTRE_ALWAYS_INLINE decltype(auto) get_primary(
     336              :       const ResultType& result_component,
     337              :       const std::array<size_t, num_tensor_indices>& result_multi_index) const {
     338              :     return get_primary(result_component,
     339              :                        get_op1_multi_index(result_multi_index),
     340              :                        get_op2_multi_index(result_multi_index));
     341              :   }
     342              : 
     343              :   /// \brief Evaluate the LHS Tensor's result component at this subtree by
     344              :   /// evaluating the two operand's subtrees separately and multiplying
     345              :   ///
     346              :   /// \details
     347              :   /// This function takes into account whether we have already computed part of
     348              :   /// the result component at a lower subtree. In recursively computing this
     349              :   /// product, the current result component will be substituted in for the most
     350              :   /// recent (highest) subtree below it that has already been evaluated.
     351              :   ///
     352              :   /// The left and right operands' subtrees are evaluated successively with
     353              :   /// two separate assignments to the LHS result component. Since `DataVector`
     354              :   /// expression runtime scales poorly with increased number of operations,
     355              :   /// evaluating the two expression subtrees separately like this is beneficial
     356              :   /// when at least one of the subtrees contains a large number of operations.
     357              :   /// Instead of evaluating a larger expression with their combined total number
     358              :   /// of operations, we evaluate two smaller ones.
     359              :   ///
     360              :   /// \param result_component the LHS tensor component to evaluate
     361              :   /// \param op1_multi_index the multi-index of the component of the first
     362              :   /// operand of the product to evaluate
     363              :   /// \param op2_multi_index the multi-index of the component of the second
     364              :   /// operand of the product to evaluate
     365              :   template <typename ResultType>
     366            1 :   SPECTRE_ALWAYS_INLINE void evaluate_primary_children(
     367              :       ResultType& result_component,
     368              :       const std::array<size_t, op1_num_tensor_indices>& op1_multi_index,
     369              :       const std::array<size_t, op2_num_tensor_indices>& op2_multi_index) const {
     370              :     if constexpr (is_primary_end) {
     371              :       (void)op1_multi_index;
     372              :       // We've already computed the whole child subtree on the primary path, so
     373              :       // just multiply the current result by the result of the other child's
     374              :       // subtree
     375              :       result_component *= t2_.get(op2_multi_index);
     376              :     } else {
     377              :       // We haven't yet evaluated the whole subtree of the primary child, so
     378              :       // first assign the result component to be the result of computing the
     379              :       // primary child's subtree
     380              :       result_component = t1_.get_primary(result_component, op1_multi_index);
     381              :       // Now that the primary child's subtree has been computed, multiply the
     382              :       // current result by the result of evaluating the other child's subtree
     383              :       result_component *= t2_.get(op2_multi_index);
     384              :     }
     385              :   }
     386              : 
     387              :   /// \brief Successively evaluate the LHS Tensor's result component at each
     388              :   /// leg in this expression's subtree
     389              :   ///
     390              :   /// \details
     391              :   /// This function takes into account whether we have already computed part of
     392              :   /// the result component at a lower subtree. In recursively computing this
     393              :   /// product, the current result component will be substituted in for the most
     394              :   /// recent (highest) subtree below it that has already been evaluated.
     395              :   ///
     396              :   /// \param result_component the LHS tensor component to evaluate
     397              :   /// \param result_multi_index the multi-index of the component of the outer
     398              :   /// product tensor to evaluate
     399              :   template <typename ResultType>
     400            1 :   SPECTRE_ALWAYS_INLINE void evaluate_primary_subtree(
     401              :       ResultType& result_component,
     402              :       const std::array<size_t, num_tensor_indices>& result_multi_index) const {
     403              :     const std::array<size_t, op1_num_tensor_indices> op1_multi_index =
     404              :         get_op1_multi_index(result_multi_index);
     405              :     if constexpr (primary_child_subtree_contains_primary_start) {
     406              :       // The primary child's subtree contains at least one leg, so recurse down
     407              :       // and evaluate that first
     408              :       t1_.evaluate_primary_subtree(result_component, op1_multi_index);
     409              :     }
     410              : 
     411              :     if constexpr (is_primary_start) {
     412              :       // We want to evaluate the subtree for this expression
     413              :       if constexpr (evaluate_children_separately) {
     414              :         // Evaluate operand's subtrees separately
     415              :         evaluate_primary_children(result_component, op1_multi_index,
     416              :                                   get_op2_multi_index(result_multi_index));
     417              :       } else {
     418              :         // Evaluate whole subtree as one expression
     419              :         result_component = get_primary(result_component, op1_multi_index,
     420              :                                        get_op2_multi_index(result_multi_index));
     421              :       }
     422              :     }
     423              :   }
     424              : 
     425              :  private:
     426              :   /// Left operand expression
     427            1 :   T1 t1_;
     428              :   /// Right operand expression
     429            1 :   T2 t2_;
     430              : };
     431              : }  // namespace tenex
     432              : 
     433              : /// \ingroup TensorExpressionsGroup
     434              : /// \brief Returns the tensor expression representing the product of two tensor
     435              : /// expressions
     436              : ///
     437              : /// \details
     438              : /// If the two operands have N pairs of indices that need to be contracted, the
     439              : /// returned expression will be an `OuterProduct` expression nested inside N
     440              : /// `TensorContract` expressions. This represents computing the inner product
     441              : /// of the outer product of the two operands. If the operands do not have any
     442              : /// indices to be contracted, the returned expression will be an `OuterProduct`.
     443              : ///
     444              : /// The two arguments are expressions that contain the two operands of the
     445              : /// product, where the types of the operands are `T1` and `T2`.
     446              : ///
     447              : /// \tparam T1 the derived TensorExpression type of the first operand of the
     448              : /// product
     449              : /// \tparam T2 the derived TensorExpression type of the second operand of the
     450              : /// product
     451              : /// \tparam ArgsList1 the TensorIndexs of the first operand
     452              : /// \tparam ArgsList2 the TensorIndexs of the second operand
     453              : /// \param t1 first operand expression of the product
     454              : /// \param t2 the second operand expression of the product
     455              : /// \return the tensor expression representing the product of two tensor
     456              : /// expressions
     457              : template <typename T1, typename T2, typename ArgsList1, typename ArgsList2>
     458            1 : SPECTRE_ALWAYS_INLINE auto operator*(
     459              :     const TensorExpression<T1, typename T1::type, typename T1::symmetry,
     460              :                            typename T1::index_list, ArgsList1>& t1,
     461              :     const TensorExpression<T2, typename T2::type, typename T2::symmetry,
     462              :                            typename T2::index_list, ArgsList2>& t2) {
     463              :   if constexpr (T1::num_ops_subtree >= T2::num_ops_subtree) {
     464              :     return tenex::contract(tenex::OuterProduct<T1, T2>(~t1, ~t2));
     465              :   } else {
     466              :     return tenex::contract(tenex::OuterProduct<T2, T1>(~t2, ~t1));
     467              :   }
     468              : }
     469              : 
     470              : /// @{
     471              : /// \ingroup TensorExpressionsGroup
     472              : /// \brief Returns the tensor expression representing the product of a tensor
     473              : /// expression and a number
     474              : ///
     475              : /// \param t the tensor expression operand of the product
     476              : /// \param number the numeric operand of the product
     477              : /// \return the tensor expression representing the product of the tensor
     478              : /// expression and the number
     479              : template <typename T, typename N, Requires<std::is_arithmetic_v<N>> = nullptr>
     480            1 : SPECTRE_ALWAYS_INLINE auto operator*(
     481              :     const TensorExpression<T, typename T::type, typename T::symmetry,
     482              :                            typename T::index_list, typename T::args_list>& t,
     483              :     const N number) {
     484              :   return t * tenex::NumberAsExpression(number);
     485              : }
     486              : template <typename T, typename N, Requires<std::is_arithmetic_v<N>> = nullptr>
     487            1 : SPECTRE_ALWAYS_INLINE auto operator*(
     488              :     const N number,
     489              :     const TensorExpression<T, typename T::type, typename T::symmetry,
     490              :                            typename T::index_list, typename T::args_list>& t) {
     491              :   return t * tenex::NumberAsExpression(number);
     492              : }
     493              : template <typename T, typename N>
     494            1 : SPECTRE_ALWAYS_INLINE auto operator*(
     495              :     const TensorExpression<T, typename T::type, typename T::symmetry,
     496              :                            typename T::index_list, typename T::args_list>& t,
     497              :     const std::complex<N>& number) {
     498              :   return t * tenex::NumberAsExpression(number);
     499              : }
     500              : template <typename T, typename N>
     501            1 : SPECTRE_ALWAYS_INLINE auto operator*(
     502              :     const std::complex<N>& number,
     503              :     const TensorExpression<T, typename T::type, typename T::symmetry,
     504              :                            typename T::index_list, typename T::args_list>& t) {
     505              :   return t * tenex::NumberAsExpression(number);
     506              : }
     507              : /// @}
        

Generated by: LCOV version 2.0-1