Line data Source code
1 1 : // Distributed under the MIT License.
2 : // See LICENSE.txt for details.
3 :
4 : /// \file
5 : /// Defines which types are allowed, whether operations with certain types are
6 : /// allowed, and other type-specific properties and configuration for
7 : /// `TensorExpression`s
8 : ///
9 : /// \details
10 : /// To add support for a data type, modify the templates in this file and the
11 : /// arithmetic operator overloads as necessary. Then, add tests as appropriate.
12 :
13 : #pragma once
14 :
15 : #include <complex>
16 : #include <cstddef>
17 : #include <limits>
18 : #include <type_traits>
19 :
20 : #include "DataStructures/ComplexDataVector.hpp"
21 : #include "DataStructures/DataVector.hpp"
22 : #include "DataStructures/Tensor/Expressions/TensorExpression.hpp"
23 : #include "DataStructures/VectorImpl.hpp"
24 : #include "Utilities/NoSuchType.hpp"
25 :
26 : namespace tenex {
27 : template <typename DataType>
28 : struct NumberAsExpression;
29 :
30 : namespace detail {
31 : /// @{
32 : /// \brief Whether or not `TensorExpression`s supports using a given type as a
33 : /// numeric term
34 : ///
35 : /// \details
36 : /// To make it possible to use a new numeric data type as a term in
37 : /// `TensorExpression`s, add the type to this alias and adjust other templates
38 : /// in this file, as necessary.
39 : ///
40 : /// \tparam X the arithmetic data type
41 : template <typename X>
42 : struct is_supported_number_datatype
43 : : std::disjunction<std::is_same<X, double>,
44 : std::is_same<X, std::complex<double>>> {};
45 :
46 : template <typename X>
47 : constexpr bool is_supported_number_datatype_v =
48 : is_supported_number_datatype<X>::value;
49 : /// @}
50 :
51 : /// @{
52 : /// \brief Whether or not `Tensor`s with the given data type are currently
53 : /// supported by `TensorExpression`s
54 : ///
55 : /// \details
56 : /// To make it possible to use a new data type in a `Tensor` term in
57 : /// `TensorExpression`s, add the type to this alias and adjust other templates
58 : /// in this file, as necessary.
59 : ///
60 : /// \tparam X the `Tensor` data type
61 : template <typename X>
62 : struct is_supported_tensor_datatype
63 : : std::disjunction<
64 : std::is_same<X, double>, std::is_same<X, std::complex<double>>,
65 : std::is_same<X, DataVector>, std::is_same<X, ComplexDataVector>> {};
66 :
67 : template <typename X>
68 : constexpr bool is_supported_tensor_datatype_v =
69 : is_supported_tensor_datatype<X>::value;
70 : /// @}
71 :
72 : /// \brief If the given type is a derived `VectorImpl` type, get the base
73 : /// `VectorImpl` type, else return the given type
74 : ///
75 : /// \tparam T the given type
76 : /// \tparam IsVector whether or not the given type is a `VectorImpl` type
77 : template <typename T, bool IsVector = is_derived_of_vector_impl_v<T>>
78 : struct upcast_if_derived_vector_type;
79 :
80 : /// If `T` is not a `VectorImpl`, the `type` is just the input
81 : template <typename T>
82 : struct upcast_if_derived_vector_type<T, false> {
83 : using type = T;
84 : };
85 : /// If `T` is a `VectorImpl`, the `type` is the base `VectorImpl` type
86 : template <typename T>
87 : struct upcast_if_derived_vector_type<T, true> {
88 : using upcasted_type = typename T::BaseType;
89 : // if we have a derived VectorImpl, get base type, else T is a base VectorImpl
90 : // and we use that
91 : using type =
92 : tmpl::conditional_t<std::is_base_of_v<MarkAsVectorImpl, upcasted_type>,
93 : upcasted_type, T>;
94 : };
95 :
96 : /// \brief Get the complex-valued partner type to a given type
97 : ///
98 : /// \details
99 : /// This is used to define pairings between real-valued types and their
100 : /// complex-valued counterparts. For example, `double`'s complex-valued partner
101 : /// is `std::complex<double>` and `DataVector`'s complex-valued partner is
102 : /// `ComplexDataVector`. Keeping track of this is useful in determining which
103 : /// operations can and can't be performed in `TensorExpression`s.
104 : ///
105 : /// To make `TensorExpression`s aware of a new pairing, modify a current
106 : /// template specialization or add a new one.
107 : ///
108 : /// \tparam X the given type
109 : template <typename X, bool IsArithmetic = std::is_arithmetic_v<X>>
110 : struct get_complex_datatype;
111 :
112 : /// If the type is not arithmetic, the complex partner to this type is not
113 : /// known
114 : template <typename X>
115 : struct get_complex_datatype<X, false> {
116 : using type = NoSuchType;
117 : };
118 : /// If the type is arithmetic, the complex partner to `X` is `std::complex<X>`
119 : template <typename X>
120 : struct get_complex_datatype<X, true> {
121 : using type = std::complex<X>;
122 : };
123 : /// The complex partner to `DataVector` is `ComplexDataVector`
124 : template <>
125 : struct get_complex_datatype<DataVector> {
126 : using type = ComplexDataVector;
127 : };
128 :
129 : /// @{
130 : /// \brief Whether or not a given type is the complex-valued partner to another
131 : /// given type
132 : ///
133 : /// \details
134 : /// See `get_complex_datatype` for which pairings are defined
135 : ///
136 : /// \tparam MaybeComplexDataType the given type to check for being the complex
137 : /// partner to the other type
138 : /// \tparam OtherDataType the other type
139 : template <typename MaybeComplexDataType, typename OtherDataType>
140 : struct is_complex_datatype_of
141 : : std::is_same<typename get_complex_datatype<OtherDataType>::type,
142 : MaybeComplexDataType> {};
143 : template <typename OtherDataType>
144 : struct is_complex_datatype_of<NoSuchType, OtherDataType> : std::false_type {};
145 :
146 : template <typename MaybeComplexDataType, typename OtherDataType>
147 : constexpr bool is_complex_datatype_of_v =
148 : is_complex_datatype_of<MaybeComplexDataType, OtherDataType>::value;
149 : /// @}
150 :
151 : /// \brief Whether or not a given type is assignable to another within
152 : /// `TensorExpression`s
153 : ///
154 : /// \details
155 : /// This is used to define which types can be assigned to which when evaluating
156 : /// the result of a `TensorExpression`. For example, you can assign a
157 : /// `DataVector` to a `double`, but not vice versa.
158 : ///
159 : /// To enable assignment between two types that is not yet supported, modify a
160 : /// current template specialization or add a new one.
161 : ///
162 : /// \tparam LhsDataType the type being assigned
163 : /// \tparam RhsDataType the type to assign the `LhsDataType` to
164 : template <typename LhsDataType, typename RhsDataType>
165 : struct is_assignable;
166 :
167 : /// Can assign a type to itself
168 : template <typename LhsDataType, typename RhsDataType>
169 : struct is_assignable : std::is_same<LhsDataType, RhsDataType> {};
170 : /// Can assign a complex numeric type to its underlying real-valued numeric type
171 : template <typename X>
172 : struct is_assignable<std::complex<X>, X> : std::true_type {};
173 : /// Can assign the LHS `VectorImpl` to the RHS `VectorImpl` if `VectorImpl`
174 : /// allows it
175 : template <typename ValueType1, typename VectorType1, size_t StaticSize1,
176 : typename ValueType2, typename VectorType2, size_t StaticSize2>
177 : struct is_assignable<VectorImpl<ValueType1, VectorType1, StaticSize1>,
178 : VectorImpl<ValueType2, VectorType2, StaticSize2>>
179 : : ::VectorImpl_detail::is_assignable<VectorType1, VectorType2> {};
180 : /// Can assign a `VectorImpl` to its value type, e.g. can assign a `DataVector`
181 : /// to a `double`
182 : template <typename ValueType, typename VectorType, size_t StaticSize>
183 : struct is_assignable<VectorImpl<ValueType, VectorType, StaticSize>, ValueType>
184 : : std::true_type {};
185 : /// Can assign a complex-valued `VectorImpl` to its real component's type, e.g.
186 : /// can assign a `ComplexDataVector` to a `double` because the underlying type
187 : /// of `ComplexDataVector` is `std::complex<double>`, whose real component is a
188 : /// `double`
189 : template <typename ValueType, typename VectorType, size_t StaticSize>
190 : struct is_assignable<
191 : VectorImpl<std::complex<ValueType>, VectorType, StaticSize>, ValueType>
192 : : std::true_type {};
193 :
194 : /// \brief Whether or not a given type is assignable to another within
195 : /// `TensorExpression`s
196 : ///
197 : /// \details
198 : /// See `is_assignable` for which assignments are permitted
199 : template <typename LhsDataType, typename RhsDataType>
200 : constexpr bool is_assignable_v = is_assignable<
201 : typename upcast_if_derived_vector_type<LhsDataType>::type,
202 : typename upcast_if_derived_vector_type<RhsDataType>::type>::value;
203 :
204 : /// \brief Get the data type of a binary operation between two data types
205 : /// that may occur in a `TensorExpression`
206 : ///
207 : /// \details
208 : /// This is used to define the resulting types of binary arithmetic operations
209 : /// within `TensorExpression`s, e.g. `double OP double = double` and
210 : /// `double OP DataVector = DataVector`.
211 : ///
212 : /// To enable binary operations between two types that is not yet supported,
213 : /// modify a current template specialization or add a new one.
214 : ///
215 : /// \tparam X1 the data type of one operand
216 : /// \tparam X2 the data type of the other operand
217 : template <typename X1, typename X2>
218 : struct get_binop_datatype_impl;
219 :
220 : /// No template specialization was matched, so it's not a known pairing
221 : template <typename X1, typename X2>
222 : struct get_binop_datatype_impl {
223 : using type = NoSuchType;
224 : };
225 : /// A binary operation between two terms of the same type will yield a result
226 : /// with that type
227 : template <typename X>
228 : struct get_binop_datatype_impl<X, X> {
229 : using type = X;
230 : };
231 : /// A binary operation between two `VectorImpl`s of the same type will
232 : /// yield the shared derived `VectorImpl`, e.g.
233 : /// `DataVector OP DataVector = DataVector`
234 : template <typename ValueType, typename VectorType, size_t StaticSize>
235 : struct get_binop_datatype_impl<VectorImpl<ValueType, VectorType, StaticSize>,
236 : VectorImpl<ValueType, VectorType, StaticSize>> {
237 : using type = VectorType;
238 : };
239 : /// @{
240 : /// A binary operation between a type `T` and `std::complex<T>` yields a
241 : /// `std::complex<T>`
242 : template <typename T>
243 : struct get_binop_datatype_impl<T, std::complex<T>> {
244 : using type = std::complex<T>;
245 : };
246 : template <typename T>
247 : struct get_binop_datatype_impl<std::complex<T>, T> {
248 : using type = std::complex<T>;
249 : };
250 : /// @}
251 : /// @{
252 : /// A binary operation between a `VectorImpl` and its underlying value type
253 : /// yields the `VectorImpl`, e.g. `DataVector OP double = DataVector`
254 : template <typename ValueType, typename VectorType, size_t StaticSize>
255 : struct get_binop_datatype_impl<VectorImpl<ValueType, VectorType, StaticSize>,
256 : ValueType> {
257 : using type = VectorType;
258 : };
259 : template <typename ValueType, typename VectorType, size_t StaticSize>
260 : struct get_binop_datatype_impl<ValueType,
261 : VectorImpl<ValueType, VectorType, StaticSize>> {
262 : using type = VectorType;
263 : };
264 : /// @}
265 : /// @{
266 : /// A binary operation between a complex-valued `VectorImpl` and its real
267 : /// component's type yields the `VectorImpl`, e.g.
268 : /// `ComplexDataVector OP double = ComplexDataVector`
269 : template <typename ValueType, typename VectorType, size_t StaticSize>
270 : struct get_binop_datatype_impl<
271 : VectorImpl<std::complex<ValueType>, VectorType, StaticSize>, ValueType> {
272 : using type = VectorType;
273 : };
274 : template <typename ValueType, typename VectorType, size_t StaticSize>
275 : struct get_binop_datatype_impl<
276 : ValueType, VectorImpl<std::complex<ValueType>, VectorType, StaticSize>> {
277 : using type = VectorType;
278 : };
279 : /// @}
280 : /// @{
281 : /// A binary operation between a real-valued `VectorImpl` and the complex-valued
282 : /// partner to the `VectorImpl`'s underlying type yields the complex partner
283 : /// type of the `VectorImpl`, e.g.
284 : /// `std::complex<double> OP DataVector = ComplexDataVector`
285 : ///
286 : /// \note Blaze supports multiplication between a `std::complex<double>` and a
287 : /// `DataVector`, but does not support addition, subtraction, or division
288 : /// between these two types. This specialization of `get_binop_datatype_impl`
289 : /// simply defines that the result of any of the binary operations should be
290 : /// `ComplexDataVector`. Because Blaze doesn't support addition, subtraction,
291 : /// and division between these two types, the `AddSub` and `Divide` classes
292 : /// disallow this type combination in their class definitions to prevent these
293 : /// operations. That way, if Blaze support is later added for e.g. division,
294 : /// we simply need to remove the assert in `Divide` that prevents it.
295 : template <typename ValueType, typename VectorType, size_t StaticSize>
296 : struct get_binop_datatype_impl<VectorImpl<ValueType, VectorType, StaticSize>,
297 : std::complex<ValueType>> {
298 : using type = typename get_complex_datatype<VectorType>::type;
299 : };
300 : template <typename ValueType, typename VectorType, size_t StaticSize>
301 : struct get_binop_datatype_impl<std::complex<ValueType>,
302 : VectorImpl<ValueType, VectorType, StaticSize>> {
303 : using type = typename get_complex_datatype<VectorType>::type;
304 : };
305 : /// @}
306 : /// @{
307 : /// A binary operation between a `DataVector` and a `ComplexDataVector` yields a
308 : /// `ComplexDataVector`
309 : template <>
310 : struct get_binop_datatype_impl<typename ComplexDataVector::BaseType,
311 : typename DataVector::BaseType> {
312 : using type = ComplexDataVector;
313 : };
314 : template <>
315 : struct get_binop_datatype_impl<typename DataVector::BaseType,
316 : typename ComplexDataVector::BaseType> {
317 : using type = ComplexDataVector;
318 : };
319 : /// @}
320 :
321 : /// \brief Get the data type of a binary operation between two data types
322 : /// that may occur in a `TensorExpression`
323 : ///
324 : /// \details
325 : /// See `get_binop_datatype_impl` for which data type combinations have a
326 : /// defined result type
327 : ///
328 : /// \tparam X1 the data type of one operand
329 : /// \tparam X2 the data type of the other operand
330 : template <typename X1, typename X2>
331 : struct get_binop_datatype {
332 : using type = typename get_binop_datatype_impl<
333 : typename upcast_if_derived_vector_type<X1>::type,
334 : typename upcast_if_derived_vector_type<X2>::type>::type;
335 :
336 : static_assert(
337 : not std::is_same_v<type, NoSuchType>,
338 : "You are attempting to perform a binary arithmetic operation between "
339 : "two data types, but the data type of the result is not known within "
340 : "TensorExpressions. See tenex::detail::get_binop_datatype_impl.");
341 : };
342 :
343 : /// @{
344 : /// \brief Whether or not it is permitted to perform binary arithmetic
345 : /// operations with the given types within `TensorExpression`
346 : ///
347 : /// \details
348 : /// See `get_binop_datatype_impl` for which data type combinations have a
349 : /// defined result type
350 : ///
351 : /// \tparam X1 the data type of one operand
352 : /// \tparam X2 the data type of the other operand
353 : template <typename X1, typename X2>
354 : struct binop_datatypes_are_supported
355 : : std::negation<std::is_same<
356 : typename get_binop_datatype_impl<
357 : typename upcast_if_derived_vector_type<X1>::type,
358 : typename upcast_if_derived_vector_type<X2>::type>::type,
359 : NoSuchType>> {};
360 :
361 : template <typename X1, typename X2>
362 : constexpr bool binop_datatypes_are_supported_v =
363 : binop_datatypes_are_supported<X1, X2>::value;
364 : /// @}
365 :
366 : /// \brief Whether or not it is permitted to perform binary arithmetic
367 : /// operations with `Tensor`s with the given types within `TensorExpression`s
368 : ///
369 : /// \details
370 : /// This is used to define which data types can be contained by the two
371 : /// `Tensor`s in a binary operation, e.g.
372 : /// `Tensor<ComplexDataVector>() OP Tensor<DataVector>()` is permitted, but
373 : /// `Tensor<DataVector>() OP Tensor<double>()` is not.
374 : ///
375 : /// To enable binary operations between `Tensor`s with types that are not yet
376 : /// supported, modify a current template specialization or add a new one.
377 : ///
378 : /// \tparam X1 the data type of one `Tensor` operand
379 : /// \tparam X2 the data type of the other `Tensor` operand
380 : template <typename X1, typename X2>
381 : struct tensor_binop_datatypes_are_supported_impl;
382 :
383 : /// Can only do `Tensor<X1>() OP Tensor<X2>()` if `X1 == X2` or if `X1` and
384 : /// `X2` are real/complex partners like `DataVector` and `ComplexDataVector`
385 : /// (see `is_complex_datatype_of`)
386 : template <typename X1, typename X2>
387 : struct tensor_binop_datatypes_are_supported_impl
388 : : std::disjunction<std::is_same<X1, X2>, is_complex_datatype_of<X1, X2>,
389 : is_complex_datatype_of<X2, X1>> {};
390 :
391 : /// @{
392 : /// \brief Whether or not it is permitted to perform binary arithmetic
393 : /// operations with `Tensor`s with the given types within `TensorExpression`s
394 : ///
395 : /// \details
396 : /// See `tensor_binop_datatypes_are_supported_impl` for which data type
397 : /// combinations are permitted
398 : ///
399 : /// \tparam X1 the data type of one `Tensor` operand
400 : /// \tparam X2 the data type of the other `Tensor` operand
401 : template <typename X1, typename X2>
402 : struct tensor_binop_datatypes_are_supported
403 : : tensor_binop_datatypes_are_supported_impl<X1, X2> {
404 : static_assert(
405 : is_supported_tensor_datatype_v<X1> and is_supported_tensor_datatype_v<X2>,
406 : "Cannot perform binary operations between the two Tensors with the "
407 : "given data types because at least one of the data types is not "
408 : "supported by TensorExpressions. See "
409 : "tenex::detail::is_supported_tensor_datatype.");
410 : };
411 :
412 : template <typename X1, typename X2>
413 : constexpr bool tensor_binop_datatypes_are_supported_v =
414 : tensor_binop_datatypes_are_supported<X1, X2>::value;
415 : /// @}
416 :
417 : /// \brief Whether or not it is permitted to perform binary arithmetic
418 : /// operations with `TensorExpression`s, based on their data types
419 : ///
420 : /// \details
421 : /// This is used to define which data types can be contained by the two
422 : /// `TensorExpression`s in a binary operation, e.g.
423 : /// `Tensor<DataVector>() OP double` and
424 : /// `Tensor<ComplexDataVector>() OP Tensor<DataVector>()` are permitted, but
425 : /// `Tensor<DataVector>() OP Tensor<double>()` is not. This differs from
426 : /// `tensor_binop_datatypes_are_supported` in that
427 : /// `tensorexpression_binop_datatypes_are_supported_impl` handles all derived
428 : /// `TensorExpression` types, whether they represent `Tensor`s or numbers.
429 : /// `tensor_binop_datatypes_are_supported` only handles the cases where both
430 : /// `TensorExpression`s represent `Tensor`s.
431 : ///
432 : /// To enable binary operations between `TensorExpression`s with types that
433 : /// are not yet supported, modify a current template specialization or add a
434 : /// new one.
435 : ///
436 : /// \tparam T1 the first `TensorExpression` operand
437 : /// \tparam T2 the second `TensorExpression` operand
438 : template <typename T1, typename T2>
439 : struct tensorexpression_binop_datatypes_are_supported_impl;
440 :
441 : /// Since `T1` and `T2` represent `Tensor`s, check if we can do
442 : /// `Tensor<T1::type>() OP Tensor<T2::type>()`
443 : template <typename T1, typename T2>
444 : struct tensorexpression_binop_datatypes_are_supported_impl
445 : : tensor_binop_datatypes_are_supported_impl<typename T1::type,
446 : typename T2::type> {};
447 : /// @{
448 : /// Can do `Tensor<X>() OP NUMBER` if we can do `X OP NUMBER`
449 : template <typename TensorExpressionType, typename NumberType>
450 : struct tensorexpression_binop_datatypes_are_supported_impl<
451 : TensorExpressionType, NumberAsExpression<NumberType>>
452 : : binop_datatypes_are_supported<typename TensorExpressionType::type,
453 : NumberType> {
454 : static_assert(
455 : is_supported_tensor_datatype_v<typename TensorExpressionType::type> and
456 : is_supported_number_datatype_v<NumberType>,
457 : "Cannot perform binary operations between Tensor and number with the "
458 : "given data types because at least one of the data types is not "
459 : "supported by TensorExpressions. See "
460 : "tenex::detail::is_supported_number_datatype and "
461 : "tenex::detail::is_supported_tensor_datatype.");
462 : };
463 : template <typename NumberType, typename TensorExpressionType>
464 : struct tensorexpression_binop_datatypes_are_supported_impl<
465 : NumberAsExpression<NumberType>, TensorExpressionType>
466 : : tensorexpression_binop_datatypes_are_supported_impl<
467 : TensorExpressionType, NumberAsExpression<NumberType>> {};
468 : /// @}
469 :
470 : /// @{
471 : /// \brief Whether or not it is permitted to perform binary arithmetic
472 : /// operations with `TensorExpression`s, based on their data types
473 : ///
474 : /// \details
475 : /// See `tensorexpression_binop_datatypes_are_supported_impl` for which data
476 : /// type combinations are permitted
477 : ///
478 : /// \tparam T1 the first `TensorExpression` operand
479 : /// \tparam T2 the second `TensorExpression` operand
480 : template <typename T1, typename T2>
481 : struct tensorexpression_binop_datatypes_are_supported
482 : : tensorexpression_binop_datatypes_are_supported_impl<T1, T2> {
483 : static_assert(
484 : std::is_base_of_v<Expression, T1> and std::is_base_of_v<Expression, T2>,
485 : "Template arguments to "
486 : "tenex::detail::tensorexpression_binop_datatypes_are_supported must be "
487 : "TensorExpressions.");
488 : };
489 :
490 : template <typename X1, typename X2>
491 : constexpr bool tensorexpression_binop_datatypes_are_supported_v =
492 : tensorexpression_binop_datatypes_are_supported<X1, X2>::value;
493 : /// @}
494 :
495 : /// \brief The maximum number of arithmetic tensor operations allowed in a
496 : /// `TensorExpression` subtree before having it be a splitting point in the
497 : /// overall RHS expression, according to the data type held by the `Tensor`s in
498 : /// the expression
499 : ///
500 : /// \details
501 : /// To enable splitting for `TensorExpression`s with data type, define a
502 : /// template specialization below for your data type and set the `value`.
503 : ///
504 : /// Before defining a max operations cap for some data type, the change should
505 : /// first be justified by benchmarking many different tensor expressions before
506 : /// and after introducing the new cap. The optimal cap will likely be
507 : /// hardware-dependent, so fine-tuning this would ideally involve benchmarking
508 : /// on each hardware architecture and then controling the value based on the
509 : /// hardware.
510 : template <typename DataType>
511 : struct max_num_ops_in_sub_expression_impl {
512 : // effectively, no splitting for any unspecialized template type
513 : static constexpr size_t value = std::numeric_limits<size_t>::max();
514 : };
515 :
516 : /// \brief When the data type of the result of a `TensorExpression` is
517 : /// `DataVector`, the maximum number of arithmetic tensor operations allowed in
518 : /// a subtree before having it be a splitting point in the overall RHS
519 : /// expression
520 : ///
521 : /// \details
522 : /// The current value set for when the data type is `DataVector` was benchmarked
523 : /// by compiling with clang-10 Release and running on Intel(R) Xeon(R)
524 : /// CPU E5-2630 v4 @ 2.20GHz.
525 : template <>
526 : struct max_num_ops_in_sub_expression_impl<DataVector> {
527 : static constexpr size_t value = 8;
528 : };
529 :
530 : /// \brief When the data type of the result of a `TensorExpression` is
531 : /// `ComplexDataVector`, the maximum number of arithmetic tensor operations
532 : /// allowed in a subtree before having it be a splitting point in the overall
533 : /// RHS expression
534 : ///
535 : /// \details
536 : /// The current value set for when the data type is `ComplexDataVector` is set
537 : /// to the value for `DataVector`, but the best `value` for `ComplexDataVector`
538 : /// should also be investigated and fine-tuned.
539 : template <>
540 : struct max_num_ops_in_sub_expression_impl<ComplexDataVector> {
541 : static constexpr size_t value =
542 : max_num_ops_in_sub_expression_impl<DataVector>::value;
543 : };
544 :
545 : /// \brief Get maximum number of arithmetic tensor operations allowed in a
546 : /// `TensorExpression` subtree before having it be a splitting point in the
547 : /// overall RHS expression, according to the `DataType` held by the `Tensor`s in
548 : /// the expression
549 : template <typename DataType>
550 : inline constexpr size_t max_num_ops_in_sub_expression =
551 : max_num_ops_in_sub_expression_impl<DataType>::value;
552 : } // namespace detail
553 : } // namespace tenex
|