SpECTRE Documentation Coverage Report
Current view: top level - DataStructures/Tensor/Expressions - Evaluate.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 100.0 % 6 6
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 functions for evaluating `TensorExpression`s
       6              : 
       7              : #pragma once
       8              : 
       9              : #include <array>
      10              : #include <complex>
      11              : #include <cstddef>
      12              : #include <type_traits>
      13              : 
      14              : #include "DataStructures/ComplexDataVector.hpp"
      15              : #include "DataStructures/DataVector.hpp"
      16              : #include "DataStructures/Tensor/Expressions/DataTypeSupport.hpp"
      17              : #include "DataStructures/Tensor/Expressions/IndexPropertyCheck.hpp"
      18              : #include "DataStructures/Tensor/Expressions/LhsTensorSymmAndIndices.hpp"
      19              : #include "DataStructures/Tensor/Expressions/SpatialSpacetimeIndex.hpp"
      20              : #include "DataStructures/Tensor/Expressions/TensorExpression.hpp"
      21              : #include "DataStructures/Tensor/Expressions/TensorIndex.hpp"
      22              : #include "DataStructures/Tensor/Expressions/TensorIndexTransformation.hpp"
      23              : #include "DataStructures/Tensor/Expressions/TimeIndex.hpp"
      24              : #include "DataStructures/Tensor/Structure.hpp"
      25              : #include "Utilities/Algorithm.hpp"
      26              : #include "Utilities/ContainerHelpers.hpp"
      27              : #include "Utilities/ErrorHandling/Assert.hpp"
      28              : #include "Utilities/Gsl.hpp"
      29              : #include "Utilities/Requires.hpp"
      30              : #include "Utilities/TMPL.hpp"
      31              : 
      32              : /// \cond
      33              : template <typename X, typename Symm, typename IndexList>
      34              : class Tensor;
      35              : /// \endcond
      36              : 
      37              : namespace tenex {
      38              : namespace detail {
      39              : template <size_t NumIndices>
      40              : constexpr bool contains_indices_to_contract(
      41              :     const std::array<size_t, NumIndices>& tensorindices) {
      42              :   if constexpr (NumIndices < 2) {
      43              :     return false;
      44              :   } else {
      45              :     for (size_t i = 0; i < NumIndices - 1; i++) {
      46              :       for (size_t j = i + 1; j < NumIndices; j++) {
      47              :         const size_t current_tensorindex = gsl::at(tensorindices, i);
      48              :         // Concrete time indices are not contracted
      49              :         if ((not is_time_index_value(current_tensorindex)) and
      50              :             current_tensorindex == get_tensorindex_value_with_opposite_valence(
      51              :                                        gsl::at(tensorindices, j))) {
      52              :           return true;
      53              :         }
      54              :       }
      55              :     }
      56              :     return false;
      57              :   }
      58              : }
      59              : 
      60              : /// \brief Given the list of the positions of the LHS tensor's spacetime indices
      61              : /// where a generic spatial index is used and the list of positions where a
      62              : /// concrete time index is used, determine whether or not the component at the
      63              : /// given LHS multi-index should be computed
      64              : ///
      65              : /// \details
      66              : /// Not all of the LHS tensor's components may need to be computed. Cases when
      67              : /// the component at a LHS multi-index should not be not evaluated:
      68              : /// - If a generic spatial index is used for a spacetime index on the LHS,
      69              : /// the components for which that index's concrete index is the time index
      70              : /// should not be computed
      71              : /// - If a concrete time index is used for a spacetime index on the LHS, the
      72              : /// components for which that index's concrete index is a spatial index should
      73              : /// not be computed
      74              : ///
      75              : /// \param lhs_multi_index the multi-index of the LHS tensor to check
      76              : /// \param lhs_spatial_spacetime_index_positions the positions of the LHS
      77              : /// tensor's spacetime indices where a generic spatial index is used
      78              : /// \param lhs_time_index_positions the positions of the LHS tensor's spacetime
      79              : /// indices where a concrete time index is used
      80              : /// \return Whether or not `lhs_multi_index` is a multi-index of a component of
      81              : /// the LHS tensor that should be computed
      82              : template <size_t NumLhsIndices, size_t NumLhsSpatialSpacetimeIndices,
      83              :           size_t NumLhsConcreteTimeIndices>
      84              : constexpr bool is_evaluated_lhs_multi_index(
      85              :     const std::array<size_t, NumLhsIndices>& lhs_multi_index,
      86              :     const std::array<size_t, NumLhsSpatialSpacetimeIndices>&
      87              :         lhs_spatial_spacetime_index_positions,
      88              :     const std::array<size_t, NumLhsConcreteTimeIndices>&
      89              :         lhs_time_index_positions) {
      90              :   for (size_t i = 0; i < lhs_spatial_spacetime_index_positions.size(); i++) {
      91              :     if (gsl::at(lhs_multi_index,
      92              :                 gsl::at(lhs_spatial_spacetime_index_positions, i)) == 0) {
      93              :       return false;
      94              :     }
      95              :   }
      96              :   for (size_t i = 0; i < lhs_time_index_positions.size(); i++) {
      97              :     if (gsl::at(lhs_multi_index, gsl::at(lhs_time_index_positions, i)) != 0) {
      98              :       return false;
      99              :     }
     100              :   }
     101              :   return true;
     102              : }
     103              : 
     104              : template <typename SymmList>
     105              : struct CheckNoLhsAntiSymmetries;
     106              : 
     107              : template <template <typename...> class SymmList, typename... Symm>
     108              : struct CheckNoLhsAntiSymmetries<SymmList<Symm...>> {
     109              :   static constexpr bool value = (... and (Symm::value > 0));
     110              : };
     111              : 
     112              : /// \brief Given a tensor and its list of tensor indices, return the
     113              : /// canonicalized order of the tensor indices according to the tensor's symmetry
     114              : ///
     115              : /// \details
     116              : /// The canonical ordering of a `Tensor`'s `TensorIndex`s
     117              : /// (e.g. `ti::a`, `ti::b`, ti::c) is relevant to sets of indices that are
     118              : /// symmetric. Within each set of symmetric indices, the `TensorIndex` used for
     119              : /// each index can be freely reordered. Given a set of symmetric indices, this
     120              : /// function defines the canonical order of the `TensorIndex`s assigned to them
     121              : /// to be such that the lowest index positions take any generic spatial tensor
     122              : /// indices (e.g. `ti::i`, `ti::j`), the next lowest index positions take any
     123              : /// generic spacetime indices (e.g. `ti::a`, `ti::b`), and the highest index
     124              : /// positions take any concrete time indices (e.g. `ti::t`, `ti::T`). We can
     125              : /// imagine the canonical ordering of symmetric indices to look generally like:
     126              : /// `[spatial indices ... | spacetime indices... | time indices...]`.
     127              : ///
     128              : /// Within the subsets of spatial, spacetime, and time indices, the
     129              : /// `TensorIndex`s in each will be ordered such that lowercase indices come
     130              : /// before uppercase, where both are ordered alphabetically. Another way of
     131              : /// saying this is that if we had a rank N `Tensor` that was fully symmetric,
     132              : /// its canonical ordering would take the following form:
     133              : ///
     134              : /// ```
     135              : /// [ti::i, ti::j, ti::k, ..., ti::I, ti::J, ti::K, ...,  // spatial
     136              : ///  ti::a, ti::b, ti::c, ..., ti::A, ti::B, ti::C, ...,  // spacetime
     137              : ///  ti::t, ti::t, ti::t, ..., ti::T, ti::T, ti::T, ...]  // time
     138              : /// ```
     139              : ///
     140              : /// Here are some examples:
     141              : ///
     142              : /// ```
     143              : /// symmetry: <1, 1, 1>
     144              : /// set of `TensorIndex`s: {ti::t, ti::i, ti::a}
     145              : /// canonical ordering: [ti::i, ti::a, ti::t]
     146              : ///
     147              : /// symmetry: <1, 1, 1>
     148              : /// set of `TensorIndex`s: {ti::A, ti::a, ti::b}
     149              : /// canonical ordering: [ti::a, ti::b, ti::A]
     150              : /// ```
     151              : ///
     152              : /// When a `Tensor` is not fully symmetric, the `TensorIndex` labels for any
     153              : /// indices that do not have symmetry with any other will simply keep their
     154              : /// label because it cannot be swapped:
     155              : ///
     156              : /// ```
     157              : /// symmetry: <1, 2, 1>
     158              : /// set of `TensorIndex`s: {ti::a, ti::b, ti::i}
     159              : /// canonical ordering: [ti::i, ti::b, ti::a]
     160              : ///
     161              : /// symmetry: <2, 1, 1>
     162              : /// set of `TensorIndex`s: {ti::t, ti::k, ti::j}
     163              : /// canonical ordering: [ti::t, ti::j, ti::k]
     164              : /// ```
     165              : ///
     166              : /// If there is more than one set of symmetric indices, each of the subsets are
     167              : /// individually reordered:
     168              : ///
     169              : /// ```
     170              : /// symmetry: <1, 2, 2, 1>
     171              : /// set of `TensorIndex`s: {ti::a, ti::b, ti::t, ti::i}
     172              : /// canonical ordering: [ti::i, ti::b, ti::t, ti::a]
     173              : /// ```
     174              : ///
     175              : /// The motivation for this specific canonical reordering is to quickly assess
     176              : /// which components to assign to and which ones to skip when generic spatial
     177              : /// and/or concrete time indices are used for symmetric spacetime indices in the
     178              : /// resulting left hand side tensor when using `TensorExpression`s.
     179              : ///
     180              : /// Let's take the spacetime metric \f$g_{ab}\f$ as our motivating example. This
     181              : /// tensor has symmetric spacetime indices, and let's say we only want to assign
     182              : /// to \f$g_{ti}\f$. We want to loop over all 10 independent components of
     183              : /// \f$g_{ab}\f$ and skip components outside of \f$g_{ti}\f$, e.g. \f$g_{xy}\f$
     184              : /// (or \f$g_{12}\f$) and \f$g_{tt}\f$ (or \f$g_{00}\f$). To do so, when we see
     185              : /// a multi-index like `{2, 1}` in our loop, we align `{2, 1}` with `{t, i}` and
     186              : /// ask if the `2` is a valid index for `t` and if the `1` is a valid index for
     187              : /// `i`. `1` is valid for `i`, but `2` is not valid for `t`, so we correctly
     188              : /// skip over `{2, 1}` and don't assign to this component.
     189              : ///
     190              : /// However, this simple logic can lead to false positives or negatives when the
     191              : /// indices are symmetric. What if the multi-index we're asking about is
     192              : /// `{0, 1}` (\f$g_{01}\f$ or \f$g_{tx}\f$)? This logic would correctly
     193              : /// determine that this is one of the components we want to assign to. But what
     194              : /// if the multi-index was `{1, 0}`? The logic would incorrectly say to skip
     195              : /// over and not assign to this multi-index because `1` is not a valid index for
     196              : /// `t` and `0` is not valid for `i`. However, because \f$g_{ab}\f$ is
     197              : /// symmetric, both `{0, 1}` and `{1, 0}` should give the same result, but
     198              : /// `{1, 0}` gives us a false negative. Moreover and more generally, assigning
     199              : /// to \f$g_{ti}\f$ and \f$g_{it}\f$ should yield the same behavior (assign to
     200              : /// the same set of components).
     201              : ///
     202              : /// One way to address this would be to check the multi-indices for all
     203              : /// permutations of symmetric index values, e.g. is `{0, 1}` *or* `{1, 0}`
     204              : /// valid? And if so, then evaluate it. However, this adds work at runtime and
     205              : /// the number of permutations to check increases as we increase the number of
     206              : /// symmetric indices.
     207              : ///
     208              : /// The canonical reordering done by *this* function solves this problem by
     209              : /// reordering the `TensorIndex`s to align nicely with the canonical multi-index
     210              : /// ordering implemented by
     211              : /// `::Tensor_detail::Structure::get_canonical_tensor_index`.
     212              : /// `get_canonical_tensor_index` takes a storage index (which corresponds to an
     213              : /// independent tensor component) and returns a canonical multi-index. This
     214              : /// canonical multi-index is such that index values for symmetric indices will
     215              : /// be ordered to increase from right to left. For example, for a rank 2
     216              : /// symmetric tensor, `{1, 0}` is the canonical multi-index corresponding to the
     217              : /// dependent multi-indices `{0, 1}` and `{1, 0}`. Therefore, `{1, 0}` would be
     218              : /// the multi-index returned by `get_canonical_tensor_index` that corresponds to
     219              : /// the single independent component. In other words, when we loop over the
     220              : /// independent canonical multi-indices, we are looping over the multi-index
     221              : /// permutations that are in the lower triangle of the N-dimensional matrix
     222              : /// containing all multi-index permutations. The canonical reordering of LHS
     223              : /// `TensorIndex`s for symmetric indices that is done by *this* function is
     224              : /// implemented to match this: by making time indices the rightmost, then
     225              : /// spacetime the next rightmost, and then spatial indices leftmost, we
     226              : /// guarantee that looping over the lower triangle permutations given by
     227              : /// `get_canonical_tensor_index` will not produce false positives or negatives
     228              : /// using the earlier simple logic to check for valid multi-indices.
     229              : ///
     230              : /// We can use the spacetime metric as an example to demonstrate this. The
     231              : /// lower triangle multi-indices that are looped over are ordered with index
     232              : /// values increasing right to left, e.g. `{0, 0}`, `{1, 0}`, `{2, 0}`,
     233              : /// `{2, 1}`, etc. If a user wants to  assign to \f$g_{ti}\f$, then after this
     234              : /// function internally reorders the LHS indices to \f$g_{it}\f$, when we loop
     235              : /// and encounter `{1, 0}`, we correctly get that we should evaluate this
     236              : /// component without having to check its other permutation. Likewise, if a user
     237              : /// wants to assign to \f$g_{it}\f$, no reordering is done and we get the same
     238              : /// correct behavior.
     239              : ///
     240              : /// This function works in general because:
     241              : /// - any `0`s in the symmetric indices will "first be dealt" to any time
     242              : ///   `TensorIndex`s in the rightmost index positions and then any spacetime
     243              : ///   `TensorIndex`s, where `0` is correctly valid for both, but
     244              : /// - if there are more `0`s than time + spacetime `TensorIndex`s, they will be
     245              : ///   "dealt" to spatial indices, which is always correctly invalid, and
     246              : /// - if there are more time `TensorIndex`s than `0`s, values `> 0` will be
     247              : ///   "dealt" to time indices, which is also always correctly invalid.
     248              : ///
     249              : /// In this way, we don't ever have to check other permutations of sets of
     250              : /// symmetric index values.
     251              : ///
     252              : /// \tparam LhsTensorIndices the `TensorIndex`s of the `Tensor`, e.g. `ti::a`,
     253              : /// `ti::b`, `ti::c`
     254              : /// \param canonical_symmetry the canonicalized symmetry values of the tensor
     255              : /// (see `Symmetry` for definition of the canonical ordering of symmetry values)
     256              : /// \return reordered values of `LhsTensorIndices::value...`
     257              : template <typename... LhsTensorIndices, size_t NumIndices>
     258              : constexpr std::array<size_t, NumIndices> get_reordered_tensorindex_values(
     259              :     const std::array<std::int32_t, NumIndices>& canonical_symmetry) {
     260              :   constexpr std::array<size_t, NumIndices> lhs_tensorindex_values = {
     261              :       {LhsTensorIndices::value...}};
     262              :   if constexpr (NumIndices < 2) {
     263              :     return lhs_tensorindex_values;
     264              :   } else {
     265              :     const auto compare = [](const size_t tensorindex_value1,
     266              :                             const size_t tensorindex_value2) {
     267              :       // clang-tidy thinks these two branches are the same but they aren't:
     268              :       //   if (tensorindex_value2 == ti::T.value)
     269              :       //   if (tensorindex_value2 == ti::t.value)
     270              :       // NOLINTNEXTLINE (clang-tidy: bugprone-branch-clone)
     271              :       if (tensorindex_value2 == ti::T.value) {
     272              :         return false;
     273              :       } else if (is_time_index_value(tensorindex_value1)) {
     274              :         return true;
     275              :       } else if (tensorindex_value2 == ti::t.value) {
     276              :         return false;
     277              :       }
     278              : 
     279              :       return (is_generic_spacetime_index_value(tensorindex_value1) and
     280              :               is_generic_spatial_index_value(tensorindex_value2)) or
     281              :              (tensorindex_value1 > tensorindex_value2 and
     282              :               is_generic_spacetime_index_value(tensorindex_value1) ==
     283              :                   is_generic_spacetime_index_value(tensorindex_value2));
     284              :     };
     285              : 
     286              :     std::array<size_t, NumIndices> reordered_lhs_tensorindex_values =
     287              :         lhs_tensorindex_values;
     288              : 
     289              :     std::int32_t max_symm_value = *alg::max_element(canonical_symmetry);
     290              :     std::int32_t symm_value_to_find = 1;
     291              :     while (symm_value_to_find <= max_symm_value) {
     292              :       size_t i = NumIndices - 1;
     293              :       while (true) {
     294              :         // skip forward until we get to the position with the value we want
     295              :         while (i > 0 and canonical_symmetry[i] != symm_value_to_find) {
     296              :           i--;
     297              :         }
     298              :         if (i == 0) {
     299              :           break;
     300              :         }
     301              : 
     302              :         size_t max_tensorindex_value = reordered_lhs_tensorindex_values[i];
     303              :         size_t max_index = i;
     304              : 
     305              :         size_t j = i - 1;
     306              :         // note: because we need to hit 0 and size_t wraps around to max size_t
     307              :         while (j < NumIndices) {
     308              :           const std::int32_t compare_symm_value = canonical_symmetry[j];
     309              :           const size_t compare_tensorindex_value =
     310              :               reordered_lhs_tensorindex_values[j];
     311              :           if (compare_symm_value == symm_value_to_find and
     312              :               compare(compare_tensorindex_value, max_tensorindex_value)) {
     313              :             max_tensorindex_value = compare_tensorindex_value;
     314              :             max_index = j;
     315              :           }
     316              :           j--;
     317              :         }
     318              :         reordered_lhs_tensorindex_values[max_index] =
     319              :             reordered_lhs_tensorindex_values[i];
     320              :         reordered_lhs_tensorindex_values[i] = max_tensorindex_value;
     321              :         i--;
     322              :       }
     323              :       symm_value_to_find++;
     324              :     }
     325              : 
     326              :     return reordered_lhs_tensorindex_values;
     327              :   }
     328              : }
     329              : 
     330              : /*!
     331              :  * \ingroup TensorExpressionsGroup
     332              :  * \brief Evaluate subtrees of the RHS expression or the RHS expression as a
     333              :  * whole and assign the result to the LHS tensor
     334              :  *
     335              :  * \details This is for internal use only and should never be directly called.
     336              :  * See `tenex::evaluate` and use it, instead.
     337              :  *
     338              :  * `EvaluateSubtrees` controls whether we wish to evaluate RHS subtrees or the
     339              :  * entire RHS expression as one expression. See`TensorExpression` documentation
     340              :  * on equation splitting for more details on what this means.
     341              :  *
     342              :  * If `EvaluateSubtrees == false`, then it's safe if the LHS tensor is used in
     343              :  * the RHS expression, so long as the generic index orders are the same. This
     344              :  * means that the callee of this function needs to first verify this is true
     345              :  * before calling this function. Under these conditions, this is a safe
     346              :  * operation because the implementation modifies each LHS component once and
     347              :  * does not revisit and access any LHS components after they've been updated.
     348              :  * For example, say we do `tenex::evaluate<ti_a, ti_b>(make_not_null(&L),
     349              :  * 5.0 * L(ti_a, ti_b));`. This function will first compute the RHS for some
     350              :  * concrete LHS, e.g. \f$L_{00}\f$. To compute this, it accesses \f$L_{00}\f$
     351              :  * in the RHS tree, multiplies it by `5.0`, then updates \f$L_{00}\f$ to be the
     352              :  * result of this multiplication. Next, it might compute \f$L_{01}\f$, where
     353              :  * only \f$L_{01}\f$ is accessed, and which hasn't yet been modified. Then the
     354              :  * next component is computed and updated, and so forth. These steps are
     355              :  * performed once for each unique LHS index. Therefore, it is important to note
     356              :  * that this kind of operation being safe to perform is
     357              :  * implementation-dependent. Specifically, the safety of the operation depends
     358              :  * on the order of LHS component access and assignment.
     359              :  *
     360              :  * \note `LhsTensorIndices` must be passed by reference because non-type
     361              :  * template parameters cannot be class types until C++20.
     362              :  *
     363              :  * @tparam EvaluateSubtrees whether or not to evaluate subtrees of RHS
     364              :  * expression
     365              :  * @tparam LhsTensorIndices the `TensorIndex`s of the `Tensor` on the LHS of the
     366              :  * tensor expression, e.g. `ti::a`, `ti::b`, `ti::c`
     367              :  * @param lhs_tensor pointer to the resultant LHS `Tensor` to fill
     368              :  * @param rhs_tensorexpression the RHS TensorExpression to be evaluated
     369              :  */
     370              : template <bool EvaluateSubtrees, typename... LhsTensorIndices,
     371              :           typename LhsDataType, typename LhsSymmetry, typename LhsIndexList,
     372              :           typename Derived, typename RhsDataType, typename RhsSymmetry,
     373              :           typename RhsIndexList, typename... RhsTensorIndices,
     374              :           size_t... LhsInts>
     375              : void evaluate_impl(
     376              :     const gsl::not_null<Tensor<LhsDataType, LhsSymmetry, LhsIndexList>*>
     377              :         lhs_tensor,
     378              :     const TensorExpression<Derived, RhsDataType, RhsSymmetry, RhsIndexList,
     379              :                            tmpl::list<RhsTensorIndices...>>&
     380              :         rhs_tensorexpression,
     381              :     const std::index_sequence<LhsInts...>& /*lhs_ints*/) {
     382              :   constexpr size_t num_lhs_indices = sizeof...(LhsTensorIndices);
     383              :   constexpr size_t num_rhs_indices = sizeof...(RhsTensorIndices);
     384              : 
     385              :   using lhs_tensorindex_list = tmpl::list<LhsTensorIndices...>;
     386              :   using rhs_tensorindex_list = tmpl::list<RhsTensorIndices...>;
     387              : 
     388              :   using lhs_tensor_type = typename std::decay_t<decltype(*lhs_tensor)>;
     389              : 
     390              :   static_assert(is_supported_tensor_datatype_v<LhsDataType> and
     391              :                     is_supported_tensor_datatype_v<RhsDataType>,
     392              :                 "TensorExpressions currently only support Tensors whose data "
     393              :                 "type is double, std::complex<double>, DataVector, or "
     394              :                 "ComplexDataVector. It is possible to add support for other "
     395              :                 "data types that are supported by Tensor.");
     396              :   static_assert(
     397              :       is_assignable_v<LhsDataType, RhsDataType>,
     398              :       "Assignment of the LHS Tensor's data type to the RHS TensorExpression's "
     399              :       "data type is not supported. This happens from doing something like e.g. "
     400              :       "trying to assign a Tensor<double> to a Tensor<DataVector> or a "
     401              :       "Tensor<DataVector> to a Tensor<ComplexDataVector>.");
     402              :   // `Symmetry` currently prevents this because antisymmetries are not currently
     403              :   // supported for `Tensor`s. This check is repeated here because if
     404              :   // antisymmetries are later supported for `Tensor`, using antisymmetries in
     405              :   // `TensorExpression`s will not automatically work. The implementations of the
     406              :   // derived `TensorExpression` types assume no antisymmetries (assume positive
     407              :   // `Symmetry` values), so support for antisymmetries in `TensorExpression`s
     408              :   // will still need to be implemented.
     409              :   static_assert(CheckNoLhsAntiSymmetries<LhsSymmetry>::value,
     410              :                 "Anti-symmetric Tensors are not currently supported by "
     411              :                 "TensorExpressions.");
     412              :   static_assert(
     413              :       tmpl::equal_members<
     414              :           typename remove_time_indices<lhs_tensorindex_list>::type,
     415              :           typename remove_time_indices<rhs_tensorindex_list>::type>::value,
     416              :       "The generic indices on the LHS of a tensor equation (that is, the "
     417              :       "template parameters specified in evaluate<...>) must match the generic "
     418              :       "indices of the RHS TensorExpression. This error occurs as a result of a "
     419              :       "call like evaluate<ti::a, ti::b>(R(ti::A, ti::b) * S(ti::a, ti::c)), "
     420              :       "where the generic indices of the evaluated RHS expression are ti::b and "
     421              :       "ti::c, but the generic indices provided for the LHS are ti::a and "
     422              :       "ti::b.");
     423              :   static_assert(
     424              :       tensorindex_list_is_valid<lhs_tensorindex_list>::value,
     425              :       "Cannot assign a tensor expression to a LHS tensor with a repeated "
     426              :       "generic index, e.g. evaluate<ti::a, ti::a>. (Note that the concrete "
     427              :       "time indices (ti::T and ti::t) can be repeated.)");
     428              :   static_assert(
     429              :       not contains_indices_to_contract<num_lhs_indices>(
     430              :           {{LhsTensorIndices::value...}}),
     431              :       "Cannot assign a tensor expression to a LHS tensor with generic "
     432              :       "indices that would be contracted, e.g. evaluate<ti::A, ti::a>.");
     433              :   // `IndexPropertyCheck` does also check that valence (Up/Lo) of indices that
     434              :   // correspond in the RHS and LHS tensors are equal, but the assertion message
     435              :   // below does not mention this because a mismatch in valence should have been
     436              :   // caught due to the combination of (i) the Tensor::operator() assertion
     437              :   // checking that generic indices' valences match the tensor's indices'
     438              :   // valences and (ii) the above assertion that RHS and LHS generic indices
     439              :   // match
     440              :   static_assert(
     441              :       IndexPropertyCheck<LhsIndexList, RhsIndexList, lhs_tensorindex_list,
     442              :                          rhs_tensorindex_list>::value,
     443              :       "At least one index of the tensor evaluated from the RHS expression "
     444              :       "cannot be evaluated to its corresponding index in the LHS tensor. This "
     445              :       "is due to a difference in number of spatial dimensions or Frame type "
     446              :       "between the index on the RHS and LHS. "
     447              :       "e.g. evaluate<ti::a, ti::b>(L, R(ti::b, ti::a));, where R's first "
     448              :       "index has 2 spatial dimensions but L's second index has 3 spatial "
     449              :       "dimensions. Check RHS and LHS indices that use the same generic index.");
     450              :   static_assert(Derived::height_relative_to_closest_tensor_leaf_in_subtree <
     451              :                     std::numeric_limits<size_t>::max(),
     452              :                 "Either no Tensors were found in the RHS TensorExpression or "
     453              :                 "the depth of the tree exceeded the maximum size_t value (very "
     454              :                 "unlikely). If there is indeed a Tensor in the RHS expression "
     455              :                 "and assuming the tree's height is not actually the maximum "
     456              :                 "size_t value, then there is a flaw in the logic for computing "
     457              :                 "the derived TensorExpression types' member, "
     458              :                 "height_relative_to_closest_tensor_leaf_in_subtree.");
     459              : 
     460              :   if constexpr (EvaluateSubtrees) {
     461              :     // Make sure the LHS tensor doesn't also appear in the RHS tensor expression
     462              :     (~rhs_tensorexpression).assert_lhs_tensor_not_in_rhs_expression(lhs_tensor);
     463              :     // If the LHS data type is a vector, size the LHS tensor components if their
     464              :     // size does not match the size from a `Tensor` in the RHS expression
     465              :     if constexpr (is_derived_of_vector_impl_v<LhsDataType>) {
     466              :       const size_t rhs_component_size =
     467              :           (~rhs_tensorexpression).get_rhs_tensor_component_size();
     468              :       if (rhs_component_size != (*lhs_tensor)[0].size()) {
     469              :         for (auto& lhs_component : *lhs_tensor) {
     470              :           lhs_component = LhsDataType(rhs_component_size);
     471              :         }
     472              :       }
     473              :     }
     474              :   }
     475              : 
     476              :   constexpr std::array<std::int32_t, num_lhs_indices> lhs_symmetry = {
     477              :       {tmpl::at_c<LhsSymmetry, LhsInts>::value...}};
     478              :   constexpr std::array<size_t, num_lhs_indices> reordered_tensorindex_values =
     479              :       get_reordered_tensorindex_values<LhsTensorIndices...>(lhs_symmetry);
     480              :   using reordered_lhs_tensorindex_list =
     481              :       tmpl::list<TensorIndex<reordered_tensorindex_values[LhsInts]>...>;
     482              : 
     483              :   constexpr std::array<size_t, num_rhs_indices> index_transformation =
     484              :       compute_tensorindex_transformation<num_lhs_indices, num_rhs_indices>(
     485              :           reordered_tensorindex_values, {{RhsTensorIndices::value...}});
     486              : 
     487              :   // positions of indices in LHS tensor where generic spatial indices are used
     488              :   // for spacetime indices
     489              :   constexpr auto lhs_spatial_spacetime_index_positions =
     490              :       get_spatial_spacetime_index_positions<LhsIndexList,
     491              :                                             reordered_lhs_tensorindex_list>();
     492              :   // positions of indices in RHS tensor where generic spatial indices are used
     493              :   // for spacetime indices
     494              :   constexpr auto rhs_spatial_spacetime_index_positions =
     495              :       get_spatial_spacetime_index_positions<RhsIndexList,
     496              :                                             rhs_tensorindex_list>();
     497              : 
     498              :   // positions of indices in LHS tensor where concrete time indices are used
     499              :   constexpr auto lhs_time_index_positions =
     500              :       get_time_index_positions<reordered_lhs_tensorindex_list>();
     501              : 
     502              :   using rhs_expression_type =
     503              :       typename std::decay_t<decltype(~rhs_tensorexpression)>;
     504              : 
     505              :   for (size_t i = 0; i < lhs_tensor_type::size(); i++) {
     506              :     auto lhs_multi_index =
     507              :         lhs_tensor_type::structure::get_canonical_tensor_index(i);
     508              :     if (is_evaluated_lhs_multi_index(lhs_multi_index,
     509              :                                      lhs_spatial_spacetime_index_positions,
     510              :                                      lhs_time_index_positions)) {
     511              :       for (size_t j = 0; j < lhs_spatial_spacetime_index_positions.size();
     512              :            j++) {
     513              :         gsl::at(lhs_multi_index,
     514              :                 gsl::at(lhs_spatial_spacetime_index_positions, j)) -= 1;
     515              :       }
     516              :       auto rhs_multi_index =
     517              :           transform_multi_index(lhs_multi_index, index_transformation);
     518              :       for (size_t j = 0; j < rhs_spatial_spacetime_index_positions.size();
     519              :            j++) {
     520              :         gsl::at(rhs_multi_index,
     521              :                 gsl::at(rhs_spatial_spacetime_index_positions, j)) += 1;
     522              :       }
     523              : 
     524              :       // The expression will either be evaluated as one whole expression
     525              :       // or it will be split up into subtrees that are evaluated one at a time.
     526              :       // See the section on splitting in the documentation for the
     527              :       // `TensorExpression` class to understand the logic and terminology used
     528              :       // in this control flow below.
     529              :       if constexpr (EvaluateSubtrees) {
     530              :         // the expression is split up, so evaluate subtrees at splits
     531              :         (~rhs_tensorexpression)
     532              :             .evaluate_primary_subtree((*lhs_tensor)[i], rhs_multi_index);
     533              :         if constexpr (not rhs_expression_type::is_primary_start) {
     534              :           // the root expression type is not the starting point of a leg, so it
     535              :           // has not yet been evaluated, so now we evaluate this last leg of the
     536              :           // expression at the root of the tree
     537              :           (*lhs_tensor)[i] =
     538              :               (~rhs_tensorexpression)
     539              :                   .get_primary((*lhs_tensor)[i], rhs_multi_index);
     540              :         }
     541              :       } else {
     542              :         // the expression is not split up, so evaluate full expression
     543              :         (*lhs_tensor)[i] = (~rhs_tensorexpression).get(rhs_multi_index);
     544              :       }
     545              :     }
     546              :   }
     547              : }
     548              : 
     549              : /*!
     550              :  * \ingroup TensorExpressionsGroup
     551              :  * \brief Assign a value to components of the LHS tensor
     552              :  *
     553              :  * \details This is for internal use only and should never be directly called.
     554              :  * See `tenex::evaluate` and use it, instead.
     555              :  *
     556              :  * \note `LhsTensorIndices` must be passed by reference because non-type
     557              :  * template parameters cannot be class types until C++20.
     558              :  *
     559              :  * @tparam LhsTensorIndices the `TensorIndex`s of the `Tensor` on the LHS of the
     560              :  * tensor expression, e.g. `ti::a`, `ti::b`, `ti::c`
     561              :  * @param lhs_tensor pointer to the resultant LHS `Tensor` to fill
     562              :  * @param rhs_value the RHS value to assigned
     563              :  */
     564              : template <typename... LhsTensorIndices, typename X, typename LhsSymmetry,
     565              :           typename LhsIndexList, typename NumberType, size_t... LhsInts>
     566              : void evaluate_impl(
     567              :     const gsl::not_null<Tensor<X, LhsSymmetry, LhsIndexList>*> lhs_tensor,
     568              :     const NumberType& rhs_value,
     569              :     const std::index_sequence<LhsInts...>& /*lhs_ints*/) {
     570              :   using lhs_tensor_type = typename std::decay_t<decltype(*lhs_tensor)>;
     571              :   constexpr size_t num_lhs_indices = sizeof...(LhsTensorIndices);
     572              :   using lhs_tensorindex_list = tmpl::list<LhsTensorIndices...>;
     573              : 
     574              :   static_assert(is_supported_tensor_datatype_v<X> and
     575              :                 "TensorExpressions currently only support Tensors whose data "
     576              :                 "type is double, std::complex<double>, DataVector, or "
     577              :                 "ComplexDataVector. It is possible to add support for other "
     578              :                 "data types that are supported by Tensor.");
     579              :   static_assert(
     580              :       is_assignable_v<X, NumberType>,
     581              :       "Assignment of the LHS Tensor's data type to the RHS number's data type "
     582              :       "is not supported within TensorExpressions. This happens from doing "
     583              :       "something like e.g. trying to assign a double to a DataVector or a "
     584              :       "DataVector to a ComplexDataVector.");
     585              :   // `Symmetry` currently prevents this because antisymmetries are not currently
     586              :   // supported for `Tensor`s. This check is repeated here because if
     587              :   // antisymmetries are later supported for `Tensor`, using antisymmetries in
     588              :   // `TensorExpression`s will not automatically work. The implementations of the
     589              :   // derived `TensorExpression` types assume no antisymmetries (assume positive
     590              :   // `Symmetry` values), so support for antisymmetries in `TensorExpression`s
     591              :   // will still need to be implemented.
     592              :   static_assert(CheckNoLhsAntiSymmetries<LhsSymmetry>::value,
     593              :                 "Anti-symmetric Tensors are not currently supported by "
     594              :                 "TensorExpressions.");
     595              :   static_assert(
     596              :       tensorindex_list_is_valid<lhs_tensorindex_list>::value,
     597              :       "Cannot assign a tensor expression to a LHS tensor with a repeated "
     598              :       "generic index, e.g. evaluate<ti::a, ti::a>. (Note that the concrete "
     599              :       "time indices (ti::T and ti::t) can be repeated.)");
     600              :   static_assert(
     601              :       not contains_indices_to_contract<num_lhs_indices>(
     602              :           {{LhsTensorIndices::value...}}),
     603              :       "Cannot assign a tensor expression to a LHS tensor with generic "
     604              :       "indices that would be contracted, e.g. evaluate<ti::A, ti::a>.");
     605              : 
     606              :   if constexpr (is_derived_of_vector_impl_v<X>) {
     607              :     ASSERT(get_size((*lhs_tensor)[0]) > 0,
     608              :            "Tensors with vector components must be sized before calling "
     609              :            "\ntenex::evaluate<...>("
     610              :            "\n\tgsl::not_null<Tensor<VectorType, ...>*>, number).");
     611              :   }
     612              : 
     613              :   constexpr std::array<std::int32_t, num_lhs_indices> lhs_symmetry = {
     614              :       {tmpl::at_c<LhsSymmetry, LhsInts>::value...}};
     615              :   constexpr std::array<size_t, num_lhs_indices> reordered_tensorindex_values =
     616              :       get_reordered_tensorindex_values<LhsTensorIndices...>(lhs_symmetry);
     617              :   (void)reordered_tensorindex_values;  // silence false unused variable warning
     618              :   using reordered_lhs_tensorindex_list =
     619              :       tmpl::list<TensorIndex<reordered_tensorindex_values[LhsInts]>...>;
     620              : 
     621              :   // positions of indices in LHS tensor where generic spatial indices are used
     622              :   // for spacetime indices
     623              :   constexpr auto lhs_spatial_spacetime_index_positions =
     624              :       get_spatial_spacetime_index_positions<LhsIndexList,
     625              :                                             reordered_lhs_tensorindex_list>();
     626              : 
     627              :   // positions of indices in LHS tensor where concrete time indices are used
     628              :   constexpr auto lhs_time_index_positions =
     629              :       get_time_index_positions<reordered_lhs_tensorindex_list>();
     630              : 
     631              :   for (size_t i = 0; i < lhs_tensor_type::size(); i++) {
     632              :     auto lhs_multi_index =
     633              :         lhs_tensor_type::structure::get_canonical_tensor_index(i);
     634              :     if (is_evaluated_lhs_multi_index(lhs_multi_index,
     635              :                                      lhs_spatial_spacetime_index_positions,
     636              :                                      lhs_time_index_positions)) {
     637              :       (*lhs_tensor)[i] = rhs_value;
     638              :     }
     639              :   }
     640              : }
     641              : }  // namespace detail
     642              : 
     643              : /*!
     644              :  * \ingroup TensorExpressionsGroup
     645              :  * \brief Assign the result of a RHS tensor expression to a tensor with the LHS
     646              :  * index order set in the template parameters
     647              :  *
     648              :  * \details Uses the right hand side (RHS) TensorExpression's index ordering
     649              :  * (`RhsTE::args_list`) and the desired left hand side (LHS) tensor's index
     650              :  * ordering (`LhsTensorIndices`) to fill the provided LHS Tensor with that LHS
     651              :  * index ordering. This can carry out the evaluation of a RHS tensor expression
     652              :  * to a LHS tensor with the same index ordering, such as \f$L_{ab} = R_{ab}\f$,
     653              :  * or different ordering, such as \f$L_{ba} = R_{ab}\f$.
     654              :  *
     655              :  * The symmetry of the provided LHS Tensor need not match the symmetry
     656              :  * determined from evaluating the RHS TensorExpression according to its order of
     657              :  * operations. This allows one to specify LHS symmetries (via `lhs_tensor`) that
     658              :  * may not be preserved by the RHS expression's order of operations, which
     659              :  * depends on how the expression is written and implemented.
     660              :  *
     661              :  * The LHS `Tensor` cannot be part of the RHS expression, e.g.
     662              :  * `evaluate(make_not_null(&L), L() + R());`, because the LHS `Tensor` will
     663              :  * generally not be computed correctly when the RHS `TensorExpression` is split
     664              :  * up and the LHS tensor components are computed by accumulating the result of
     665              :  * subtrees (see the section on splitting in the documentation for the
     666              :  * `TensorExpression` class). If you need to use the LHS `Tensor` on the RHS,
     667              :  * use `tenex::update` instead.
     668              :  *
     669              :  * ### Example usage
     670              :  * Given `Tensor`s `R`, `S`, `T`, `G`, and `H`, we can compute the LHS tensor
     671              :  * \f$L\f$ in the equation \f$L_{a} = R_{ab} S^{b} + G_{a} - H_{ba}{}^{b} T\f$
     672              :  * by doing:
     673              :  *
     674              :  * \snippet Test_MixedOperations.cpp use_evaluate_with_result_as_arg
     675              :  *
     676              :  * \note `LhsTensorIndices` must be passed by reference because non-type
     677              :  * template parameters cannot be class types until C++20.
     678              :  *
     679              :  * @tparam LhsTensorIndices the `TensorIndex`s of the `Tensor` on the LHS of the
     680              :  * tensor expression, e.g. `ti::a`, `ti::b`, `ti::c`
     681              :  * @param lhs_tensor pointer to the resultant LHS `Tensor` to fill
     682              :  * @param rhs_tensorexpression the RHS TensorExpression to be evaluated
     683              :  */
     684              : template <auto&... LhsTensorIndices, typename LhsDataType, typename LhsSymmetry,
     685              :           typename LhsIndexList, typename Derived, typename RhsDataType,
     686              :           typename RhsSymmetry, typename RhsIndexList,
     687              :           typename... RhsTensorIndices>
     688            1 : void evaluate(
     689              :     const gsl::not_null<Tensor<LhsDataType, LhsSymmetry, LhsIndexList>*>
     690              :         lhs_tensor,
     691              :     const TensorExpression<Derived, RhsDataType, RhsSymmetry, RhsIndexList,
     692              :                            tmpl::list<RhsTensorIndices...>>&
     693              :         rhs_tensorexpression) {
     694              :   using rhs_expression_type =
     695              :       typename std::decay_t<decltype(~rhs_tensorexpression)>;
     696              :   constexpr bool evaluate_subtrees =
     697              :       rhs_expression_type::primary_subtree_contains_primary_start;
     698              :   detail::evaluate_impl<evaluate_subtrees,
     699              :                         std::decay_t<decltype(LhsTensorIndices)>...>(
     700              :       lhs_tensor, rhs_tensorexpression,
     701              :       std::make_index_sequence<sizeof...(LhsTensorIndices)>{});
     702              : }
     703              : 
     704              : /// @{
     705              : /*!
     706              :  * \ingroup TensorExpressionsGroup
     707              :  * \brief Assign a number to components of a tensor with the LHS index order
     708              :  * set in the template parameters
     709              :  *
     710              :  * \details
     711              :  * Example usage:
     712              :  * \snippet Test_MixedOperations.cpp assign_double_to_index_subsets
     713              :  *
     714              :  * \note The components of the LHS `Tensor` passed in must already be sized
     715              :  * because there is no way to infer component size from the RHS
     716              :  *
     717              :  * \note `LhsTensorIndices` must be passed by reference because non-type
     718              :  * template parameters cannot be class types until C++20.
     719              :  *
     720              :  * @tparam LhsTensorIndices the `TensorIndex`s of the `Tensor` on the LHS of the
     721              :  * tensor expression, e.g. `ti::a`, `ti::b`, `ti::c`
     722              :  * @param lhs_tensor pointer to the resultant LHS `Tensor` to fill
     723              :  * @param rhs_value the RHS value to assign
     724              :  */
     725              : template <auto&... LhsTensorIndices, typename X, typename LhsSymmetry,
     726              :           typename LhsIndexList, typename N,
     727              :           Requires<std::is_arithmetic_v<N>> = nullptr>
     728            1 : void evaluate(
     729              :     const gsl::not_null<Tensor<X, LhsSymmetry, LhsIndexList>*> lhs_tensor,
     730              :     const N rhs_value) {
     731              :   detail::evaluate_impl<std::decay_t<decltype(LhsTensorIndices)>...>(
     732              :       lhs_tensor, rhs_value,
     733              :       std::make_index_sequence<sizeof...(LhsTensorIndices)>{});
     734              : }
     735              : template <auto&... LhsTensorIndices, typename X, typename LhsSymmetry,
     736              :           typename LhsIndexList, typename N>
     737            1 : void evaluate(
     738              :     const gsl::not_null<Tensor<X, LhsSymmetry, LhsIndexList>*> lhs_tensor,
     739              :     const std::complex<N>& rhs_value) {
     740              :   detail::evaluate_impl<std::decay_t<decltype(LhsTensorIndices)>...>(
     741              :       lhs_tensor, rhs_value,
     742              :       std::make_index_sequence<sizeof...(LhsTensorIndices)>{});
     743              : }
     744              : /// @}
     745              : 
     746              : /*!
     747              :  * \ingroup TensorExpressionsGroup
     748              :  * \brief Assign the result of a RHS tensor expression to a tensor with the LHS
     749              :  * index order set in the template parameters
     750              :  *
     751              :  * \details Uses the right hand side (RHS) TensorExpression's index ordering
     752              :  * (`RhsTE::args_list`) and the desired left hand side (LHS) tensor's index
     753              :  * ordering (`LhsTensorIndices`) to construct a LHS Tensor with that LHS index
     754              :  * ordering. This can carry out the evaluation of a RHS tensor expression to a
     755              :  * LHS tensor with the same index ordering, such as \f$L_{ab} = R_{ab}\f$, or
     756              :  * different ordering, such as \f$L_{ba} = R_{ab}\f$.
     757              :  *
     758              :  * The symmetry of the returned LHS Tensor depends on the order of operations in
     759              :  * the RHS TensorExpression, i.e. how the expression is written. If you would
     760              :  * like to specify the symmetry of the LHS Tensor instead of it being determined
     761              :  * by the order of operations in the RHS expression, please use the other
     762              :  * `tenex::evaluate` overload that takes an empty LHS Tensor as its first
     763              :  * argument.
     764              :  *
     765              :  * ### Example usage
     766              :  * Given `Tensor`s `R`, `S`, `T`, `G`, and `H`, we can compute the LHS tensor
     767              :  * \f$L\f$ in the equation \f$L_{a} = R_{ab} S^{b} + G_{a} - H_{ba}{}^{b} T\f$
     768              :  * by doing:
     769              :  *
     770              :  * \snippet Test_MixedOperations.cpp use_evaluate_to_return_result
     771              :  *
     772              :  * \parblock
     773              :  * \note If a generic spatial index is used for a spacetime index in the RHS
     774              :  * tensor, its corresponding index in the LHS tensor type will be a spatial
     775              :  * index with the same valence, frame, and number of spatial dimensions. If a
     776              :  * concrete time index is used for a spacetime index in the RHS tensor, the
     777              :  * index will not appear in the LHS tensor (i.e. there will NOT be a
     778              :  * corresponding LHS index where only the time index of that index has been
     779              :  * computed and its spatial indices are empty).
     780              :  * \endparblock
     781              :  *
     782              :  * \parblock
     783              :  * \note `LhsTensorIndices` must be passed by reference because non-type
     784              :  * template parameters cannot be class types until C++20.
     785              :  * \endparblock
     786              :  *
     787              :  * @tparam LhsTensorIndices the TensorIndexs of the Tensor on the LHS of the
     788              :  * tensor expression, e.g. `ti::a`, `ti::b`, `ti::c`
     789              :  * @param rhs_tensorexpression the RHS TensorExpression to be evaluated
     790              :  * @return the resultant LHS Tensor with index order specified by
     791              :  * LhsTensorIndices
     792              :  */
     793              : template <auto&... LhsTensorIndices, typename RhsTE,
     794              :           Requires<std::is_base_of_v<Expression, RhsTE>> = nullptr>
     795            1 : auto evaluate(const RhsTE& rhs_tensorexpression) {
     796              :   using lhs_tensorindex_list =
     797              :       tmpl::list<std::decay_t<decltype(LhsTensorIndices)>...>;
     798              :   using rhs_tensorindex_list = typename RhsTE::args_list;
     799              :   using rhs_symmetry = typename RhsTE::symmetry;
     800              :   using rhs_tensorindextype_list = typename RhsTE::index_list;
     801              : 
     802              :   // Stores (potentially reordered) symmetry and indices needed for constructing
     803              :   // the LHS tensor, with index order specified by LhsTensorIndices
     804              :   using lhs_tensor_symm_and_indices =
     805              :       LhsTensorSymmAndIndices<rhs_tensorindex_list, lhs_tensorindex_list,
     806              :                               rhs_symmetry, rhs_tensorindextype_list>;
     807              : 
     808              :   Tensor<typename RhsTE::type, typename lhs_tensor_symm_and_indices::symmetry,
     809              :          typename lhs_tensor_symm_and_indices::tensorindextype_list>
     810              :       lhs_tensor{};
     811              : 
     812              :   evaluate<LhsTensorIndices...>(make_not_null(&lhs_tensor),
     813              :                                 rhs_tensorexpression);
     814              :   return lhs_tensor;
     815              : }
     816              : 
     817              : /*!
     818              :  * \ingroup TensorExpressionsGroup
     819              :  * \brief If the LHS tensor is used in the RHS expression, this should be used
     820              :  * to assign a LHS tensor to the result of a RHS tensor expression that contains
     821              :  * it
     822              :  *
     823              :  * \details See documentation for `tenex::evaluate` for basic functionality.
     824              :  *
     825              :  * `tenex::update` differs from `tenex::evaluate` in that `tenex::update` should
     826              :  * be used when some LHS `Tensor` has been partially computed, and now we would
     827              :  * like to update it with a RHS expression that contains it. In other words,
     828              :  * this should be used when we would like to emulate assignment operations like
     829              :  * `LHS +=`, `LHS -=`, `LHS *=`, etc.
     830              :  *
     831              :  * One important difference to note with `tenex::update` is that it cannot split
     832              :  * up the RHS expression and evaluate subtrees, while `tenex::evaluate` can (see
     833              :  * `TensorExpression` documentation). From benchmarking, it was found that the
     834              :  * runtime of `DataVector` expressions scales poorly as we increase the number
     835              :  * of operations. For this reason, when the data type held by the tensors in the
     836              :  * expression is `DataVector`, it's best to avoid passing RHS expressions with a
     837              :  * large number of operations (e.g. an inner product that sums over many terms).
     838              :  *
     839              :  * ### Example usage
     840              :  * In implementing a large equation with many operations, we can manually break
     841              :  * up the equation and evaluate different subexpressions at a time by making one
     842              :  * initial call to `tenex::evaluate` followed by any number of calls to
     843              :  * `tenex::update` that use the LHS tensor in the RHS expression and will
     844              :  * compute the rest of the equation:
     845              :  *
     846              :  * \snippet Test_MixedOperations.cpp use_update
     847              :  *
     848              :  * \note `LhsTensorIndices` must be passed by reference because non-type
     849              :  * template parameters cannot be class types until C++20.
     850              :  *
     851              :  * @tparam LhsTensorIndices the TensorIndexs of the Tensor on the LHS of the
     852              :  * tensor expression, e.g. `ti_a`, `ti_b`, `ti_c`
     853              :  * @param lhs_tensor pointer to the resultant LHS Tensor to fill
     854              :  * @param rhs_tensorexpression the RHS TensorExpression to be evaluated
     855              :  */
     856              : template <auto&... LhsTensorIndices, typename LhsDataType, typename RhsDataType,
     857              :           typename LhsSymmetry, typename LhsIndexList, typename Derived,
     858              :           typename RhsSymmetry, typename RhsIndexList,
     859              :           typename... RhsTensorIndices>
     860            1 : void update(
     861              :     const gsl::not_null<Tensor<LhsDataType, LhsSymmetry, LhsIndexList>*>
     862              :         lhs_tensor,
     863              :     const TensorExpression<Derived, RhsDataType, RhsSymmetry, RhsIndexList,
     864              :                            tmpl::list<RhsTensorIndices...>>&
     865              :         rhs_tensorexpression) {
     866              :   using lhs_tensorindex_list =
     867              :       tmpl::list<std::decay_t<decltype(LhsTensorIndices)>...>;
     868              :   // Assert that each instance of the LHS tensor in the RHS tensor expression
     869              :   // uses the same generic index order that the LHS uses
     870              :   (~rhs_tensorexpression)
     871              :       .template assert_lhs_tensorindices_same_in_rhs<lhs_tensorindex_list>(
     872              :           lhs_tensor);
     873              : 
     874              :   detail::evaluate_impl<false, std::decay_t<decltype(LhsTensorIndices)>...>(
     875              :       lhs_tensor, rhs_tensorexpression,
     876              :       std::make_index_sequence<sizeof...(LhsTensorIndices)>{});
     877              : }
     878              : }  // namespace tenex
        

Generated by: LCOV version 2.0-1