Line data Source code
1 1 : // Distributed under the MIT License.
2 : // See LICENSE.txt for details.
3 :
4 : /// \file
5 : /// Defines functions useful for transforming tensor multi-indices according to
6 : /// a different generic index order
7 :
8 : #pragma once
9 :
10 : #include <array>
11 : #include <cstddef>
12 : #include <iterator>
13 : #include <limits>
14 :
15 : #include "DataStructures/Tensor/Expressions/TensorIndex.hpp"
16 : #include "DataStructures/Tensor/Expressions/TimeIndex.hpp"
17 : #include "Utilities/Algorithm.hpp"
18 : #include "Utilities/ForceInline.hpp"
19 : #include "Utilities/Gsl.hpp"
20 : #include "Utilities/MakeArray.hpp"
21 :
22 : namespace tenex {
23 : namespace TensorIndexTransformation_detail {
24 : static constexpr size_t time_index_position_placeholder =
25 : std::numeric_limits<size_t>::max();
26 : } // namespace TensorIndexTransformation_detail
27 :
28 : /// \brief Computes a transformation from one generic tensor index order to
29 : /// another
30 : ///
31 : /// \details
32 : /// In most cases, the elements of the transformation are simply the positions
33 : /// of the second list of generic indices in the first list of generic indices.
34 : /// Put another way, for some `i`,
35 : /// `tensorindices2[i] == tensorindices1[index_transformation[i]]`.
36 : ///
37 : /// Here is an example of what the algorithm does:
38 : ///
39 : /// Transformation between (1) \f$R_{cab}\f$ and (2) \f$S_{abc}\f$
40 : /// `tensorindices1`:
41 : /// \code
42 : /// {2, 0, 1} // TensorIndex values for {c, a, b}
43 : /// \endcode
44 : /// `tensorindices2`:
45 : /// \code
46 : /// {0, 1, 2} // TensorIndex values for {a, b, c}
47 : /// \endcode
48 : /// returned `tensorindex_transformation`:
49 : /// \code
50 : /// {1, 2, 0} // positions of S' indices {a, b, c} in R's indices {c, a, b}
51 : /// \endcode
52 : ///
53 : /// One special case scenario to note is when concrete time indices are
54 : /// involved in the transformation. Consider transforming a multi-index for
55 : /// some tensor \f$R_{ab}\f$ to another tensor \f$S_{btat}\f$. This would be
56 : /// necessary for evaluating the LHS of a simple equation such as
57 : /// \f$R_{ab} = S_{btat}\f$. The transformation between a multi-index for
58 : /// \f$R\f$ to the equivalent multi-index for \f$S\f$ cannot simply be the list
59 : /// of positions of \f$S\f$' indices in \f$R\f$'s indices, as \f$R\f$ does not
60 : /// contain all of \f$S\f$' indices, because it has no time indices. To handle
61 : /// cases like this, a placeholder value for the position of any time index is
62 : /// substituted for an actual position, since one may not exist. In this
63 : /// example, the transformation would be
64 : /// `{1, PLACEHOLDER_VALUE, 0, PLACEHOLDER_VALUE}`, where `PLACEHOLDER_VALUE` is
65 : /// defined by
66 : /// `TensorIndexTransformation_detail::time_index_position_placeholder`. `1` and
67 : /// `0` are the positions of \f$b\f$ and \f$a\f$ in \f$R\f$, and the placeholder
68 : /// is used for the positions of time indices.
69 : ///
70 : /// \tparam NumIndices1 the number of indices for the first generic index order
71 : /// \tparam NumIndices2 the number of indices for the second generic index order
72 : /// \param tensorindices1 the TensorIndex values of the first generic index
73 : /// order
74 : /// \param tensorindices2 the TensorIndex values of the second generic index
75 : /// order
76 : /// \return a transformation from the first generic index order to the second
77 : template <size_t NumIndices1, size_t NumIndices2>
78 : SPECTRE_ALWAYS_INLINE constexpr std::array<size_t, NumIndices2>
79 1 : compute_tensorindex_transformation(
80 : const std::array<size_t, NumIndices1>& tensorindices1,
81 : const std::array<size_t, NumIndices2>& tensorindices2) {
82 : std::array<size_t, NumIndices2> tensorindex_transformation{};
83 : for (size_t i = 0; i < NumIndices2; i++) {
84 : gsl::at(tensorindex_transformation, i) =
85 : detail::is_time_index_value(gsl::at(tensorindices2, i))
86 : ? TensorIndexTransformation_detail::time_index_position_placeholder
87 : : static_cast<size_t>(std::distance(
88 : tensorindices1.begin(),
89 : alg::find(tensorindices1, gsl::at(tensorindices2, i))));
90 : }
91 : return tensorindex_transformation;
92 : }
93 :
94 : /// \brief Computes the tensor multi-index that is equivalent to a given tensor
95 : /// multi-index, according to the differences in their generic index orders
96 : ///
97 : /// \details
98 : /// Here is an example of what the algorithm does:
99 : ///
100 : /// Transform (input) multi-index of \f$R_{cab}\f$ to the equivalent (output)
101 : /// multi-index of \f$S_{abc}\f$
102 : /// `tensorindex_transformation`:
103 : /// \code
104 : /// {1, 2, 0} // positions of S' indices {a, b, c} in R's indices {c, a, b}
105 : /// \endcode
106 : /// `input_multi_index`:
107 : /// \code
108 : /// {3, 4, 5} // i.e. c = 3, a = 4, b = 5
109 : /// \endcode
110 : /// returned equivalent `output_multi_index`:
111 : /// \code
112 : /// {4, 5, 3} // i.e. a = 4, b = 5, c = 3
113 : /// \endcode
114 : ///
115 : /// One special case scenario to note is when concrete time indices are
116 : /// involved in the transformation. Consider transforming a multi-index for
117 : /// some tensor \f$R_{ab}\f$ to another tensor \f$S_{btat}\f$. This would be
118 : /// necessary for evaluating the LHS of a simple equation such as
119 : /// \f$R_{ab} = S_{btat}\f$. The transformation between a multi-index for
120 : /// \f$R\f$ to the equivalent multi-index for \f$S\f$ cannot simply be the list
121 : /// of positions of \f$S\f$' indices in \f$R\f$'s indices, as \f$R\f$ does not
122 : /// contain all of \f$S\f$' indices, because it has no time indices. To handle
123 : /// cases like this, a placeholder value for the position of any time index
124 : /// must be substituted for an actual position, since one may not exist. In this
125 : /// example, the proper input transformation (`tensorindex_transformation`)
126 : /// would need to be
127 : /// `{1, PLACEHOLDER_VALUE, 0, PLACEHOLDER_VALUE}`, where `PLACEHOLDER_VALUE` is
128 : /// defined by
129 : /// `TensorIndexTransformation_detail::time_index_position_placeholder`. `1` and
130 : /// `0` are the positions of \f$b\f$ and \f$a\f$ in \f$R\f$, and the placeholder
131 : /// is used for the positions of time indices. In computing the output
132 : /// transformed multi-index, the function will insert a `0` at each position
133 : /// where this placeholder is found in the transformation. For example, if
134 : /// `input_multi_index` is `{1, 2}`, representing \f$R_{12}\f$, the returned
135 : /// output multi-index will be `{2, 0, 1, 0}`, representing \f$S_{2010}\f$.
136 : ///
137 : /// \tparam NumIndicesIn the number of indices
138 : /// \tparam NumIndicesOut the number of indices
139 : /// \param input_multi_index the input tensor multi-index to transform
140 : /// \param tensorindex_transformation the positions of the output's generic
141 : /// indices in the input's generic indices (see example in details)
142 : /// \return the output tensor multi-index that is equivalent to
143 : /// `input_multi_index`, according to generic index order differences
144 : // (`tensorindex_transformation`)
145 : template <size_t NumIndicesIn, size_t NumIndicesOut>
146 : SPECTRE_ALWAYS_INLINE constexpr std::array<size_t, NumIndicesOut>
147 1 : transform_multi_index(
148 : const std::array<size_t, NumIndicesIn>& input_multi_index,
149 : const std::array<size_t, NumIndicesOut>& tensorindex_transformation) {
150 : std::array<size_t, NumIndicesOut> output_multi_index =
151 : make_array<NumIndicesOut, size_t>(0);
152 : for (size_t i = 0; i < NumIndicesOut; i++) {
153 : gsl::at(output_multi_index, i) =
154 : // Check that the index is not a time index instead of checking that it
155 : // is, because we expect it to not be a time index most of the time
156 : (gsl::at(tensorindex_transformation, i) !=
157 : TensorIndexTransformation_detail::time_index_position_placeholder)
158 : ? gsl::at(input_multi_index, gsl::at(tensorindex_transformation, i))
159 : : 0;
160 : }
161 : return output_multi_index;
162 : }
163 : } // namespace tenex
|