MFEM v4.9.0
Finite element discretization library
Loading...
Searching...
No Matches
tensor.hpp
Go to the documentation of this file.
1// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
2// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
3// LICENSE and NOTICE for details. LLNL-CODE-806117.
4//
5// This file is part of the MFEM library. For more information and source code
6// availability visit https://mfem.org.
7//
8// MFEM is free software; you can redistribute it and/or modify it under the
9// terms of the BSD-3 license. We welcome feedback and contributions, see file
10// CONTRIBUTING.md for details.
11
12/**
13 * @file tensor.hpp
14 *
15 * @brief Implementation of the tensor class
16 */
17
18#pragma once
19
21#include "dual.hpp"
22#include <limits>
23#include <type_traits> // for std::false_type
24
25namespace mfem
26{
27namespace future
28{
29
30template <typename T, int... n>
31struct tensor;
32
33/// The implementation can be drastically generalized by using concepts of the
34/// c++17 standard.
35
36template < typename T >
37struct tensor<T>
38{
39 using type = T;
40 static constexpr int ndim = 1;
41 static constexpr int first_dim = 0;
42 MFEM_HOST_DEVICE T& operator[](int /*unused*/) { return values; }
43 MFEM_HOST_DEVICE const T& operator[](int /*unused*/) const { return values; }
44 MFEM_HOST_DEVICE T& operator()(int /*unused*/) { return values; }
45 MFEM_HOST_DEVICE const T& operator()(int /*unused*/) const { return values; }
46 MFEM_HOST_DEVICE operator T() const { return values; }
48};
49
50template < typename T, int n0 >
51struct tensor<T, n0>
52{
53 using type = T;
54 static constexpr int ndim = 1;
55 static constexpr int first_dim = n0;
56 MFEM_HOST_DEVICE T& operator[](int i) { return values[i]; }
57 MFEM_HOST_DEVICE const T& operator[](int i) const { return values[i]; }
58 MFEM_HOST_DEVICE T& operator()(int i) { return values[i]; }
59 MFEM_HOST_DEVICE const T& operator()(int i) const { return values[i]; }
60 T values[n0];
61};
62
63template < typename T >
64struct tensor<T, 0>
65{
66 using type = T;
67 static constexpr int ndim = 1;
68 static constexpr int first_dim = 0;
69 MFEM_HOST_DEVICE T& operator[](int /*unused*/) { return values; }
70 MFEM_HOST_DEVICE const T& operator[](int /*unused*/) const { return values; }
71 MFEM_HOST_DEVICE T& operator()(int /*unused*/) { return values; }
72 MFEM_HOST_DEVICE const T& operator()(int /*unused*/) const { return values; }
74};
75
76template < typename T, int n0, int n1 >
77struct tensor<T, n0, n1>
78{
79 using type = T;
80 static constexpr int ndim = 2;
81 static constexpr int first_dim = n0;
82 MFEM_HOST_DEVICE tensor< T, n1 >& operator[](int i) { return values[i]; }
83 MFEM_HOST_DEVICE const tensor< T, n1 >& operator[](int i) const { return values[i]; }
84 MFEM_HOST_DEVICE tensor< T, n1 >& operator()(int i) { return values[i]; }
85 MFEM_HOST_DEVICE const tensor< T, n1 >& operator()(int i) const { return values[i]; }
86 MFEM_HOST_DEVICE T& operator()(int i, int j) { return values[i][j]; }
87 MFEM_HOST_DEVICE const T& operator()(int i, int j) const { return values[i][j]; }
88 tensor < T, n1 > values[n0];
89};
90
91template < typename T, int n1 >
92struct tensor<T, 0, n1>
93{
94 using type = T;
95 static constexpr int ndim = 2;
96 static constexpr int first_dim = 0;
97 MFEM_HOST_DEVICE tensor< T, n1 >& operator[](int /*unused*/) { return values; }
98 MFEM_HOST_DEVICE const tensor< T, n1 >& operator[](int /*unused*/) const { return values; }
99 MFEM_HOST_DEVICE tensor< T, n1 >& operator()(int /*unused*/) { return values; }
100 MFEM_HOST_DEVICE const tensor< T, n1 >& operator()(int /*unused*/) const { return values; }
101 MFEM_HOST_DEVICE T& operator()(int /*unused*/, int j) { return values[j]; }
102 MFEM_HOST_DEVICE const T& operator()(int /*unused*/, int j) const { return values[j]; }
103 tensor < T, n1 > values;
104};
105
106template < typename T, int n0, int n1, int n2 >
107struct tensor<T, n0, n1, n2>
108{
109 using type = T;
110 static constexpr int ndim = 3;
111 static constexpr int first_dim = n0;
112 MFEM_HOST_DEVICE tensor< T, n1, n2 >& operator[](int i) { return values[i]; }
113 MFEM_HOST_DEVICE const tensor< T, n1, n2 >& operator[](int i) const { return values[i]; }
114 MFEM_HOST_DEVICE tensor< T, n1, n2 >& operator()(int i) { return values[i]; }
115 MFEM_HOST_DEVICE const tensor< T, n1, n2 >& operator()(int i) const { return values[i]; }
116 MFEM_HOST_DEVICE tensor< T, n2 >& operator()(int i, int j) { return values[i][j]; }
117 MFEM_HOST_DEVICE const tensor< T, n2 >& operator()(int i, int j) const { return values[i][j]; }
118 MFEM_HOST_DEVICE T& operator()(int i, int j, int k) { return values[i][j][k]; }
119 MFEM_HOST_DEVICE const T& operator()(int i, int j, int k) const { return values[i][j][k]; }
120 tensor < T, n1, n2 > values[n0];
121};
122
123template < typename T, int n0, int n1, int n2, int n3 >
124struct tensor<T, n0, n1, n2, n3>
125{
126 using type = T;
127 static constexpr int ndim = 4;
128 static constexpr int first_dim = n0;
129 MFEM_HOST_DEVICE tensor< T, n1, n2, n3 >& operator[](int i) { return values[i]; }
130 MFEM_HOST_DEVICE const tensor< T, n1, n2, n3 >& operator[](int i) const { return values[i]; }
131 MFEM_HOST_DEVICE tensor< T, n1, n2, n3 >& operator()(int i) { return values[i]; }
132 MFEM_HOST_DEVICE const tensor< T, n1, n2, n3 >& operator()(int i) const { return values[i]; }
133 MFEM_HOST_DEVICE tensor< T, n2, n3 >& operator()(int i, int j) { return values[i][j]; }
134 MFEM_HOST_DEVICE const tensor< T, n2, n3 >& operator()(int i, int j) const { return values[i][j]; }
135 MFEM_HOST_DEVICE tensor< T, n3 >& operator()(int i, int j, int k) { return values[i][j][k]; }
136 MFEM_HOST_DEVICE const tensor< T, n3 >& operator()(int i, int j, int k) const { return values[i][j][k]; }
137 MFEM_HOST_DEVICE T& operator()(int i, int j, int k, int l) { return values[i][j][k][l]; }
138 MFEM_HOST_DEVICE const T& operator()(int i, int j, int k, int l) const { return values[i][j][k][l]; }
139 tensor < T, n1, n2, n3 > values[n0];
140};
141
142template < typename T, int n0, int n1, int n2, int n3, int n4 >
143struct tensor<T, n0, n1, n2, n3, n4>
144{
145 using type = T;
146 static constexpr int ndim = 5;
147 static constexpr int first_dim = n0;
148 MFEM_HOST_DEVICE tensor< T, n1, n2, n3, n4 >& operator[](int i) { return values[i]; }
149 MFEM_HOST_DEVICE const tensor< T, n1, n2, n3, n4 >& operator[](int i) const { return values[i]; }
150 MFEM_HOST_DEVICE tensor< T, n1, n2, n3, n4 >& operator()(int i) { return values[i]; }
151 MFEM_HOST_DEVICE const tensor< T, n1, n2, n3, n4 >& operator()(int i) const { return values[i]; }
152 MFEM_HOST_DEVICE tensor< T, n2, n3, n4 >& operator()(int i, int j) { return values[i][j]; }
153 MFEM_HOST_DEVICE const tensor< T, n2, n3, n4 >& operator()(int i,
154 int j) const { return values[i][j]; }
155 MFEM_HOST_DEVICE tensor< T, n3, n4>& operator()(int i, int j, int k) { return values[i][j][k]; }
156 MFEM_HOST_DEVICE const tensor< T, n3, n4>& operator()(int i, int j,
157 int k) const { return values[i][j][k]; }
158 MFEM_HOST_DEVICE tensor< T, n4 >& operator()(int i, int j, int k, int l) { return values[i][j][k][l]; }
159 MFEM_HOST_DEVICE const tensor< T, n4 >& operator()(int i, int j, int k,
160 int l) const { return values[i][j][k][l]; }
161 MFEM_HOST_DEVICE T& operator()(int i, int j, int k, int l, int m) { return values[i][j][k][l][m]; }
162 MFEM_HOST_DEVICE const T& operator()(int i, int j, int k, int l, int m) const { return values[i][j][k][l][m]; }
163 tensor < T, n1, n2, n3, n4 > values[n0];
164};
165
166/**
167 * @brief A sentinel struct for eliding no-op tensor operations
168 */
169struct zero
170{
171 /** @brief `zero` is implicitly convertible to real_t with value 0.0 */
172 MFEM_HOST_DEVICE operator real_t() { return 0.0; }
173
174 /** @brief `zero` is implicitly convertible to a tensor of any shape */
175 template <typename T, int... n>
176 MFEM_HOST_DEVICE operator tensor<T, n...>()
177 {
178 return tensor<T, n...> {};
179 }
180
181 /** @brief `zero` can be accessed like a multidimensional array */
182 template <typename... T>
183 MFEM_HOST_DEVICE zero operator()(T...)
184 {
185 return zero{};
186 }
187
188 /** @brief anything assigned to `zero` does not change its value and returns `zero` */
189 template <typename T>
190 MFEM_HOST_DEVICE zero operator=(T)
191 {
192 return zero{};
193 }
194};
195
196/** @brief checks if a type is `zero` */
197template <typename T>
198struct is_zero : std::false_type
199{
200};
201
202/** @overload */
203template <>
204struct is_zero<zero> : std::true_type
205{
206};
207
208/** @brief the sum of two `zero`s is `zero` */
209MFEM_HOST_DEVICE constexpr zero operator+(zero, zero) { return zero{}; }
210
211/** @brief the sum of `zero` with something non-`zero` just returns the other value */
212template <typename T>
213MFEM_HOST_DEVICE constexpr T operator+(zero, T other)
214{
215 return other;
216}
217
218/** @brief the sum of `zero` with something non-`zero` just returns the other value */
219template <typename T>
220MFEM_HOST_DEVICE constexpr T operator+(T other, zero)
221{
222 return other;
223}
224
225/////////////////////////////////////////////////
226
227/** @brief the unary negation of `zero` is `zero` */
228MFEM_HOST_DEVICE constexpr zero operator-(zero) { return zero{}; }
229
230/** @brief the difference of two `zero`s is `zero` */
231MFEM_HOST_DEVICE constexpr zero operator-(zero, zero) { return zero{}; }
232
233/** @brief the difference of `zero` with something else is the unary negation of the other thing */
234template <typename T>
235MFEM_HOST_DEVICE constexpr T operator-(zero, T other)
236{
237 return -other;
238}
239
240/** @brief the difference of something else with `zero` is the other thing itself */
241template <typename T>
242MFEM_HOST_DEVICE constexpr T operator-(T other, zero)
243{
244 return other;
245}
246
247/////////////////////////////////////////////////
248
249/** @brief the product of two `zero`s is `zero` */
250MFEM_HOST_DEVICE constexpr zero operator*(zero, zero) { return zero{}; }
251
252/** @brief the product `zero` with something else is also `zero` */
253template <typename T>
254MFEM_HOST_DEVICE constexpr zero operator*(zero, T /*other*/)
255{
256 return zero{};
257}
258
259/** @brief the product `zero` with something else is also `zero` */
260template <typename T>
261MFEM_HOST_DEVICE constexpr zero operator*(T /*other*/, zero)
262{
263 return zero{};
264}
265
266/** @brief `zero` divided by something is `zero` */
267template <typename T>
268MFEM_HOST_DEVICE constexpr zero operator/(zero, T /*other*/)
269{
270 return zero{};
271}
272
273/** @brief `zero` plus `zero` is `zero */
274MFEM_HOST_DEVICE constexpr zero operator+=(zero, zero) { return zero{}; }
275
276/** @brief `zero` minus `zero` is `zero */
277MFEM_HOST_DEVICE constexpr zero operator-=(zero, zero) { return zero{}; }
278
279/** @brief let `zero` be accessed like a tuple */
280template <int i>
281MFEM_HOST_DEVICE zero& get(zero& x)
282{
283 return x;
284}
285
286/** @brief the dot product of anything with `zero` is `zero` */
287template <typename T>
288MFEM_HOST_DEVICE zero dot(const T&, zero)
289{
290 return zero{};
291}
292
293/** @brief the dot product of anything with `zero` is `zero` */
294template <typename T>
295MFEM_HOST_DEVICE zero dot(zero, const T&)
296{
297 return zero{};
298}
299
300/**
301 * @brief Removes 1s from tensor dimensions
302 * For example, a tensor<T, 1, 10> is equivalent to a tensor<T, 10>
303 * @tparam T The scalar type of the tensor
304 * @tparam n1 The first dimension
305 * @tparam n2 The second dimension
306 */
307template <typename T, int n1, int n2 = 1>
308using reduced_tensor = typename std::conditional<
309 (n1 == 1 && n2 == 1), T,
310 typename std::conditional<n1 == 1, tensor<T, n2>,
311 typename std::conditional<n2 == 1, tensor<T, n1>, tensor<T, n1, n2>
312 >::type
313 >::type
314 >::type;
315
316/**
317 * @brief Creates a tensor of requested dimension by subsequent calls to a functor
318 * Can be thought of as analogous to @p std::transform in that the set of possible
319 * indices for dimensions @p n are transformed into the values of the tensor by @a f
320 * @tparam lambda_type The type of the functor
321 * @param[in] f The functor to generate the tensor values from
322 *
323 * @note the different cases of 0D, 1D, 2D, 3D, and 4D are implemented separately
324 * to work around a limitation in nvcc involving __host__ __device__ lambdas with `auto` parameters.
325 */
326template <typename lambda_type>
327MFEM_HOST_DEVICE constexpr auto make_tensor(lambda_type f) ->
328tensor<decltype(f())>
329{
330 return {f()};
331}
332
333/**
334 * @brief Creates a tensor of requested dimension by subsequent calls to a functor
335 *
336 * @tparam n1 The dimension of the tensor
337 * @tparam lambda_type The type of the functor
338 * @param[in] f The functor to generate the tensor values from
339 * @pre @a f must accept @p n1 arguments of type @p int
340 *
341 * @note the different cases of 0D, 1D, 2D, 3D, and 4D are implemented separately
342 * to work around a limitation in nvcc involving __host__ __device__ lambdas with `auto` parameters.
343 */
344template <int n1, typename lambda_type>
345MFEM_HOST_DEVICE auto make_tensor(lambda_type f) ->
346tensor<decltype(f(n1)), n1>
347{
348 using T = decltype(f(n1));
349 tensor<T, n1> A{};
350 for (int i = 0; i < n1; i++)
351 {
352 A(i) = f(i);
353 }
354 return A;
355}
356
357/**
358 * @brief Creates a tensor of requested dimension by subsequent calls to a functor
359 *
360 * @tparam n1 The first dimension of the tensor
361 * @tparam n2 The second dimension of the tensor
362 * @tparam lambda_type The type of the functor
363 * @param[in] f The functor to generate the tensor values from
364 * @pre @a f must accept @p n1 x @p n2 arguments of type @p int
365 *
366 * @note the different cases of 0D, 1D, 2D, 3D, and 4D are implemented separately
367 * to work around a limitation in nvcc involving __host__ __device__ lambdas with `auto` parameters.
368 */
369template <int n1, int n2, typename lambda_type>
370MFEM_HOST_DEVICE auto make_tensor(lambda_type f) ->
371tensor<decltype(f(n1, n2)), n1, n2>
372{
373 using T = decltype(f(n1, n2));
375 for (int i = 0; i < n1; i++)
376 {
377 for (int j = 0; j < n2; j++)
378 {
379 A(i, j) = f(i, j);
380 }
381 }
382 return A;
383}
384
385/**
386 * @brief Creates a tensor of requested dimension by subsequent calls to a functor
387 *
388 * @tparam n1 The first dimension of the tensor
389 * @tparam n2 The second dimension of the tensor
390 * @tparam n3 The third dimension of the tensor
391 * @tparam lambda_type The type of the functor
392 * @param[in] f The functor to generate the tensor values from
393 * @pre @a f must accept @p n1 x @p n2 x @p n3 arguments of type @p int
394 *
395 * @note the different cases of 0D, 1D, 2D, 3D, and 4D are implemented separately
396 * to work around a limitation in nvcc involving __host__ __device__ lambdas with `auto` parameters.
397 */
398template <int n1, int n2, int n3, typename lambda_type>
399MFEM_HOST_DEVICE auto make_tensor(lambda_type f) ->
400tensor<decltype(f(n1, n2, n3)), n1, n2, n3>
401{
402 using T = decltype(f(n1, n2, n3));
404 for (int i = 0; i < n1; i++)
405 {
406 for (int j = 0; j < n2; j++)
407 {
408 for (int k = 0; k < n3; k++)
409 {
410 A(i, j, k) = f(i, j, k);
411 }
412 }
413 }
414 return A;
415}
416
417/**
418 * @brief Creates a tensor of requested dimension by subsequent calls to a functor
419 *
420 * @tparam n1 The first dimension of the tensor
421 * @tparam n2 The second dimension of the tensor
422 * @tparam n3 The third dimension of the tensor
423 * @tparam n4 The fourth dimension of the tensor
424 * @tparam lambda_type The type of the functor
425 * @param[in] f The functor to generate the tensor values from
426 * @pre @a f must accept @p n1 x @p n2 x @p n3 x @p n4 arguments of type @p int
427 *
428 * @note the different cases of 0D, 1D, 2D, 3D, and 4D are implemented separately
429 * to work around a limitation in nvcc involving __host__ __device__ lambdas with `auto` parameters.
430 */
431template <int n1, int n2, int n3, int n4, typename lambda_type>
432MFEM_HOST_DEVICE auto make_tensor(lambda_type f) ->
433tensor<decltype(f(n1, n2, n3, n4)), n1, n2, n3, n4>
434{
435 using T = decltype(f(n1, n2, n3, n4));
437 for (int i = 0; i < n1; i++)
438 {
439 for (int j = 0; j < n2; j++)
440 {
441 for (int k = 0; k < n3; k++)
442 {
443 for (int l = 0; l < n4; l++)
444 {
445 A(i, j, k, l) = f(i, j, k, l);
446 }
447 }
448 }
449 }
450 return A;
451}
452
453// needs to be generalized
454template <typename T, int m, int n> MFEM_HOST_DEVICE
456{
457 tensor<T, n> c{};
458 c(0) = A[0][j];
459 c(1) = A[1][j];
460 return c;
461}
462
463/// @overload
464template <typename T> MFEM_HOST_DEVICE
466{
467 return tensor<T, 1> {A[0][0]};
468}
469
470/**
471 * @brief return the sum of two tensors
472 * @tparam S the underlying type of the lefthand argument
473 * @tparam T the underlying type of the righthand argument
474 * @tparam n integers describing the tensor shape
475 * @param[in] A The lefthand operand
476 * @param[in] B The righthand operand
477 */
478template <typename S, typename T, int... n>
479MFEM_HOST_DEVICE auto operator+(const tensor<S, n...>& A,
480 const tensor<T, n...>& B) ->
481tensor<decltype(S {} + T{}), n...>
482{
483 tensor<decltype(S{} + T{}), n...> C{};
484 for (int i = 0; i < tensor<T, n...>::first_dim; i++)
485 {
486 C[i] = A[i] + B[i];
487 }
488 return C;
489}
490
491/**
492 * @brief return the unary negation of a tensor
493 * @tparam T the underlying type of the righthand argument
494 * @tparam n integers describing the tensor shape
495 * @param[in] A The tensor to negate
496 */
497template <typename T, int... n>
498MFEM_HOST_DEVICE tensor<T, n...> operator-(const tensor<T, n...>& A)
499{
500 tensor<T, n...> B{};
501 for (int i = 0; i < tensor<T, n...>::first_dim; i++)
502 {
503 B[i] = -A[i];
504 }
505 return B;
506}
507
508/**
509 * @brief return the difference of two tensors
510 * @tparam S the underlying type of the lefthand argument
511 * @tparam T the underlying type of the righthand argument
512 * @tparam n integers describing the tensor shape
513 * @param[in] A The lefthand operand
514 * @param[in] B The righthand operand
515 */
516template <typename S, typename T, int... n>
517MFEM_HOST_DEVICE auto operator-(const tensor<S, n...>& A,
518 const tensor<T, n...>& B) ->
519tensor<decltype(S {} + T{}), n...>
520{
521 tensor<decltype(S{} + T{}), n...> C{};
522 for (int i = 0; i < tensor<T, n...>::first_dim; i++)
523 {
524 C[i] = A[i] - B[i];
525 }
526 return C;
527}
528
529/**
530 * @brief multiply a tensor by a scalar value
531 * @tparam S the scalar value type. Must be arithmetic (e.g. float, real_t, int) or a dual number
532 * @tparam T the underlying type of the tensor (righthand) argument
533 * @tparam n integers describing the tensor shape
534 * @param[in] scale The scaling factor
535 * @param[in] A The tensor to be scaled
536 */
537template <typename S, typename T, int... n,
538 typename = typename std::enable_if<std::is_arithmetic<S>::value ||
539 is_dual_number<S>::value>::type>
540MFEM_HOST_DEVICE auto operator*(S scale, const tensor<T, n...>& A) ->
541tensor<decltype(S {} * T{}), n...>
542{
543 tensor<decltype(S{} * T{}), n...> C{};
544 for (int i = 0; i < tensor<T, n...>::first_dim; i++)
545 {
546 C[i] = scale * A[i];
547 }
548 return C;
549}
550
551/**
552 * @brief multiply a tensor by a scalar value
553 * @tparam S the scalar value type. Must be arithmetic (e.g. float, real_t, int) or a dual number
554 * @tparam T the underlying type of the tensor (righthand) argument
555 * @tparam n integers describing the tensor shape
556 * @param[in] A The tensor to be scaled
557 * @param[in] scale The scaling factor
558 */
559template <typename S, typename T, int... n,
560 typename = typename std::enable_if<std::is_arithmetic<S>::value ||
561 is_dual_number<S>::value>::type>
562MFEM_HOST_DEVICE auto operator*(const tensor<T, n...>& A, S scale) ->
563tensor<decltype(T {} * S{}), n...>
564{
565 tensor<decltype(T{} * S{}), n...> C{};
566 for (int i = 0; i < tensor<T, n...>::first_dim; i++)
567 {
568 C[i] = A[i] * scale;
569 }
570 return C;
571}
572
573/**
574 * @brief divide a scalar by each element in a tensor
575 * @tparam S the scalar value type. Must be arithmetic (e.g. float, real_t, int) or a dual number
576 * @tparam T the underlying type of the tensor (righthand) argument
577 * @tparam n integers describing the tensor shape
578 * @param[in] scale The numerator
579 * @param[in] A The tensor of denominators
580 */
581template <typename S, typename T, int... n,
582 typename = typename std::enable_if<std::is_arithmetic<S>::value ||
583 is_dual_number<S>::value>::type>
584MFEM_HOST_DEVICE auto operator/(S scale, const tensor<T, n...>& A) ->
585tensor<decltype(S {} * T{}), n...>
586{
587 tensor<decltype(S{} * T{}), n...> C{};
588 for (int i = 0; i < tensor<T, n...>::first_dim; i++)
589 {
590 C[i] = scale / A[i];
591 }
592 return C;
593}
594
595/**
596 * @brief divide a tensor by a scalar
597 * @tparam S the scalar value type. Must be arithmetic (e.g. float, real_t, int) or a dual number
598 * @tparam T the underlying type of the tensor (righthand) argument
599 * @tparam n integers describing the tensor shape
600 * @param[in] A The tensor of numerators
601 * @param[in] scale The denominator
602 */
603template <typename S, typename T, int... n,
604 typename = typename std::enable_if<std::is_arithmetic<S>::value ||
605 is_dual_number<S>::value>::type>
606MFEM_HOST_DEVICE auto operator/(const tensor<T, n...>& A, S scale) ->
607tensor<decltype(T {} * S{}), n...>
608{
609 tensor<decltype(T{} * S{}), n...> C{};
610 for (int i = 0; i < tensor<T, n...>::first_dim; i++)
611 {
612 C[i] = A[i] / scale;
613 }
614 return C;
615}
616
617/**
618 * @brief compound assignment (+) on tensors
619 * @tparam S the underlying type of the tensor (lefthand) argument
620 * @tparam T the underlying type of the tensor (righthand) argument
621 * @tparam n integers describing the tensor shape
622 * @param[in] A The lefthand tensor
623 * @param[in] B The righthand tensor
624 */
625template <typename S, typename T, int... n> MFEM_HOST_DEVICE
627 const tensor<T, n...>& B)
628{
629 for (int i = 0; i < tensor<S, n...>::first_dim; i++)
630 {
631 A[i] += B[i];
632 }
633 return A;
634}
635
636/**
637 * @brief compound assignment (+) on tensors
638 * @tparam T the underlying type of the tensor argument
639 * @param[in] A The lefthand tensor
640 * @param[in] B The righthand tensor
641 */
642template <typename T> MFEM_HOST_DEVICE
644{
645 return A.values += B;
646}
647
648/**
649 * @brief compound assignment (+) on tensors
650 * @tparam T the underlying type of the tensor argument
651 * @param[in] A The lefthand tensor
652 * @param[in] B The righthand tensor
653 */
654template <typename T> MFEM_HOST_DEVICE
656{
657 return A.values += B;
658}
659
660/**
661 * @brief compound assignment (+) on tensors
662 * @tparam T the underlying type of the tensor argument
663 * @param[in] A The lefthand tensor
664 * @param[in] B The righthand tensor
665 */
666template <typename T> MFEM_HOST_DEVICE
668{
669 return A.values += B;
670}
671
672/**
673 * @brief compound assignment (+) between a tensor and zero (no-op)
674 * @tparam T the underlying type of the tensor (righthand) argument
675 * @tparam n integers describing the tensor shape
676 * @param[in] A The lefthand tensor
677 */
678template <typename T, int... n> MFEM_HOST_DEVICE
680{
681 return A;
682}
683
684/**
685 * @brief compound assignment (-) on tensors
686 * @tparam S the underlying type of the tensor (lefthand) argument
687 * @tparam T the underlying type of the tensor (righthand) argument
688 * @tparam n integers describing the tensor shape
689 * @param[in] A The lefthand tensor
690 * @param[in] B The righthand tensor
691 */
692template <typename S, typename T, int... n> MFEM_HOST_DEVICE
694{
695 for (int i = 0; i < tensor<S, n...>::first_dim; i++)
696 {
697 A[i] -= B[i];
698 }
699 return A;
700}
701
702/**
703 * @brief compound assignment (-) between a tensor and zero (no-op)
704 * @tparam T the underlying type of the tensor (righthand) argument
705 * @tparam n integers describing the tensor shape
706 * @param[in] A The lefthand tensor
707 */
708template <typename T, int... n> MFEM_HOST_DEVICE
709constexpr tensor<T, n...>& operator-=(tensor<T, n...>& A, zero)
710{
711 return A;
712}
713
714/**
715 * @brief compute the outer product of two tensors
716 * @tparam S the type of the lefthand argument
717 * @tparam T the type of the righthand argument
718 * @param[in] A The lefthand argument
719 * @param[in] B The righthand argument
720 *
721 * @note this overload implements the special case where both arguments are scalars
722 */
723template <typename S, typename T> MFEM_HOST_DEVICE
724auto outer(S A, T B) -> decltype(A * B)
725{
726 static_assert(std::is_arithmetic<S>::value && std::is_arithmetic<T>::value,
727 "outer product types must be tensor or arithmetic_type");
728 return A * B;
729}
730
731template <typename T, int n, int m> MFEM_HOST_DEVICE
733{
735 for (int i = 0; i < n; i++)
736 {
737 for (int j = 0; j < m; j++)
738 {
739 B(i + j * m) = A(i, j);
740 }
741 }
742 return B;
743}
744
745/**
746 * @overload
747 * @note this overload implements the case where the left argument is a scalar, and the right argument is a tensor
748 */
749template <typename S, typename T, int n> MFEM_HOST_DEVICE
750tensor<decltype(S{} * T{}), n> outer(S A, tensor<T, n> B)
751{
752 static_assert(std::is_arithmetic<S>::value,
753 "outer product types must be tensor or arithmetic_type");
754 tensor<decltype(S{} * T{}), n> AB{};
755 for (int i = 0; i < n; i++)
756 {
757 AB[i] = A * B[i];
758 }
759 return AB;
760}
761
762/**
763 * @overload
764 * @note this overload implements the case where the left argument is a tensor, and the right argument is a scalar
765 */
766template <typename S, typename T, int m> MFEM_HOST_DEVICE
767tensor<decltype(S{} * T{}), m> outer(const tensor<S, m>& A, T B)
768{
769 static_assert(std::is_arithmetic<T>::value,
770 "outer product types must be tensor or arithmetic_type");
771 tensor<decltype(S{} * T{}), m> AB{};
772 for (int i = 0; i < m; i++)
773 {
774 AB[i] = A[i] * B;
775 }
776 return AB;
777}
778
779/**
780 * @overload
781 * @note this overload implements the case where the left argument is `zero`, and the right argument is a tensor
782 */
783template <typename T, int n> MFEM_HOST_DEVICE
785{
786 return zero{};
787}
788
789/**
790 * @overload
791 * @note this overload implements the case where the left argument is a tensor, and the right argument is `zero`
792 */
793template <typename T, int n> MFEM_HOST_DEVICE
795{
796 return zero{};
797}
798
799/**
800 * @overload
801 * @note this overload implements the case where the left argument is a scalar,
802 * and the right argument is a tensor
803 */
804template <typename S, typename T, int m, int n> MFEM_HOST_DEVICE
805tensor<decltype(S{} * T{}), m, n> outer(S A, const tensor<T, m, n>& B)
806{
807 static_assert(std::is_arithmetic<S>::value,
808 "outer product types must be tensor or arithmetic_type");
809 tensor<decltype(S{} * T{}), m, n> AB{};
810 for (int i = 0; i < m; i++)
811 {
812 for (int j = 0; j < n; j++)
813 {
814 AB[i][j] = A * B[i][j];
815 }
816 }
817 return AB;
818}
819
820/**
821 * @overload
822 * @note this overload implements the case where both arguments are vectors
823 */
824template <typename S, typename T, int m, int n> MFEM_HOST_DEVICE
825tensor<decltype(S{} * T{}), m, n> outer(const tensor<S, m>& A,
826 const tensor<T, n>& B)
827{
828 tensor<decltype(S{} * T{}), m, n> AB{};
829 for (int i = 0; i < m; i++)
830 {
831 for (int j = 0; j < n; j++)
832 {
833 AB[i][j] = A[i] * B[j];
834 }
835 }
836 return AB;
837}
838
839/**
840 * @overload
841 * @note this overload implements the case where the left argument is a 2nd order tensor, and the right argument is a
842 * scalar
843 */
844template <typename S, typename T, int m, int n> MFEM_HOST_DEVICE
845tensor<decltype(S{} * T{}), m, n> outer(const tensor<S, m, n>& A, T B)
846{
847 static_assert(std::is_arithmetic<T>::value,
848 "outer product types must be tensor or arithmetic_type");
849 tensor<decltype(S{} * T{}), m, n> AB{};
850 for (int i = 0; i < m; i++)
851 {
852 for (int j = 0; j < n; j++)
853 {
854 AB[i][j] = A[i][j] * B;
855 }
856 }
857 return AB;
858}
859
860/**
861 * @overload
862 * @note this overload implements the case where the left argument is a 2nd order tensor, and the right argument is a
863 * first order tensor
864 */
865template <typename S, typename T, int m, int n, int p> MFEM_HOST_DEVICE
866tensor<decltype(S{} * T{}), m, n, p> outer(const tensor<S, m, n>& A,
867 const tensor<T, p>& B)
868{
869 tensor<decltype(S{} * T{}), m, n, p> AB{};
870 for (int i = 0; i < m; i++)
871 {
872 for (int j = 0; j < n; j++)
873 {
874 for (int k = 0; k < p; k++)
875 {
876 AB[i][j][k] = A[i][j] * B[k];
877 }
878 }
879 }
880 return AB;
881}
882
883/**
884 * @overload
885 * @note this overload implements the case where the left argument is a 1st order tensor, and the right argument is a
886 * 2nd order tensor
887 */
888template <typename S, typename T, int m, int n, int p> MFEM_HOST_DEVICE
889tensor<decltype(S{} * T{}), m, n, p> outer(const tensor<S, m>& A,
890 const tensor<T, n, p>& B)
891{
892 tensor<decltype(S{} * T{}), m, n, p> AB{};
893 for (int i = 0; i < m; i++)
894 {
895 for (int j = 0; j < n; j++)
896 {
897 for (int k = 0; k < p; k++)
898 {
899 AB[i][j][k] = A[i] * B[j][k];
900 }
901 }
902 }
903 return AB;
904}
905
906/**
907 * @overload
908 * @note this overload implements the case where both arguments are second order tensors
909 */
910template <typename S, typename T, int m, int n, int p, int q> MFEM_HOST_DEVICE
911tensor<decltype(S{} * T{}), m, n, p, q> outer(const tensor<S, m, n>& A,
912 const tensor<T, p, q>& B)
913{
914 tensor<decltype(S{} * T{}), m, n, p, q> AB{};
915 for (int i = 0; i < m; i++)
916 {
917 for (int j = 0; j < n; j++)
918 {
919 for (int k = 0; k < p; k++)
920 {
921 for (int l = 0; l < q; l++)
922 {
923 AB[i][j][k][l] = A[i][j] * B[k][l];
924 }
925 }
926 }
927 }
928 return AB;
929}
930
931/**
932 * @brief this function contracts over all indices of the two tensor arguments
933 * @tparam S the underlying type of the tensor (lefthand) argument
934 * @tparam T the underlying type of the tensor (righthand) argument
935 * @tparam m the number of rows
936 * @tparam n the number of columns
937 * @param[in] A The lefthand tensor
938 * @param[in] B The righthand tensor
939 */
940template <typename S, typename T, int m, int n> MFEM_HOST_DEVICE
941auto inner(const tensor<S, m, n>& A, const tensor<T, m, n>& B) ->
942decltype(S {} * T{})
943{
944 decltype(S{} * T{}) sum{};
945 for (int i = 0; i < m; i++)
946 {
947 for (int j = 0; j < n; j++)
948 {
949 sum += A[i][j] * B[i][j];
950 }
951 }
952 return sum;
953}
954
955/**
956 * @brief this function contracts over the "middle" index of the two tensor
957 * arguments. E.g. returns tensor C, such that C_ij = sum_kl A_ijkl B_kl.
958 * @tparam S the underlying type of the tensor (lefthand) argument
959 * @tparam T the underlying type of the tensor (righthand) argument
960 * @tparam n integers describing the tensor shape
961 * @param[in] A The lefthand tensor
962 * @param[in] B The righthand tensor
963 */
964template <typename S, typename T, int m, int n, int p> MFEM_HOST_DEVICE
965auto dot(const tensor<S, m, n>& A,
966 const tensor<T, n, p>& B) ->
967tensor<decltype(S {} * T{}), m, p>
968{
969 tensor<decltype(S{} * T{}), m, p> AB{};
970 for (int i = 0; i < m; i++)
971 {
972 for (int j = 0; j < p; j++)
973 {
974 for (int k = 0; k < n; k++)
975 {
976 AB[i][j] = AB[i][j] + A[i][k] * B[k][j];
977 }
978 }
979 }
980 return AB;
981}
982
983/**
984 * @overload
985 * @note vector . matrix
986 */
987template <typename S, typename T, int m, int n> MFEM_HOST_DEVICE
988auto dot(const tensor<S, m>& A, const tensor<T, m, n>& B) ->
989tensor<decltype(S {} * T{}), n>
990{
991 tensor<decltype(S{} * T{}), n> AB{};
992 for (int i = 0; i < n; i++)
993 {
994 for (int j = 0; j < m; j++)
995 {
996 AB[i] = AB[i] + A[j] * B[j][i];
997 }
998 }
999 return AB;
1000}
1001
1002/**
1003 * @overload
1004 * @note matrix . vector
1005 */
1006template <typename S, typename T, int m, int n> MFEM_HOST_DEVICE
1007auto dot(const tensor<S, m, n>& A, const tensor<T, n>& B) ->
1008tensor<decltype(S {} * T{}), m>
1009{
1010 tensor<decltype(S{} * T{}), m> AB{};
1011 for (int i = 0; i < m; i++)
1012 {
1013 for (int j = 0; j < n; j++)
1014 {
1015 AB[i] = AB[i] + A[i][j] * B[j];
1016 }
1017 }
1018 return AB;
1019}
1020
1021/**
1022 * @overload
1023 * @note 3rd-order-tensor . vector
1024 */
1025template <typename S, typename T, int m, int n, int p> MFEM_HOST_DEVICE
1026auto dot(const tensor<S, m, n, p>& A, const tensor<T, p>& B) ->
1027tensor<decltype(S {} * T{}), m, n>
1028{
1029 tensor<decltype(S{} * T{}), m, n> AB{};
1030 for (int i = 0; i < m; i++)
1031 {
1032 for (int j = 0; j < n; j++)
1033 {
1034 for (int k = 0; k < p; k++)
1035 {
1036 AB[i][j] += A[i][j][k] * B[k];
1037 }
1038 }
1039 }
1040 return AB;
1041}
1042
1043// /**
1044// * @brief Dot product of a vector . vector and vector . tensor
1045// *
1046// * @tparam S the underlying type of the tensor (lefthand) argument
1047// * @tparam T the underlying type of the tensor (righthand) argument
1048// * @tparam m the dimension of the first tensor
1049// * @tparam n the parameter pack of dimensions of the second tensor
1050// * @param A The lefthand tensor
1051// * @param B The righthand tensor
1052// * @return The computed dot product
1053// */
1054// template <typename S, typename T, int m, int... n>
1055// auto dot(const tensor<S, m>& A, const tensor<T, m, n...>& B)
1056// {
1057// // this dot product function includes the vector * vector implementation and
1058// // the vector * tensor one, since clang emits an error about ambiguous
1059// // overloads if they are separate functions. The `if constexpr` expression avoids
1060// // using an `else` because that confuses nvcc (11.2) into thinking there's not
1061// // a return statement
1062// if constexpr (sizeof...(n) == 0)
1063// {
1064// decltype(S{} * T{}) AB{};
1065// for (int i = 0; i < m; i++)
1066// {
1067// AB += A[i] * B[i];
1068// }
1069// return AB;
1070// }
1071
1072// if constexpr (sizeof...(n) > 0)
1073// {
1074// constexpr int dimensions[] = {n...};
1075// tensor<decltype(S{} * T{}), n...> AB{};
1076// for (int i = 0; i < dimensions[0]; i++)
1077// {
1078// for (int j = 0; j < m; j++)
1079// {
1080// AB[i] = AB[i] + A[j] * B[j][i];
1081// }
1082// }
1083// return AB;
1084// }
1085// }
1086
1087template <typename S, typename T, int m> MFEM_HOST_DEVICE
1088auto dot(const tensor<S, m>& A, const tensor<T, m>& B) ->
1089decltype(S {} * T{})
1090{
1091 decltype(S{} * T{}) AB{};
1092 for (int i = 0; i < m; i++)
1093 {
1094 AB += A[i] * B[i];
1095 }
1096 return AB;
1097}
1098
1099template <typename T, int m> MFEM_HOST_DEVICE
1100auto dot(const tensor<T, m>& A, const tensor<T, m>& B) ->
1101decltype(T {})
1102{
1103 decltype(T{}) AB{};
1104 for (int i = 0; i < m; i++)
1105 {
1106 AB += A[i] * B[i];
1107 }
1108 return AB;
1109}
1110
1111template <typename S, typename T, int m, int n0, int n1, int... n>
1112MFEM_HOST_DEVICE
1114tensor<decltype(S {} * T{}), n0, n1, n...>
1115{
1116 tensor<decltype(S{} * T{}), n0, n1, n...> AB{};
1117 for (int i = 0; i < n0; i++)
1118 {
1119 for (int j = 0; j < m; j++)
1120 {
1121 AB[i] = AB[i] + A[j] * B[j][i];
1122 }
1123 }
1124 return AB;
1125}
1126
1127/**
1128 * @overload
1129 * @note vector . matrix . vector
1130 */
1131template <typename S, typename T, typename U, int m, int n> MFEM_HOST_DEVICE
1132auto dot(const tensor<S, m>& u, const tensor<T, m, n>& A,
1133 const tensor<U, n>& v) ->
1134decltype(S {} * T{} * U{})
1135{
1136 decltype(S{} * T{} * U{}) uAv{};
1137 for (int i = 0; i < m; i++)
1138 {
1139 for (int j = 0; j < n; j++)
1140 {
1141 uAv += u[i] * A[i][j] * v[j];
1142 }
1143 }
1144 return uAv;
1145}
1146
1147/**
1148 * @brief real_t dot product, contracting over the two "middle" indices
1149 * @tparam S the underlying type of the tensor (lefthand) argument
1150 * @tparam T the underlying type of the tensor (righthand) argument
1151 * @tparam m first dimension of A
1152 * @tparam n second dimension of A
1153 * @tparam p third dimension of A, first dimensions of B
1154 * @tparam q fourth dimension of A, second dimensions of B
1155 * @param[in] A The lefthand tensor
1156 * @param[in] B The righthand tensor
1157 */
1158template <typename S, typename T, int m, int n, int p, int q> MFEM_HOST_DEVICE
1159auto ddot(const tensor<S, m, n, p, q>& A, const tensor<T, p, q>& B) ->
1160tensor<decltype(S {} * T{}), m, n>
1161{
1162 tensor<decltype(S{} * T{}), m, n> AB{};
1163 for (int i = 0; i < m; i++)
1164 {
1165 for (int j = 0; j < n; j++)
1166 {
1167 for (int k = 0; k < p; k++)
1168 {
1169 for (int l = 0; l < q; l++)
1170 {
1171 AB[i][j] += A[i][j][k][l] * B[k][l];
1172 }
1173 }
1174 }
1175 }
1176 return AB;
1177}
1178
1179/**
1180 * @overload
1181 * @note 3rd-order-tensor : 2nd-order-tensor. Returns vector C, such that C_i =
1182 * sum_jk A_ijk B_jk.
1183 */
1184template <typename S, typename T, int m, int n, int p> MFEM_HOST_DEVICE
1185auto ddot(const tensor<S, m, n, p>& A, const tensor<T, n, p>& B) ->
1186tensor<decltype(S {} * T{}), m>
1187{
1188 tensor<decltype(S{} * T{}), m> AB{};
1189 for (int i = 0; i < m; i++)
1190 {
1191 for (int j = 0; j < n; j++)
1192 {
1193 for (int k = 0; k < p; k++)
1194 {
1195 AB[i] += A[i][j][k] * B[j][k];
1196 }
1197 }
1198 }
1199 return AB;
1200}
1201
1202/**
1203 * @overload
1204 * @note 2nd-order-tensor : 2nd-order-tensor, like inner()
1205 */
1206template <typename S, typename T, int m, int n> MFEM_HOST_DEVICE
1207auto ddot(const tensor<S, m, n>& A, const tensor<T, m, n>& B) ->
1208decltype(S {} * T{})
1209{
1210 decltype(S{} * T{}) AB{};
1211 for (int i = 0; i < m; i++)
1212 {
1213 for (int j = 0; j < n; j++)
1214 {
1215 AB += A[i][j] * B[i][j];
1216 }
1217 }
1218 return AB;
1219}
1220
1221/**
1222 * @brief this is a shorthand for dot(A, B)
1223 */
1224template <typename S, typename T, int... m, int... n> MFEM_HOST_DEVICE
1225auto operator*(const tensor<S, m...>& A, const tensor<T, n...>& B) ->
1226decltype(dot(A, B))
1227{
1228 return dot(A, B);
1229}
1230
1231/**
1232 * @brief Returns the squared Frobenius norm of the tensor
1233 * @param[in] A The tensor to obtain the squared norm from
1234 */
1235template <typename T, int m> MFEM_HOST_DEVICE
1237{
1238 T total{};
1239 for (int i = 0; i < m; i++)
1240 {
1241 total += A[i] * A[i];
1242 }
1243 return total;
1244}
1245
1246/**
1247 * @overload
1248 * @brief Returns the squared Frobenius norm of the tensor
1249 */
1250template <typename T, int m, int n> MFEM_HOST_DEVICE
1252{
1253 T total{};
1254 for (int i = 0; i < m; i++)
1255 {
1256 for (int j = 0; j < n; j++)
1257 {
1258 total += A[i][j] * A[i][j];
1259 }
1260 }
1261 return total;
1262}
1263
1264/**
1265 * @brief Returns the Frobenius norm of the tensor
1266 * @param[in] A The tensor to obtain the norm from
1267 */
1268template <typename T, int... n> MFEM_HOST_DEVICE
1270{
1271 return std::sqrt(sqnorm(A));
1272}
1273
1274template <typename T, int n, int m> MFEM_HOST_DEVICE
1276{
1277 static_assert((n == m) || ((n == 2) && (m == 1)) || ((n == 3) && (m == 1)) ||
1278 ((n == 3) && (m == 2)), "unsupported combination of n and m");
1279 if constexpr (n == m)
1280 {
1281 return det(A);
1282 }
1283 if constexpr (((n == 2) && (m == 1)) ||
1284 ((n == 3) && (m == 1)))
1285 {
1286 return norm(A);
1287 }
1288 else if constexpr ((n == 3) && (m == 2))
1289 {
1290 T E = A[0][0] * A[0][0] + A[1][0] * A[1][0] + A[2][0] * A[2][0];
1291 T G = A[0][1] * A[0][1] + A[1][1] * A[1][1] + A[2][1] * A[2][1];
1292 T F = A[0][0] * A[0][1] + A[1][0] * A[1][1] + A[2][0] * A[2][1];
1293 return std::sqrt(E * G - F * F);
1294 }
1295 // Never reached because of the static_assert, but avoids compiler warning.
1296 return T{};
1297}
1298
1299/**
1300 * @brief Normalizes the tensor
1301 * Each element is divided by the Frobenius norm of the tensor, @see norm
1302 * @param[in] A The tensor to normalize
1303 */
1304template <typename T, int... n> MFEM_HOST_DEVICE
1306decltype(A / norm(A))
1307{
1308 return A / norm(A);
1309}
1310
1311/**
1312 * @brief Returns the trace of a square matrix
1313 * @param[in] A The matrix to compute the trace of
1314 * @return The sum of the elements on the main diagonal
1315 */
1316template <typename T, int n> MFEM_HOST_DEVICE
1318{
1319 T trA{};
1320 for (int i = 0; i < n; i++)
1321 {
1322 trA = trA + A[i][i];
1323 }
1324 return trA;
1325}
1326
1327/**
1328 * @brief Returns the symmetric part of a square matrix
1329 * @param[in] A The matrix to obtain the symmetric part of
1330 * @return (1/2) * (A + A^T)
1331 */
1332template <typename T, int n> MFEM_HOST_DEVICE
1334{
1335 tensor<T, n, n> symA{};
1336 for (int i = 0; i < n; i++)
1337 {
1338 for (int j = 0; j < n; j++)
1339 {
1340 symA[i][j] = 0.5 * (A[i][j] + A[j][i]);
1341 }
1342 }
1343 return symA;
1344}
1345
1346/**
1347 * @brief Calculates the deviator of a matrix (rank-2 tensor)
1348 * @param[in] A The matrix to calculate the deviator of
1349 * In the context of stress tensors, the deviator is obtained by
1350 * subtracting the mean stress (average of main diagonal elements)
1351 * from each element on the main diagonal
1352 */
1353template <typename T, int n> MFEM_HOST_DEVICE
1355{
1356 auto devA = A;
1357 auto trA = tr(A);
1358 for (int i = 0; i < n; i++)
1359 {
1360 devA[i][i] -= trA / n;
1361 }
1362 return devA;
1363}
1364
1365/**
1366 * @brief Obtains the identity matrix of the specified dimension
1367 * @return I_dim
1368 */
1369template <int dim>
1371{
1373 for (int i = 0; i < dim; i++)
1374 {
1375 for (int j = 0; j < dim; j++)
1376 {
1377 I[i][j] = (i == j);
1378 }
1379 }
1380 return I;
1381}
1382
1383/**
1384 * @brief Returns the transpose of the matrix
1385 * @param[in] A The matrix to obtain the transpose of
1386 */
1387template <typename T, int m, int n> MFEM_HOST_DEVICE
1389{
1390 tensor<T, n, m> AT{};
1391 for (int i = 0; i < n; i++)
1392 {
1393 for (int j = 0; j < m; j++)
1394 {
1395 AT[i][j] = A[j][i];
1396 }
1397 }
1398 return AT;
1399}
1400
1401/**
1402 * @brief Returns the determinant of a matrix
1403 * @param[in] A The matrix to obtain the determinant of
1404 */
1405template <typename T> MFEM_HOST_DEVICE
1407{
1408 return A[0][0];
1409}
1410/// @overload
1411template <typename T> MFEM_HOST_DEVICE
1413{
1414 return A[0][0] * A[1][1] - A[0][1] * A[1][0];
1415}
1416/// @overload
1417template <typename T> MFEM_HOST_DEVICE
1419{
1420 return A[0][0] * A[1][1] * A[2][2] + A[0][1] * A[1][2] * A[2][0] + A[0][2] *
1421 A[1][0] * A[2][1] -
1422 A[0][0] * A[1][2] * A[2][1] - A[0][1] * A[1][0] * A[2][2] - A[0][2] * A[1][1] *
1423 A[2][0];
1424}
1425
1426template <typename T> MFEM_HOST_DEVICE
1427std::tuple<tensor<T, 1>, tensor<T, 1, 1>> eig(tensor<T, 1, 1> &A)
1428{
1429 return {tensor<T, 1>{A[0][0]}, tensor<T, 1, 1>{{{1.0}}}};
1430}
1431
1432template <typename T> MFEM_HOST_DEVICE
1433std::tuple<tensor<T, 2>, tensor<T, 2, 2>> eig(tensor<T, 2, 2> &A)
1434{
1435 tensor<T, 2> e;
1437
1438 real_t d0 = A(0, 0);
1439 real_t d2 = A(0, 1);
1440 real_t d3 = A(1, 1);
1441 real_t c, s;
1442
1443 if (d2 == 0.0)
1444 {
1445 c = 1.0;
1446 s = 0.0;
1447 }
1448 else
1449 {
1450 real_t t;
1451 const real_t zeta = (d3 - d0) / (2.0 * d2);
1452 const real_t azeta = fabs(zeta);
1453 if (azeta < std::sqrt(1.0/std::numeric_limits<T>::epsilon()))
1454 {
1455 t = copysign(1./(azeta + std::sqrt(1. + zeta*zeta)), zeta);
1456 }
1457 else
1458 {
1459 t = copysign(0.5/azeta, zeta);
1460 }
1461 c = std::sqrt(1./(1. + t*t));
1462 s = c*t;
1463 t *= d2;
1464 d0 -= t;
1465 d3 += t;
1466 }
1467
1468 if (d0 <= d3)
1469 {
1470 e(0) = d0;
1471 e(1) = d3;
1472 v(0, 0) = c;
1473 v(1, 0) = -s;
1474 v(0, 1) = s;
1475 v(1, 1) = c;
1476 }
1477 else
1478 {
1479 e(0) = d3;
1480 e(1) = d0;
1481 v(0, 0) = s;
1482 v(1, 0) = c;
1483 v(0, 1) = c;
1484 v(1, 1) = -s;
1485 }
1486
1487 return {e, v};
1488}
1489
1490template <typename T> MFEM_HOST_DEVICE
1491void GetScalingFactor(const T &d_max, T &mult)
1492{
1493 int d_exp;
1494 if (d_max > 0.)
1495 {
1496 mult = frexp(d_max, &d_exp);
1497 if (d_exp == std::numeric_limits<T>::max_exponent)
1498 {
1499 mult *= std::numeric_limits<T>::radix;
1500 }
1501 mult = d_max/mult;
1502 }
1503 else
1504 {
1505 mult = 1.;
1506 }
1507}
1508
1509template <typename T> MFEM_HOST_DEVICE
1510T calcsv(const tensor<T, 1, 1> A, const int i)
1511{
1512 return A[0][0];
1513}
1514
1515/**
1516 * @brief Compute the i-th singular value of a 2x2 matrix A
1517 */
1518template <typename T> MFEM_HOST_DEVICE
1519T calcsv(const tensor<T, 2, 2> A, const int i)
1520{
1521 real_t mult;
1522 real_t d0, d1, d2, d3;
1523 d0 = A(0, 0);
1524 d1 = A(1, 0);
1525 d2 = A(0, 1);
1526 d3 = A(1, 1);
1527
1528 real_t d_max = fabs(d0);
1529 if (d_max < fabs(d1)) { d_max = fabs(d1); }
1530 if (d_max < fabs(d2)) { d_max = fabs(d2); }
1531 if (d_max < fabs(d3)) { d_max = fabs(d3); }
1532
1533 GetScalingFactor(d_max, mult);
1534
1535 d0 /= mult;
1536 d1 /= mult;
1537 d2 /= mult;
1538 d3 /= mult;
1539
1540 real_t t = 0.5*((d0+d2)*(d0-d2)+(d1-d3)*(d1+d3));
1541 real_t s = d0*d2 + d1*d3;
1542 s = std::sqrt(0.5*(d0*d0 + d1*d1 + d2*d2 + d3*d3) + std::sqrt(t*t + s*s));
1543
1544 if (s == 0.0)
1545 {
1546 return 0.0;
1547 }
1548 t = fabs(d0*d3 - d1*d2) / s;
1549 if (t > s)
1550 {
1551 if (i == 0)
1552 {
1553 return t*mult;
1554 }
1555 return s*mult;
1556 }
1557 if (i == 0)
1558 {
1559 return s*mult;
1560 }
1561 return t*mult;
1562}
1563
1564
1565/**
1566 * @brief Return whether a square rank 2 tensor is symmetric
1567 *
1568 * @tparam n The height of the tensor
1569 * @param A The square rank 2 tensor
1570 * @param abs_tolerance The absolute tolerance to check for symmetry
1571 * @return Whether the square rank 2 tensor (matrix) is symmetric
1572 */
1573template <int n> MFEM_HOST_DEVICE
1574bool is_symmetric(tensor<real_t, n, n> A, real_t abs_tolerance = 1.0e-8_r)
1575{
1576 for (int i = 0; i < n; ++i)
1577 {
1578 for (int j = i + 1; j < n; ++j)
1579 {
1580 if (std::abs(A(i, j) - A(j, i)) > abs_tolerance)
1581 {
1582 return false;
1583 }
1584 }
1585 }
1586 return true;
1587}
1588
1589/**
1590 * @brief Return whether a matrix is symmetric and positive definite
1591 * This check uses Sylvester's criterion, checking that each upper left subtensor has a
1592 * determinant greater than zero.
1593 *
1594 * @param A The matrix to test for positive definiteness
1595 * @return Whether the matrix is positive definite
1596 */
1597inline MFEM_HOST_DEVICE
1599{
1600 if (!is_symmetric(A))
1601 {
1602 return false;
1603 }
1604 if (A(0, 0) < 0.0)
1605 {
1606 return false;
1607 }
1608 if (det(A) < 0.0)
1609 {
1610 return false;
1611 }
1612 return true;
1613}
1614/// @overload
1615inline MFEM_HOST_DEVICE
1617{
1618 if (!is_symmetric(A))
1619 {
1620 return false;
1621 }
1622 if (det(A) < 0.0)
1623 {
1624 return false;
1625 }
1626 auto subtensor = make_tensor<2, 2>([A](int i, int j) { return A(i, j); });
1627 if (!is_symmetric_and_positive_definite(subtensor))
1628 {
1629 return false;
1630 }
1631 return true;
1632}
1633
1634/**
1635 * @brief Solves Ax = b for x using Gaussian elimination with partial pivoting
1636 * @param[in] A The coefficient matrix A
1637 * @param[in] b The righthand side vector b
1638 * @note @a A and @a b are by-value as they are mutated as part of the elimination
1639 */
1640template <typename T, int n> MFEM_HOST_DEVICE
1642{
1643 auto abs = [](real_t x) { return (x < 0) ? -x : x; };
1644 auto swap_vector = [](tensor<T, n>& x, tensor<T, n>& y)
1645 {
1646 auto tmp = x;
1647 x = y;
1648 y = tmp;
1649 };
1650 auto swap_scalar = [](T& x, T& y)
1651 {
1652 auto tmp = x;
1653 x = y;
1654 y = tmp;
1655 };
1656
1657
1659
1660 for (int i = 0; i < n; i++)
1661 {
1662 // Search for maximum in this column
1663 real_t max_val = abs(A[i][i]);
1664
1665 int max_row = i;
1666 for (int j = i + 1; j < n; j++)
1667 {
1668 if (abs(A[j][i]) > max_val)
1669 {
1670 max_val = abs(A[j][i]);
1671 max_row = j;
1672 }
1673 }
1674
1675 swap_scalar(b[max_row], b[i]);
1676 swap_vector(A[max_row], A[i]);
1677
1678 // zero entries below in this column
1679 for (int j = i + 1; j < n; j++)
1680 {
1681 real_t c = -A[j][i] / A[i][i];
1682 A[j] += c * A[i];
1683 b[j] += c * b[i];
1684 A[j][i] = 0;
1685 }
1686 }
1687
1688 // Solve equation Ax=b for an upper triangular matrix A
1689 for (int i = n - 1; i >= 0; i--)
1690 {
1691 x[i] = b[i] / A[i][i];
1692 for (int j = i - 1; j >= 0; j--)
1693 {
1694 b[j] -= A[j][i] * x[i];
1695 }
1696 }
1697
1698 return x;
1699}
1700
1701/**
1702 * @brief Inverts a matrix
1703 * @param[in] A The matrix to invert
1704 * @note Uses a shortcut for inverting a 1x1, 2x2 and 3x3 matrix
1705 */
1706template <typename T>
1707inline MFEM_HOST_DEVICE tensor<T, 1, 1> inv(const tensor<T, 1, 1>& A)
1708{
1709 return tensor<T, 1, 1> {{{T{1.0} / A[0][0]}}};
1710}
1711
1712template <typename T>
1713inline MFEM_HOST_DEVICE tensor<T, 2, 2> inv(const tensor<T, 2, 2>& A)
1714{
1715 T inv_detA(1.0_r / det(A));
1716
1717 tensor<T, 2, 2> invA{};
1718
1719 invA[0][0] = A[1][1] * inv_detA;
1720 invA[0][1] = -A[0][1] * inv_detA;
1721 invA[1][0] = -A[1][0] * inv_detA;
1722 invA[1][1] = A[0][0] * inv_detA;
1723
1724 return invA;
1725}
1726
1727/**
1728 * @overload
1729 * @note Uses a shortcut for inverting a 3-by-3 matrix
1730 */
1731template <typename T>
1732inline MFEM_HOST_DEVICE tensor<T, 3, 3> inv(const tensor<T, 3, 3>& A)
1733{
1734 T inv_detA(1.0_r / det(A));
1735
1736 tensor<T, 3, 3> invA{};
1737
1738 invA[0][0] = (A[1][1] * A[2][2] - A[1][2] * A[2][1]) * inv_detA;
1739 invA[0][1] = (A[0][2] * A[2][1] - A[0][1] * A[2][2]) * inv_detA;
1740 invA[0][2] = (A[0][1] * A[1][2] - A[0][2] * A[1][1]) * inv_detA;
1741 invA[1][0] = (A[1][2] * A[2][0] - A[1][0] * A[2][2]) * inv_detA;
1742 invA[1][1] = (A[0][0] * A[2][2] - A[0][2] * A[2][0]) * inv_detA;
1743 invA[1][2] = (A[0][2] * A[1][0] - A[0][0] * A[1][2]) * inv_detA;
1744 invA[2][0] = (A[1][0] * A[2][1] - A[1][1] * A[2][0]) * inv_detA;
1745 invA[2][1] = (A[0][1] * A[2][0] - A[0][0] * A[2][1]) * inv_detA;
1746 invA[2][2] = (A[0][0] * A[1][1] - A[0][1] * A[1][0]) * inv_detA;
1747
1748 return invA;
1749}
1750/**
1751 * @overload
1752 * @note For N-by-N matrices with N > 3, requires Gaussian elimination
1753 * with partial pivoting
1754 */
1755template <typename T, int n>
1756MFEM_HOST_DEVICE
1757typename std::enable_if<(n > 3), tensor<T, n, n>>::type
1759{
1760 auto abs = [](T x) { return (x < 0) ? -x : x; };
1761 auto swap = [](tensor<T, n>& x, tensor<T, n>& y)
1762 {
1763 auto tmp = x;
1764 x = y;
1765 y = tmp;
1766 };
1767
1768 tensor<T, n, n> B = IdentityMatrix<n>();
1769
1770 for (int i = 0; i < n; i++)
1771 {
1772 // Search for maximum in this column
1773 T max_val = abs(A[i][i]);
1774
1775 int max_row = i;
1776 for (int j = i + 1; j < n; j++)
1777 {
1778 if (abs(A[j][i]) > max_val)
1779 {
1780 max_val = abs(A[j][i]);
1781 max_row = j;
1782 }
1783 }
1784
1785 swap(B[max_row], B[i]);
1786 swap(A[max_row], A[i]);
1787
1788 // zero entries below in this column
1789 for (int j = i + 1; j < n; j++)
1790 {
1791 if (A[j][i] != 0.0)
1792 {
1793 T c = -A[j][i] / A[i][i];
1794 A[j] += c * A[i];
1795 B[j] += c * B[i];
1796 A[j][i] = 0;
1797 }
1798 }
1799 }
1800
1801 // upper triangular solve
1802 for (int i = n - 1; i >= 0; i--)
1803 {
1804 B[i] = B[i] / A[i][i];
1805 for (int j = i - 1; j >= 0; j--)
1806 {
1807 if (A[j][i] != 0.0)
1808 {
1809 B[j] -= A[j][i] * B[i];
1810 }
1811 }
1812 }
1813
1814 return B;
1815}
1816
1817/**
1818 * @overload
1819 * @note when inverting a tensor of dual numbers,
1820 * hardcode the analytic derivative of the
1821 * inverse of a square matrix, rather than
1822 * apply Gauss elimination directly on the dual number types
1823 *
1824 * TODO: compare performance of this hardcoded implementation to just using inv() directly
1825 */
1826template <typename value_type, typename gradient_type, int n> MFEM_HOST_DEVICE
1829{
1830 auto invA = inv(get_value(A));
1831 return make_tensor<n, n>([&](int i, int j)
1832 {
1833 auto value = invA[i][j];
1834 gradient_type gradient{};
1835 for (int k = 0; k < n; k++)
1836 {
1837 for (int l = 0; l < n; l++)
1838 {
1839 gradient -= invA[i][k] * A[k][l].gradient * invA[l][j];
1840 }
1841 }
1842 return dual<value_type, gradient_type> {value, gradient};
1843 });
1844}
1845
1846/**
1847 * @brief recursively serialize the entries in a tensor to an output stream.
1848 * Output format uses braces and comma separators to mimic C syntax for multidimensional array
1849 * initialization.
1850 *
1851 * @param[in] os The stream to work with standard output streams
1852 * @param[in] A The tensor to write out
1853 */
1854template <typename T, int... n>
1855std::ostream& operator<<(std::ostream& os, const tensor<T, n...>& A)
1856{
1857 os << '{' << A[0];
1858 for (int i = 1; i < tensor<T, n...>::first_dim; i++)
1859 {
1860 os << ", " << A[i];
1861 }
1862 os << '}';
1863 return os;
1864}
1865
1866/**
1867 * @brief replace all entries in a tensor satisfying |x| < 1.0e-10 by literal zero
1868 * @param[in] A The tensor to "chop"
1869 */
1870template <int n> MFEM_HOST_DEVICE
1872{
1873 auto copy = A;
1874 for (int i = 0; i < n; i++)
1875 {
1876 if (copy[i] * copy[i] < 1.0e-20)
1877 {
1878 copy[i] = 0.0;
1879 }
1880 }
1881 return copy;
1882}
1883
1884/// @overload
1885template <int m, int n> MFEM_HOST_DEVICE
1887{
1888 auto copy = A;
1889 for (int i = 0; i < m; i++)
1890 {
1891 for (int j = 0; j < n; j++)
1892 {
1893 if (copy[i][j] * copy[i][j] < 1.0e-20)
1894 {
1895 copy[i][j] = 0.0;
1896 }
1897 }
1898 }
1899 return copy;
1900}
1901
1902/// @cond
1903namespace detail
1904{
1905template <typename T1, typename T2>
1906struct outer_prod;
1907
1908template <int... m, int... n>
1909struct outer_prod<tensor<real_t, m...>, tensor<real_t, n...>>
1910{
1911 using type = tensor<real_t, m..., n...>;
1912};
1913
1914template <int... n>
1915struct outer_prod<real_t, tensor<real_t, n...>>
1916{
1917 using type = tensor<real_t, n...>;
1918};
1919
1920template <int... n>
1921struct outer_prod<tensor<real_t, n...>, real_t>
1922{
1923 using type = tensor<real_t, n...>;
1924};
1925
1926template <>
1927struct outer_prod<real_t, real_t>
1928{
1929 using type = tensor<real_t>;
1930};
1931
1932template <typename T>
1933struct outer_prod<zero, T>
1934{
1935 using type = zero;
1936};
1937
1938template <typename T>
1939struct outer_prod<T, zero>
1940{
1941 using type = zero;
1942};
1943
1944} // namespace detail
1945/// @endcond
1946
1947/**
1948 * @brief a type function that returns the tensor type of an outer product of two tensors
1949 * @tparam T1 the first argument to the outer product
1950 * @tparam T2 the second argument to the outer product
1951 */
1952template <typename T1, typename T2>
1953using outer_product_t = typename detail::outer_prod<T1, T2>::type;
1954
1955/**
1956 * @brief Retrieves the gradient component of a real_t (which is nothing)
1957 * @return The sentinel, @see zero
1958 */
1959inline MFEM_HOST_DEVICE zero get_gradient(real_t /* arg */) { return zero{}; }
1960
1961/**
1962 * @brief get the gradient of type `tensor` (note: since its stored type is not a dual
1963 * number, the derivative term is identically zero)
1964 * @return The sentinel, @see zero
1965 */
1966template <int... n>
1967MFEM_HOST_DEVICE zero get_gradient(const tensor<real_t, n...>& /* arg */)
1968{
1969 return zero{};
1970}
1971
1972/**
1973 * @brief evaluate the change (to first order) in a function, f, given a small change in the input argument, dx.
1974 */
1975inline MFEM_HOST_DEVICE zero chain_rule(const zero /* df_dx */,
1976 const zero /* dx */) { return zero{}; }
1977
1978/**
1979 * @overload
1980 * @note this overload implements a no-op for the case where the gradient w.r.t. an input argument is identically zero
1981 */
1982template <typename T>
1983MFEM_HOST_DEVICE zero chain_rule(const zero /* df_dx */,
1984 const T /* dx */)
1985{
1986 return zero{};
1987}
1988
1989/**
1990 * @overload
1991 * @note this overload implements a no-op for the case where the small change is identically zero
1992 */
1993template <typename T>
1994MFEM_HOST_DEVICE zero chain_rule(const T /* df_dx */,
1995 const zero /* dx */)
1996{
1997 return zero{};
1998}
1999
2000/**
2001 * @overload
2002 * @note for a scalar-valued function of a scalar, the chain rule is just multiplication
2003 */
2004inline MFEM_HOST_DEVICE real_t chain_rule(const real_t df_dx,
2005 const real_t dx) { return df_dx * dx; }
2006
2007/**
2008 * @overload
2009 * @note for a tensor-valued function of a scalar, the chain rule is just scalar multiplication
2010 */
2011template <int... n>
2012MFEM_HOST_DEVICE auto chain_rule(const tensor<real_t, n...>& df_dx,
2013 const real_t dx) ->
2014decltype(df_dx * dx)
2015{
2016 return df_dx * dx;
2017}
2018
2019template <int n> struct always_false : std::false_type { };
2020
2021template <typename T, int... n> struct isotropic_tensor;
2022
2023template <typename T, int n>
2025{
2026 static_assert(always_false<n> {},
2027 "error: there is no such thing as a rank-1 isotropic tensor!");
2028};
2029
2030// rank-2 isotropic tensors are just identity matrices
2031template <typename T, int m>
2032struct isotropic_tensor<T, m, m>
2033{
2034 MFEM_HOST_DEVICE constexpr T operator()(int i, int j) const
2035 {
2036 return (i == j) * value;
2037 }
2039};
2040
2041template <int m>
2043{
2044 return isotropic_tensor<real_t, m, m> {1.0};
2045}
2046
2047template <typename S, typename T, int m> MFEM_HOST_DEVICE constexpr
2048auto operator*(S scale,
2049 const isotropic_tensor<T, m, m> & I)
2050-> isotropic_tensor<decltype(S {} * T{}), m, m>
2051{
2052 return {I.value * scale};
2053}
2054
2055template <typename S, typename T, int m> MFEM_HOST_DEVICE constexpr
2057 const S scale)
2058-> isotropic_tensor<decltype(S {}, T{}), m, m>
2059{
2060 return {I.value * scale};
2061}
2062
2063template <typename S, typename T, int m> MFEM_HOST_DEVICE constexpr
2065 const isotropic_tensor<T, m, m>& I2)
2066-> isotropic_tensor<decltype(S {} + T{}), m, m>
2067{
2068 return {I1.value + I2.value};
2069}
2070
2071template <typename S, typename T, int m> MFEM_HOST_DEVICE constexpr
2073 const isotropic_tensor<T, m, m>& I2)
2074-> isotropic_tensor<decltype(S {} - T{}), m, m>
2075{
2076 return {I1.value - I2.value};
2077}
2078
2079template <typename S, typename T, int m> MFEM_HOST_DEVICE //constexpr
2081 const tensor<T, m, m>& A)
2082-> tensor<decltype(S {} + T{}), m, m>
2083{
2084 tensor<decltype(S{} + T{}), m, m> output{};
2085 for (int i = 0; i < m; i++)
2086 {
2087 for (int j = 0; j < m; j++)
2088 {
2089 output[i][j] = I.value * (i == j) + A[i][j];
2090 }
2091 }
2092 return output;
2093}
2094
2095template <typename S, typename T, int m> MFEM_HOST_DEVICE //constexpr
2098-> tensor<decltype(S {} + T{}), m, m>
2099{
2100 tensor<decltype(S{} + T{}), m, m> output{};
2101 for (int i = 0; i < m; i++)
2102 {
2103 for (int j = 0; j < m; j++)
2104 {
2105 output[i][j] = A[i][j] + I.value * (i == j);
2106 }
2107 }
2108 return output;
2109}
2110
2111template <typename S, typename T, int m> MFEM_HOST_DEVICE //constexpr
2113 const tensor<T, m, m>& A)
2114-> tensor<decltype(S {} - T{}), m, m>
2115{
2116 tensor<decltype(S{} - T{}), m, m> output{};
2117 for (int i = 0; i < m; i++)
2118 {
2119 for (int j = 0; j < m; j++)
2120 {
2121 output[i][j] = I.value * (i == j) - A[i][j];
2122 }
2123 }
2124 return output;
2125}
2126
2127template <typename S, typename T, int m> MFEM_HOST_DEVICE // constexpr
2130-> tensor<decltype(S {} - T{}), m, m>
2131{
2132 tensor<decltype(S{} - T{}), m, m> output{};
2133 for (int i = 0; i < m; i++)
2134 {
2135 for (int j = 0; j < m; j++)
2136 {
2137 output[i][j] = A[i][j] - I.value * (i == j);
2138 }
2139 }
2140 return output;
2141}
2142
2143template <typename S, typename T, int m, int... n> MFEM_HOST_DEVICE constexpr
2145 const tensor<T, m, n...>& A)
2146-> tensor<decltype(S {} * T{}), m, n...>
2147{
2148 return I.value * A;
2149}
2150
2151template <typename S, typename T, int m, int... n> MFEM_HOST_DEVICE //constexpr
2152auto dot(const tensor<S, n...>& A,
2153 const isotropic_tensor<T, m, m> & I)
2154-> tensor<decltype(S {} * T{}), n...>
2155{
2156 constexpr int dimensions[sizeof...(n)] = {n...};
2157 static_assert(dimensions[sizeof...(n) - 1] == m, "n-1 != m");
2158 return A * I.value;
2159}
2160
2161template <typename S, typename T, int m, int... n> MFEM_HOST_DEVICE constexpr
2163 const tensor<T, m, m>& A)
2164-> decltype(S {} * T{})
2165{
2166 return I.value * tr(A);
2167}
2168
2169template <typename T, int m> MFEM_HOST_DEVICE constexpr
2171{
2172 return I;
2173}
2174
2175template <typename T, int m> MFEM_HOST_DEVICE constexpr
2177{
2178 return zero{};
2179}
2180
2181template <typename T, int m> MFEM_HOST_DEVICE constexpr
2182auto tr(const isotropic_tensor<T, m, m>& I) -> decltype(T {} * m)
2183{
2184 return I.value * m;
2185}
2186
2187template <typename T, int m> MFEM_HOST_DEVICE constexpr
2189{
2190 return I;
2191}
2192
2193template <typename T, int m> MFEM_HOST_DEVICE constexpr
2195{
2196 return std::pow(I.value, m);
2197}
2198
2199template <typename T, int m> MFEM_HOST_DEVICE constexpr
2201{
2202 return sqrt(I.value * I.value * m);
2203}
2204
2205template <typename T, int m> MFEM_HOST_DEVICE constexpr
2207{
2208 return I.value * I.value * m;
2209}
2210
2211// rank-3 isotropic tensors are just the alternating symbol
2212template <typename T>
2213struct isotropic_tensor<T, 3, 3, 3>
2214{
2215 MFEM_HOST_DEVICE constexpr T operator()(int i, int j, int k) const
2216 {
2217 return 0.5 * (i - j) * (j - k) * (k - i) * value;
2218 }
2220};
2221
2222// there are 3 linearly-independent rank-4 isotropic tensors,
2223// so the general one will be some linear combination of them
2224template <typename T, int m>
2225struct isotropic_tensor<T, m, m, m, m>
2226{
2227 T c1, c2, c3;
2228
2229 MFEM_HOST_DEVICE constexpr T operator()(int i, int j, int k, int l) const
2230 {
2231 return c1 * (i == j) * (k == l)
2232 + c2 * ((i == k) * (j == l) + (i == l) * (j == k)) * 0.5
2233 + c3 * ((i == k) * (j == l) - (i == l) * (j == k)) * 0.5;
2234 }
2235};
2236
2237template <int m> MFEM_HOST_DEVICE constexpr
2239{
2240 return {0.0, 1.0, 0.0};
2241}
2242
2243template <int m>MFEM_HOST_DEVICE constexpr
2245{
2246 return {0.0, 0.0, 1.0};
2247}
2248
2249template <typename S, typename T, int m> MFEM_HOST_DEVICE constexpr
2250auto operator*(S scale,
2252-> isotropic_tensor<decltype(S {} * T{}), m, m, m, m>
2253{
2254 return {I.c1 * scale, I.c2 * scale, I.c3 * scale};
2255}
2256
2257template <typename S, typename T, int m> MFEM_HOST_DEVICE constexpr
2259 T scale)
2260-> isotropic_tensor<decltype(S {} * T{}), m, m, m, m>
2261{
2262 return {I.c1 * scale, I.c2 * scale, I.c3 * scale};
2263}
2264
2265template <typename S, typename T, int m> MFEM_HOST_DEVICE constexpr
2268-> isotropic_tensor<decltype(S {} + T{}), m, m, m, m>
2269{
2270 return {I1.c1 + I2.c1, I1.c2 + I2.c2, I1.c3 + I2.c3};
2271}
2272
2273template <typename S, typename T, int m> MFEM_HOST_DEVICE constexpr
2276-> isotropic_tensor<decltype(S {} - T{}), m, m, m, m>
2277{
2278 return {I1.c1 - I2.c1, I1.c2 - I2.c2, I1.c3 - I2.c3};
2279}
2280
2281template <typename S, typename T, int m, int... n> MFEM_HOST_DEVICE constexpr
2283 const tensor<T, m, m>& A)
2284-> tensor<decltype(S {} * T{}), m, m>
2285{
2286 return I.c1 * tr(A) * IdentityMatrix<m>() + I.c2 * sym(A) + I.c3 * antisym(A);
2287}
2288
2289} // namespace future
2290} // namespace mfem
This file contains the declaration of a dual number class.
ostream & operator<<(ostream &v, void(*f)(VisMan &))
Definition ex17.cpp:406
int dim
Definition ex24.cpp:53
real_t b
Definition lissajous.cpp:42
mfem::real_t real_t
MFEM_HOST_DEVICE constexpr auto operator-(dual< value_type, gradient_type > x) -> dual< value_type, gradient_type >
unary negation of a dual number
Definition dual.hpp:112
MFEM_HOST_DEVICE constexpr auto operator+(dual< value_type, gradient_type > a, other_type b) -> dual< value_type, gradient_type >
addition of a dual number and a non-dual number
Definition dual.hpp:74
MFEM_HOST_DEVICE auto ddot(const tensor< S, m, n, p, q > &A, const tensor< T, p, q > &B) -> tensor< decltype(S {} *T{}), m, n >
real_t dot product, contracting over the two "middle" indices
Definition tensor.hpp:1159
MFEM_HOST_DEVICE T get_value(const T &arg)
return the "value" part from a given type. For non-dual types, this is just the identity function
Definition dual.hpp:422
MFEM_HOST_DEVICE T sqnorm(const tensor< T, m > &A)
Returns the squared Frobenius norm of the tensor.
Definition tensor.hpp:1236
gradient_type MFEM_HOST_DEVICE dual< value_type, gradient_type > & operator+=(dual< value_type, gradient_type > &a, const dual< value_type, gradient_type > &b)
Definition dual.hpp:243
MFEM_HOST_DEVICE tensor< T, n > linear_solve(tensor< T, n, n > A, const tensor< T, n > b)
Solves Ax = b for x using Gaussian elimination with partial pivoting.
Definition tensor.hpp:1641
MFEM_HOST_DEVICE constexpr isotropic_tensor< real_t, m, m > IsotropicIdentity()
Definition tensor.hpp:2042
MFEM_HOST_DEVICE constexpr auto AntisymmetricIdentity() -> isotropic_tensor< real_t, m, m, m, m >
Definition tensor.hpp:2244
MFEM_HOST_DEVICE void copy(DeviceTensor< n > &u, DeviceTensor< n > &v)
Copy data from DeviceTensor u to DeviceTensor v.
Definition util.hpp:2147
MFEM_HOST_DEVICE auto outer(S A, T B) -> decltype(A *B)
compute the outer product of two tensors
Definition tensor.hpp:724
MFEM_HOST_DEVICE void GetScalingFactor(const T &d_max, T &mult)
Definition tensor.hpp:1491
MFEM_HOST_DEVICE auto normalize(const tensor< T, n... > &A) -> decltype(A/norm(A))
Normalizes the tensor Each element is divided by the Frobenius norm of the tensor,...
Definition tensor.hpp:1305
MFEM_HOST_DEVICE T det(const tensor< T, 1, 1 > &A)
Returns the determinant of a matrix.
Definition tensor.hpp:1406
MFEM_HOST_DEVICE T tr(const tensor< T, n, n > &A)
Returns the trace of a square matrix.
Definition tensor.hpp:1317
MFEM_HOST_DEVICE auto inner(const tensor< S, m, n > &A, const tensor< T, m, n > &B) -> decltype(S {} *T{})
this function contracts over all indices of the two tensor arguments
Definition tensor.hpp:941
MFEM_HOST_DEVICE constexpr auto SymmetricIdentity() -> isotropic_tensor< real_t, m, m, m, m >
Definition tensor.hpp:2238
typename detail::outer_prod< T1, T2 >::type outer_product_t
a type function that returns the tensor type of an outer product of two tensors
Definition tensor.hpp:1953
MFEM_HOST_DEVICE constexpr auto antisym(const isotropic_tensor< T, m, m > &) -> zero
Definition tensor.hpp:2176
MFEM_HOST_DEVICE dual< value_type, gradient_type > & operator-=(dual< value_type, gradient_type > &a, const dual< value_type, gradient_type > &b)
compound assignment (-) for dual numbers
Definition dual.hpp:253
MFEM_HOST_DEVICE T weight(const tensor< T, n, m > &A)
Definition tensor.hpp:1275
MFEM_HOST_DEVICE tensor< real_t, n > chop(const tensor< real_t, n > &A)
replace all entries in a tensor satisfying |x| < 1.0e-10 by literal zero
Definition tensor.hpp:1871
MFEM_HOST_DEVICE tensor< real_t, dim, dim > IdentityMatrix()
Obtains the identity matrix of the specified dimension.
Definition tensor.hpp:1370
MFEM_HOST_DEVICE constexpr auto make_tensor(lambda_type f) -> tensor< decltype(f())>
Creates a tensor of requested dimension by subsequent calls to a functor Can be thought of as analogo...
Definition tensor.hpp:327
MFEM_HOST_DEVICE T calcsv(const tensor< T, 1, 1 > A, const int i)
Definition tensor.hpp:1510
MFEM_HOST_DEVICE constexpr auto type(const tuple< T... > &values)
a function intended to be used for extracting the ith type from a tuple.
Definition tuple.hpp:347
MFEM_HOST_DEVICE zero chain_rule(const zero, const zero)
evaluate the change (to first order) in a function, f, given a small change in the input argument,...
Definition tensor.hpp:1975
MFEM_HOST_DEVICE tensor< T, n+m > flatten(tensor< T, n, m > A)
Definition tensor.hpp:732
MFEM_HOST_DEVICE tensor< T, 1, 1 > inv(const tensor< T, 1, 1 > &A)
Inverts a matrix.
Definition tensor.hpp:1707
MFEM_HOST_DEVICE tensor< T, n, n > dev(const tensor< T, n, n > &A)
Calculates the deviator of a matrix (rank-2 tensor)
Definition tensor.hpp:1354
MFEM_HOST_DEVICE tensor< T, n, m > transpose(const tensor< T, m, n > &A)
Returns the transpose of the matrix.
Definition tensor.hpp:1388
typename std::conditional<(n1==1 &&n2==1), T, typename std::conditional< n1==1, tensor< T, n2 >, typename std::conditional< n2==1, tensor< T, n1 >, tensor< T, n1, n2 > >::type >::type >::type reduced_tensor
Removes 1s from tensor dimensions For example, a tensor<T, 1, 10> is equivalent to a tensor<T,...
Definition tensor.hpp:308
MFEM_HOST_DEVICE bool is_symmetric(tensor< real_t, n, n > A, real_t abs_tolerance=1.0e-8_r)
Return whether a square rank 2 tensor is symmetric.
Definition tensor.hpp:1574
MFEM_HOST_DEVICE dual< value_type, gradient_type > sqrt(dual< value_type, gradient_type > x)
implementation of square root for dual numbers
Definition dual.hpp:288
MFEM_HOST_DEVICE std::tuple< tensor< T, 1 >, tensor< T, 1, 1 > > eig(tensor< T, 1, 1 > &A)
Definition tensor.hpp:1427
MFEM_HOST_DEVICE tensor< T, n, n > sym(const tensor< T, n, n > &A)
Returns the symmetric part of a square matrix.
Definition tensor.hpp:1333
MFEM_HOST_DEVICE zero dot(const T &, zero)
the dot product of anything with zero is zero
Definition tensor.hpp:288
MFEM_HOST_DEVICE tensor< T, n > get_col(tensor< T, m, n > A, int j)
Definition tensor.hpp:455
MFEM_HOST_DEVICE constexpr auto operator*(const dual< value_type, gradient_type > &a, real_t b) -> dual< decltype(a.value *b), decltype(a.gradient *b)>
multiplication of a dual number and a non-dual number
Definition dual.hpp:146
MFEM_HOST_DEVICE constexpr auto operator/(const dual< value_type, gradient_type > &a, real_t b) -> dual< decltype(a.value/b), decltype(a.gradient/b)>
division of a dual number by a non-dual number
Definition dual.hpp:173
MFEM_HOST_DEVICE zero & get(zero &x)
let zero be accessed like a tuple
Definition tensor.hpp:281
MFEM_HOST_DEVICE gradient_type get_gradient(dual< value_type, gradient_type > arg)
return the "gradient" part from a dual number type
Definition dual.hpp:433
MFEM_HOST_DEVICE bool is_symmetric_and_positive_definite(tensor< real_t, 2, 2 > A)
Return whether a matrix is symmetric and positive definite This check uses Sylvester's criterion,...
Definition tensor.hpp:1598
void swap(Array< T > &a, Array< T > &b)
Swap of Array<T> objects for use with standard library algorithms. Also, used by mfem::Swap().
Definition array.hpp:747
real_t u(const Vector &xvec)
Definition lor_mms.hpp:22
float real_t
Definition config.hpp:46
std::function< real_t(const Vector &)> f(real_t mass_coeff)
Definition lor_mms.hpp:30
real_t p(const Vector &x, real_t t)
MFEM_HOST_DEVICE real_t norm(const Complex &z)
MFEM_HOST_DEVICE Complex operator+(const Complex &a, const Complex &b)
MFEM_HOST_DEVICE Complex operator*(const Complex &x, const real_t &y)
MFEM_HOST_DEVICE real_t abs(const Complex &z)
MFEM_HOST_DEVICE Complex operator/(const Complex &z, const real_t &d)
Dual number struct (value plus gradient)
Definition dual.hpp:36
checks if a type is zero
Definition tensor.hpp:199
MFEM_HOST_DEVICE constexpr T operator()(int i, int j, int k) const
Definition tensor.hpp:2215
MFEM_HOST_DEVICE constexpr T operator()(int i, int j, int k, int l) const
Definition tensor.hpp:2229
MFEM_HOST_DEVICE constexpr T operator()(int i, int j) const
Definition tensor.hpp:2034
MFEM_HOST_DEVICE tensor< T, n1 > & operator[](int)
Definition tensor.hpp:97
MFEM_HOST_DEVICE const tensor< T, n1 > & operator[](int) const
Definition tensor.hpp:98
MFEM_HOST_DEVICE const tensor< T, n1 > & operator()(int) const
Definition tensor.hpp:100
MFEM_HOST_DEVICE T & operator()(int, int j)
Definition tensor.hpp:101
MFEM_HOST_DEVICE tensor< T, n1 > & operator()(int)
Definition tensor.hpp:99
MFEM_HOST_DEVICE const T & operator()(int, int j) const
Definition tensor.hpp:102
MFEM_HOST_DEVICE T & operator[](int)
Definition tensor.hpp:69
MFEM_HOST_DEVICE const T & operator[](int) const
Definition tensor.hpp:70
MFEM_HOST_DEVICE T & operator()(int)
Definition tensor.hpp:71
MFEM_HOST_DEVICE const T & operator()(int) const
Definition tensor.hpp:72
MFEM_HOST_DEVICE const tensor< T, n1, n2, n3, n4 > & operator()(int i) const
Definition tensor.hpp:151
MFEM_HOST_DEVICE const tensor< T, n2, n3, n4 > & operator()(int i, int j) const
Definition tensor.hpp:153
MFEM_HOST_DEVICE tensor< T, n3, n4 > & operator()(int i, int j, int k)
Definition tensor.hpp:155
MFEM_HOST_DEVICE T & operator()(int i, int j, int k, int l, int m)
Definition tensor.hpp:161
MFEM_HOST_DEVICE const tensor< T, n3, n4 > & operator()(int i, int j, int k) const
Definition tensor.hpp:156
MFEM_HOST_DEVICE tensor< T, n2, n3, n4 > & operator()(int i, int j)
Definition tensor.hpp:152
MFEM_HOST_DEVICE const tensor< T, n1, n2, n3, n4 > & operator[](int i) const
Definition tensor.hpp:149
MFEM_HOST_DEVICE const tensor< T, n4 > & operator()(int i, int j, int k, int l) const
Definition tensor.hpp:159
MFEM_HOST_DEVICE tensor< T, n1, n2, n3, n4 > & operator()(int i)
Definition tensor.hpp:150
MFEM_HOST_DEVICE const T & operator()(int i, int j, int k, int l, int m) const
Definition tensor.hpp:162
MFEM_HOST_DEVICE tensor< T, n1, n2, n3, n4 > & operator[](int i)
Definition tensor.hpp:148
MFEM_HOST_DEVICE tensor< T, n4 > & operator()(int i, int j, int k, int l)
Definition tensor.hpp:158
MFEM_HOST_DEVICE T & operator()(int i, int j, int k, int l)
Definition tensor.hpp:137
MFEM_HOST_DEVICE tensor< T, n2, n3 > & operator()(int i, int j)
Definition tensor.hpp:133
MFEM_HOST_DEVICE const tensor< T, n1, n2, n3 > & operator()(int i) const
Definition tensor.hpp:132
MFEM_HOST_DEVICE const tensor< T, n1, n2, n3 > & operator[](int i) const
Definition tensor.hpp:130
MFEM_HOST_DEVICE const tensor< T, n3 > & operator()(int i, int j, int k) const
Definition tensor.hpp:136
MFEM_HOST_DEVICE const T & operator()(int i, int j, int k, int l) const
Definition tensor.hpp:138
MFEM_HOST_DEVICE const tensor< T, n2, n3 > & operator()(int i, int j) const
Definition tensor.hpp:134
MFEM_HOST_DEVICE tensor< T, n1, n2, n3 > & operator[](int i)
Definition tensor.hpp:129
MFEM_HOST_DEVICE tensor< T, n1, n2, n3 > & operator()(int i)
Definition tensor.hpp:131
MFEM_HOST_DEVICE tensor< T, n3 > & operator()(int i, int j, int k)
Definition tensor.hpp:135
MFEM_HOST_DEVICE tensor< T, n2 > & operator()(int i, int j)
Definition tensor.hpp:116
MFEM_HOST_DEVICE T & operator()(int i, int j, int k)
Definition tensor.hpp:118
MFEM_HOST_DEVICE tensor< T, n1, n2 > & operator()(int i)
Definition tensor.hpp:114
MFEM_HOST_DEVICE const tensor< T, n1, n2 > & operator()(int i) const
Definition tensor.hpp:115
MFEM_HOST_DEVICE tensor< T, n1, n2 > & operator[](int i)
Definition tensor.hpp:112
MFEM_HOST_DEVICE const tensor< T, n1, n2 > & operator[](int i) const
Definition tensor.hpp:113
MFEM_HOST_DEVICE const tensor< T, n2 > & operator()(int i, int j) const
Definition tensor.hpp:117
MFEM_HOST_DEVICE const T & operator()(int i, int j, int k) const
Definition tensor.hpp:119
MFEM_HOST_DEVICE tensor< T, n1 > & operator[](int i)
Definition tensor.hpp:82
MFEM_HOST_DEVICE const T & operator()(int i, int j) const
Definition tensor.hpp:87
MFEM_HOST_DEVICE const tensor< T, n1 > & operator()(int i) const
Definition tensor.hpp:85
MFEM_HOST_DEVICE tensor< T, n1 > & operator()(int i)
Definition tensor.hpp:84
MFEM_HOST_DEVICE T & operator()(int i, int j)
Definition tensor.hpp:86
MFEM_HOST_DEVICE const tensor< T, n1 > & operator[](int i) const
Definition tensor.hpp:83
MFEM_HOST_DEVICE T & operator()(int i)
Definition tensor.hpp:58
MFEM_HOST_DEVICE const T & operator[](int i) const
Definition tensor.hpp:57
MFEM_HOST_DEVICE const T & operator()(int i) const
Definition tensor.hpp:59
MFEM_HOST_DEVICE T & operator[](int i)
Definition tensor.hpp:56
MFEM_HOST_DEVICE const T & operator()(int) const
Definition tensor.hpp:45
MFEM_HOST_DEVICE const T & operator[](int) const
Definition tensor.hpp:43
MFEM_HOST_DEVICE T & operator()(int)
Definition tensor.hpp:44
MFEM_HOST_DEVICE T & operator[](int)
Definition tensor.hpp:42
A sentinel struct for eliding no-op tensor operations.
Definition tensor.hpp:170
MFEM_HOST_DEVICE zero operator()(T...)
zero can be accessed like a multidimensional array
Definition tensor.hpp:183
MFEM_HOST_DEVICE zero operator=(T)
anything assigned to zero does not change its value and returns zero
Definition tensor.hpp:190